Changeset - r15175:66e0817dc450
[Not reviewed]
master
0 30 0
rubidium - 14 years ago 2010-05-13 10:14:29
rubidium@openttd.org
(svn r19814) -Codechange: give some more unnamed enums a name, in case they consisted of unrelated values use static const (u)int
17 files changed:
0 comments (0 inline, 0 general)
src/ai/ai_instance.cpp
Show inline comments
 
@@ -79,707 +79,705 @@
 

	
 
#include "../company_base.h"
 
#include "../fileio_func.h"
 

	
 
AIStorage::~AIStorage()
 
{
 
	/* Free our pointers */
 
	if (event_data != NULL) AIEventController::FreeEventPointer();
 
	if (log_data != NULL) AILog::FreeLogPointer();
 
}
 

	
 
static void PrintFunc(bool error_msg, const SQChar *message)
 
{
 
	/* Convert to OpenTTD internal capable string */
 
	AIController::Print(error_msg, SQ2OTTD(message));
 
}
 

	
 
AIInstance::AIInstance(AIInfo *info) :
 
	controller(NULL),
 
	storage(NULL),
 
	engine(NULL),
 
	instance(NULL),
 
	is_started(false),
 
	is_dead(false),
 
	is_save_data_on_stack(false),
 
	suspend(0),
 
	callback(NULL)
 
{
 
	/* Set the instance already, so we can use AIObject::Set commands */
 
	Company::Get(_current_company)->ai_instance = this;
 

	
 
	this->controller = new AIController();
 
	this->storage    = new AIStorage();
 
	this->engine     = new Squirrel();
 
	this->engine->SetPrintFunction(&PrintFunc);
 

	
 
	/* The import method is available at a very early stage */
 
	this->engine->AddMethod("import", &AILibrary::Import, 4, ".ssi");
 

	
 
	/* Register the AIController */
 
	SQAIController_Register(this->engine);
 

	
 
	/* Register the API functions and classes */
 
	this->RegisterAPI();
 

	
 
	if (!this->LoadCompatibilityScripts(info->GetAPIVersion())) {
 
		this->Died();
 
		return;
 
	}
 

	
 
	try {
 
		AIObject::SetAllowDoCommand(false);
 
		/* Load and execute the script for this AI */
 
		const char *main_script = info->GetMainScript();
 
		if (strcmp(main_script, "%_dummy") == 0) {
 
			extern void AI_CreateAIDummy(HSQUIRRELVM vm);
 
			AI_CreateAIDummy(this->engine->GetVM());
 
		} else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
 
			if (this->engine->IsSuspended()) AILog::Error("This AI took too long to load script. AI is not started.");
 
			this->Died();
 
			return;
 
		}
 

	
 
		/* Create the main-class */
 
		this->instance = MallocT<SQObject>(1);
 
		if (!this->engine->CreateClassInstance(info->GetInstanceName(), this->controller, this->instance)) {
 
			this->Died();
 
			return;
 
		}
 
		AIObject::SetAllowDoCommand(true);
 
	} catch (AI_FatalError e) {
 
		this->is_dead = true;
 
		this->engine->ThrowError(e.GetErrorMessage());
 
		this->engine->ResumeError();
 
		this->Died();
 
	}
 
}
 

	
 
AIInstance::~AIInstance()
 
{
 
	if (instance != NULL) this->engine->ReleaseObject(this->instance);
 
	if (engine != NULL) delete this->engine;
 
	delete this->storage;
 
	delete this->controller;
 
	free(this->instance);
 
}
 

	
 
void AIInstance::RegisterAPI()
 
{
 
/* Register all classes */
 
	squirrel_register_std(this->engine);
 
	SQAIAbstractList_Register(this->engine);
 
	SQAIAccounting_Register(this->engine);
 
	SQAIAirport_Register(this->engine);
 
	SQAIBase_Register(this->engine);
 
	SQAIBaseStation_Register(this->engine);
 
	SQAIBridge_Register(this->engine);
 
	SQAIBridgeList_Register(this->engine);
 
	SQAIBridgeList_Length_Register(this->engine);
 
	SQAICargo_Register(this->engine);
 
	SQAICargoList_Register(this->engine);
 
	SQAICargoList_IndustryAccepting_Register(this->engine);
 
	SQAICargoList_IndustryProducing_Register(this->engine);
 
	SQAICompany_Register(this->engine);
 
	SQAIDate_Register(this->engine);
 
	SQAIDepotList_Register(this->engine);
 
	SQAIEngine_Register(this->engine);
 
	SQAIEngineList_Register(this->engine);
 
	SQAIError_Register(this->engine);
 
	SQAIEvent_Register(this->engine);
 
	SQAIEventCompanyAskMerger_Register(this->engine);
 
	SQAIEventCompanyBankrupt_Register(this->engine);
 
	SQAIEventCompanyInTrouble_Register(this->engine);
 
	SQAIEventCompanyMerger_Register(this->engine);
 
	SQAIEventCompanyNew_Register(this->engine);
 
	SQAIEventController_Register(this->engine);
 
	SQAIEventDisasterZeppelinerCleared_Register(this->engine);
 
	SQAIEventDisasterZeppelinerCrashed_Register(this->engine);
 
	SQAIEventEngineAvailable_Register(this->engine);
 
	SQAIEventEnginePreview_Register(this->engine);
 
	SQAIEventIndustryClose_Register(this->engine);
 
	SQAIEventIndustryOpen_Register(this->engine);
 
	SQAIEventStationFirstVehicle_Register(this->engine);
 
	SQAIEventSubsidyAwarded_Register(this->engine);
 
	SQAIEventSubsidyExpired_Register(this->engine);
 
	SQAIEventSubsidyOffer_Register(this->engine);
 
	SQAIEventSubsidyOfferExpired_Register(this->engine);
 
	SQAIEventVehicleCrashed_Register(this->engine);
 
	SQAIEventVehicleLost_Register(this->engine);
 
	SQAIEventVehicleUnprofitable_Register(this->engine);
 
	SQAIEventVehicleWaitingInDepot_Register(this->engine);
 
	SQAIExecMode_Register(this->engine);
 
	SQAIGameSettings_Register(this->engine);
 
	SQAIGroup_Register(this->engine);
 
	SQAIGroupList_Register(this->engine);
 
	SQAIIndustry_Register(this->engine);
 
	SQAIIndustryList_Register(this->engine);
 
	SQAIIndustryList_CargoAccepting_Register(this->engine);
 
	SQAIIndustryList_CargoProducing_Register(this->engine);
 
	SQAIIndustryType_Register(this->engine);
 
	SQAIIndustryTypeList_Register(this->engine);
 
	SQAIList_Register(this->engine);
 
	SQAILog_Register(this->engine);
 
	SQAIMap_Register(this->engine);
 
	SQAIMarine_Register(this->engine);
 
	SQAIOrder_Register(this->engine);
 
	SQAIRail_Register(this->engine);
 
	SQAIRailTypeList_Register(this->engine);
 
	SQAIRoad_Register(this->engine);
 
	SQAISign_Register(this->engine);
 
	SQAISignList_Register(this->engine);
 
	SQAIStation_Register(this->engine);
 
	SQAIStationList_Register(this->engine);
 
	SQAIStationList_Vehicle_Register(this->engine);
 
	SQAISubsidy_Register(this->engine);
 
	SQAISubsidyList_Register(this->engine);
 
	SQAITestMode_Register(this->engine);
 
	SQAITile_Register(this->engine);
 
	SQAITileList_Register(this->engine);
 
	SQAITileList_IndustryAccepting_Register(this->engine);
 
	SQAITileList_IndustryProducing_Register(this->engine);
 
	SQAITileList_StationType_Register(this->engine);
 
	SQAITown_Register(this->engine);
 
	SQAITownList_Register(this->engine);
 
	SQAITunnel_Register(this->engine);
 
	SQAIVehicle_Register(this->engine);
 
	SQAIVehicleList_Register(this->engine);
 
	SQAIVehicleList_DefaultGroup_Register(this->engine);
 
	SQAIVehicleList_Depot_Register(this->engine);
 
	SQAIVehicleList_Group_Register(this->engine);
 
	SQAIVehicleList_SharedOrders_Register(this->engine);
 
	SQAIVehicleList_Station_Register(this->engine);
 
	SQAIWaypoint_Register(this->engine);
 
	SQAIWaypointList_Register(this->engine);
 
	SQAIWaypointList_Vehicle_Register(this->engine);
 

	
 
	this->engine->SetGlobalPointer(this->engine);
 
}
 

	
 
bool AIInstance::LoadCompatibilityScripts(const char *api_version)
 
{
 
	char script_name[32];
 
	seprintf(script_name, lastof(script_name), "compat_%s.nut", api_version);
 
	char buf[MAX_PATH];
 
	Searchpath sp;
 
	FOR_ALL_SEARCHPATHS(sp) {
 
		FioAppendDirectory(buf, MAX_PATH, sp, AI_DIR);
 
		ttd_strlcat(buf, script_name, MAX_PATH);
 
		if (!FileExists(buf)) continue;
 

	
 
		if (this->engine->LoadScript(buf)) return true;
 

	
 
		AILog::Error("Failed to load API compatibility script");
 
		DEBUG(ai, 0, "Error compiling / running API compatibility script: %s", buf);
 
		return false;
 
	}
 

	
 
	AILog::Warning("API compatibility script not found");
 
	return true;
 
}
 

	
 
void AIInstance::Continue()
 
{
 
	assert(this->suspend < 0);
 
	this->suspend = -this->suspend - 1;
 
}
 

	
 
void AIInstance::Died()
 
{
 
	DEBUG(ai, 0, "The AI died unexpectedly.");
 
	this->is_dead = true;
 

	
 
	if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
 
	delete this->engine;
 
	this->instance = NULL;
 
	this->engine = NULL;
 

	
 
	ShowAIDebugWindow(_current_company);
 

	
 
	const AIInfo *info = AIConfig::GetConfig(_current_company)->GetInfo();
 
	if (info != NULL) {
 
		ShowErrorMessage(STR_ERROR_AI_PLEASE_REPORT_CRASH, INVALID_STRING_ID, WL_WARNING);
 

	
 
		if (info->GetURL() != NULL) {
 
			AILog::Info("Please report the error to the following URL:");
 
			AILog::Info(info->GetURL());
 
		}
 
	}
 
}
 

	
 
void AIInstance::GameLoop()
 
{
 
	if (this->IsDead()) return;
 
	if (this->engine->HasScriptCrashed()) {
 
		/* The script crashed during saving, kill it here. */
 
		this->Died();
 
		return;
 
	}
 
	this->controller->ticks++;
 

	
 
	if (this->suspend   < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
 
	if (this->suspend   < 0)  return;          // Multiplayer suspend, wait for Continue().
 
	if (--this->suspend > 0)  return;          // Singleplayer suspend, decrease to 0.
 

	
 
	/* If there is a callback to call, call that first */
 
	if (this->callback != NULL) {
 
		if (this->is_save_data_on_stack) {
 
			sq_poptop(this->engine->GetVM());
 
			this->is_save_data_on_stack = false;
 
		}
 
		try {
 
			this->callback(this);
 
		} catch (AI_VMSuspend e) {
 
			this->suspend  = e.GetSuspendTime();
 
			this->callback = e.GetSuspendCallback();
 

	
 
			return;
 
		}
 
	}
 

	
 
	this->suspend  = 0;
 
	this->callback = NULL;
 

	
 
	if (!this->is_started) {
 
		try {
 
			AIObject::SetAllowDoCommand(false);
 
			/* Run the constructor if it exists. Don't allow any DoCommands in it. */
 
			if (this->engine->MethodExists(*this->instance, "constructor")) {
 
				if (!this->engine->CallMethod(*this->instance, "constructor", 100000) || this->engine->IsSuspended()) {
 
					if (this->engine->IsSuspended()) AILog::Error("This AI took too long to initialize. AI is not started.");
 
					this->Died();
 
					return;
 
				}
 
			}
 
			if (!this->CallLoad() || this->engine->IsSuspended()) {
 
				if (this->engine->IsSuspended()) AILog::Error("This AI took too long in the Load function. AI is not started.");
 
				this->Died();
 
				return;
 
			}
 
			AIObject::SetAllowDoCommand(true);
 
			/* Start the AI by calling Start() */
 
			if (!this->engine->CallMethod(*this->instance, "Start",  _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
 
		} catch (AI_VMSuspend e) {
 
			this->suspend  = e.GetSuspendTime();
 
			this->callback = e.GetSuspendCallback();
 
		} catch (AI_FatalError e) {
 
			this->is_dead = true;
 
			this->engine->ThrowError(e.GetErrorMessage());
 
			this->engine->ResumeError();
 
			this->Died();
 
		}
 

	
 
		this->is_started = true;
 
		return;
 
	}
 
	if (this->is_save_data_on_stack) {
 
		sq_poptop(this->engine->GetVM());
 
		this->is_save_data_on_stack = false;
 
	}
 

	
 
	/* Continue the VM */
 
	try {
 
		if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
 
	} catch (AI_VMSuspend e) {
 
		this->suspend  = e.GetSuspendTime();
 
		this->callback = e.GetSuspendCallback();
 
	} catch (AI_FatalError e) {
 
		this->is_dead = true;
 
		this->engine->ThrowError(e.GetErrorMessage());
 
		this->engine->ResumeError();
 
		this->Died();
 
	}
 
}
 

	
 
void AIInstance::CollectGarbage() const
 
{
 
	if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
 
}
 

	
 
/* static */ void AIInstance::DoCommandReturn(AIInstance *instance)
 
{
 
	instance->engine->InsertResult(AIObject::GetLastCommandRes());
 
}
 

	
 
/* static */ void AIInstance::DoCommandReturnVehicleID(AIInstance *instance)
 
{
 
	instance->engine->InsertResult(AIObject::GetNewVehicleID());
 
}
 

	
 
/* static */ void AIInstance::DoCommandReturnSignID(AIInstance *instance)
 
{
 
	instance->engine->InsertResult(AIObject::GetNewSignID());
 
}
 

	
 
/* static */ void AIInstance::DoCommandReturnGroupID(AIInstance *instance)
 
{
 
	instance->engine->InsertResult(AIObject::GetNewGroupID());
 
}
 

	
 
/* static */ AIStorage *AIInstance::GetStorage()
 
{
 
	assert(Company::IsValidAiID(_current_company));
 
	return Company::Get(_current_company)->ai_instance->storage;
 
}
 

	
 
/*
 
 * All data is stored in the following format:
 
 * First 1 byte indicating if there is a data blob at all.
 
 * 1 byte indicating the type of data.
 
 * The data itself, this differs per type:
 
 *  - integer: a binary representation of the integer (int32).
 
 *  - string:  First one byte with the string length, then a 0-terminated char
 
 *             array. The string can't be longer than 255 bytes (including
 
 *             terminating '\0').
 
 *  - array:   All data-elements of the array are saved recursive in this
 
 *             format, and ended with an element of the type
 
 *             SQSL_ARRAY_TABLE_END.
 
 *  - table:   All key/value pairs are saved in this format (first key 1, then
 
 *             value 1, then key 2, etc.). All keys and values can have an
 
 *             arbitrary type (as long as it is supported by the save function
 
 *             of course). The table is ended with an element of the type
 
 *             SQSL_ARRAY_TABLE_END.
 
 *  - bool:    A single byte with value 1 representing true and 0 false.
 
 *  - null:    No data.
 
 */
 

	
 
/** The type of the data that follows in the savegame. */
 
enum SQSaveLoadType {
 
	SQSL_INT             = 0x00, ///< The following data is an integer.
 
	SQSL_STRING          = 0x01, ///< The following data is an string.
 
	SQSL_ARRAY           = 0x02, ///< The following data is an array.
 
	SQSL_TABLE           = 0x03, ///< The following data is an table.
 
	SQSL_BOOL            = 0x04, ///< The following data is a boolean.
 
	SQSL_NULL            = 0x05, ///< A null variable.
 
	SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows.
 
};
 

	
 
static byte _ai_sl_byte;
 

	
 
static const SaveLoad _ai_byte[] = {
 
	SLEG_VAR(_ai_sl_byte, SLE_UINT8),
 
	SLE_END()
 
};
 

	
 
enum {
 
	AISAVE_MAX_DEPTH = 25, ///< The maximum recursive depth for items stored in the savegame.
 
};
 
static const uint AISAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame.
 

	
 
/* static */ bool AIInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
 
{
 
	if (max_depth == 0) {
 
		AILog::Error("Savedata can only be nested to 25 deep. No data saved.");
 
		return false;
 
	}
 

	
 
	switch (sq_gettype(vm, index)) {
 
		case OT_INTEGER: {
 
			if (!test) {
 
				_ai_sl_byte = SQSL_INT;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			SQInteger res;
 
			sq_getinteger(vm, index, &res);
 
			if (!test) {
 
				int value = (int)res;
 
				SlArray(&value, 1, SLE_INT32);
 
			}
 
			return true;
 
		}
 

	
 
		case OT_STRING: {
 
			if (!test) {
 
				_ai_sl_byte = SQSL_STRING;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			const SQChar *res;
 
			sq_getstring(vm, index, &res);
 
			/* @bug if a string longer than 512 characters is given to SQ2OTTD, the
 
			 *  internal buffer overflows. */
 
			const char *buf = SQ2OTTD(res);
 
			size_t len = strlen(buf) + 1;
 
			if (len >= 255) {
 
				AILog::Error("Maximum string length is 254 chars. No data saved.");
 
				return false;
 
			}
 
			if (!test) {
 
				_ai_sl_byte = (byte)len;
 
				SlObject(NULL, _ai_byte);
 
				SlArray((void*)buf, len, SLE_CHAR);
 
			}
 
			return true;
 
		}
 

	
 
		case OT_ARRAY: {
 
			if (!test) {
 
				_ai_sl_byte = SQSL_ARRAY;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			sq_pushnull(vm);
 
			while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
 
				/* Store the value */
 
				bool res = SaveObject(vm, -1, max_depth - 1, test);
 
				sq_pop(vm, 2);
 
				if (!res) {
 
					sq_pop(vm, 1);
 
					return false;
 
				}
 
			}
 
			sq_pop(vm, 1);
 
			if (!test) {
 
				_ai_sl_byte = SQSL_ARRAY_TABLE_END;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			return true;
 
		}
 

	
 
		case OT_TABLE: {
 
			if (!test) {
 
				_ai_sl_byte = SQSL_TABLE;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			sq_pushnull(vm);
 
			while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
 
				/* Store the key + value */
 
				bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
 
				sq_pop(vm, 2);
 
				if (!res) {
 
					sq_pop(vm, 1);
 
					return false;
 
				}
 
			}
 
			sq_pop(vm, 1);
 
			if (!test) {
 
				_ai_sl_byte = SQSL_ARRAY_TABLE_END;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			return true;
 
		}
 

	
 
		case OT_BOOL: {
 
			if (!test) {
 
				_ai_sl_byte = SQSL_BOOL;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			SQBool res;
 
			sq_getbool(vm, index, &res);
 
			if (!test) {
 
				_ai_sl_byte = res ? 1 : 0;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			return true;
 
		}
 

	
 
		case OT_NULL: {
 
			if (!test) {
 
				_ai_sl_byte = SQSL_NULL;
 
				SlObject(NULL, _ai_byte);
 
			}
 
			return true;
 
		}
 

	
 
		default:
 
			AILog::Error("You tried to save an unsupported type. No data saved.");
 
			return false;
 
	}
 
}
 

	
 
/* static */ void AIInstance::SaveEmpty()
 
{
 
	_ai_sl_byte = 0;
 
	SlObject(NULL, _ai_byte);
 
}
 

	
 
void AIInstance::Save()
 
{
 
	/* Don't save data if the AI didn't start yet or if it crashed. */
 
	if (this->engine == NULL || this->engine->HasScriptCrashed()) {
 
		SaveEmpty();
 
		return;
 
	}
 

	
 
	HSQUIRRELVM vm = this->engine->GetVM();
 
	if (this->is_save_data_on_stack) {
 
		_ai_sl_byte = 1;
 
		SlObject(NULL, _ai_byte);
 
		/* Save the data that was just loaded. */
 
		SaveObject(vm, -1, AISAVE_MAX_DEPTH, false);
 
	} else if (!this->is_started) {
 
		SaveEmpty();
 
		return;
 
	} else if (this->engine->MethodExists(*this->instance, "Save")) {
 
		HSQOBJECT savedata;
 
		/* We don't want to be interrupted during the save function. */
 
		bool backup_allow = AIObject::GetAllowDoCommand();
 
		AIObject::SetAllowDoCommand(false);
 
		try {
 
			if (!this->engine->CallMethod(*this->instance, "Save", &savedata)) {
 
				/* The script crashed in the Save function. We can't kill
 
				 * it here, but do so in the next AI tick. */
 
				SaveEmpty();
 
				this->engine->CrashOccurred();
 
				return;
 
			}
 
		} catch (AI_FatalError e) {
 
			/* If we don't mark the AI as dead here cleaning up the squirrel
 
			 * stack could throw AI_FatalError again. */
 
			this->is_dead = true;
 
			this->engine->ThrowError(e.GetErrorMessage());
 
			this->engine->ResumeError();
 
			SaveEmpty();
 
			/* We can't kill the AI here, so mark it as crashed (not dead) and
 
			 * kill it in the next AI tick. */
 
			this->is_dead = false;
 
			this->engine->CrashOccurred();
 
			return;
 
		}
 
		AIObject::SetAllowDoCommand(backup_allow);
 

	
 
		if (!sq_istable(savedata)) {
 
			AILog::Error("Save function should return a table.");
 
			SaveEmpty();
 
			this->engine->CrashOccurred();
 
			return;
 
		}
 
		sq_pushobject(vm, savedata);
 
		if (SaveObject(vm, -1, AISAVE_MAX_DEPTH, true)) {
 
			_ai_sl_byte = 1;
 
			SlObject(NULL, _ai_byte);
 
			SaveObject(vm, -1, AISAVE_MAX_DEPTH, false);
 
			this->is_save_data_on_stack = true;
 
		} else {
 
			SaveEmpty();
 
			this->engine->CrashOccurred();
 
		}
 
	} else {
 
		AILog::Warning("Save function is not implemented");
 
		_ai_sl_byte = 0;
 
		SlObject(NULL, _ai_byte);
 
	}
 

	
 
}
 

	
 
void AIInstance::Suspend()
 
{
 
	HSQUIRRELVM vm = this->engine->GetVM();
 
	Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend);
 
}
 

	
 
/* static */ bool AIInstance::LoadObjects(HSQUIRRELVM vm)
 
{
 
	SlObject(NULL, _ai_byte);
 
	switch (_ai_sl_byte) {
 
		case SQSL_INT: {
 
			int value;
 
			SlArray(&value, 1, SLE_INT32);
 
			if (vm != NULL) sq_pushinteger(vm, (SQInteger)value);
 
			return true;
 
		}
 

	
 
		case SQSL_STRING: {
 
			SlObject(NULL, _ai_byte);
 
			static char buf[256];
 
			SlArray(buf, _ai_sl_byte, SLE_CHAR);
 
			if (vm != NULL) sq_pushstring(vm, OTTD2SQ(buf), -1);
 
			return true;
 
		}
 

	
 
		case SQSL_ARRAY: {
 
			if (vm != NULL) sq_newarray(vm, 0);
 
			while (LoadObjects(vm)) {
 
				if (vm != NULL) sq_arrayappend(vm, -2);
 
				/* The value is popped from the stack by squirrel. */
 
			}
 
			return true;
 
		}
 

	
 
		case SQSL_TABLE: {
 
			if (vm != NULL) sq_newtable(vm);
 
			while (LoadObjects(vm)) {
 
				LoadObjects(vm);
 
				if (vm != NULL) sq_rawset(vm, -3);
 
				/* The key (-2) and value (-1) are popped from the stack by squirrel. */
 
			}
 
			return true;
 
		}
 

	
 
		case SQSL_BOOL: {
 
			SlObject(NULL, _ai_byte);
 
			if (vm != NULL) sq_pushinteger(vm, (SQBool)(_ai_sl_byte != 0));
 
			return true;
 
		}
 

	
 
		case SQSL_NULL: {
 
			if (vm != NULL) sq_pushnull(vm);
 
			return true;
 
		}
 

	
 
		case SQSL_ARRAY_TABLE_END: {
 
			return false;
 
		}
 

	
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/* static */ void AIInstance::LoadEmpty()
 
{
 
	SlObject(NULL, _ai_byte);
 
	/* Check if there was anything saved at all. */
 
	if (_ai_sl_byte == 0) return;
 

	
 
	LoadObjects(NULL);
 
}
 

	
 
void AIInstance::Load(int version)
 
{
 
	if (this->engine == NULL || version == -1) {
 
		LoadEmpty();
 
		return;
 
	}
 
	HSQUIRRELVM vm = this->engine->GetVM();
 

	
 
	SlObject(NULL, _ai_byte);
 
	/* Check if there was anything saved at all. */
 
	if (_ai_sl_byte == 0) return;
 

	
 
	sq_pushinteger(vm, version);
 
	LoadObjects(vm);
 
	this->is_save_data_on_stack = true;
 
}
 

	
 
bool AIInstance::CallLoad()
 
{
 
	HSQUIRRELVM vm = this->engine->GetVM();
 
	/* Is there save data that we should load? */
 
	if (!this->is_save_data_on_stack) return true;
 
	/* Whatever happens, after CallLoad the savegame data is removed from the stack. */
 
	this->is_save_data_on_stack = false;
 

	
 
	if (!this->engine->MethodExists(*this->instance, "Load")) {
 
		AILog::Warning("Loading failed: there was data for the AI to load, but the AI does not have a Load() function.");
 

	
 
		/* Pop the savegame data and version. */
 
		sq_pop(vm, 2);
 
		return true;
 
	}
 

	
 
	/* Go to the instance-root */
 
	sq_pushobject(vm, *this->instance);
 
	/* Find the function-name inside the script */
 
	sq_pushstring(vm, OTTD2SQ("Load"), -1);
 
	/* Change the "Load" string in a function pointer */
 
	sq_get(vm, -2);
 
	/* Push the main instance as "this" object */
 
	sq_pushobject(vm, *this->instance);
 
	/* Push the version data and savegame data as arguments */
 
	sq_push(vm, -5);
 
	sq_push(vm, -5);
 

	
 
	/* Call the AI load function. sq_call removes the arguments (but not the
 
	 * function pointer) from the stack. */
 
	if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, 100000))) return false;
 

	
 
	/* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
 
	sq_pop(vm, 4);
 
	return true;
 
}
src/airport.h
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file airport.h Various declarations for airports */
 

	
 
#ifndef AIRPORT_H
 
#define AIRPORT_H
 

	
 
#include "direction_type.h"
 
#include "tile_type.h"
 

	
 
/** Some airport-related constants */
 
enum {
 
	MAX_TERMINALS =  10,                    ///< maximum number of terminals per airport
 
	MAX_HELIPADS  =   4,                    ///< maximum number of helipads per airport
 
	MAX_ELEMENTS  = 255,                    ///< maximum number of aircraft positions at airport
 
	NUM_AIRPORTTILES = 256,                 ///< total number of airport tiles
 
	NEW_AIRPORTTILE_OFFSET = 74,            ///< offset of first newgrf airport tile
 
	INVALID_AIRPORTTILE = NUM_AIRPORTTILES, ///< id for an invalid airport tile
 
};
 
static const uint MAX_TERMINALS =  10;                       ///< maximum number of terminals per airport
 
static const uint MAX_HELIPADS  =   4;                       ///< maximum number of helipads per airport
 
static const uint MAX_ELEMENTS  = 255;                       ///< maximum number of aircraft positions at airport
 

	
 
static const uint NUM_AIRPORTTILES       = 256;              ///< total number of airport tiles
 
static const uint NEW_AIRPORTTILE_OFFSET = 74;               ///< offset of first newgrf airport tile
 
static const uint INVALID_AIRPORTTILE    = NUM_AIRPORTTILES; ///< id for an invalid airport tile
 

	
 
/** Airport types */
 
enum AirportTypes {
 
	AT_SMALL         =   0,
 
	AT_LARGE         =   1,
 
	AT_HELIPORT      =   2,
 
	AT_METROPOLITAN  =   3,
 
	AT_INTERNATIONAL =   4,
 
	AT_COMMUTER      =   5,
 
	AT_HELIDEPOT     =   6,
 
	AT_INTERCON      =   7,
 
	AT_HELISTATION   =   8,
 
	AT_OILRIG        =   9,
 
	NEW_AIRPORT_OFFSET = 10,
 
	NUM_AIRPORTS     =  128,
 
	AT_INVALID       = 254,
 
	AT_DUMMY         = 255
 
};
 

	
 
enum AirportMovingDataFlags {
 
	AMED_NOSPDCLAMP = 1 << 0,
 
	AMED_TAKEOFF    = 1 << 1,
 
	AMED_SLOWTURN   = 1 << 2,
 
	AMED_LAND       = 1 << 3,
 
	AMED_EXACTPOS   = 1 << 4,
 
	AMED_BRAKE      = 1 << 5,
 
	AMED_HELI_RAISE = 1 << 6,
 
	AMED_HELI_LOWER = 1 << 7,
 
	AMED_HOLD       = 1 << 8
 
};
 

	
 
/* Movement States on Airports (headings target) */
 
enum AirportMovementStates {
 
	TO_ALL         =  0,
 
	HANGAR         =  1,
 
	TERM1          =  2,
 
	TERM2          =  3,
 
	TERM3          =  4,
 
	TERM4          =  5,
 
	TERM5          =  6,
 
	TERM6          =  7,
 
	HELIPAD1       =  8,
 
	HELIPAD2       =  9,
 
	TAKEOFF        = 10,
 
	STARTTAKEOFF   = 11,
 
	ENDTAKEOFF     = 12,
 
	HELITAKEOFF    = 13,
 
	FLYING         = 14,
 
	LANDING        = 15,
 
	ENDLANDING     = 16,
 
	HELILANDING    = 17,
 
	HELIENDLANDING = 18,
 
	TERM7          = 19,
 
	TERM8          = 20,
 
	HELIPAD3       = 21,
 
	HELIPAD4       = 22,
 
	MAX_HEADINGS   = 22,
 
};
 

	
 
/* Movement Blocks on Airports
 
 * blocks (eg_airport_flags) */
 
static const uint64
 
	TERM1_block              = 1ULL <<  0,
 
	TERM2_block              = 1ULL <<  1,
 
	TERM3_block              = 1ULL <<  2,
 
	TERM4_block              = 1ULL <<  3,
 
	TERM5_block              = 1ULL <<  4,
 
	TERM6_block              = 1ULL <<  5,
 
	HELIPAD1_block           = 1ULL <<  6,
 
	HELIPAD2_block           = 1ULL <<  7,
 
	RUNWAY_IN_OUT_block      = 1ULL <<  8,
 
	RUNWAY_IN_block          = 1ULL <<  8,
 
	AIRPORT_BUSY_block       = 1ULL <<  8,
 
	RUNWAY_OUT_block         = 1ULL <<  9,
 
	TAXIWAY_BUSY_block       = 1ULL << 10,
 
	OUT_WAY_block            = 1ULL << 11,
 
	IN_WAY_block             = 1ULL << 12,
 
	AIRPORT_ENTRANCE_block   = 1ULL << 13,
 
	TERM_GROUP1_block        = 1ULL << 14,
 
	TERM_GROUP2_block        = 1ULL << 15,
 
	HANGAR2_AREA_block       = 1ULL << 16,
 
	TERM_GROUP2_ENTER1_block = 1ULL << 17,
 
	TERM_GROUP2_ENTER2_block = 1ULL << 18,
 
	TERM_GROUP2_EXIT1_block  = 1ULL << 19,
 
	TERM_GROUP2_EXIT2_block  = 1ULL << 20,
 
	PRE_HELIPAD_block        = 1ULL << 21,
 

	
 
	/* blocks for new airports */
 
	TERM7_block              = 1ULL << 22,
 
	TERM8_block              = 1ULL << 23,
 
	TERM9_block              = 1ULL << 24,
 
	HELIPAD3_block           = 1ULL << 24,
 
	TERM10_block             = 1ULL << 25,
 
	HELIPAD4_block           = 1ULL << 25,
 
	HANGAR1_AREA_block       = 1ULL << 26,
 
	OUT_WAY2_block           = 1ULL << 27,
 
	IN_WAY2_block            = 1ULL << 28,
 
	RUNWAY_IN2_block         = 1ULL << 29,
 
	RUNWAY_OUT2_block        = 1ULL << 10,   ///< note re-uses TAXIWAY_BUSY
 
	HELIPAD_GROUP_block      = 1ULL << 13,   ///< note re-uses AIRPORT_ENTRANCE
 
	OUT_WAY_block2           = 1ULL << 31,
 
	/* end of new blocks */
 

	
 
	NOTHING_block            = 1ULL << 30;
 

	
 
struct AirportMovingData {
 
	int16 x;
 
	int16 y;
 
	uint16 flag;
 
	DirectionByte direction;
 
};
 

	
 
struct AirportFTAbuildup;
 

	
 
/** Finite sTate mAchine --> FTA */
 
struct AirportFTAClass {
 
public:
 
	enum Flags {
 
		AIRPLANES   = 0x1,
 
		HELICOPTERS = 0x2,
 
		ALL         = AIRPLANES | HELICOPTERS,
 
		SHORT_STRIP = 0x4
 
	};
 

	
 
	AirportFTAClass(
 
		const AirportMovingData *moving_data,
 
		const byte *terminals,
 
		const byte *helipads,
 
		const byte *entry_points,
 
		Flags flags,
 
		const AirportFTAbuildup *apFA,
 
		byte delta_z
 
	);
 

	
 
	~AirportFTAClass();
 

	
 
	const AirportMovingData *MovingData(byte position) const
 
	{
 
		assert(position < nofelements);
 
		return &moving_data[position];
 
	}
 

	
 
	const AirportMovingData *moving_data;
 
	struct AirportFTA *layout;            ///< state machine for airport
 
	const byte *terminals;
 
	const byte *helipads;
 
	Flags flags;
 
	byte nofelements;                     ///< number of positions the airport consists of
 
	const byte *entry_points;             ///< when an airplane arrives at this airport, enter it at position entry_point, index depends on direction
 
	byte delta_z;                         ///< Z adjustment for helicopter pads
 
};
 

	
 
DECLARE_ENUM_AS_BIT_SET(AirportFTAClass::Flags)
 

	
 

	
 
/** Internal structure used in openttd - Finite sTate mAchine --> FTA */
 
struct AirportFTA {
 
	AirportFTA *next;        ///< possible extra movement choices from this position
 
	uint64 block;            ///< 64 bit blocks (st->airport.flags), should be enough for the most complex airports
 
	byte position;           ///< the position that an airplane is at
 
	byte next_position;      ///< next position from this position
 
	byte heading;            ///< heading (current orders), guiding an airplane to its target on an airport
 
};
 

	
 
const AirportFTAClass *GetAirport(const byte airport_type);
 
byte GetVehiclePosOnBuild(TileIndex hangar_tile);
 

	
 
#endif /* AIRPORT_H */
src/airport_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file airport_gui.cpp The GUI for airports. */
 

	
 
#include "stdafx.h"
 
#include "window_gui.h"
 
#include "station_gui.h"
 
#include "terraform_gui.h"
 
#include "airport.h"
 
#include "sound_func.h"
 
#include "window_func.h"
 
#include "strings_func.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "company_func.h"
 
#include "tilehighlight_func.h"
 
#include "company_base.h"
 
#include "station_type.h"
 
#include "newgrf_airport.h"
 
#include "widgets/dropdown_type.h"
 
#include "core/geometry_func.hpp"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
static AirportClassID _selected_airport_class; ///< the currently visible airport class
 
static int _selected_airport_index;            ///< the index of the selected airport in the current class or -1
 

	
 
static void ShowBuildAirportPicker(Window *parent);
 

	
 

	
 
void CcBuildAirport(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (result.Failed()) return;
 

	
 
	SndPlayTileFx(SND_1F_SPLAT, tile);
 
	if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
}
 

	
 
static void PlaceAirport(TileIndex tile)
 
{
 
	if (_selected_airport_index == -1) return;
 
	uint32 p2 = _ctrl_pressed;
 
	SB(p2, 16, 16, INVALID_STATION); // no station to join
 

	
 
	uint32 p1 = GetAirportSpecFromClass(_selected_airport_class, _selected_airport_index)->GetIndex();
 
	CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_AIRPORT | CMD_MSG(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE), CcBuildAirport, "" };
 
	ShowSelectStationIfNeeded(cmdcont, TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE));
 
}
 

	
 
/** Widget number of the airport build window. */
 
enum {
 
enum AirportToolbarWidgets {
 
	ATW_AIRPORT,
 
	ATW_DEMOLISH,
 
};
 

	
 

	
 
static void BuildAirClick_Airport(Window *w)
 
{
 
	if (HandlePlacePushButton(w, ATW_AIRPORT, SPR_CURSOR_AIRPORT, HT_RECT, PlaceAirport)) ShowBuildAirportPicker(w);
 
}
 

	
 
static void BuildAirClick_Demolish(Window *w)
 
{
 
	HandlePlacePushButton(w, ATW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 

	
 
typedef void OnButtonClick(Window *w);
 
static OnButtonClick * const _build_air_button_proc[] = {
 
	BuildAirClick_Airport,
 
	BuildAirClick_Demolish,
 
};
 

	
 
struct BuildAirToolbarWindow : Window {
 
	BuildAirToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
 
	}
 

	
 
	~BuildAirToolbarWindow()
 
	{
 
		if (_settings_client.gui.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0, false);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget, int click_count)
 
	{
 
		if (!IsInsideBS(widget, ATW_AIRPORT, lengthof(_build_air_button_proc))) return;
 

	
 
		_build_air_button_proc[widget - ATW_AIRPORT](this);
 
	}
 

	
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case '1': BuildAirClick_Airport(this); break;
 
			case '2': BuildAirClick_Demolish(this); break;
 
			default: return ES_NOT_HANDLED;
 
		}
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1 && select_proc == DDSP_DEMOLISH_AREA) {
 
			GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 

	
 
		DeleteWindowById(WC_BUILD_STATION, TRANSPORT_AIR);
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_air_toolbar_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_TOOLBAR_AIRCRAFT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, ATW_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, ATW_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _air_toolbar_desc(
 
	WDP_ALIGN_TOOLBAR, 0, 0,
 
	WC_BUILD_TOOLBAR, WC_NONE,
 
	WDF_CONSTRUCTION,
 
	_nested_air_toolbar_widgets, lengthof(_nested_air_toolbar_widgets)
 
);
 

	
 
void ShowBuildAirToolbar()
 
{
 
	if (!Company::IsValidID(_local_company)) return;
 

	
 
	DeleteWindowByClass(WC_BUILD_TOOLBAR);
 
	AllocateWindowDescFront<BuildAirToolbarWindow>(&_air_toolbar_desc, TRANSPORT_AIR);
 
}
 

	
 
/** Airport widgets in the airport picker window. */
 
enum AirportPickerWidgets {
 
	BAIRW_CLASS_DROPDOWN,
 
	BAIRW_AIRPORT_LIST,
 
	BAIRW_SCROLLBAR,
 
	BAIRW_BOTTOMPANEL,
 
	BAIRW_COVERAGE_LABEL,
 
	BAIRW_BTN_DONTHILIGHT,
 
	BAIRW_BTN_DOHILIGHT,
 
};
 

	
 
class BuildAirportWindow : public PickerWindowBase {
 
	int line_height;
 

	
 
	/** Build a dropdown list of available airport classes */
 
	static DropDownList *BuildAirportClassDropDown()
 
	{
 
		DropDownList *list = new DropDownList();
 

	
 
		for (uint i = 0; i < GetNumAirportClasses(); i++) {
 
			list->push_back(new DropDownListStringItem(GetAirportClassName((AirportClassID)i), i, false));
 
		}
 

	
 
		return list;
 
	}
 

	
 
public:
 
	BuildAirportWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->vscroll.SetCapacity(5);
 
		this->vscroll.SetPosition(0);
 
		this->InitNested(desc, TRANSPORT_AIR);
 

	
 
		this->SetWidgetLoweredState(BAIRW_BTN_DONTHILIGHT, !_settings_client.gui.station_show_coverage);
 
		this->SetWidgetLoweredState(BAIRW_BTN_DOHILIGHT, _settings_client.gui.station_show_coverage);
 
		this->OnInvalidateData();
 

	
 
		this->vscroll.SetCount(GetNumAirportsInClass(_selected_airport_class));
 
		this->SelectFirstAvailableAirport(true);
 
	}
 

	
 
	virtual ~BuildAirportWindow()
 
	{
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget != BAIRW_CLASS_DROPDOWN) return;
 

	
 
		SetDParam(0, GetAirportClassName(_selected_airport_class));
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case BAIRW_CLASS_DROPDOWN: {
 
				Dimension d = {0, 0};
 
				for (uint i = 0; i < GetNumAirportClasses(); i++) {
 
					SetDParam(0, GetAirportClassName((AirportClassID)i));
 
					d = maxdim(d, GetStringBoundingBox(STR_BLACK_STRING));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
			} break;
 

	
 
			case BAIRW_AIRPORT_LIST: {
 
				for (int i = 0; i < NUM_AIRPORTS; i++) {
 
					const AirportSpec *as = AirportSpec::Get(i);
 
					if (!as->enabled) continue;
 

	
 
					size->width = max(size->width, GetStringBoundingBox(as->name).width);
 
				}
 

	
 
				this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				size->height = this->vscroll.GetCapacity() * this->line_height;
 
			} break;
 

	
 
			default: break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != BAIRW_AIRPORT_LIST) return;
 

	
 
		int y = r.top;
 
		for (uint i = this->vscroll.GetPosition(); this->vscroll.IsVisible(i) && i < GetNumAirportsInClass(_selected_airport_class); i++) {
 
			const AirportSpec *as = GetAirportSpecFromClass(_selected_airport_class, i);
 
			if (!as->IsAvailable()) {
 
				GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->line_height - 2, 0, FILLRECT_CHECKER);
 
			}
 
			DrawString(r.left + WD_MATRIX_LEFT, r.right + WD_MATRIX_RIGHT, y + WD_MATRIX_TOP, as->name, ((int)i == _selected_airport_index) ? TC_WHITE : TC_BLACK);
 
			y += this->line_height;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		uint16 top = this->GetWidget<NWidgetBase>(BAIRW_BTN_DOHILIGHT)->pos_y + this->GetWidget<NWidgetBase>(BAIRW_BTN_DOHILIGHT)->current_y + WD_PAR_VSEP_NORMAL;
 
		NWidgetBase *panel_nwi = this->GetWidget<NWidgetBase>(BAIRW_BOTTOMPANEL);
 

	
 
		int right = panel_nwi->pos_x +  panel_nwi->current_x;
 
		int bottom = panel_nwi->pos_y +  panel_nwi->current_y;
 

	
 
		if (_selected_airport_index != -1) {
 
			const AirportSpec *as = GetAirportSpecFromClass(_selected_airport_class, _selected_airport_index);
 
			int rad = _settings_game.station.modified_catchment ? as->catchment : (uint)CA_UNMODIFIED;
 

	
 
			/* only show the station (airport) noise, if the noise option is activated */
 
			if (_settings_game.economy.station_noise_level) {
 
				/* show the noise of the selected airport */
 
				SetDParam(0, as->noise_level);
 
				DrawString(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, STR_STATION_BUILD_NOISE);
 
				top += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
			}
 

	
 
			/* strings such as 'Size' and 'Coverage Area' */
 
			top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
 
			top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
 
		}
 

	
 
		/* Resize background if the text is not equally long as the window. */
 
		if (top > bottom || (top < bottom && panel_nwi->current_y > panel_nwi->smallest_y)) {
 
			ResizeWindow(this, 0, top - bottom);
 
		}
 
	}
 

	
 
	void SelectOtherAirport(int airport_index)
 
	{
 
		_selected_airport_index = airport_index;
 

	
 
		this->UpdateSelectSize();
 
		this->SetDirty();
 
	}
 

	
 
	void UpdateSelectSize()
 
	{
 
		if (_selected_airport_index == -1) {
 
			SetTileSelectSize(1, 1);
 
		} else {
 
			const AirportSpec *as = GetAirportSpecFromClass(_selected_airport_class, _selected_airport_index);
 
			SetTileSelectSize(as->size_x, as->size_y);
 

	
 
			int rad = _settings_game.station.modified_catchment ? as->catchment : (uint)CA_UNMODIFIED;
 
			if (_settings_client.gui.station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget, int click_count)
 
	{
 
		switch (widget) {
 
			case BAIRW_CLASS_DROPDOWN:
 
				ShowDropDownList(this, BuildAirportClassDropDown(), _selected_airport_class, BAIRW_CLASS_DROPDOWN);
 
				break;
 

	
 
			case BAIRW_AIRPORT_LIST: {
 
				int num_clicked = this->vscroll.GetPosition() + (pt.y - this->nested_array[widget]->pos_y) / this->line_height;
 
				if (num_clicked >= this->vscroll.GetCount()) break;
 
				const AirportSpec *as = GetAirportSpecFromClass(_selected_airport_class, num_clicked);
 
				if (as->IsAvailable()) this->SelectOtherAirport(num_clicked);
 
			} break;
 

	
 
			case BAIRW_BTN_DONTHILIGHT: case BAIRW_BTN_DOHILIGHT:
 
				_settings_client.gui.station_show_coverage = (widget != BAIRW_BTN_DONTHILIGHT);
 
				this->SetWidgetLoweredState(BAIRW_BTN_DONTHILIGHT, !_settings_client.gui.station_show_coverage);
 
				this->SetWidgetLoweredState(BAIRW_BTN_DOHILIGHT, _settings_client.gui.station_show_coverage);
 
				this->SetDirty();
 
				SndPlayFx(SND_15_BEEP);
 
				this->UpdateSelectSize();
 
				break;
 
		}
 
	}
 

	
 
	/**
 
	 * Select the first available airport.
 
	 * @param change_class If true, change the class if no airport in the current
 
	 *   class is available.
 
	 */
 
	void SelectFirstAvailableAirport(bool change_class)
 
	{
 
		/* First try to select an airport in the selected class. */
 
		for (uint i = 0; i < GetNumAirportsInClass(_selected_airport_class); i++) {
 
			const AirportSpec *as = GetAirportSpecFromClass(_selected_airport_class, i);
 
			if (as->IsAvailable()) {
 
				this->SelectOtherAirport(i);
 
				return;
 
			}
 
		}
 
		if (change_class) {
 
			/* If that fails, select the first available airport
 
			 * from a random class. */
 
			for (AirportClassID j = APC_BEGIN; j < APC_MAX; j++) {
 
				for (uint i = 0; i < GetNumAirportsInClass(j); i++) {
 
					const AirportSpec *as = GetAirportSpecFromClass(j, i);
 
					if (as->IsAvailable()) {
 
						_selected_airport_class = j;
 
						this->SelectOtherAirport(i);
 
						return;
 
					}
 
				}
 
			}
 
		}
 
		/* If all airports are unavailable, select nothing. */
 
		this->SelectOtherAirport(-1);
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		assert(widget == BAIRW_CLASS_DROPDOWN);
 
		_selected_airport_class = (AirportClassID)index;
 
		this->vscroll.SetCount(GetNumAirportsInClass(_selected_airport_class));
 
		this->SelectFirstAvailableAirport(false);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		CheckRedrawStationCoverage(this);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_build_airport_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_AIRPORT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(true, false), SetPIP(2, 0, 2),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_AIRPORT_CLASS_LABEL, STR_NULL), SetFill(true, false),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, BAIRW_CLASS_DROPDOWN), SetFill(true, false), SetDataTip(STR_BLACK_STRING, STR_NULL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(WWT_MATRIX, COLOUR_GREY, BAIRW_AIRPORT_LIST), SetFill(true, false), SetDataTip(0x501, STR_NULL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_GREY, BAIRW_SCROLLBAR),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Bottom panel. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BAIRW_BOTTOMPANEL), SetPIP(2, 2, 2),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(true, false),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(14, 0), SetFill(true, false),
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAIRW_BTN_DONTHILIGHT), SetMinimalSize(60, 12), SetFill(1, 0),
 
											SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAIRW_BTN_DOHILIGHT), SetMinimalSize(60, 12), SetFill(1, 0),
 
											SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(14, 0), SetFill(true, false),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), SetFill(1, 0),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_airport_desc(
 
	WDP_AUTO, 0, 0,
 
	WC_BUILD_STATION, WC_BUILD_TOOLBAR,
 
	WDF_CONSTRUCTION,
 
	_nested_build_airport_widgets, lengthof(_nested_build_airport_widgets)
 
);
 

	
 
static void ShowBuildAirportPicker(Window *parent)
 
{
 
	new BuildAirportWindow(&_build_airport_desc, parent);
 
}
 

	
 
void InitializeAirportGui()
 
{
 
	_selected_airport_class = APC_BEGIN;
 
}
src/company_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file company_gui.cpp Company related GUIs. */
 

	
 
#include "stdafx.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "viewport_func.h"
 
#include "company_func.h"
 
#include "command_func.h"
 
#include "network/network.h"
 
#include "network/network_gui.h"
 
#include "network/network_func.h"
 
#include "economy_func.h"
 
#include "vehicle_base.h"
 
#include "newgrf.h"
 
#include "company_manager_face.h"
 
#include "strings_func.h"
 
#include "date_func.h"
 
#include "widgets/dropdown_type.h"
 
#include "tilehighlight_func.h"
 
#include "sprite.h"
 
#include "company_base.h"
 
#include "core/geometry_func.hpp"
 

	
 
#include "table/strings.h"
 

	
 
/** Company GUI constants. */
 
enum {
 
	FIRST_GUI_CALL = INT_MAX,  ///< default value to specify this is the first call of the resizable gui
 
static const int FIRST_GUI_CALL = INT_MAX; ///< default value to specify this is the first call of the resizable gui
 

	
 
	EXP_LINESPACE  = 2,        ///< Amount of vertical space for a horizontal (sub-)total line.
 
	EXP_BLOCKSPACE = 10,       ///< Amount of vertical space between two blocks of numbers.
 
};
 
static const uint EXP_LINESPACE  = 2;      ///< Amount of vertical space for a horizontal (sub-)total line.
 
static const uint EXP_BLOCKSPACE = 10;     ///< Amount of vertical space between two blocks of numbers.
 

	
 
static void DoSelectCompanyManagerFace(Window *parent);
 

	
 
/** Standard unsorted list of expenses. */
 
static ExpensesType _expenses_list_1[] = {
 
	EXPENSES_CONSTRUCTION,
 
	EXPENSES_NEW_VEHICLES,
 
	EXPENSES_TRAIN_RUN,
 
	EXPENSES_ROADVEH_RUN,
 
	EXPENSES_AIRCRAFT_RUN,
 
	EXPENSES_SHIP_RUN,
 
	EXPENSES_PROPERTY,
 
	EXPENSES_TRAIN_INC,
 
	EXPENSES_ROADVEH_INC,
 
	EXPENSES_AIRCRAFT_INC,
 
	EXPENSES_SHIP_INC,
 
	EXPENSES_LOAN_INT,
 
	EXPENSES_OTHER,
 
};
 

	
 
/** Grouped list of expenses. */
 
static ExpensesType _expenses_list_2[] = {
 
	EXPENSES_TRAIN_INC,
 
	EXPENSES_ROADVEH_INC,
 
	EXPENSES_AIRCRAFT_INC,
 
	EXPENSES_SHIP_INC,
 
	INVALID_EXPENSES,
 
	EXPENSES_TRAIN_RUN,
 
	EXPENSES_ROADVEH_RUN,
 
	EXPENSES_AIRCRAFT_RUN,
 
	EXPENSES_SHIP_RUN,
 
	EXPENSES_PROPERTY,
 
	EXPENSES_LOAN_INT,
 
	INVALID_EXPENSES,
 
	EXPENSES_CONSTRUCTION,
 
	EXPENSES_NEW_VEHICLES,
 
	EXPENSES_OTHER,
 
	INVALID_EXPENSES,
 
};
 

	
 
/** Expense list container. */
 
struct ExpensesList {
 
	const ExpensesType *et;   ///< Expenses items.
 
	const uint length;        ///< Number of items in list.
 
	const uint num_subtotals; ///< Number of sub-totals in the list.
 

	
 
	ExpensesList(ExpensesType *et, int length, int num_subtotals) : et(et), length(length), num_subtotals(num_subtotals)
 
	{
 
	}
 

	
 
	uint GetHeight() const
 
	{
 
		/* heading + line + texts of expenses + sub-totals + total line + total text */
 
		return FONT_HEIGHT_NORMAL + EXP_LINESPACE + this->length * FONT_HEIGHT_NORMAL + num_subtotals * (EXP_BLOCKSPACE + EXP_LINESPACE) + EXP_LINESPACE + FONT_HEIGHT_NORMAL;
 
	}
 

	
 
	/** Compute width of the expenses categories in pixels. */
 
	uint GetCategoriesWidth() const
 
	{
 
		uint width = 0;
 
		bool invalid_expenses_measured = false; // Measure 'Total' width only once.
 
		for (uint i = 0; i < this->length; i++) {
 
			ExpensesType et = this->et[i];
 
			if (et == INVALID_EXPENSES) {
 
				if (!invalid_expenses_measured) {
 
					width = max(width, GetStringBoundingBox(STR_FINANCES_TOTAL_CAPTION).width);
 
					invalid_expenses_measured = true;
 
				}
 
			} else {
 
				width = max(width, GetStringBoundingBox(STR_FINANCES_SECTION_CONSTRUCTION + et).width);
 
			}
 
		}
 
		return width;
 
	}
 
};
 

	
 
static const ExpensesList _expenses_list_types[] = {
 
	ExpensesList(_expenses_list_1, lengthof(_expenses_list_1), 0),
 
	ExpensesList(_expenses_list_2, lengthof(_expenses_list_2), 3),
 
};
 

	
 
/** Widgets of the company finances windows. */
 
enum CompanyFinancesWindowWidgets {
 
	CFW_CAPTION,       ///< Caption of the window
 
	CFW_TOGGLE_SIZE,   ///< Toggle windows size
 
	CFW_SEL_PANEL,     ///< Select panel or nothing
 
	CFW_EXPS_CATEGORY, ///< Column for expenses category strings
 
	CFW_EXPS_PRICE1,   ///< Column for year Y-2 expenses
 
	CFW_EXPS_PRICE2,   ///< Column for year Y-1 expenses
 
	CFW_EXPS_PRICE3,   ///< Column for year Y expenses
 
	CFW_TOTAL_PANEL,   ///< Panel for totals
 
	CFW_SEL_MAXLOAN,   ///< Selection of maxloan column
 
	CFW_BALANCE_VALUE, ///< Bank balance value
 
	CFW_LOAN_VALUE,    ///< Loan
 
	CFW_LOAN_LINE,     ///< Line for summing bank balance and loan
 
	CFW_TOTAL_VALUE,   ///< Total
 
	CFW_MAXLOAN_GAP,   ///< Gap above max loan widget
 
	CFW_MAXLOAN_VALUE, ///< Max loan widget
 
	CFW_SEL_BUTTONS,   ///< Selection of buttons
 
	CFW_INCREASE_LOAN, ///< Increase loan
 
	CFW_REPAY_LOAN,    ///< Decrease loan
 
};
 

	
 
/** Draw the expenses categories.
 
 * @param r Available space for drawing.
 
 * @note The environment must provide padding at the left and right of \a r.
 
 */
 
static void DrawCategories(const Rect &r)
 
{
 
	int y = r.top;
 

	
 
	DrawString(r.left, r.right, y, STR_FINANCES_EXPENDITURE_INCOME_TITLE, TC_FROMSTRING, SA_CENTER, true);
 
	y += FONT_HEIGHT_NORMAL + EXP_LINESPACE;
 

	
 
	int type = _settings_client.gui.expenses_layout;
 
	for (uint i = 0; i < _expenses_list_types[type].length; i++) {
 
		const ExpensesType et = _expenses_list_types[type].et[i];
 
		if (et == INVALID_EXPENSES) {
 
			y += EXP_LINESPACE;
 
			DrawString(r.left, r.right, y, STR_FINANCES_TOTAL_CAPTION, TC_FROMSTRING, SA_RIGHT);
 
			y += FONT_HEIGHT_NORMAL + EXP_BLOCKSPACE;
 
		} else {
 
			DrawString(r.left, r.right, y, STR_FINANCES_SECTION_CONSTRUCTION + et);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	DrawString(r.left, r.right, y + EXP_LINESPACE, STR_FINANCES_TOTAL_CAPTION, TC_FROMSTRING, SA_RIGHT);
 
}
 

	
 
/** Draw an amount of money.
 
 * @param amount Amount of money to draw,
 
 * @param left   Left coordinate of the space to draw in.
 
 * @param right  Right coordinate of the space to draw in.
 
 * @param top    Top coordinate of the space to draw in.
 
 */
 
static void DrawPrice(Money amount, int left, int right, int top)
 
{
 
	StringID str = STR_FINANCES_NEGATIVE_INCOME;
 
	if (amount < 0) {
 
		amount = -amount;
 
		str++;
 
	}
 
	SetDParam(0, amount);
 
	DrawString(left, right, top, str, TC_FROMSTRING, SA_RIGHT);
 
}
 

	
 
/** Draw a column with prices.
 
 * @param r    Available space for drawing.
 
 * @param year Year being drawn.
 
 * @param tbl  Pointer to table of amounts for \a year.
 
 * @note The environment must provide padding at the left and right of \a r.
 
 */
 
static void DrawYearColumn(const Rect &r, int year, const Money (*tbl)[EXPENSES_END])
 
{
 
	int y = r.top;
 

	
 
	SetDParam(0, year);
 
	DrawString(r.left, r.right, y, STR_FINANCES_YEAR, TC_FROMSTRING, SA_RIGHT, true);
 
	y += FONT_HEIGHT_NORMAL + EXP_LINESPACE;
 

	
 
	Money sum = 0;
 
	Money subtotal = 0;
 
	int type = _settings_client.gui.expenses_layout;
 
	for (uint i = 0; i < _expenses_list_types[type].length; i++) {
 
		const ExpensesType et = _expenses_list_types[type].et[i];
 
		if (et == INVALID_EXPENSES) {
 
			Money cost = subtotal;
 
			subtotal = 0;
 
			GfxFillRect(r.left, y, r.right, y, 215);
 
			y += EXP_LINESPACE;
 
			DrawPrice(cost, r.left, r.right, y);
 
			y += FONT_HEIGHT_NORMAL + EXP_BLOCKSPACE;
 
		} else {
 
			Money cost = (*tbl)[et];
 
			subtotal += cost;
 
			sum += cost;
 
			if (cost != 0) DrawPrice(cost, r.left, r.right, y);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	GfxFillRect(r.left, y, r.right, y, 215);
 
	y += EXP_LINESPACE;
 
	DrawPrice(sum, r.left, r.right, y);
 
}
 

	
 
static const NWidgetPart _nested_company_finances_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CFW_CAPTION), SetDataTip(STR_FINANCES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, CFW_TOGGLE_SIZE), SetDataTip(SPR_LARGE_SMALL_WINDOW, STR_TOOLTIP_TOGGLE_LARGE_SMALL_WINDOW),
 
		NWidget(WWT_SHADEBOX, COLOUR_GREY),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY),
 
	EndContainer(),
 
	NWidget(NWID_SELECTION, INVALID_COLOUR, CFW_SEL_PANEL),
 
		NWidget(WWT_PANEL, COLOUR_GREY),
 
			NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, WD_FRAMERECT_RIGHT, WD_FRAMERECT_BOTTOM, WD_FRAMERECT_LEFT), SetPIP(0, 9, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_CATEGORY), SetMinimalSize(120, 0), SetFill(0, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE1), SetMinimalSize(86, 0), SetFill(0, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE2), SetMinimalSize(86, 0), SetFill(0, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE3), SetMinimalSize(86, 0), SetFill(0, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY),
 
		NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, WD_FRAMERECT_RIGHT, WD_FRAMERECT_BOTTOM, WD_FRAMERECT_LEFT),
 
			NWidget(NWID_VERTICAL), // Vertical column with 'bank balance', 'loan'
 
				NWidget(WWT_TEXT, COLOUR_GREY), SetDataTip(STR_FINANCES_BANK_BALANCE_TITLE, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_GREY), SetDataTip(STR_FINANCES_LOAN_TITLE, STR_NULL), SetFill(1, 0),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetFill(0, 0), SetMinimalSize(30, 0),
 
			NWidget(NWID_VERTICAL), // Vertical column with bank balance amount, loan amount, and total.
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_BALANCE_VALUE), SetDataTip(STR_NULL, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_LOAN_VALUE), SetDataTip(STR_NULL, STR_NULL),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_LOAN_LINE), SetMinimalSize(0, 2), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_TOTAL_VALUE), SetDataTip(STR_NULL, STR_NULL),
 
			EndContainer(),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, CFW_SEL_MAXLOAN),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(NWID_SPACER), SetFill(0, 1), SetMinimalSize(25, 0),
 
					NWidget(NWID_VERTICAL), // Max loan information
 
						NWidget(WWT_EMPTY, COLOUR_GREY, CFW_MAXLOAN_GAP), SetFill(0, 0),
 
						NWidget(WWT_TEXT, COLOUR_GREY, CFW_MAXLOAN_VALUE), SetDataTip(STR_FINANCES_MAX_LOAN, STR_NULL),
 
						NWidget(NWID_SPACER), SetFill(0, 1),
 
					EndContainer(),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetFill(1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_SELECTION, INVALID_COLOUR, CFW_SEL_BUTTONS),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CFW_INCREASE_LOAN), SetFill(1, 0), SetDataTip(STR_FINANCES_BORROW_BUTTON, STR_FINANCES_BORROW_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CFW_REPAY_LOAN), SetFill(1, 0), SetDataTip(STR_FINANCES_REPAY_BUTTON, STR_FINANCES_REPAY_TOOLTIP),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Window class displaying the company finances.
 
 * @todo #money_width should be calculated dynamically.
 
 */
 
struct CompanyFinancesWindow : Window {
 
	static Money max_money; ///< The maximum amount of money a company has had this 'run'
 
	bool small;             ///< Window is toggled to 'small'.
 

	
 
	CompanyFinancesWindow(const WindowDesc *desc, CompanyID company) : Window()
 
	{
 
		this->small = false;
 
		this->CreateNestedTree(desc);
 
		this->SetupWidgets();
 
		this->FinishInitNested(desc, company);
 

	
 
		this->owner = (Owner)this->window_number;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case CFW_CAPTION:
 
				SetDParam(0, (CompanyID)this->window_number);
 
				SetDParam(1, (CompanyID)this->window_number);
 
				break;
 

	
 
			case CFW_MAXLOAN_VALUE:
 
				SetDParam(0, _economy.max_loan);
 
				break;
 

	
 
			case CFW_INCREASE_LOAN:
 
			case CFW_REPAY_LOAN:
 
				SetDParam(0, LOAN_INTERVAL);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
 
	{
 
		int type = _settings_client.gui.expenses_layout;
 
		switch (widget) {
 
			case CFW_EXPS_CATEGORY:
 
				size->width  = _expenses_list_types[type].GetCategoriesWidth();
 
				size->height = _expenses_list_types[type].GetHeight();
 
				break;
 

	
 
			case CFW_EXPS_PRICE1:
 
			case CFW_EXPS_PRICE2:
 
			case CFW_EXPS_PRICE3:
 
				size->height = _expenses_list_types[type].GetHeight();
 
				/* Fall through */
 
			case CFW_BALANCE_VALUE:
 
			case CFW_LOAN_VALUE:
 
			case CFW_TOTAL_VALUE:
 
				SetDParam(0, CompanyFinancesWindow::max_money);
 
				size->width = max(GetStringBoundingBox(STR_FINANCES_NEGATIVE_INCOME).width, GetStringBoundingBox(STR_FINANCES_POSITIVE_INCOME).width) + padding.width;
 
				break;
 

	
 
			case CFW_MAXLOAN_GAP:
 
				size->height = FONT_HEIGHT_NORMAL;
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case CFW_EXPS_CATEGORY:
 
				DrawCategories(r);
 
				break;
 

	
 
			case CFW_EXPS_PRICE1:
 
			case CFW_EXPS_PRICE2:
 
			case CFW_EXPS_PRICE3: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				int age = min(_cur_year - c->inaugurated_year, 2);
 
				int wid_offset = widget - CFW_EXPS_PRICE1;
 
				if (wid_offset <= age) {
 
					DrawYearColumn(r, _cur_year - (age - wid_offset), c->yearly_expenses + (age - wid_offset));
 
				}
 
				break;
 
			}
 

	
 
			case CFW_BALANCE_VALUE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->money);
 
				DrawString(r.left, r.right, r.top, STR_FINANCES_TOTAL_CURRENCY, TC_FROMSTRING, SA_RIGHT);
 
				break;
 
			}
 

	
 
			case CFW_LOAN_VALUE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->current_loan);
 
				DrawString(r.left, r.right, r.top, STR_FINANCES_TOTAL_CURRENCY, TC_FROMSTRING, SA_RIGHT);
 
				break;
 
			}
 

	
 
			case CFW_TOTAL_VALUE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->money - c->current_loan);
 
				DrawString(r.left, r.right, r.top, STR_FINANCES_TOTAL_CURRENCY, TC_FROMSTRING, SA_RIGHT);
 
				break;
 
			}
 

	
 
			case CFW_LOAN_LINE:
 
				GfxFillRect(r.left, r.top, r.right, r.top, 215);
 
				break;
 
		}
 
	}
 

	
 
	/** Setup the widgets in the nested tree, such that the finances window is displayed properly.
 
	 * @note After setup, the window must be (re-)initialized.
 
	 */
 
	void SetupWidgets()
 
	{
 
		int plane = this->small ? SZSP_NONE : 0;
 
		this->GetWidget<NWidgetStacked>(CFW_SEL_PANEL)->SetDisplayedPlane(plane);
 
		this->GetWidget<NWidgetStacked>(CFW_SEL_MAXLOAN)->SetDisplayedPlane(plane);
 

	
 
		CompanyID company = (CompanyID)this->window_number;
 
		plane = (company != _local_company) ? SZSP_NONE : 0;
 
		this->GetWidget<NWidgetStacked>(CFW_SEL_BUTTONS)->SetDisplayedPlane(plane);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		if (!this->IsShaded()) {
 
			if (!this->small) {
 
				/* Check that the expenses panel height matches the height needed for the layout. */
 
				int type = _settings_client.gui.expenses_layout;
 
				if (_expenses_list_types[type].GetHeight() != this->GetWidget<NWidgetBase>(CFW_EXPS_CATEGORY)->current_y) {
 
					this->SetupWidgets();
 
					this->ReInit();
 
					return;
 
				}
 
			}
 

	
 
			/* Check that the loan buttons are shown only when the user owns the company. */
 
			CompanyID company = (CompanyID)this->window_number;
 
			int req_plane = (company != _local_company) ? SZSP_NONE : 0;
 
			if (req_plane != this->GetWidget<NWidgetStacked>(CFW_SEL_BUTTONS)->shown_plane) {
 
				this->SetupWidgets();
 
				this->ReInit();
 
				return;
 
			}
src/console_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file console_gui.cpp Handling the GUI of the in-game console. */
 

	
 
#include "stdafx.h"
 
#include "textbuf_gui.h"
 
#include "window_gui.h"
 
#include "console_gui.h"
 
#include "console_internal.h"
 
#include "window_func.h"
 
#include "string_func.h"
 
#include "gfx_func.h"
 
#include "settings_type.h"
 
#include "console_func.h"
 
#include "rev.h"
 

	
 

	
 
enum {
 
	ICON_HISTORY_SIZE       = 20,
 
	ICON_LINE_SPACING       =  2,
 
	ICON_RIGHT_BORDERWIDTH  = 10,
 
	ICON_BOTTOM_BORDERWIDTH = 12,
 
};
 
static const uint ICON_HISTORY_SIZE       = 20;
 
static const uint ICON_LINE_SPACING       =  2;
 
static const uint ICON_RIGHT_BORDERWIDTH  = 10;
 
static const uint ICON_BOTTOM_BORDERWIDTH = 12;
 

	
 
/**
 
 * Container for a single line of console output
 
 */
 
struct IConsoleLine {
 
	static IConsoleLine *front; ///< The front of the console backlog buffer
 
	static int size;            ///< The amount of items in the backlog
 

	
 
	IConsoleLine *previous; ///< The previous console message.
 
	char *buffer;           ///< The data to store.
 
	TextColour colour;      ///< The colour of the line.
 
	uint16 time;            ///< The amount of time the line is in the backlog.
 

	
 
	/**
 
	 * Initialize the console line.
 
	 * @param buffer the data to print.
 
	 * @param colour the colour of the line.
 
	 */
 
	IConsoleLine(char *buffer, TextColour colour) :
 
			previous(IConsoleLine::front),
 
			buffer(buffer),
 
			colour(colour),
 
			time(0)
 
	{
 
		IConsoleLine::front = this;
 
		IConsoleLine::size++;
 
	}
 

	
 
	/**
 
	 * Clear this console line and any further ones.
 
	 */
 
	~IConsoleLine()
 
	{
 
		IConsoleLine::size--;
 
		free(buffer);
 

	
 
		delete previous;
 
	}
 

	
 
	/**
 
	 * Get the index-ed item in the list.
 
	 */
 
	static const IConsoleLine *Get(uint index)
 
	{
 
		const IConsoleLine *item = IConsoleLine::front;
 
		while (index != 0 && item != NULL) {
 
			index--;
 
			item = item->previous;
 
		}
 

	
 
		return item;
 
	}
 

	
 
	/**
 
	 * Truncate the list removing everything older than/more than the amount
 
	 * as specified in the config file.
 
	 * As a side effect also increase the time the other lines have been in
 
	 * the list.
 
	 * @return true if and only if items got removed.
 
	 */
 
	static bool Truncate()
 
	{
 
		IConsoleLine *cur = IConsoleLine::front;
 
		if (cur == NULL) return false;
 

	
 
		int count = 1;
 
		for (IConsoleLine *item = cur->previous; item != NULL; count++, cur = item, item = item->previous) {
 
			if (item->time > _settings_client.gui.console_backlog_timeout &&
 
					count > _settings_client.gui.console_backlog_length) {
 
				delete item;
 
				cur->previous = NULL;
 
				return true;
 
			}
 

	
 
			if (item->time != MAX_UVALUE(uint16)) item->time++;
 
		}
 

	
 
		return false;
 
	}
 

	
 
	/**
 
	 * Reset the complete console line backlog.
 
	 */
 
	static void Reset()
 
	{
 
		delete IConsoleLine::front;
 
		IConsoleLine::front = NULL;
 
		IConsoleLine::size = 0;
 
	}
 
};
 

	
 
/* static */ IConsoleLine *IConsoleLine::front = NULL;
 
/* static */ int IConsoleLine::size  = 0;
 

	
 

	
 
/* ** main console cmd buffer ** */
 
static Textbuf _iconsole_cmdline;
 
static char *_iconsole_history[ICON_HISTORY_SIZE];
 
static byte _iconsole_historypos;
 
IConsoleModes _iconsole_mode;
 

	
 
/* *************** *
 
 *  end of header  *
 
 * *************** */
 

	
 
static void IConsoleClearCommand()
 
{
 
	memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE);
 
	_iconsole_cmdline.size = 1; // only terminating zero
 
	_iconsole_cmdline.width = 0;
 
	_iconsole_cmdline.caretpos = 0;
 
	_iconsole_cmdline.caretxoffs = 0;
 
	SetWindowDirty(WC_CONSOLE, 0);
 
}
 

	
 
static inline void IConsoleResetHistoryPos() {_iconsole_historypos = ICON_HISTORY_SIZE - 1;}
 

	
 

	
 
static const char *IConsoleHistoryAdd(const char *cmd);
 
static void IConsoleHistoryNavigate(int direction);
 

	
 
/** Widgets of the console window. */
 
enum ConsoleWidgets {
 
	CW_BACKGROUND, ///< Background of the console
 
};
 

	
 
static const struct NWidgetPart _nested_console_window_widgets[] = {
 
	NWidget(WWT_EMPTY, INVALID_COLOUR, CW_BACKGROUND), SetResize(1, 1),
 
};
 

	
 
static const WindowDesc _console_window_desc(
 
	WDP_MANUAL, 0, 0,
 
	WC_CONSOLE, WC_NONE,
 
	0,
 
	_nested_console_window_widgets, lengthof(_nested_console_window_widgets)
 
);
 

	
 
struct IConsoleWindow : Window
 
{
 
	static int scroll;
 
	int line_height;
 
	int line_offset;
 

	
 
	IConsoleWindow() : Window()
 
	{
 
		_iconsole_mode = ICONSOLE_OPENED;
 
		this->line_height = FONT_HEIGHT_NORMAL + ICON_LINE_SPACING;
 
		this->line_offset = GetStringBoundingBox("] ").width + 5;
 

	
 
		this->InitNested(&_console_window_desc, 0);
 
		ResizeWindow(this, _screen.width, _screen.height / 3);
 
	}
 

	
 
	~IConsoleWindow()
 
	{
 
		_iconsole_mode = ICONSOLE_CLOSED;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const int max = (this->height / this->line_height) - 1;
 
		const int right = this->width - 5;
 

	
 
		const IConsoleLine *print = IConsoleLine::Get(IConsoleWindow::scroll);
 
		GfxFillRect(this->left, this->top, this->width, this->height - 1, 0);
 
		for (int i = 0; i < max && print != NULL; i++, print = print->previous) {
 
			DrawString(5, right, this->height - (2 + i) * this->line_height, print->buffer, print->colour, SA_LEFT | SA_FORCE);
 
		}
 
		/* If the text is longer than the window, don't show the starting ']' */
 
		int delta = this->width - this->line_offset - _iconsole_cmdline.width - ICON_RIGHT_BORDERWIDTH;
 
		if (delta > 0) {
 
			DrawString(5, right, this->height - this->line_height, "]", (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
 
			delta = 0;
 
		}
 

	
 
		DrawString(this->line_offset + delta, right, this->height - this->line_height, _iconsole_cmdline.buf, (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
 

	
 
		if (_focused_window == this && _iconsole_cmdline.caret) {
 
			DrawString(this->line_offset + delta + _iconsole_cmdline.caretxoffs, right, this->height - this->line_height, "_", TC_WHITE, SA_LEFT | SA_FORCE);
 
		}
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		if (IConsoleLine::Truncate() &&
 
				(IConsoleWindow::scroll > IConsoleLine::size)) {
 
			IConsoleWindow::scroll = max(0, IConsoleLine::size - (this->height / this->line_height) + 1);
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		if (HandleCaret(&_iconsole_cmdline)) this->SetDirty();
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		if (_focused_window != this) return ES_NOT_HANDLED;
 

	
 
		const int scroll_height = (this->height / this->line_height) - 1;
 
		switch (keycode) {
 
			case WKC_UP:
 
				IConsoleHistoryNavigate(1);
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_DOWN:
 
				IConsoleHistoryNavigate(-1);
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_SHIFT | WKC_PAGEDOWN:
 
				if (IConsoleWindow::scroll - scroll_height < 0) {
 
					IConsoleWindow::scroll = 0;
 
				} else {
 
					IConsoleWindow::scroll -= scroll_height;
 
				}
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_SHIFT | WKC_PAGEUP:
 
				if (IConsoleWindow::scroll + scroll_height > IConsoleLine::size - scroll_height) {
 
					IConsoleWindow::scroll = IConsoleLine::size - scroll_height;
 
				} else {
 
					IConsoleWindow::scroll += scroll_height;
 
				}
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_SHIFT | WKC_DOWN:
 
				if (IConsoleWindow::scroll <= 0) {
 
					IConsoleWindow::scroll = 0;
 
				} else {
 
					--IConsoleWindow::scroll;
 
				}
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_SHIFT | WKC_UP:
 
				if (IConsoleWindow::scroll >= IConsoleLine::size) {
 
					IConsoleWindow::scroll = IConsoleLine::size;
 
				} else {
 
					++IConsoleWindow::scroll;
 
				}
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_BACKQUOTE:
 
				IConsoleSwitch();
 
				break;
 

	
 
			case WKC_RETURN: case WKC_NUM_ENTER: {
 
				IConsolePrintF(CC_COMMAND, "] %s", _iconsole_cmdline.buf);
 
				const char *cmd = IConsoleHistoryAdd(_iconsole_cmdline.buf);
 
				IConsoleClearCommand();
 

	
 
				if (cmd != NULL) IConsoleCmdExec(cmd);
 
			} break;
 

	
 
			case WKC_CTRL | WKC_RETURN:
 
				_iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL;
 
				IConsoleResize(this);
 
				MarkWholeScreenDirty();
 
				break;
 

	
 
#ifdef WITH_COCOA
 
			case (WKC_META | 'V'):
 
#endif
 
			case (WKC_CTRL | 'V'):
 
				if (InsertTextBufferClipboard(&_iconsole_cmdline)) {
 
					IConsoleResetHistoryPos();
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			case (WKC_CTRL | 'L'):
 
				IConsoleCmdExec("clear");
 
				break;
 

	
 
#ifdef WITH_COCOA
 
			case (WKC_META | 'U'):
 
#endif
 
			case (WKC_CTRL | 'U'):
 
				DeleteTextBufferAll(&_iconsole_cmdline);
 
				this->SetDirty();
 
				break;
 

	
 
			case WKC_BACKSPACE: case WKC_DELETE:
 
				if (DeleteTextBufferChar(&_iconsole_cmdline, keycode)) {
 
					IConsoleResetHistoryPos();
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
 
				if (MoveTextBufferPos(&_iconsole_cmdline, keycode)) {
 
					IConsoleResetHistoryPos();
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			default:
 
				if (IsValidChar(key, CS_ALPHANUMERAL)) {
 
					IConsoleWindow::scroll = 0;
 
					InsertTextBufferChar(&_iconsole_cmdline, key);
 
					IConsoleResetHistoryPos();
 
					this->SetDirty();
 
				} else {
 
					return ES_NOT_HANDLED;
 
				}
 
		}
 
		return ES_HANDLED;
 
	}
 
};
 

	
 
int IConsoleWindow::scroll = 0;
 

	
 
void IConsoleGUIInit()
 
{
 
	_iconsole_historypos = ICON_HISTORY_SIZE - 1;
 
	_iconsole_mode = ICONSOLE_CLOSED;
 

	
 
	IConsoleLine::Reset();
 
	memset(_iconsole_history, 0, sizeof(_iconsole_history));
 

	
 
	_iconsole_cmdline.buf = CallocT<char>(ICON_CMDLN_SIZE); // create buffer and zero it
 
	_iconsole_cmdline.maxsize = ICON_CMDLN_SIZE;
 

	
 
	IConsolePrintF(CC_WARNING, "OpenTTD Game Console Revision 7 - %s", _openttd_revision);
 
	IConsolePrint(CC_WHITE,  "------------------------------------");
 
	IConsolePrint(CC_WHITE,  "use \"help\" for more information");
 
	IConsolePrint(CC_WHITE,  "");
 
	IConsoleClearCommand();
 
}
 

	
 
void IConsoleClearBuffer()
 
{
 
	IConsoleLine::Reset();
 
}
 

	
 
void IConsoleGUIFree()
 
{
 
	free(_iconsole_cmdline.buf);
 
	IConsoleClearBuffer();
 
}
 

	
 
void IConsoleResize(Window *w)
 
{
 
	switch (_iconsole_mode) {
 
		case ICONSOLE_OPENED:
 
			w->height = _screen.height / 3;
 
			w->width = _screen.width;
 
			break;
 
		case ICONSOLE_FULL:
 
			w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH;
 
			w->width = _screen.width;
 
			break;
 
		default: return;
 
	}
 

	
 
	MarkWholeScreenDirty();
 
}
 

	
 
void IConsoleSwitch()
 
{
 
	switch (_iconsole_mode) {
 
		case ICONSOLE_CLOSED:
 
			new IConsoleWindow();
 
			break;
 

	
 
		case ICONSOLE_OPENED: case ICONSOLE_FULL:
 
			DeleteWindowById(WC_CONSOLE, 0);
 
			break;
 
	}
 

	
 
	MarkWholeScreenDirty();
 
}
 

	
 
void IConsoleClose() {if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();}
 

	
 
/**
 
 * Add the entered line into the history so you can look it back
 
 * scroll, etc. Put it to the beginning as it is the latest text
 
 * @param cmd Text to be entered into the 'history'
 
 * @return the command to execute
 
 */
 
static const char *IConsoleHistoryAdd(const char *cmd)
 
{
 
	/* Strip all spaces at the begin */
 
	while (IsWhitespace(*cmd)) cmd++;
 

	
 
	/* Do not put empty command in history */
 
	if (StrEmpty(cmd)) return NULL;
 

	
 
	/* Do not put in history if command is same as previous */
 
	if (_iconsole_history[0] == NULL || strcmp(_iconsole_history[0], cmd) != 0) {
 
		free(_iconsole_history[ICON_HISTORY_SIZE - 1]);
 
		memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1));
 
		_iconsole_history[0] = strdup(cmd);
 
	}
 

	
 
	/* Reset the history position */
 
	IConsoleResetHistoryPos();
 
	return _iconsole_history[0];
 
}
 

	
 
/**
 
 * Navigate Up/Down in the history of typed commands
 
 * @param direction Go further back in history (+1), go to recently typed commands (-1)
 
 */
 
static void IConsoleHistoryNavigate(int direction)
 
{
 
	if (_iconsole_history[0] == NULL) return; // Empty history
 
	int i = _iconsole_historypos + direction;
 

	
 
	/* watch out for overflows, just wrap around */
 
	if (i < 0) i = ICON_HISTORY_SIZE - 1;
 
	if (i >= ICON_HISTORY_SIZE) i = 0;
 
	if ((uint)i >= ICON_HISTORY_SIZE) i = 0;
 

	
 
	if (direction > 0) {
 
		if (_iconsole_history[i] == NULL) i = 0;
 
	}
 

	
 
	if (direction < 0) {
 
		while (i > 0 && _iconsole_history[i] == NULL) i--;
 
	}
 

	
 
	_iconsole_historypos = i;
 
	IConsoleClearCommand();
 
	/* copy history to 'command prompt / bash' */
 
	assert(_iconsole_history[i] != NULL && IsInsideMM(i, 0, ICON_HISTORY_SIZE));
 
	ttd_strlcpy(_iconsole_cmdline.buf, _iconsole_history[i], _iconsole_cmdline.maxsize);
 
	UpdateTextBufferSize(&_iconsole_cmdline);
 
}
 

	
 
/**
 
 * Handle the printing of text entered into the console or redirected there
 
 * by any other means. Text can be redirected to other clients in a network game
 
 * as well as to a logfile. If the network server is a dedicated server, all activities
 
 * are also logged. All lines to print are added to a temporary buffer which can be
 
 * used as a history to print them onscreen
 
 * @param colour_code the colour of the command. Red in case of errors, etc.
 
 * @param str the message entered or output on the console (notice, error, etc.)
 
 */
 
void IConsoleGUIPrint(ConsoleColour colour_code, char *str)
 
{
 
	new IConsoleLine(str, (TextColour)colour_code);
 
	SetWindowDirty(WC_CONSOLE, 0);
 
}
src/currency.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file currency.cpp Support for different currencies. */
 

	
 
#include "stdafx.h"
 
#include "currency.h"
 
#include "news_func.h"
 
#include "settings_type.h"
 
#include "date_func.h"
 

	
 
#include "table/strings.h"
 

	
 
	/*   exchange rate    prefix                      symbol_pos
 
	 *   |  separator        |           postfix          |
 
	 *   |   |   Euro year   |              |             | name
 
	 *   |   |    |          |              |             |  | */
 
static const CurrencySpec origin_currency_specs[NUM_CURRENCY] = {
 
	{    1, "", CF_NOEURO, "\xC2\xA3",     "",           0, STR_GAME_OPTIONS_CURRENCY_GBP    }, ///< british pounds
 
	{    2, "", CF_NOEURO, "$",            "",           0, STR_GAME_OPTIONS_CURRENCY_USD    }, ///< us dollars
 
	{    2, "", CF_ISEURO, "\xE2\x82\xAC", "",           0, STR_GAME_OPTIONS_CURRENCY_EUR    }, ///< Euro
 
	{  220, "", CF_NOEURO, "\xC2\xA5",     "",           0, STR_GAME_OPTIONS_CURRENCY_YEN    }, ///< yen
 
	{   20, "", 2002,      "",             " S.",        1, STR_GAME_OPTIONS_CURRENCY_ATS    }, ///< austrian schilling
 
	{   59, "", 2002,      "BEF ",         "",           0, STR_GAME_OPTIONS_CURRENCY_BEF    }, ///< belgian franc
 
	{    2, "", CF_NOEURO, "CHF ",         "",           0, STR_GAME_OPTIONS_CURRENCY_CHF    }, ///< swiss franc
 
	{   41, "", CF_NOEURO, "",             " K\xC4\x8D", 1, STR_GAME_OPTIONS_CURRENCY_CZK    }, ///< czech koruna
 
	{    3, "", 2002,      "DM ",          "",           0, STR_GAME_OPTIONS_CURRENCY_DEM    }, ///< deutsche mark
 
	{   11, "", CF_NOEURO, "",             " kr",        1, STR_GAME_OPTIONS_CURRENCY_DKK    }, ///< danish krone
 
	{  245, "", 2002,      "Pts ",         "",           0, STR_GAME_OPTIONS_CURRENCY_ESP    }, ///< spanish pesetas
 
	{    9, "", 2002,      "",             " mk",        1, STR_GAME_OPTIONS_CURRENCY_FIM    }, ///< finnish markka
 
	{   10, "", 2002,      "FF ",          "",           0, STR_GAME_OPTIONS_CURRENCY_FRF    }, ///< french francs
 
	{  500, "", 2002,      "",             "Dr.",        1, STR_GAME_OPTIONS_CURRENCY_GRD    }, ///< greek drachma
 
	{  378, "", CF_NOEURO, "",             " Ft",        1, STR_GAME_OPTIONS_CURRENCY_HUF    }, ///< hungarian forint
 
	{  130, "", CF_NOEURO, "",             " Kr",        1, STR_GAME_OPTIONS_CURRENCY_ISK    }, ///< icelandic krona
 
	{ 2850, "", 2002,      "",             " L.",        1, STR_GAME_OPTIONS_CURRENCY_ITL    }, ///< italian lira
 
	{    3, "", 2002,      "NLG ",         "",           0, STR_GAME_OPTIONS_CURRENCY_NLG    }, ///< dutch gulden
 
	{   12, "", CF_NOEURO, "",             " Kr",        1, STR_GAME_OPTIONS_CURRENCY_NOK    }, ///< norwegian krone
 
	{    6, "", CF_NOEURO, "",             " z\xC5\x82", 1, STR_GAME_OPTIONS_CURRENCY_PLN    }, ///< polish zloty
 
	{    5, "", CF_NOEURO, "",             " Lei",       1, STR_GAME_OPTIONS_CURRENCY_RON    }, ///< romanian Lei
 
	{   50, "", CF_NOEURO, "",             " p",         1, STR_GAME_OPTIONS_CURRENCY_RUR    }, ///< russian rouble
 
	{  352, "", 2007,      "",             " SIT",       1, STR_GAME_OPTIONS_CURRENCY_SIT    }, ///< slovenian tolar
 
	{   13, "", CF_NOEURO, "",             " Kr",        1, STR_GAME_OPTIONS_CURRENCY_SEK    }, ///< swedish krona
 
	{    3, "", CF_NOEURO, "",             " TL",        1, STR_GAME_OPTIONS_CURRENCY_TRY    }, ///< turkish lira
 
	{   52, "", 2009,      "",             " Sk",        1, STR_GAME_OPTIONS_CURRENCY_SKK    }, ///< slovak koruna
 
	{    4, "", CF_NOEURO, "R$ ",          "",           0, STR_GAME_OPTIONS_CURRENCY_BRL    }, ///< brazil real
 
	{   20, "", CF_NOEURO, "",             " EEK",       1, STR_GAME_OPTIONS_CURRENCY_EEK    }, ///< estonian krooni
 
	{    1, "", CF_NOEURO, "",             "",           2, STR_GAME_OPTIONS_CURRENCY_CUSTOM }, ///< custom currency
 
};
 

	
 
/* Array of currencies used by the system */
 
CurrencySpec _currency_specs[NUM_CURRENCY];
 

	
 
/**
 
 * These enums are only declared in order to make sens
 
 * out of the TTDPatch_To_OTTDIndex array that will follow
 
 * Every currency used by Ottd is there, just in case TTDPatch will
 
 * add those missing in its code
 
 **/
 
enum {
 
enum Currencies {
 
	CURR_GBP,
 
	CURR_USD,
 
	CURR_EUR,
 
	CURR_YEN,
 
	CURR_ATS,
 
	CURR_BEF,
 
	CURR_CHF,
 
	CURR_CZK,
 
	CURR_DEM,
 
	CURR_DKK,
 
	CURR_ESP,
 
	CURR_FIM,
 
	CURR_FRF,
 
	CURR_GRD,
 
	CURR_HUF,
 
	CURR_ISK,
 
	CURR_ITL,
 
	CURR_NLG,
 
	CURR_NOK,
 
	CURR_PLN,
 
	CURR_RON,
 
	CURR_RUR,
 
	CURR_SIT,
 
	CURR_SEK,
 
	CURR_YTL,
 
	CURR_SKK,
 
	CURR_BRL,
 
	CURR_EEK,
 
};
 

	
 
/**
 
 * This array represent the position of OpenTTD's currencies,
 
 * compared to TTDPatch's ones.
 
 * When a grf sends currencies, they are based on the order defined by TTDPatch.
 
 * So, we must reindex them to our own order.
 
 **/
 
const byte TTDPatch_To_OTTDIndex[] =
 
{
 
	CURR_GBP,
 
	CURR_USD,
 
	CURR_FRF,
 
	CURR_DEM,
 
	CURR_YEN,
 
	CURR_ESP,
 
	CURR_HUF,
 
	CURR_PLN,
 
	CURR_ATS,
 
	CURR_BEF,
 
	CURR_DKK,
 
	CURR_FIM,
 
	CURR_GRD,
 
	CURR_CHF,
 
	CURR_NLG,
 
	CURR_ITL,
 
	CURR_SEK,
 
	CURR_RUR,
 
	CURR_EUR,
 
};
 

	
 
/**
 
 * Will return the ottd's index correspondance to
 
 * the ttdpatch's id.  If the id is bigger than the array,
 
 * it is a grf written for ottd, thus returning the same id.
 
 * Only called from newgrf.cpp
 
 * @param grfcurr_id currency id coming from newgrf
 
 * @return the corrected index
 
 **/
 
byte GetNewgrfCurrencyIdConverted(byte grfcurr_id)
 
{
 
	return (grfcurr_id >= lengthof(TTDPatch_To_OTTDIndex)) ? grfcurr_id : TTDPatch_To_OTTDIndex[grfcurr_id];
 
}
 

	
 
/**
 
 * get a mask of the allowed currencies depending on the year
 
 * @return mask of currencies
 
 */
 
uint GetMaskOfAllowedCurrencies()
 
{
 
	uint mask = 0;
 
	uint i;
 

	
 
	for (i = 0; i < NUM_CURRENCY; i++) {
 
		Year to_euro = _currency_specs[i].to_euro;
 

	
 
		if (to_euro != CF_NOEURO && to_euro != CF_ISEURO && _cur_year >= to_euro) continue;
 
		if (to_euro == CF_ISEURO && _cur_year < 2000) continue;
 
		mask |= (1 << i);
 
	}
 
	mask |= (1 << CUSTOM_CURRENCY_ID); // always allow custom currency
 
	return mask;
 
}
 

	
 
/**
 
 * Verify if the currency chosen by the user is about to be converted to Euro
 
 **/
 
void CheckSwitchToEuro()
 
{
 
	if (_currency_specs[_settings_game.locale.currency].to_euro != CF_NOEURO &&
 
			_currency_specs[_settings_game.locale.currency].to_euro != CF_ISEURO &&
 
			_cur_year >= _currency_specs[_settings_game.locale.currency].to_euro) {
 
		_settings_game.locale.currency = 2; // this is the index of euro above.
 
		AddNewsItem(STR_NEWS_EURO_INTRODUCTION, NS_ECONOMY);
 
	}
 
}
 

	
 
/**
 
 * Will fill _currency_specs array with
 
 * default values from origin_currency_specs
 
 * Called only from newgrf.cpp and settings.cpp.
 
 * @param preserve_custom will not reset custom currency (the latest one on the list)
 
 *        if ever it is flagged to true. In which case, the total size of the memory to move
 
 *        will be one currency spec less, thus preserving the custom curreny from been
 
 *        overwritten.
 
 **/
 
void ResetCurrencies(bool preserve_custom)
 
{
 
	memcpy(&_currency_specs, &origin_currency_specs, sizeof(origin_currency_specs) - (preserve_custom ? sizeof(_custom_currency) : 0));
 
}
 

	
 
/**
 
 * Build a list of currency names StringIDs to use in a dropdown list
 
 * @return Pointer to a (static) array of StringIDs
 
 */
 
StringID *BuildCurrencyDropdown()
 
{
 
	/* Allow room for all currencies, plus a terminator entry */
 
	static StringID names[NUM_CURRENCY + 1];
 
	uint i;
 

	
 
	/* Add each name */
 
	for (i = 0; i < NUM_CURRENCY; i++) {
 
		names[i] = _currency_specs[i].name;
 
	}
 
	/* Terminate the list */
 
	names[i] = INVALID_STRING_ID;
 

	
 
	return names;
 
}
src/date.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file date.cpp Handling of dates in our native format and transforming them to something human readable. */
 

	
 
#include "stdafx.h"
 
#include "variables.h"
 
#include "network/network.h"
 
#include "network/network_func.h"
 
#include "currency.h"
 
#include "window_func.h"
 
#include "functions.h"
 
#include "date_func.h"
 
#include "vehicle_base.h"
 
#include "debug.h"
 
#include "rail_gui.h"
 
#include "saveload/saveload.h"
 

	
 
Year      _cur_year;   ///< Current year, starting at 0
 
Month     _cur_month;  ///< Current month (0..11)
 
Date      _date;       ///< Current date in days (day counter)
 
DateFract _date_fract;
 

	
 

	
 
void SetDate(Date date)
 
{
 
	YearMonthDay ymd;
 

	
 
	_date = date;
 
	ConvertDateToYMD(date, &ymd);
 
	_cur_year = ymd.year;
 
	_cur_month = ymd.month;
 
}
 

	
 
#define M(a, b) ((a << 5) | b)
 
static const uint16 _month_date_from_year_day[] = {
 
	M( 0, 1), M( 0, 2), M( 0, 3), M( 0, 4), M( 0, 5), M( 0, 6), M( 0, 7), M( 0, 8), M( 0, 9), M( 0, 10), M( 0, 11), M( 0, 12), M( 0, 13), M( 0, 14), M( 0, 15), M( 0, 16), M( 0, 17), M( 0, 18), M( 0, 19), M( 0, 20), M( 0, 21), M( 0, 22), M( 0, 23), M( 0, 24), M( 0, 25), M( 0, 26), M( 0, 27), M( 0, 28), M( 0, 29), M( 0, 30), M( 0, 31),
 
	M( 1, 1), M( 1, 2), M( 1, 3), M( 1, 4), M( 1, 5), M( 1, 6), M( 1, 7), M( 1, 8), M( 1, 9), M( 1, 10), M( 1, 11), M( 1, 12), M( 1, 13), M( 1, 14), M( 1, 15), M( 1, 16), M( 1, 17), M( 1, 18), M( 1, 19), M( 1, 20), M( 1, 21), M( 1, 22), M( 1, 23), M( 1, 24), M( 1, 25), M( 1, 26), M( 1, 27), M( 1, 28), M( 1, 29),
 
	M( 2, 1), M( 2, 2), M( 2, 3), M( 2, 4), M( 2, 5), M( 2, 6), M( 2, 7), M( 2, 8), M( 2, 9), M( 2, 10), M( 2, 11), M( 2, 12), M( 2, 13), M( 2, 14), M( 2, 15), M( 2, 16), M( 2, 17), M( 2, 18), M( 2, 19), M( 2, 20), M( 2, 21), M( 2, 22), M( 2, 23), M( 2, 24), M( 2, 25), M( 2, 26), M( 2, 27), M( 2, 28), M( 2, 29), M( 2, 30), M( 2, 31),
 
	M( 3, 1), M( 3, 2), M( 3, 3), M( 3, 4), M( 3, 5), M( 3, 6), M( 3, 7), M( 3, 8), M( 3, 9), M( 3, 10), M( 3, 11), M( 3, 12), M( 3, 13), M( 3, 14), M( 3, 15), M( 3, 16), M( 3, 17), M( 3, 18), M( 3, 19), M( 3, 20), M( 3, 21), M( 3, 22), M( 3, 23), M( 3, 24), M( 3, 25), M( 3, 26), M( 3, 27), M( 3, 28), M( 3, 29), M( 3, 30),
 
	M( 4, 1), M( 4, 2), M( 4, 3), M( 4, 4), M( 4, 5), M( 4, 6), M( 4, 7), M( 4, 8), M( 4, 9), M( 4, 10), M( 4, 11), M( 4, 12), M( 4, 13), M( 4, 14), M( 4, 15), M( 4, 16), M( 4, 17), M( 4, 18), M( 4, 19), M( 4, 20), M( 4, 21), M( 4, 22), M( 4, 23), M( 4, 24), M( 4, 25), M( 4, 26), M( 4, 27), M( 4, 28), M( 4, 29), M( 4, 30), M( 4, 31),
 
	M( 5, 1), M( 5, 2), M( 5, 3), M( 5, 4), M( 5, 5), M( 5, 6), M( 5, 7), M( 5, 8), M( 5, 9), M( 5, 10), M( 5, 11), M( 5, 12), M( 5, 13), M( 5, 14), M( 5, 15), M( 5, 16), M( 5, 17), M( 5, 18), M( 5, 19), M( 5, 20), M( 5, 21), M( 5, 22), M( 5, 23), M( 5, 24), M( 5, 25), M( 5, 26), M( 5, 27), M( 5, 28), M( 5, 29), M( 5, 30),
 
	M( 6, 1), M( 6, 2), M( 6, 3), M( 6, 4), M( 6, 5), M( 6, 6), M( 6, 7), M( 6, 8), M( 6, 9), M( 6, 10), M( 6, 11), M( 6, 12), M( 6, 13), M( 6, 14), M( 6, 15), M( 6, 16), M( 6, 17), M( 6, 18), M( 6, 19), M( 6, 20), M( 6, 21), M( 6, 22), M( 6, 23), M( 6, 24), M( 6, 25), M( 6, 26), M( 6, 27), M( 6, 28), M( 6, 29), M( 6, 30), M( 6, 31),
 
	M( 7, 1), M( 7, 2), M( 7, 3), M( 7, 4), M( 7, 5), M( 7, 6), M( 7, 7), M( 7, 8), M( 7, 9), M( 7, 10), M( 7, 11), M( 7, 12), M( 7, 13), M( 7, 14), M( 7, 15), M( 7, 16), M( 7, 17), M( 7, 18), M( 7, 19), M( 7, 20), M( 7, 21), M( 7, 22), M( 7, 23), M( 7, 24), M( 7, 25), M( 7, 26), M( 7, 27), M( 7, 28), M( 7, 29), M( 7, 30), M( 7, 31),
 
	M( 8, 1), M( 8, 2), M( 8, 3), M( 8, 4), M( 8, 5), M( 8, 6), M( 8, 7), M( 8, 8), M( 8, 9), M( 8, 10), M( 8, 11), M( 8, 12), M( 8, 13), M( 8, 14), M( 8, 15), M( 8, 16), M( 8, 17), M( 8, 18), M( 8, 19), M( 8, 20), M( 8, 21), M( 8, 22), M( 8, 23), M( 8, 24), M( 8, 25), M( 8, 26), M( 8, 27), M( 8, 28), M( 8, 29), M( 8, 30),
 
	M( 9, 1), M( 9, 2), M( 9, 3), M( 9, 4), M( 9, 5), M( 9, 6), M( 9, 7), M( 9, 8), M( 9, 9), M( 9, 10), M( 9, 11), M( 9, 12), M( 9, 13), M( 9, 14), M( 9, 15), M( 9, 16), M( 9, 17), M( 9, 18), M( 9, 19), M( 9, 20), M( 9, 21), M( 9, 22), M( 9, 23), M( 9, 24), M( 9, 25), M( 9, 26), M( 9, 27), M( 9, 28), M( 9, 29), M( 9, 30), M( 9, 31),
 
	M(10, 1), M(10, 2), M(10, 3), M(10, 4), M(10, 5), M(10, 6), M(10, 7), M(10, 8), M(10, 9), M(10, 10), M(10, 11), M(10, 12), M(10, 13), M(10, 14), M(10, 15), M(10, 16), M(10, 17), M(10, 18), M(10, 19), M(10, 20), M(10, 21), M(10, 22), M(10, 23), M(10, 24), M(10, 25), M(10, 26), M(10, 27), M(10, 28), M(10, 29), M(10, 30),
 
	M(11, 1), M(11, 2), M(11, 3), M(11, 4), M(11, 5), M(11, 6), M(11, 7), M(11, 8), M(11, 9), M(11, 10), M(11, 11), M(11, 12), M(11, 13), M(11, 14), M(11, 15), M(11, 16), M(11, 17), M(11, 18), M(11, 19), M(11, 20), M(11, 21), M(11, 22), M(11, 23), M(11, 24), M(11, 25), M(11, 26), M(11, 27), M(11, 28), M(11, 29), M(11, 30), M(11, 31),
 
};
 
#undef M
 

	
 
enum {
 
enum DaysTillMonth {
 
	ACCUM_JAN = 0,
 
	ACCUM_FEB = ACCUM_JAN + 31,
 
	ACCUM_MAR = ACCUM_FEB + 29,
 
	ACCUM_APR = ACCUM_MAR + 31,
 
	ACCUM_MAY = ACCUM_APR + 30,
 
	ACCUM_JUN = ACCUM_MAY + 31,
 
	ACCUM_JUL = ACCUM_JUN + 30,
 
	ACCUM_AUG = ACCUM_JUL + 31,
 
	ACCUM_SEP = ACCUM_AUG + 31,
 
	ACCUM_OCT = ACCUM_SEP + 30,
 
	ACCUM_NOV = ACCUM_OCT + 31,
 
	ACCUM_DEC = ACCUM_NOV + 30,
 
};
 

	
 
static const uint16 _accum_days_for_month[] = {
 
	ACCUM_JAN, ACCUM_FEB, ACCUM_MAR, ACCUM_APR,
 
	ACCUM_MAY, ACCUM_JUN, ACCUM_JUL, ACCUM_AUG,
 
	ACCUM_SEP, ACCUM_OCT, ACCUM_NOV, ACCUM_DEC,
 
};
 

	
 
/**
 
 * Converts a Date to a Year, Month & Day.
 
 * @param date the date to convert from
 
 * @param ymd  the year, month and day to write to
 
 */
 
void ConvertDateToYMD(Date date, YearMonthDay *ymd)
 
{
 
	/*
 
	 * Year determination in multiple steps to account for leap
 
	 * years. First do the large steps, then the smaller ones.
 
	 */
 

	
 
	/* There are 97 leap years in 400 years */
 
	Year yr = 400 * (date / (DAYS_IN_YEAR * 400 + 97));
 
	int rem = date % (DAYS_IN_YEAR * 400 + 97);
 
	uint16 x;
 

	
 
	if (rem >= DAYS_IN_YEAR * 100 + 25) {
 
		/* There are 25 leap years in the first 100 years after
 
		 * every 400th year, as every 400th year is a leap year */
 
		yr  += 100;
 
		rem -= DAYS_IN_YEAR * 100 + 25;
 

	
 
		/* There are 24 leap years in the next couple of 100 years */
 
		yr += 100 * (rem / (DAYS_IN_YEAR * 100 + 24));
 
		rem = (rem % (DAYS_IN_YEAR * 100 + 24));
 
	}
 

	
 
	if (!IsLeapYear(yr) && rem >= DAYS_IN_YEAR * 4) {
 
		/* The first 4 year of the century are not always a leap year */
 
		yr  += 4;
 
		rem -= DAYS_IN_YEAR * 4;
 
	}
 

	
 
	/* There is 1 leap year every 4 years */
 
	yr += 4 * (rem / (DAYS_IN_YEAR * 4 + 1));
 
	rem = rem % (DAYS_IN_YEAR * 4 + 1);
 

	
 
	/* The last (max 3) years to account for; the first one
 
	 * can be, but is not necessarily a leap year */
 
	while (rem >= (IsLeapYear(yr) ? DAYS_IN_LEAP_YEAR : DAYS_IN_YEAR)) {
 
		rem -= IsLeapYear(yr) ? DAYS_IN_LEAP_YEAR : DAYS_IN_YEAR;
 
		yr++;
 
	}
 

	
 
	/* Skip the 29th of February in non-leap years */
 
	if (!IsLeapYear(yr) && rem >= ACCUM_MAR - 1) rem++;
 

	
 
	ymd->year = yr;
 

	
 
	x = _month_date_from_year_day[rem];
 
	ymd->month = x >> 5;
 
	ymd->day = x & 0x1F;
 
}
 

	
 
/**
 
 * Converts a tupe of Year, Month and Day to a Date.
 
 * @param year  is a number between 0..MAX_YEAR
 
 * @param month is a number between 0..11
 
 * @param day   is a number between 1..31
 
 */
 
Date ConvertYMDToDate(Year year, Month month, Day day)
 
{
 
	/* Day-offset in a leap year */
 
	int days = _accum_days_for_month[month] + day - 1;
 

	
 
	/* Account for the missing of the 29th of February in non-leap years */
 
	if (!IsLeapYear(year) && days >= ACCUM_MAR) days--;
 

	
 
	return DAYS_TILL(year) + days;
 
}
 

	
 
/** Functions used by the IncreaseDate function */
 

	
 
extern void EnginesDailyLoop();
 
extern void DisasterDailyLoop();
 
extern void IndustryDailyLoop();
 
extern void CompaniesMonthlyLoop();
 
extern void EnginesMonthlyLoop();
 
extern void TownsMonthlyLoop();
 
extern void IndustryMonthlyLoop();
 
extern void StationMonthlyLoop();
 
extern void SubsidyMonthlyLoop();
 

	
 
extern void CompaniesYearlyLoop();
 
extern void VehiclesYearlyLoop();
 
extern void TownsYearlyLoop();
 

	
 
extern void ShowEndGameChart();
 

	
 

	
 
static const Month _autosave_months[] = {
 
	 0, ///< never
 
	 1, ///< every month
 
	 3, ///< every 3 months
 
	 6, ///< every 6 months
 
	12, ///< every 12 months
 
};
 

	
 
/**
 
 * Runs various procedures that have to be done yearly
 
 */
 
static void OnNewYear()
 
{
 
	CompaniesYearlyLoop();
 
	VehiclesYearlyLoop();
 
	TownsYearlyLoop();
 
	InvalidateWindowClassesData(WC_BUILD_STATION);
 
#ifdef ENABLE_NETWORK
 
	if (_network_server) NetworkServerYearlyLoop();
 
#endif /* ENABLE_NETWORK */
 

	
 
	if (_cur_year == _settings_client.gui.semaphore_build_before) ResetSignalVariant();
 

	
 
	/* check if we reached end of the game */
 
	if (_cur_year == ORIGINAL_END_YEAR) {
 
		ShowEndGameChart();
 
	/* check if we reached the maximum year, decrement dates by a year */
 
	} else if (_cur_year == MAX_YEAR + 1) {
 
		Vehicle *v;
 
		uint days_this_year;
 

	
 
		_cur_year--;
 
		days_this_year = IsLeapYear(_cur_year) ? DAYS_IN_LEAP_YEAR : DAYS_IN_YEAR;
 
		_date -= days_this_year;
 
		FOR_ALL_VEHICLES(v) v->date_of_last_service -= days_this_year;
 

	
 
#ifdef ENABLE_NETWORK
 
		/* Because the _date wraps here, and text-messages expire by game-days, we have to clean out
 
		 *  all of them if the date is set back, else those messages will hang for ever */
 
		NetworkInitChatMessage();
 
#endif /* ENABLE_NETWORK */
 
	}
 

	
 
	if (_settings_client.gui.auto_euro) CheckSwitchToEuro();
 
}
 

	
 
/**
 
 * Runs various procedures that have to be done monthly
 
 */
 
static void OnNewMonth()
 
{
 
	if (_settings_client.gui.autosave != 0 && (_cur_month % _autosave_months[_settings_client.gui.autosave]) == 0) {
 
		_do_autosave = true;
 
		RedrawAutosave();
 
	}
 

	
 
	SetWindowClassesDirty(WC_CHEATS);
 
	CompaniesMonthlyLoop();
 
	SubsidyMonthlyLoop();
 
	EnginesMonthlyLoop();
 
	TownsMonthlyLoop();
 
	IndustryMonthlyLoop();
 
	StationMonthlyLoop();
 
#ifdef ENABLE_NETWORK
 
	if (_network_server) NetworkServerMonthlyLoop();
 
#endif /* ENABLE_NETWORK */
 
}
 

	
 
/**
 
 * Runs various procedures that have to be done daily
 
 */
 
static void OnNewDay()
 
{
 
#ifdef ENABLE_NETWORK
 
	NetworkChatMessageDailyLoop();
 
#endif /* ENABLE_NETWORK */
 

	
 
	DisasterDailyLoop();
 
	IndustryDailyLoop();
 

	
 
	SetWindowWidgetDirty(WC_STATUS_BAR, 0, 0);
 
	EnginesDailyLoop();
 

	
 
	/* Refresh after possible snowline change */
 
	SetWindowClassesDirty(WC_TOWN_VIEW);
 
}
 

	
 
/**
 
 * Increases the tick counter, increases date  and possibly calls
 
 * procedures that have to be called daily, monthly or yearly.
 
 */
 
void IncreaseDate()
 
{
 
	/* increase day, and check if a new day is there? */
 
	_tick_counter++;
 

	
 
	if (_game_mode == GM_MENU) return;
 

	
 
	_date_fract++;
 
	if (_date_fract < DAY_TICKS) return;
 
	_date_fract = 0;
 

	
 
	/* increase day counter and call various daily loops */
 
	_date++;
 
	OnNewDay();
 

	
 
	YearMonthDay ymd;
 

	
 
	/* check if we entered a new month? */
 
	ConvertDateToYMD(_date, &ymd);
 
	if (ymd.month == _cur_month) return;
 

	
 
	/* yes, call various monthly loops */
 
	_cur_month = ymd.month;
 
	OnNewMonth();
 

	
 
	/* check if we entered a new year? */
 
	if (ymd.year == _cur_year) return;
 

	
 
	/* yes, call various yearly loops */
 
	_cur_year = ymd.year;
 
	OnNewYear();
 
}
src/fontcache.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file fontcache.cpp Cache for characters from fonts. */
 

	
 
#include "stdafx.h"
 
#include "fontcache.h"
 
#include "blitter/factory.hpp"
 
#include "core/math_func.hpp"
 

	
 
#include "table/sprites.h"
 
#include "table/control_codes.h"
 

	
 
static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter.
 

	
 
/** Semi-constant for the height of the different sizes of fonts. */
 
int _font_height[FS_END];
 

	
 
#ifdef WITH_FREETYPE
 
#include <ft2build.h>
 
#include FT_FREETYPE_H
 
#include FT_GLYPH_H
 

	
 
#ifdef WITH_FONTCONFIG
 
#include <fontconfig/fontconfig.h>
 
#endif
 

	
 
static FT_Library _library = NULL;
 
static FT_Face _face_small = NULL;
 
static FT_Face _face_medium = NULL;
 
static FT_Face _face_large = NULL;
 
static int _ascender[FS_END];
 

	
 
FreeTypeSettings _freetype;
 

	
 
enum {
 
	FACE_COLOUR = 1,
 
	SHADOW_COLOUR = 2,
 
};
 
static const byte FACE_COLOUR   = 1;
 
static const byte SHADOW_COLOUR = 2;
 

	
 
/** Get the font loaded into a Freetype face by using a font-name.
 
 * If no appropiate font is found, the function returns an error */
 
#ifdef WIN32
 
#include <windows.h>
 
#include <shlobj.h> /* SHGetFolderPath */
 
#include "os/windows/win32.h"
 

	
 
/**
 
 * Get the short DOS 8.3 format for paths.
 
 * FreeType doesn't support Unicode filenames and Windows' fopen (as used
 
 * by FreeType) doesn't support UTF-8 filenames. So we have to convert the
 
 * filename into something that isn't UTF-8 but represents the Unicode file
 
 * name. This is the short DOS 8.3 format. This does not contain any
 
 * characters that fopen doesn't support.
 
 * @param long_path the path in UTF-8.
 
 * @return the short path in ANSI (ASCII).
 
 */
 
char *GetShortPath(const char *long_path)
 
{
 
	static char short_path[MAX_PATH];
 
#ifdef UNICODE
 
	/* The non-unicode GetShortPath doesn't support UTF-8...,
 
	 * so convert the path to wide chars, then get the short
 
	 * path and convert it back again. */
 
	wchar_t long_path_w[MAX_PATH];
 
	MultiByteToWideChar(CP_UTF8, 0, long_path, -1, long_path_w, MAX_PATH);
 

	
 
	wchar_t short_path_w[MAX_PATH];
 
	GetShortPathNameW(long_path_w, short_path_w, MAX_PATH);
 

	
 
	WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, MAX_PATH, NULL, NULL);
 
#else
 
	/* Technically not needed, but do it for consistency. */
 
	GetShortPathNameA(long_path, short_path, MAX_PATH);
 
#endif
 
	return short_path;
 
}
 

	
 
/* Get the font file to be loaded into Freetype by looping the registry
 
 * location where windows lists all installed fonts. Not very nice, will
 
 * surely break if the registry path changes, but it works. Much better
 
 * solution would be to use CreateFont, and extract the font data from it
 
 * by GetFontData. The problem with this is that the font file needs to be
 
 * kept in memory then until the font is no longer needed. This could mean
 
 * an additional memory usage of 30MB (just for fonts!) when using an eastern
 
 * font for all font sizes */
 
#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
 
#define FONT_DIR_9X "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts"
 
static FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
 
{
 
	FT_Error err = FT_Err_Cannot_Open_Resource;
 
	HKEY hKey;
 
	LONG ret;
 
	TCHAR vbuffer[MAX_PATH], dbuffer[256];
 
	TCHAR *font_namep;
 
	char *font_path;
 
	uint index;
 

	
 
	/* On windows NT (2000, NT3.5, XP, etc.) the fonts are stored in the
 
	 * "Windows NT" key, on Windows 9x in the Windows key. To save us having
 
	 * to retrieve the windows version, we'll just query both */
 
	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_NT), 0, KEY_READ, &hKey);
 
	if (ret != ERROR_SUCCESS) ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_9X), 0, KEY_READ, &hKey);
 

	
 
	if (ret != ERROR_SUCCESS) {
 
		DEBUG(freetype, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows (NT)\\CurrentVersion\\Fonts");
 
		return err;
 
	}
 

	
 
	/* For Unicode we need some conversion between widechar and
 
	 * normal char to match the data returned by RegEnumValue,
 
	 * otherwise just use parameter */
 
#if defined(UNICODE)
 
	font_namep = MallocT<TCHAR>(MAX_PATH);
 
	MB_TO_WIDE_BUFFER(font_name, font_namep, MAX_PATH * sizeof(TCHAR));
 
#else
 
	font_namep = const_cast<char *>(font_name); // only cast because in unicode pointer is not const
 
#endif
 

	
 
	for (index = 0;; index++) {
 
		TCHAR *s;
 
		DWORD vbuflen = lengthof(vbuffer);
 
		DWORD dbuflen = lengthof(dbuffer);
 

	
 
		ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, NULL, NULL, (byte*)dbuffer, &dbuflen);
 
		if (ret != ERROR_SUCCESS) goto registry_no_font_found;
 

	
 
		/* The font names in the registry are of the following 3 forms:
 
		 * - ADMUI3.fon
 
		 * - Book Antiqua Bold (TrueType)
 
		 * - Batang & BatangChe & Gungsuh & GungsuhChe (TrueType)
 
		 * We will strip the font-type '()' if any and work with the font name
 
		 * itself, which must match exactly; if...
 
		 * TTC files, font files which contain more than one font are seperated
 
		 * byt '&'. Our best bet will be to do substr match for the fontname
 
		 * and then let FreeType figure out which index to load */
 
		s = _tcschr(vbuffer, _T('('));
 
		if (s != NULL) s[-1] = '\0';
 

	
 
		if (_tcschr(vbuffer, _T('&')) == NULL) {
 
			if (_tcsicmp(vbuffer, font_namep) == 0) break;
 
		} else {
 
			if (_tcsstr(vbuffer, font_namep) != NULL) break;
 
		}
 
	}
 

	
 
	if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, vbuffer))) {
 
		DEBUG(freetype, 0, "SHGetFolderPath cannot return fonts directory");
 
		goto folder_error;
 
	}
 

	
 
	/* Some fonts are contained in .ttc files, TrueType Collection fonts. These
 
	 * contain multiple fonts inside this single file. GetFontData however
 
	 * returns the whole file, so we need to check each font inside to get the
 
	 * proper font.
 
	 * Also note that FreeType does not support UNICODE filesnames! */
 
#if defined(UNICODE)
 
	/* We need a cast here back from wide because FreeType doesn't support
 
	 * widechar filenames. Just use the buffer we allocated before for the
 
	 * font_name search */
 
	font_path = (char*)font_namep;
 
	WIDE_TO_MB_BUFFER(vbuffer, font_path, MAX_PATH * sizeof(TCHAR));
 
#else
 
	font_path = vbuffer;
 
#endif
 

	
 
	ttd_strlcat(font_path, "\\", MAX_PATH * sizeof(TCHAR));
 
	ttd_strlcat(font_path, WIDE_TO_MB(dbuffer), MAX_PATH * sizeof(TCHAR));
 

	
 
	/* Convert the path into something that FreeType understands */
 
	font_path = GetShortPath(font_path);
 

	
 
	index = 0;
 
	do {
 
		err = FT_New_Face(_library, font_path, index, face);
 
		if (err != FT_Err_Ok) break;
 

	
 
		if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break;
 
		/* Try english name if font name failed */
 
		if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break;
 
		err = FT_Err_Cannot_Open_Resource;
 

	
 
	} while ((FT_Long)++index != (*face)->num_faces);
 

	
 

	
 
folder_error:
 
registry_no_font_found:
 
#if defined(UNICODE)
 
	free(font_namep);
 
#endif
 
	RegCloseKey(hKey);
 
	return err;
 
}
 

	
 
/**
 
 * Fonts can have localised names and when the system locale is the same as
 
 * one of those localised names Windows will always return that localised name
 
 * instead of allowing to get the non-localised (English US) name of the font.
 
 * This will later on give problems as freetype uses the non-localised name of
 
 * the font and we need to compare based on that name.
 
 * Windows furthermore DOES NOT have an API to get the non-localised name nor
 
 * can we override the system locale. This means that we have to actually read
 
 * the font itself to gather the font name we want.
 
 * Based on: http://blogs.msdn.com/michkap/archive/2006/02/13/530814.aspx
 
 * @param logfont the font information to get the english name of.
 
 * @return the English name (if it could be found).
 
 */
 
static const char *GetEnglishFontName(const ENUMLOGFONTEX *logfont)
 
{
 
	static char font_name[MAX_PATH];
 
	const char *ret_font_name = NULL;
 
	uint pos = 0;
 
	HDC dc;
 
	HGDIOBJ oldfont;
 
	byte *buf;
 
	DWORD dw;
 
	uint16 format, count, stringOffset, platformId, encodingId, languageId, nameId, length, offset;
 

	
 
	HFONT font = CreateFontIndirect(&logfont->elfLogFont);
 
	if (font == NULL) goto err1;
 

	
 
	dc = GetDC(NULL);
 
	oldfont = SelectObject(dc, font);
 
	dw = GetFontData(dc, 'eman', 0, NULL, 0);
 
	if (dw == GDI_ERROR) goto err2;
 

	
 
	buf = MallocT<byte>(dw);
 
	dw = GetFontData(dc, 'eman', 0, buf, dw);
 
	if (dw == GDI_ERROR) goto err3;
 

	
 
	format = buf[pos++] << 8;
 
	format += buf[pos++];
 
	assert(format == 0);
 
	count = buf[pos++] << 8;
 
	count += buf[pos++];
 
	stringOffset = buf[pos++] << 8;
 
	stringOffset += buf[pos++];
 
	for (uint i = 0; i < count; i++) {
 
		platformId = buf[pos++] << 8;
 
		platformId += buf[pos++];
 
		encodingId = buf[pos++] << 8;
 
		encodingId += buf[pos++];
 
		languageId = buf[pos++] << 8;
 
		languageId += buf[pos++];
 
		nameId = buf[pos++] << 8;
 
		nameId += buf[pos++];
 
		if (nameId != 1) {
 
			pos += 4; // skip length and offset
 
			continue;
 
		}
 
		length = buf[pos++] << 8;
 
		length += buf[pos++];
 
		offset = buf[pos++] << 8;
 
		offset += buf[pos++];
 

	
 
		/* Don't buffer overflow */
 
		length = min(length, MAX_PATH - 1);
 
		for (uint j = 0; j < length; j++) font_name[j] = buf[stringOffset + offset + j];
 
		font_name[length] = '\0';
 

	
 
		if ((platformId == 1 && languageId == 0) ||      // Macintosh English
 
			(platformId == 3 && languageId == 0x0409)) { // Microsoft English (US)
 
			ret_font_name = font_name;
 
			break;
 
		}
 
	}
 

	
 
err3:
 
	free(buf);
 
err2:
 
	SelectObject(dc, oldfont);
 
	ReleaseDC(NULL, dc);
 
err1:
 
	DeleteObject(font);
 

	
 
	return ret_font_name == NULL ? WIDE_TO_MB((const TCHAR*)logfont->elfFullName) : ret_font_name;
 
}
 

	
 
struct EFCParam {
 
	FreeTypeSettings *settings;
 
	LOCALESIGNATURE  locale;
 
};
 

	
 
static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam)
 
{
 
	EFCParam *info = (EFCParam *)lParam;
 

	
 
	/* Only use TrueType fonts */
 
	if (!(type & TRUETYPE_FONTTYPE)) return 1;
 
	/* Don't use SYMBOL fonts */
 
	if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
 

	
 
	/* The font has to have at least one of the supported locales to be usable. */
 
	if ((metric->ntmFontSig.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (metric->ntmFontSig.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) {
 
		/* On win9x metric->ntmFontSig seems to contain garbage. */
 
		FONTSIGNATURE fs;
 
		memset(&fs, 0, sizeof(fs));
 
		HFONT font = CreateFontIndirect(&logfont->elfLogFont);
 
		if (font != NULL) {
 
			HDC dc = GetDC(NULL);
 
			HGDIOBJ oldfont = SelectObject(dc, font);
 
			GetTextCharsetInfo(dc, &fs, 0);
 
			SelectObject(dc, oldfont);
 
			ReleaseDC(NULL, dc);
 
			DeleteObject(font);
 
		}
 
		if ((fs.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (fs.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) return 1;
 
	}
 

	
 
	char font_name[MAX_PATH];
 
#if defined(UNICODE)
 
	WIDE_TO_MB_BUFFER((const TCHAR*)logfont->elfFullName, font_name, lengthof(font_name));
 
#else
 
	strecpy(font_name, (const TCHAR*)logfont->elfFullName, lastof(font_name));
 
#endif
 

	
 
	/* Add english name after font name */
 
	const char *english_name = GetEnglishFontName(logfont);
 
	strecpy(font_name + strlen(font_name) + 1, english_name, lastof(font_name));
 

	
 
	/* Check whether we can actually load the font. */
 
	bool ft_init = _library != NULL;
 
	bool found = false;
 
	FT_Face face;
 
	/* Init FreeType if needed. */
 
	if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName(font_name, &face) == FT_Err_Ok) {
 
		FT_Done_Face(face);
 
		found = true;
 
	}
 
	if (!ft_init) {
 
		/* Uninit FreeType if we did the init. */
 
		FT_Done_FreeType(_library);
 
		_library = NULL;
 
	}
 

	
 
	if (!found) return 1;
 

	
 
	DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name);
 
	strecpy(info->settings->small_font,  font_name, lastof(info->settings->small_font));
 
	strecpy(info->settings->medium_font, font_name, lastof(info->settings->medium_font));
 
	strecpy(info->settings->large_font,  font_name, lastof(info->settings->large_font));
 
	return 0; // stop enumerating
 
}
 

	
 
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, const char *str)
 
{
 
	DEBUG(freetype, 1, "Trying fallback fonts");
 
	EFCParam langInfo;
 
	if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&langInfo.locale, sizeof(langInfo.locale) / sizeof(TCHAR)) == 0) {
 
		/* Invalid langid or some other mysterious error, can't determine fallback font. */
 
		DEBUG(freetype, 1, "Can't get locale info for fallback font (langid=0x%x)", winlangid);
 
		return false;
 
	}
 
	langInfo.settings = settings;
 

	
 
	LOGFONT font;
 
	/* Enumerate all fonts. */
 
	font.lfCharSet = DEFAULT_CHARSET;
 
	font.lfFaceName[0] = '\0';
 
	font.lfPitchAndFamily = 0;
 

	
 
	HDC dc = GetDC(NULL);
 
	int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0);
 
	ReleaseDC(NULL, dc);
 
	return ret == 0;
 
}
 

	
 
#elif defined(__APPLE__)
 

	
 
#include "os/macosx/macos.h"
 
#include <ApplicationServices/ApplicationServices.h>
 

	
 
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
 
{
 
	FT_Error err = FT_Err_Cannot_Open_Resource;
 

	
 
	/* Get font reference from name. */
 
	CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8);
 
	ATSFontRef font = ATSFontFindFromName(name, kATSOptionFlagsDefault);
 
	CFRelease(name);
 
	if (font == kInvalidFont) return err;
 

	
 
	/* Get a file system reference for the font. */
 
	FSRef ref;
 
	OSStatus os_err = -1;
 
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
 
	if (MacOSVersionIsAtLeast(10, 5, 0)) {
 
		os_err = ATSFontGetFileReference(font, &ref);
 
	} else
 
#endif
 
	{
 
#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) && !__LP64__
 
		/* This type was introduced with the 10.5 SDK. */
 
#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)
 
	#define ATSFSSpec FSSpec
 
#endif
 
		FSSpec spec;
 
		os_err = ATSFontGetFileSpecification(font, (ATSFSSpec *)&spec);
 
		if (os_err == noErr) os_err = FSpMakeFSRef(&spec, &ref);
 
#endif
 
	}
 

	
 
	if (os_err == noErr) {
 
		/* Get unix path for file. */
 
		UInt8 file_path[PATH_MAX];
 
		if (FSRefMakePath(&ref, file_path, sizeof(file_path)) == noErr) {
 
			DEBUG(freetype, 3, "Font path for %s: %s", font_name, file_path);
 
			err = FT_New_Face(_library, (const char *)file_path, 0, face);
 
		}
 
	}
 

	
 
	return err;
 
}
 

	
 
bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, const char *str)
 
{
 
	bool result = false;
 

	
 
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
 
	if (MacOSVersionIsAtLeast(10, 5, 0)) {
 
		/* Determine fallback font using CoreText. This uses the language isocode
 
		 * to find a suitable font. CoreText is available from 10.5 onwards. */
 
		char lang[16];
src/gamelog.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file gamelog.cpp Definition of functions used for logging of important changes in the game */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "saveload/saveload.h"
 
#include "core/alloc_func.hpp"
 
#include "variables.h"
 
#include "string_func.h"
 
#include "settings_type.h"
 
#include "gamelog_internal.h"
 
#include "console_func.h"
 
#include "debug.h"
 
#include "rev.h"
 

	
 
#include <stdarg.h>
 

	
 
extern const uint16 SAVEGAME_VERSION;  ///< current savegame version
 

	
 
extern SavegameType _savegame_type; ///< type of savegame we are loading
 

	
 
extern uint32 _ttdp_version;     ///< version of TTDP savegame (if applicable)
 
extern uint16 _sl_version;       ///< the major savegame version identifier
 
extern byte   _sl_minor_version; ///< the minor savegame version, DO NOT USE!
 

	
 

	
 
static GamelogActionType _gamelog_action_type = GLAT_NONE; ///< action to record if anything changes
 

	
 
LoggedAction *_gamelog_action = NULL;        ///< first logged action
 
uint _gamelog_actions         = 0;           ///< number of actions
 
static LoggedAction *_current_action = NULL; ///< current action we are logging, NULL when there is no action active
 

	
 

	
 
/** Stores information about new action, but doesn't allocate it
 
 * Action is allocated only when there is at least one change
 
 * @param at type of action
 
 */
 
void GamelogStartAction(GamelogActionType at)
 
{
 
	assert(_gamelog_action_type == GLAT_NONE); // do not allow starting new action without stopping the previous first
 
	_gamelog_action_type = at;
 
}
 

	
 
/** Stops logging of any changes
 
 */
 
void GamelogStopAction()
 
{
 
	assert(_gamelog_action_type != GLAT_NONE); // nobody should try to stop if there is no action in progress
 

	
 
	bool print = _current_action != NULL;
 

	
 
	_current_action = NULL;
 
	_gamelog_action_type = GLAT_NONE;
 

	
 
	if (print) GamelogPrintDebug(5);
 
}
 

	
 
/** Resets and frees all memory allocated - used before loading or starting a new game
 
 */
 
void GamelogReset()
 
{
 
	assert(_gamelog_action_type == GLAT_NONE);
 

	
 
	for (uint i = 0; i < _gamelog_actions; i++) {
 
		const LoggedAction *la = &_gamelog_action[i];
 
		for (uint j = 0; j < la->changes; j++) {
 
			const LoggedChange *lc = &la->change[j];
 
			if (lc->ct == GLCT_SETTING) free(lc->setting.name);
 
		}
 
		free(la->change);
 
	}
 

	
 
	free(_gamelog_action);
 

	
 
	_gamelog_action  = NULL;
 
	_gamelog_actions = 0;
 
	_current_action  = NULL;
 
}
 

	
 
enum {
 
	GAMELOG_BUF_LEN = 1024 ///< length of buffer for one line of text
 
};
 
static const uint GAMELOG_BUF_LEN = 1024; ///< length of buffer for one line of text
 

	
 
static int _dbgofs = 0; ///< offset in current output buffer
 
static uint _dbgofs = 0; ///< offset in current output buffer
 

	
 
static void AddDebugText(char *buf, const char *s, ...) WARN_FORMAT(2, 3);
 

	
 
static void AddDebugText(char *buf, const char *s, ...)
 
{
 
	if (GAMELOG_BUF_LEN <= _dbgofs) return;
 

	
 
	va_list va;
 

	
 
	va_start(va, s);
 
	_dbgofs += vsnprintf(buf + _dbgofs, GAMELOG_BUF_LEN - _dbgofs, s, va);
 
	va_end(va);
 
}
 

	
 

	
 
/** Prints GRF filename if found
 
 * @param buf The location in the _dbgofs buffer to draw
 
 * @param grfid GRF which filename to print
 
 */
 
static void PrintGrfFilename(char *buf, uint grfid)
 
{
 
	const GRFConfig *gc = FindGRFConfig(grfid);
 

	
 
	if (gc == NULL) return;
 

	
 
	AddDebugText(buf, ", filename: %s", gc->filename);
 
}
 

	
 
/** Prints GRF ID, checksum and filename if found
 
 * @param buf The location in the _dbgofs buffer to draw
 
 * @param grfid GRF ID
 
 * @param md5sum array of md5sum to print
 
 */
 
static void PrintGrfInfo(char *buf, uint grfid, const uint8 *md5sum)
 
{
 
	char txt[40];
 

	
 
	md5sumToString(txt, lastof(txt), md5sum);
 

	
 
	AddDebugText(buf, "GRF ID %08X, checksum %s", BSWAP32(grfid), txt);
 

	
 
	PrintGrfFilename(buf, grfid);
 

	
 
	return;
 
}
 

	
 

	
 
/** Text messages for various logged actions */
 
static const char * const la_text[] = {
 
	"new game started",
 
	"game loaded",
 
	"GRF config changed",
 
	"cheat was used",
 
	"settings changed",
 
	"GRF bug triggered",
 
	"emergency savegame",
 
};
 

	
 
assert_compile(lengthof(la_text) == GLAT_END);
 

	
 

	
 
/**
 
 * Prints active gamelog
 
 * @param proc the procedure to draw with
 
 */
 
void GamelogPrint(GamelogPrintProc *proc)
 
{
 
	char buf[GAMELOG_BUF_LEN];
 

	
 
	proc("---- gamelog start ----");
 

	
 
	const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
 

	
 
	for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
 
		assert((uint)la->at < GLAT_END);
 

	
 
		snprintf(buf, GAMELOG_BUF_LEN, "Tick %u: %s", (uint)la->tick, la_text[(uint)la->at]);
 
		proc(buf);
 

	
 
		const LoggedChange *lcend = &la->change[la->changes];
 

	
 
		for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
 
			_dbgofs = 0;
 
			AddDebugText(buf, "     ");
 

	
 
			switch (lc->ct) {
 
				default: NOT_REACHED();
 
				case GLCT_MODE:
 
					AddDebugText(buf, "New game mode: %u landscape: %u",
 
						(uint)lc->mode.mode, (uint)lc->mode.landscape);
 
					break;
 

	
 
				case GLCT_REVISION:
 
					AddDebugText(buf, "Revision text changed to %s, savegame version %u, ",
 
						lc->revision.text, lc->revision.slver);
 

	
 
					switch (lc->revision.modified) {
 
						case 0: AddDebugText(buf, "not "); break;
 
						case 1: AddDebugText(buf, "maybe "); break;
 
						default: break;
 
					}
 

	
 
					AddDebugText(buf, "modified, _openttd_newgrf_version = 0x%08x", lc->revision.newgrf);
 
					break;
 

	
 
				case GLCT_OLDVER:
 
					AddDebugText(buf, "Conversion from ");
 
					switch (lc->oldver.type) {
 
						default: NOT_REACHED();
 
						case SGT_OTTD:
 
							AddDebugText(buf, "OTTD savegame without gamelog: version %u, %u",
 
								GB(lc->oldver.version, 8, 16), GB(lc->oldver.version, 0, 8));
 
							break;
 

	
 
						case SGT_TTO:
 
							AddDebugText(buf, "TTO savegame");
 
							break;
 

	
 
						case SGT_TTD:
 
							AddDebugText(buf, "TTD savegame");
 
							break;
 

	
 
						case SGT_TTDP1:
 
						case SGT_TTDP2:
 
							AddDebugText(buf, "TTDP savegame, %s format",
 
								lc->oldver.type == SGT_TTDP1 ? "old" : "new");
 
							if (lc->oldver.version != 0) {
 
								AddDebugText(buf, ", TTDP version %u.%u.%u.%u",
 
									GB(lc->oldver.version, 24, 8), GB(lc->oldver.version, 20, 4),
 
									GB(lc->oldver.version, 16, 4), GB(lc->oldver.version, 0, 16));
 
							}
 
							break;
 
					}
 
					break;
 

	
 
				case GLCT_SETTING:
 
					AddDebugText(buf, "Setting changed: %s : %d -> %d", lc->setting.name, lc->setting.oldval, lc->setting.newval);
 
					break;
 

	
 
				case GLCT_GRFADD:
 
					AddDebugText(buf, "Added NewGRF: ");
 
					PrintGrfInfo(buf, lc->grfadd.grfid, lc->grfadd.md5sum);
 
					break;
 

	
 
				case GLCT_GRFREM:
 
					AddDebugText(buf, "Removed NewGRF: %08X", BSWAP32(lc->grfrem.grfid));
 
					PrintGrfFilename(buf, lc->grfrem.grfid);
 
					break;
 

	
 
				case GLCT_GRFCOMPAT:
 
					AddDebugText(buf, "Compatible NewGRF loaded: ");
 
					PrintGrfInfo(buf, lc->grfcompat.grfid, lc->grfcompat.md5sum);
 
					break;
 

	
 
				case GLCT_GRFPARAM:
 
					AddDebugText(buf, "GRF parameter changed: %08X", BSWAP32(lc->grfparam.grfid));
 
					PrintGrfFilename(buf, lc->grfparam.grfid);
 
					break;
 

	
 
				case GLCT_GRFMOVE:
 
					AddDebugText(buf, "GRF order changed: %08X moved %d places %s",
 
						BSWAP32(lc->grfmove.grfid), abs(lc->grfmove.offset), lc->grfmove.offset >= 0 ? "down" : "up" );
 
					PrintGrfFilename(buf, lc->grfmove.grfid);
 
					break;
 

	
 
				case GLCT_GRFBUG:
 
					switch (lc->grfbug.bug) {
 
						default: NOT_REACHED();
 
						case GBUG_VEH_LENGTH:
 
							AddDebugText(buf, "Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(lc->grfbug.grfid), (uint)lc->grfbug.data);
 
							PrintGrfFilename(buf, lc->grfbug.grfid);
 
							break;
 
					}
 

	
 
				case GLCT_EMERGENCY:
 
					break;
 
			}
 

	
 
			proc(buf);
 
		}
 
	}
 

	
 
	proc("---- gamelog end ----");
 
}
 

	
 

	
 
static void GamelogPrintConsoleProc(const char *s)
 
{
 
	IConsolePrint(CC_WARNING, s);
 
}
 

	
 
void GamelogPrintConsole()
 
{
 
	GamelogPrint(&GamelogPrintConsoleProc);
 
}
 

	
 
static int _gamelog_print_level = 0; ///< gamelog debug level we need to print stuff
 

	
 
static void GamelogPrintDebugProc(const char *s)
 
{
 
	DEBUG(gamelog, _gamelog_print_level, "%s", s);
 
}
 

	
 

	
 
/** Prints gamelog to debug output. Code is executed even when
 
 * there will be no output. It is called very seldom, so it
 
 * doesn't matter that much. At least it gives more uniform code...
 
 * @param level debug level we need to print stuff
 
 */
 
void GamelogPrintDebug(int level)
 
{
 
	_gamelog_print_level = level;
 
	GamelogPrint(&GamelogPrintDebugProc);
 
}
 

	
 

	
 
/** Allocates new LoggedChange and new LoggedAction if needed.
 
 * If there is no action active, NULL is returned.
 
 * @param ct type of change
 
 * @return new LoggedChange, or NULL if there is no action active
 
 */
 
static LoggedChange *GamelogChange(GamelogChangeType ct)
 
{
 
	if (_current_action == NULL) {
 
		if (_gamelog_action_type == GLAT_NONE) return NULL;
 

	
 
		_gamelog_action  = ReallocT(_gamelog_action, _gamelog_actions + 1);
 
		_current_action  = &_gamelog_action[_gamelog_actions++];
 

	
 
		_current_action->at      = _gamelog_action_type;
 
		_current_action->tick    = _tick_counter;
 
		_current_action->change  = NULL;
 
		_current_action->changes = 0;
 
	}
 

	
 
	_current_action->change = ReallocT(_current_action->change, _current_action->changes + 1);
 

	
 
	LoggedChange *lc = &_current_action->change[_current_action->changes++];
 
	lc->ct = ct;
 

	
 
	return lc;
 
}
 

	
 

	
 
/** Logs a emergency savegame
 
 */
 
void GamelogEmergency()
 
{
 
	/* Terminate any active action */
 
	if (_gamelog_action_type != GLAT_NONE) GamelogStopAction();
 
	GamelogStartAction(GLAT_EMERGENCY);
 
	GamelogChange(GLCT_EMERGENCY);
 
	GamelogStopAction();
 
}
 

	
 
/** Finds out if current game is a loaded emergency savegame.
 
 */
 
bool GamelogTestEmergency()
 
{
 
	const LoggedChange *emergency = NULL;
 

	
 
	const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
 
	for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
 
		const LoggedChange *lcend = &la->change[la->changes];
 
		for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
 
			if (lc->ct == GLCT_EMERGENCY) emergency = lc;
 
		}
 
	}
 

	
 
	return (emergency != NULL);
 
}
 

	
 
/** Logs a change in game revision
 
 */
 
void GamelogRevision()
 
{
 
	assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD);
 

	
 
	LoggedChange *lc = GamelogChange(GLCT_REVISION);
 
	if (lc == NULL) return;
 

	
 
	memset(lc->revision.text, 0, sizeof(lc->revision.text));
 
	strecpy(lc->revision.text, _openttd_revision, lastof(lc->revision.text));
 
	lc->revision.slver = SAVEGAME_VERSION;
 
	lc->revision.modified = _openttd_revision_modified;
 
	lc->revision.newgrf = _openttd_newgrf_version;
 
}
 

	
 
/** Logs a change in game mode (scenario editor or game)
 
 */
 
void GamelogMode()
 
{
 
	assert(_gamelog_action_type == GLAT_START || _gamelog_action_type == GLAT_LOAD || _gamelog_action_type == GLAT_CHEAT);
 

	
 
	LoggedChange *lc = GamelogChange(GLCT_MODE);
 
	if (lc == NULL) return;
 

	
 
	lc->mode.mode      = _game_mode;
 
	lc->mode.landscape = _settings_game.game_creation.landscape;
 
}
 

	
 
/** Logs loading from savegame without gamelog
 
 */
 
void GamelogOldver()
 
{
 
	assert(_gamelog_action_type == GLAT_LOAD);
 

	
 
	LoggedChange *lc = GamelogChange(GLCT_OLDVER);
 
	if (lc == NULL) return;
 

	
 
	lc->oldver.type = _savegame_type;
 
	lc->oldver.version = (_savegame_type == SGT_OTTD ? ((uint32)_sl_version << 8 | _sl_minor_version) : _ttdp_version);
 
}
 

	
 
/** Logs change in game settings. Only non-networksafe settings are logged
 
 * @param name setting name
 
 * @param oldval old setting value
 
 * @param newval new setting value
 
 */
 
void GamelogSetting(const char *name, int32 oldval, int32 newval)
 
{
 
	assert(_gamelog_action_type == GLAT_SETTING);
 

	
 
	LoggedChange *lc = GamelogChange(GLCT_SETTING);
 
	if (lc == NULL) return;
 

	
 
	lc->setting.name = strdup(name);
 
	lc->setting.oldval = oldval;
 
	lc->setting.newval = newval;
 
}
 

	
 

	
 
/** Finds out if current revision is different than last revision stored in the savegame.
 
 * Appends GLCT_REVISION when the revision string changed
 
 */
 
void GamelogTestRevision()
 
{
 
	const LoggedChange *rev = NULL;
 

	
 
	const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
 
	for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
 
		const LoggedChange *lcend = &la->change[la->changes];
 
		for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
 
			if (lc->ct == GLCT_REVISION) rev = lc;
 
		}
 
	}
 

	
 
	if (rev == NULL || strcmp(rev->revision.text, _openttd_revision) != 0 ||
 
			rev->revision.modified != _openttd_revision_modified ||
 
			rev->revision.newgrf != _openttd_newgrf_version) {
 
		GamelogRevision();
 
	}
 
}
 

	
 
/** Finds last stored game mode or landscape.
 
 * Any change is logged
 
 */
 
void GamelogTestMode()
 
{
 
	const LoggedChange *mode = NULL;
 

	
 
	const LoggedAction *laend = &_gamelog_action[_gamelog_actions];
 
	for (const LoggedAction *la = _gamelog_action; la != laend; la++) {
 
		const LoggedChange *lcend = &la->change[la->changes];
 
		for (const LoggedChange *lc = la->change; lc != lcend; lc++) {
 
			if (lc->ct == GLCT_MODE) mode = lc;
 
		}
 
	}
 

	
 
	if (mode == NULL || mode->mode.mode != _game_mode || mode->mode.landscape != _settings_game.game_creation.landscape) GamelogMode();
 
}
 

	
 

	
 
/** Logs triggered GRF bug.
 
 * @param grfid ID of problematic GRF
 
 * @param bug type of bug, @see enum GRFBugs
 
 * @param data additional data
 
 */
 
static void GamelogGRFBug(uint32 grfid, byte bug, uint64 data)
 
{
 
	assert(_gamelog_action_type == GLAT_GRFBUG);
 

	
 
	LoggedChange *lc = GamelogChange(GLCT_GRFBUG);
 
	if (lc == NULL) return;
src/gfx.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file gfx.cpp Handling of drawing text and other gfx related stuff. */
 

	
 
#include "stdafx.h"
 
#include "gfx_func.h"
 
#include "variables.h"
 
#include "fontcache.h"
 
#include "genworld.h"
 
#include "zoom_func.h"
 
#include "blitter/factory.hpp"
 
#include "video/video_driver.hpp"
 
#include "strings_func.h"
 
#include "settings_type.h"
 
#include "landscape_type.h"
 
#include "network/network.h"
 
#include "network/network_func.h"
 
#include "thread/thread.h"
 
#include "window_func.h"
 
#include "newgrf_debug.h"
 

	
 
#include "table/palettes.h"
 
#include "table/sprites.h"
 
#include "table/control_codes.h"
 

	
 
byte _dirkeys;        ///< 1 = left, 2 = up, 4 = right, 8 = down
 
bool _fullscreen;
 
CursorVars _cursor;
 
bool _ctrl_pressed;   ///< Is Ctrl pressed?
 
bool _shift_pressed;  ///< Is Shift pressed?
 
byte _fast_forward;
 
bool _left_button_down;     ///< Is left mouse button pressed?
 
bool _left_button_clicked;  ///< Is left mouse button clicked?
 
bool _right_button_down;    ///< Is right mouse button pressed?
 
bool _right_button_clicked; ///< Is right mouse button clicked?
 
DrawPixelInfo _screen;
 
bool _screen_disable_anim = false;   ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
 
bool _exit_game;
 
GameMode _game_mode;
 
SwitchMode _switch_mode;  ///< The next mainloop command.
 
PauseModeByte _pause_mode;
 
int _pal_first_dirty;
 
int _pal_count_dirty;
 

	
 
Colour _cur_palette[256];
 

	
 
static int _max_char_height; ///< Cache of the height of the largest font
 
static int _max_char_width;  ///< Cache of the width of the largest font
 
static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
 
DrawPixelInfo *_cur_dpi;
 
byte _colour_gradient[COLOUR_END][8];
 

	
 
static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE);
 

	
 
FontSize _cur_fontsize;
 
static FontSize _last_fontsize;
 
static ReusableBuffer<uint8> _cursor_backup;
 

	
 
/**
 
 * The rect for repaint.
 
 *
 
 * This rectangle defines the area which should be repaint by the video driver.
 
 *
 
 * @ingroup dirty
 
 */
 
static Rect _invalid_rect;
 
static const byte *_colour_remap_ptr;
 
static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures, that ST_FONT sprites only use colours 0 to 2.
 

	
 
enum {
 
	DIRTY_BLOCK_HEIGHT   = 8,
 
	DIRTY_BLOCK_WIDTH    = 64,
 
};
 
static const uint DIRTY_BLOCK_HEIGHT   = 8;
 
static const uint DIRTY_BLOCK_WIDTH    = 64;
 

	
 
static uint _dirty_bytes_per_line = 0;
 
static byte *_dirty_blocks = NULL;
 

	
 
void GfxScroll(int left, int top, int width, int height, int xo, int yo)
 
{
 
	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 

	
 
	if (xo == 0 && yo == 0) return;
 

	
 
	if (_cursor.visible) UndrawMouseCursor();
 

	
 
#ifdef ENABLE_NETWORK
 
	if (_networking) NetworkUndrawChatMessage();
 
#endif /* ENABLE_NETWORK */
 

	
 
	blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
 
	/* This part of the screen is now dirty. */
 
	_video_driver->MakeDirty(left, top, width, height);
 
}
 

	
 

	
 
/**
 
 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
 
 *
 
 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
 
 * @param left Minimum X (inclusive)
 
 * @param top Minimum Y (inclusive)
 
 * @param right Maximum X (inclusive)
 
 * @param bottom Maximum Y (inclusive)
 
 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
 
 * @param mode
 
 *         FILLRECT_OPAQUE:   Fill the rectangle with the specified colour
 
 *         FILLRECT_CHECKER:  Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
 
 *         FILLRECT_RECOLOUR:  Apply a recolour sprite to every pixel in the rectangle currently on screen
 
 */
 
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
 
{
 
	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 
	const DrawPixelInfo *dpi = _cur_dpi;
 
	void *dst;
 
	const int otop = top;
 
	const int oleft = left;
 

	
 
	if (dpi->zoom != ZOOM_LVL_NORMAL) return;
 
	if (left > right || top > bottom) return;
 
	if (right < dpi->left || left >= dpi->left + dpi->width) return;
 
	if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
 

	
 
	if ( (left -= dpi->left) < 0) left = 0;
 
	right = right - dpi->left + 1;
 
	if (right > dpi->width) right = dpi->width;
 
	right -= left;
 
	assert(right > 0);
 

	
 
	if ( (top -= dpi->top) < 0) top = 0;
 
	bottom = bottom - dpi->top + 1;
 
	if (bottom > dpi->height) bottom = dpi->height;
 
	bottom -= top;
 
	assert(bottom > 0);
 

	
 
	dst = blitter->MoveTo(dpi->dst_ptr, left, top);
 

	
 
	switch (mode) {
 
		default: // FILLRECT_OPAQUE
 
			blitter->DrawRect(dst, right, bottom, (uint8)colour);
 
			break;
 

	
 
		case FILLRECT_RECOLOUR:
 
			blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
 
			break;
 

	
 
		case FILLRECT_CHECKER: {
 
			byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
 
			do {
 
				for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
 
				dst = blitter->MoveTo(dst, 0, 1);
 
			} while (--bottom > 0);
 
			break;
 
		}
 
	}
 
}
 

	
 
void GfxDrawLine(int x, int y, int x2, int y2, int colour)
 
{
 
	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 
	DrawPixelInfo *dpi = _cur_dpi;
 

	
 
	x -= dpi->left;
 
	x2 -= dpi->left;
 
	y -= dpi->top;
 
	y2 -= dpi->top;
 

	
 
	/* Check clipping */
 
	if (x < 0 && x2 < 0) return;
 
	if (y < 0 && y2 < 0) return;
 
	if (x > dpi->width  && x2 > dpi->width)  return;
 
	if (y > dpi->height && y2 > dpi->height) return;
 

	
 
	blitter->DrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour);
 
}
 

	
 
void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
 
{
 
	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 
	DrawPixelInfo *dpi = _cur_dpi;
 

	
 
	x -= dpi->left;
 
	x2 -= dpi->left;
 
	y -= dpi->top;
 
	y2 -= dpi->top;
 

	
 
	/* Check clipping */
 
	if (x < 0 && x2 < 0) return;
 
	if (y < 0 && y2 < 0) return;
 
	if (x > dpi->width  && x2 > dpi->width)  return;
 
	if (y > dpi->height && y2 > dpi->height) return;
 

	
 
	blitter->DrawLine(dpi->dst_ptr, UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
 
			UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
 
			UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour);
 
}
 

	
 
/**
 
 * Draws the projection of a parallelepiped.
 
 * This can be used to draw boxes in world coordinates.
 
 *
 
 * @param x   Screen X-coordinate of top front corner.
 
 * @param y   Screen Y-coordinate of top front corner.
 
 * @param dx1 Screen X-length of first edge.
 
 * @param dy1 Screen Y-length of first edge.
 
 * @param dx2 Screen X-length of second edge.
 
 * @param dy2 Screen Y-length of second edge.
 
 * @param dx3 Screen X-length of third edge.
 
 * @param dy3 Screen Y-length of third edge.
 
 */
 
void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
 
{
 
	/*           ....
 
	 *         ..    ....
 
	 *       ..          ....
 
	 *     ..                ^
 
	 *   <--__(dx1,dy1)    /(dx2,dy2)
 
	 *   :    --__       /   :
 
	 *   :        --__ /     :
 
	 *   :            *(x,y) :
 
	 *   :            |      :
 
	 *   :            |     ..
 
	 *    ....        |(dx3,dy3)
 
	 *        ....    | ..
 
	 *            ....V.
 
	 */
 

	
 
	static const byte colour = 255;
 

	
 
	GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
 
	GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
 
	GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
 

	
 
	GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
 
	GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
 
	GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
 
	GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
 
	GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
 
	GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
 
}
 

	
 
/**
 
 * Set the colour remap to be for the given colour.
 
 * @param colour the new colour of the remap.
 
 */
 
static void SetColourRemap(TextColour colour)
 
{
 
	if (colour == TC_INVALID) return;
 

	
 
	if (colour & IS_PALETTE_COLOUR) {
 
		_string_colourremap[1] = colour & ~IS_PALETTE_COLOUR;
 
		_string_colourremap[2] = (_use_palette == PAL_DOS) ? 1 : 215;
 
	} else {
 
		_string_colourremap[1] = _string_colourmap[_use_palette][colour].text;
 
		_string_colourremap[2] = _string_colourmap[_use_palette][colour].shadow;
 
	}
 
	_colour_remap_ptr = _string_colourremap;
 
}
 

	
 
#if !defined(WITH_ICU)
 
typedef WChar UChar;
 
static UChar *HandleBiDiAndArabicShapes(UChar *text) { return text; }
 
#else
 
#include <unicode/ubidi.h>
 
#include <unicode/ushape.h>
 

	
 
/**
 
 * Function to be able to handle right-to-left text and Arabic chars properly.
 
 *
 
 * First: right-to-left (RTL) is stored 'logically' in almost all applications
 
 *        and so do we. This means that their text is stored from right to the
 
 *        left in memory and any non-RTL text (like numbers or English) are
 
 *        then stored from left-to-right. When we want to actually draw the
 
 *        text we need to reverse the RTL text in memory, which is what
 
 *        happens in ubidi_writeReordered.
 
 * Second: Arabic characters "differ" based on their context. To draw the
 
 *        correct variant we pass it through u_shapeArabic. This function can
 
 *        add or remove some characters. This is the reason for the lastof
 
 *        so we know till where we can fill the output.
 
 *
 
 * Sadly enough these functions work with a custom character format, UChar,
 
 * which isn't the same size as WChar. Because of that we need to transform
 
 * our text first to UChars and then back to something we can use.
 
 *
 
 * To be able to truncate strings properly you must truncate before passing to
 
 * this function. This way the logical begin of the string remains and the end
 
 * gets chopped of instead of the other way around.
 
 *
 
 * The reshaping of Arabic characters might increase or decrease the width of
 
 * the characters/string. So it might still overflow after truncation, though
 
 * the chance is fairly slim as most characters get shorter instead of longer.
 
 * @param buffer the buffer to read from/to
 
 * @param lastof the end of the buffer
 
 * @return the buffer to draw from
 
 */
 
static UChar *HandleBiDiAndArabicShapes(UChar *buffer)
 
{
 
	static UChar input_output[DRAW_STRING_BUFFER];
 
	UChar intermediate[DRAW_STRING_BUFFER];
 

	
 
	UChar *t = buffer;
 
	size_t length = 0;
 
	while (*t != '\0' && length < lengthof(input_output) - 1) {
 
		input_output[length++] = *t++;
 
	}
 
	input_output[length] = 0;
 

	
 
	UErrorCode err = U_ZERO_ERROR;
 
	UBiDi *para = ubidi_openSized((int32_t)length, 0, &err);
 
	if (para == NULL) return buffer;
 

	
 
	ubidi_setPara(para, input_output, (int32_t)length, _dynlang.text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &err);
 
	ubidi_writeReordered(para, intermediate, (int32_t)length, UBIDI_REMOVE_BIDI_CONTROLS, &err);
 
	length = u_shapeArabic(intermediate, (int32_t)length, input_output, lengthof(input_output), U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | U_SHAPE_LETTERS_SHAPE, &err);
 
	ubidi_close(para);
 

	
 
	if (U_FAILURE(err)) return buffer;
 

	
 
	input_output[length] = '\0';
 
	return input_output;
 
}
 
#endif /* WITH_ICU */
 

	
 

	
 
/** Truncate a given string to a maximum width if neccessary.
 
 * If the string is truncated, add three dots ('...') to show this.
 
 * @param *str string that is checked and possibly truncated
 
 * @param maxw maximum width in pixels of the string
 
 * @param ignore_setxy whether to ignore SETX(Y) or not
 
 * @return new width of (truncated) string
 
 */
 
static int TruncateString(char *str, int maxw, bool ignore_setxy)
 
{
 
	int w = 0;
 
	FontSize size = _cur_fontsize;
 
	int ddd, ddd_w;
 

	
 
	WChar c;
 
	char *ddd_pos;
 

	
 
	ddd_w = ddd = GetCharacterWidth(size, '.') * 3;
 

	
 
	for (ddd_pos = str; (c = Utf8Consume(const_cast<const char **>(&str))) != '\0'; ) {
 
		if (IsPrintable(c)) {
 
			w += GetCharacterWidth(size, c);
 

	
 
			if (w > maxw) {
 
				/* string got too big... insert dotdotdot, but make sure we do not
 
				 * print anything beyond the string termination character. */
 
				for (int i = 0; *ddd_pos != '\0' && i < 3; i++, ddd_pos++) *ddd_pos = '.';
 
				*ddd_pos = '\0';
 
				return ddd_w;
 
			}
 
		} else {
 
			if (c == SCC_SETX) {
 
				if (!ignore_setxy) w = *str;
 
				str++;
 
			} else if (c == SCC_SETXY) {
 
				if (!ignore_setxy) w = *str;
 
				str += 2;
 
			} else if (c == SCC_TINYFONT) {
 
				size = FS_SMALL;
 
				ddd = GetCharacterWidth(size, '.') * 3;
 
			} else if (c == SCC_BIGFONT) {
 
				size = FS_LARGE;
 
				ddd = GetCharacterWidth(size, '.') * 3;
 
			} else if (c == '\n') {
 
				DEBUG(misc, 0, "Drawing string using newlines with DrawString instead of DrawStringMultiLine. Please notify the developers of this: [%s]", str);
 
			}
 
		}
 

	
 
		/* Remember the last position where three dots fit. */
 
		if (w + ddd < maxw) {
 
			ddd_w = w + ddd;
 
			ddd_pos = str;
 
		}
 
	}
 

	
 
	return w;
 
}
 

	
 
static int ReallyDoDrawString(const UChar *string, int x, int y, TextColour &colour, bool parse_string_also_when_clipped = false);
 

	
 
/**
 
 * Get the real width of the string.
 
 * @param str the string to draw
 
 * @return the width.
 
 */
 
static int GetStringWidth(const UChar *str)
 
{
 
	FontSize size = _cur_fontsize;
 
	int max_width;
 
	int width;
 
	WChar c;
 

	
 
	width = max_width = 0;
 
	for (;;) {
 
		c = *str++;
 
		if (c == 0) break;
 
		if (IsPrintable(c)) {
 
			width += GetCharacterWidth(size, c);
 
		} else {
 
			switch (c) {
 
				case SCC_SETX:
 
				case SCC_SETXY:
 
					/* At this point there is no SCC_SETX(Y) anymore */
 
					NOT_REACHED();
 
					break;
 
				case SCC_TINYFONT: size = FS_SMALL; break;
 
				case SCC_BIGFONT:  size = FS_LARGE; break;
 
				case '\n':
 
					max_width = max(max_width, width);
 
					break;
 
			}
 
		}
 
	}
 

	
 
	return max(max_width, width);
 
}
 

	
 
/**
 
 * Draw string, possibly truncated to make it fit in its allocated space
 
 *
 
 * @param left   The left most position to draw on.
 
 * @param right  The right most position to draw on.
 
 * @param top    The top most position to draw on.
 
 * @param str    String to draw.
 
 * @param last   The end of the string buffer to draw.
 
 * @param colour Colour used for drawing the string, see DoDrawString() for details
 
 * @param align  The alignment of the string when drawing left-to-right. In the
 
 *               case a right-to-left language is chosen this is inverted so it
 
 *               will be drawn in the right direction.
 
 * @param underline Whether to underline what has been drawn or not.
 
 * @param truncate  Whether to truncate the string or not.
 
 *
 
 * @return In case of left or center alignment the right most pixel we have drawn to.
 
 *         In case of right alignment the left most pixel we have drawn to.
 
 */
 
static int DrawString(int left, int right, int top, char *str, const char *last, TextColour colour, StringAlignment align, bool underline = false, bool truncate = true)
 
{
 
	/* We need the outer limits of both left/right */
 
	int min_left = INT32_MAX;
 
	int max_right = INT32_MIN;
 

	
 
	int initial_left = left;
 
	int initial_right = right;
 
	int initial_top = top;
 

	
 
	if (truncate) TruncateString(str, right - left + 1, (align & SA_STRIP) == SA_STRIP);
 

	
 
	/*
 
	 * To support SETX and SETXY properly with RTL languages we have to
 
	 * calculate the offsets from the right. To do this we need to split
 
	 * the string and draw the parts separated by SETX(Y).
 
	 * So here we split
 
	 */
 
	static SmallVector<UChar *, 4> setx_offsets;
 
	setx_offsets.Clear();
 

	
src/industry_cmd.cpp
Show inline comments
 
@@ -1806,729 +1806,727 @@ CommandCost CmdBuildIndustry(TileIndex t
 
		const IndustryTileTable * const *itt = indspec->table;
 
		int num = GB(p1, 8, 8);
 
		if (num >= count) return CMD_ERROR;
 

	
 
		CommandCost ret = CommandCost(STR_ERROR_SITE_UNSUITABLE);
 
		do {
 
			if (--count < 0) return ret;
 
			if (--num < 0) num = indspec->num_table - 1;
 
			ret = CheckIfIndustryTilesAreFree(tile, itt[num], num, it, random_initial_bits);
 
		} while (ret.Failed());
 

	
 
		ret = CreateNewIndustryHelper(tile, it, flags, indspec, num, random_var8f, random_initial_bits, _current_company, &ind);
 
		if (ret.Failed()) return ret;
 
	}
 

	
 
	if ((flags & DC_EXEC) && ind != NULL && _game_mode != GM_EDITOR) {
 
		/* Created a new industry in-game, advertise the event. */
 
		SetDParam(0, indspec->name);
 
		if (indspec->new_industry_text > STR_LAST_STRINGID) {
 
			SetDParam(1, STR_TOWN_NAME);
 
			SetDParam(2, ind->town->index);
 
		} else {
 
			SetDParam(1, ind->town->index);
 
		}
 
		AddIndustryNewsItem(indspec->new_industry_text, NS_INDUSTRY_OPEN, ind->index);
 
		AI::BroadcastNewEvent(new AIEventIndustryOpen(ind->index));
 
	}
 

	
 
	return CommandCost(EXPENSES_OTHER, indspec->GetConstructionCost());
 
}
 

	
 

	
 
static Industry *CreateNewIndustry(TileIndex tile, IndustryType type)
 
{
 
	const IndustrySpec *indspec = GetIndustrySpec(type);
 

	
 
	uint32 seed = Random();
 
	uint32 seed2 = Random();
 
	Industry *i = NULL;
 
	CommandCost ret = CreateNewIndustryHelper(tile, type, DC_EXEC, indspec, RandomRange(indspec->num_table), seed, GB(seed2, 0, 16), OWNER_NONE, &i);
 
	assert(i != NULL || ret.Failed());
 
	return i;
 
}
 

	
 
/**
 
 * Compute the appearance probability for an industry during map creation.
 
 * @param it Industrytype to compute for
 
 * @param force_at_least_one Returns whether at least one instance should be forced on map creation
 
 * @return relative probability for the industry to appear
 
 */
 
static uint32 GetScaledIndustryProbability(IndustryType it, bool *force_at_least_one)
 
{
 
	const IndustrySpec *ind_spc = GetIndustrySpec(it);
 
	uint32 chance = ind_spc->appear_creation[_settings_game.game_creation.landscape] * 16; // * 16 to increase precision
 
	if (!ind_spc->enabled || chance == 0 || ind_spc->num_table == 0 ||
 
			!CheckIfCallBackAllowsAvailability(it, IACT_MAPGENERATION) || _settings_game.difficulty.number_industries == 0) {
 
		*force_at_least_one = false;
 
		return 0;
 
	} else {
 
		/* We want industries appearing at coast to appear less often on bigger maps, as length of coast increases slower than map area.
 
		 * For simplicity we scale in both cases, though scaling the probabilities of all industries has no effect. */
 
		chance = (ind_spc->check_proc == CHECK_REFINERY || ind_spc->check_proc == CHECK_OIL_RIG) ? ScaleByMapSize1D(chance) : ScaleByMapSize(chance);
 

	
 
		*force_at_least_one = (chance > 0) && !(ind_spc->behaviour & INDUSTRYBEH_NOBUILT_MAPCREATION);
 
		return chance;
 
	}
 
}
 

	
 
/** Number of industries on a 256x256 map */
 
static const byte _numof_industry_table[]= {
 
	0,    // none
 
	10,   // very low
 
	25,   // low
 
	55,   // normal
 
	80,   // high
 
};
 

	
 
/**
 
 * Try to build a industry on the map.
 
 * @param type IndustryType of the desired industry
 
 * @param try_hard Try very hard to find a place. (Used to place at least one industry per type)
 
 */
 
static void PlaceInitialIndustry(IndustryType type, bool try_hard)
 
{
 
	CompanyID old_company = _current_company;
 
	_current_company = OWNER_NONE;
 

	
 
	IncreaseGeneratingWorldProgress(GWP_INDUSTRY);
 

	
 
	for (uint i = 0; i < (try_hard ? 10000u : 2000u); i++) {
 
		if (CreateNewIndustry(RandomTile(), type) != NULL) break;
 
	}
 

	
 
	_current_company = old_company;
 
}
 

	
 
/**
 
 * This function will create random industries during game creation.
 
 * It will scale the amount of industries by mapsize and difficulty level.
 
 */
 
void GenerateIndustries()
 
{
 
	assert(_settings_game.difficulty.number_industries < lengthof(_numof_industry_table));
 
	uint total_amount = ScaleByMapSize(_numof_industry_table[_settings_game.difficulty.number_industries]);
 

	
 
	/* Do not create any industries? */
 
	if (total_amount == 0) return;
 

	
 
	uint32 industry_probs[NUM_INDUSTRYTYPES];
 
	bool force_at_least_one[NUM_INDUSTRYTYPES];
 
	uint32 total_prob = 0;
 
	uint num_forced = 0;
 

	
 
	for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
 
		industry_probs[it] = GetScaledIndustryProbability(it, force_at_least_one + it);
 
		total_prob += industry_probs[it];
 
		if (force_at_least_one[it]) num_forced++;
 
	}
 

	
 
	if (total_prob == 0 || total_amount < num_forced) {
 
		/* Only place the forced ones */
 
		total_amount = num_forced;
 
	}
 

	
 
	SetGeneratingWorldProgress(GWP_INDUSTRY, total_amount);
 

	
 
	/* Try to build one industry per type independent of any probabilities */
 
	for (IndustryType it = 0; it < NUM_INDUSTRYTYPES; it++) {
 
		if (force_at_least_one[it]) {
 
			assert(total_amount > 0);
 
			total_amount--;
 
			PlaceInitialIndustry(it, true);
 
		}
 
	}
 

	
 
	/* Add the remaining industries according to their probabilities */
 
	for (uint i = 0; i < total_amount; i++) {
 
		uint32 r = RandomRange(total_prob);
 
		IndustryType it = 0;
 
		while (it < NUM_INDUSTRYTYPES && r >= industry_probs[it]) {
 
			r -= industry_probs[it];
 
			it++;
 
		}
 
		assert(it < NUM_INDUSTRYTYPES && industry_probs[it] > 0);
 
		PlaceInitialIndustry(it, false);
 
	}
 
}
 

	
 
static void UpdateIndustryStatistics(Industry *i)
 
{
 
	for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
 
		if (i->produced_cargo[j] != CT_INVALID) {
 
			byte pct = 0;
 
			if (i->this_month_production[j] != 0) {
 
				i->last_prod_year = _cur_year;
 
				pct = min(i->this_month_transported[j] * 256 / i->this_month_production[j], 255);
 
			}
 
			i->last_month_pct_transported[j] = pct;
 

	
 
			i->last_month_production[j] = i->this_month_production[j];
 
			i->this_month_production[j] = 0;
 

	
 
			i->last_month_transported[j] = i->this_month_transported[j];
 
			i->this_month_transported[j] = 0;
 
		}
 
	}
 
}
 

	
 
/** Simple helper that will collect data for the generation of industries */
 
struct ProbabilityHelper {
 
	uint16 prob;      ///< probability
 
	IndustryType ind; ///< industry id correcponding
 
};
 

	
 
/**
 
 * Try to create a random industry, during gameplay
 
 */
 
static void MaybeNewIndustry()
 
{
 
	Industry *ind;               // will receive the industry's creation pointer
 
	IndustryType rndtype, j;     // Loop controlers
 
	const IndustrySpec *ind_spc;
 
	uint num = 0;
 
	ProbabilityHelper cumulative_probs[NUM_INDUSTRYTYPES]; // probability collector
 
	uint16 probability_max = 0;
 

	
 
	/* Generate a list of all possible industries that can be built. */
 
	for (j = 0; j < NUM_INDUSTRYTYPES; j++) {
 
		ind_spc = GetIndustrySpec(j);
 
		byte chance = ind_spc->appear_ingame[_settings_game.game_creation.landscape];
 

	
 
		if (!ind_spc->enabled || chance == 0 || ind_spc->num_table == 0) continue;
 

	
 
		/* If there is no Callback CBID_INDUSTRY_AVAILABLE or if this one did anot failed,
 
		 * and if appearing chance for this landscape is above 0, this industry can be chosen */
 
		if (CheckIfCallBackAllowsAvailability(j, IACT_RANDOMCREATION)) {
 
			probability_max += chance;
 
			/* adds the result for this industry */
 
			cumulative_probs[num].ind = j;
 
			cumulative_probs[num++].prob = probability_max;
 
		}
 
	}
 

	
 
	/* Abort if there is no industry buildable */
 
	if (probability_max == 0) return;
 

	
 
	/* Find a random type, with maximum being what has been evaluate above*/
 
	rndtype = RandomRange(probability_max);
 
	for (j = 0; j < NUM_INDUSTRYTYPES; j++) {
 
		/* and choose the index of the industry that matches as close as possible this random type */
 
		if (cumulative_probs[j].prob >= rndtype) break;
 
	}
 

	
 
	ind_spc = GetIndustrySpec(cumulative_probs[j].ind);
 
	/*  Check if it is allowed */
 
	if ((ind_spc->behaviour & INDUSTRYBEH_BEFORE_1950) && _cur_year > 1950) return;
 
	if ((ind_spc->behaviour & INDUSTRYBEH_AFTER_1960) && _cur_year < 1960) return;
 

	
 
	/* try to create 2000 times this industry */
 
	num = 2000;
 
	for (;;) {
 
		ind = CreateNewIndustry(RandomTile(), cumulative_probs[j].ind);
 
		if (ind != NULL) break;
 
		if (--num == 0) return;
 
	}
 

	
 
	SetDParam(0, ind_spc->name);
 
	if (ind_spc->new_industry_text > STR_LAST_STRINGID) {
 
		SetDParam(1, STR_TOWN_NAME);
 
		SetDParam(2, ind->town->index);
 
	} else {
 
		SetDParam(1, ind->town->index);
 
	}
 
	AddIndustryNewsItem(ind_spc->new_industry_text, NS_INDUSTRY_OPEN, ind->index);
 
	AI::BroadcastNewEvent(new AIEventIndustryOpen(ind->index));
 
}
 

	
 
/**
 
 * Protects an industry from closure if the appropriate flags and conditions are met
 
 * INDUSTRYBEH_CANCLOSE_LASTINSTANCE must be set (which, by default, it is not) and the
 
 * count of industries of this type must one (or lower) in order to be protected
 
 * against closure.
 
 * @param type IndustryType been queried
 
 * @result true if protection is on, false otherwise (except for oil wells)
 
 */
 
static bool CheckIndustryCloseDownProtection(IndustryType type)
 
{
 
	const IndustrySpec *indspec = GetIndustrySpec(type);
 

	
 
	/* oil wells (or the industries with that flag set) are always allowed to closedown */
 
	if ((indspec->behaviour & INDUSTRYBEH_DONT_INCR_PROD) && _settings_game.game_creation.landscape == LT_TEMPERATE) return false;
 
	return (indspec->behaviour & INDUSTRYBEH_CANCLOSE_LASTINSTANCE) == 0 && Industry::GetIndustryTypeCount(type) <= 1;
 
}
 

	
 
/**
 
 * Can given cargo type be accepted or produced by the industry?
 
 * @param cargo: Cargo type
 
 * @param ind: Industry
 
 * @param *c_accepts: Pointer to boolean for acceptance of cargo
 
 * @param *c_produces: Pointer to boolean for production of cargo
 
 * @return: \c *c_accepts is set when industry accepts the cargo type,
 
 *          \c *c_produces is set when the industry produces the cargo type
 
 */
 
static void CanCargoServiceIndustry(CargoID cargo, Industry *ind, bool *c_accepts, bool *c_produces)
 
{
 
	const IndustrySpec *indspec = GetIndustrySpec(ind->type);
 

	
 
	/* Check for acceptance of cargo */
 
	for (byte j = 0; j < lengthof(ind->accepts_cargo); j++) {
 
		if (ind->accepts_cargo[j] == CT_INVALID) continue;
 
		if (cargo == ind->accepts_cargo[j]) {
 
			if (HasBit(indspec->callback_mask, CBM_IND_REFUSE_CARGO)) {
 
				uint16 res = GetIndustryCallback(CBID_INDUSTRY_REFUSE_CARGO,
 
						0, GetReverseCargoTranslation(cargo, indspec->grf_prop.grffile),
 
						ind, ind->type, ind->location.tile);
 
				if (res == 0) continue;
 
			}
 
			*c_accepts = true;
 
			break;
 
		}
 
	}
 

	
 
	/* Check for produced cargo */
 
	for (byte j = 0; j < lengthof(ind->produced_cargo); j++) {
 
		if (ind->produced_cargo[j] == CT_INVALID) continue;
 
		if (cargo == ind->produced_cargo[j]) {
 
			*c_produces = true;
 
			break;
 
		}
 
	}
 
}
 

	
 
/**
 
 * Compute who can service the industry.
 
 *
 
 * Here, 'can service' means that he/she has trains and stations close enough
 
 * to the industry with the right cargo type and the right orders (ie has the
 
 * technical means).
 
 *
 
 * @param ind: Industry being investigated.
 
 *
 
 * @return: 0 if nobody can service the industry, 2 if the local company can
 
 * service the industry, and 1 otherwise (only competitors can service the
 
 * industry)
 
 */
 
static int WhoCanServiceIndustry(Industry *ind)
 
{
 
	/* Find all stations within reach of the industry */
 
	StationList stations;
 
	FindStationsAroundTiles(ind->location, &stations);
 

	
 
	if (stations.Length() == 0) return 0; // No stations found at all => nobody services
 

	
 
	const Vehicle *v;
 
	int result = 0;
 
	FOR_ALL_VEHICLES(v) {
 
		/* Is it worthwhile to try this vehicle? */
 
		if (v->owner != _local_company && result != 0) continue;
 

	
 
		/* Check whether it accepts the right kind of cargo */
 
		bool c_accepts = false;
 
		bool c_produces = false;
 
		if (v->type == VEH_TRAIN && Train::From(v)->IsFrontEngine()) {
 
			for (const Vehicle *u = v; u != NULL; u = u->Next()) {
 
				CanCargoServiceIndustry(u->cargo_type, ind, &c_accepts, &c_produces);
 
			}
 
		} else if (v->type == VEH_ROAD || v->type == VEH_SHIP || v->type == VEH_AIRCRAFT) {
 
			CanCargoServiceIndustry(v->cargo_type, ind, &c_accepts, &c_produces);
 
		} else {
 
			continue;
 
		}
 
		if (!c_accepts && !c_produces) continue; // Wrong cargo
 

	
 
		/* Check orders of the vehicle.
 
		 * We cannot check the first of shared orders only, since the first vehicle in such a chain
 
		 * may have a different cargo type.
 
		 */
 
		const Order *o;
 
		FOR_VEHICLE_ORDERS(v, o) {
 
			if (o->IsType(OT_GOTO_STATION) && !(o->GetUnloadType() & OUFB_TRANSFER)) {
 
				/* Vehicle visits a station to load or unload */
 
				Station *st = Station::Get(o->GetDestination());
 
				assert(st != NULL);
 

	
 
				/* Same cargo produced by industry is dropped here => not serviced by vehicle v */
 
				if ((o->GetUnloadType() & OUFB_UNLOAD) && !c_accepts) break;
 

	
 
				if (stations.Contains(st)) {
 
					if (v->owner == _local_company) return 2; // Company services industry
 
					result = 1; // Competitor services industry
 
				}
 
			}
 
		}
 
	}
 
	return result;
 
}
 

	
 
/**
 
 * Report news that industry production has changed significantly
 
 *
 
 * @param ind: Industry with changed production
 
 * @param type: Cargo type that has changed
 
 * @param percent: Percentage of change (>0 means increase, <0 means decrease)
 
 */
 
static void ReportNewsProductionChangeIndustry(Industry *ind, CargoID type, int percent)
 
{
 
	NewsSubtype ns;
 

	
 
	switch (WhoCanServiceIndustry(ind)) {
 
		case 0: ns = NS_INDUSTRY_NOBODY;  break;
 
		case 1: ns = NS_INDUSTRY_OTHER;   break;
 
		case 2: ns = NS_INDUSTRY_COMPANY; break;
 
		default: NOT_REACHED();
 
	}
 
	SetDParam(2, abs(percent));
 
	SetDParam(0, CargoSpec::Get(type)->name);
 
	SetDParam(1, ind->index);
 
	AddIndustryNewsItem(
 
		percent >= 0 ? STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_SMOOTH : STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_SMOOTH,
 
		ns,
 
		ind->index
 
	);
 
}
 

	
 
enum {
 
	PERCENT_TRANSPORTED_60 = 153,
 
	PERCENT_TRANSPORTED_80 = 204,
 
};
 
static const uint PERCENT_TRANSPORTED_60 = 153;
 
static const uint PERCENT_TRANSPORTED_80 = 204;
 

	
 
/** Change industry production or do closure
 
 * @param i Industry for which changes are performed
 
 * @param monthly true if it's the monthly call, false if it's the random call
 
 */
 
static void ChangeIndustryProduction(Industry *i, bool monthly)
 
{
 
	StringID str = STR_NULL;
 
	bool closeit = false;
 
	const IndustrySpec *indspec = GetIndustrySpec(i->type);
 
	bool standard = false;
 
	bool suppress_message = false;
 
	bool recalculate_multipliers = false; ///< reinitialize production_rate to match prod_level
 
	/* don't use smooth economy for industries using production related callbacks */
 
	bool smooth_economy = _settings_game.economy.smooth_economy &&
 
	                      !(HasBit(indspec->callback_mask, CBM_IND_PRODUCTION_256_TICKS) || HasBit(indspec->callback_mask, CBM_IND_PRODUCTION_CARGO_ARRIVAL)) && // production callbacks
 
	                      !(HasBit(indspec->callback_mask, CBM_IND_MONTHLYPROD_CHANGE) || HasBit(indspec->callback_mask, CBM_IND_PRODUCTION_CHANGE));            // production change callbacks
 
	byte div = 0;
 
	byte mul = 0;
 
	int8 increment = 0;
 

	
 
	bool callback_enabled = HasBit(indspec->callback_mask, monthly ? CBM_IND_MONTHLYPROD_CHANGE : CBM_IND_PRODUCTION_CHANGE);
 
	if (callback_enabled) {
 
		uint16 res = GetIndustryCallback(monthly ? CBID_INDUSTRY_MONTHLYPROD_CHANGE : CBID_INDUSTRY_PRODUCTION_CHANGE, 0, Random(), i, i->type, i->location.tile);
 
		if (res != CALLBACK_FAILED) { // failed callback means "do nothing"
 
			suppress_message = HasBit(res, 7);
 
			/* Get the custom message if any */
 
			if (HasBit(res, 8)) str = MapGRFStringID(indspec->grf_prop.grffile->grfid, GB(GetRegister(0x100), 0, 16));
 
			res = GB(res, 0, 4);
 
			switch (res) {
 
				default: NOT_REACHED();
 
				case 0x0: break;                  // Do nothing, but show the custom message if any
 
				case 0x1: div = 1; break;         // Halve industry production. If production reaches the quarter of the default, the industry is closed instead.
 
				case 0x2: mul = 1; break;         // Double industry production if it hasn't reached eight times of the original yet.
 
				case 0x3: closeit = true; break;  // The industry announces imminent closure, and is physically removed from the map next month.
 
				case 0x4: standard = true; break; // Do the standard random production change as if this industry was a primary one.
 
				case 0x5: case 0x6: case 0x7:     // Divide production by 4, 8, 16
 
				case 0x8: div = res - 0x3; break; // Divide production by 32
 
				case 0x9: case 0xA: case 0xB:     // Multiply production by 4, 8, 16
 
				case 0xC: mul = res - 0x7; break; // Multiply production by 32
 
				case 0xD:                         // decrement production
 
				case 0xE:                         // increment production
 
					increment = res == 0x0D ? -1 : 1;
 
					break;
 
				case 0xF:                         // Set production to third byte of register 0x100
 
					i->prod_level = Clamp(GB(GetRegister(0x100), 16, 8), PRODLEVEL_MINIMUM, PRODLEVEL_MAXIMUM);
 
					recalculate_multipliers = true;
 
					break;
 
			}
 
		}
 
	} else {
 
		if (monthly != smooth_economy) return;
 
		if (indspec->life_type == INDUSTRYLIFE_BLACK_HOLE) return;
 
	}
 

	
 
	if (standard || (!callback_enabled && (indspec->life_type & (INDUSTRYLIFE_ORGANIC | INDUSTRYLIFE_EXTRACTIVE)) != 0)) {
 
		/* decrease or increase */
 
		bool only_decrease = (indspec->behaviour & INDUSTRYBEH_DONT_INCR_PROD) && _settings_game.game_creation.landscape == LT_TEMPERATE;
 

	
 
		if (smooth_economy) {
 
			closeit = true;
 
			for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
 
				if (i->produced_cargo[j] == CT_INVALID) continue;
 
				uint32 r = Random();
 
				int old_prod, new_prod, percent;
 
				/* If over 60% is transported, mult is 1, else mult is -1. */
 
				int mult = (i->last_month_pct_transported[j] > PERCENT_TRANSPORTED_60) ? 1 : -1;
 

	
 
				new_prod = old_prod = i->production_rate[j];
 

	
 
				/* For industries with only_decrease flags (temperate terrain Oil Wells),
 
				 * the multiplier will always be -1 so they will only decrease. */
 
				if (only_decrease) {
 
					mult = -1;
 
				/* For normal industries, if over 60% is transported, 33% chance for decrease.
 
				 * Bonus for very high station ratings (over 80%): 16% chance for decrease. */
 
				} else if (Chance16I(1, ((i->last_month_pct_transported[j] > PERCENT_TRANSPORTED_80) ? 6 : 3), r)) {
 
					mult *= -1;
 
				}
 

	
 
				/* 4.5% chance for 3-23% (or 1 unit for very low productions) production change,
 
				 * determined by mult value. If mult = 1 prod. increases, else (-1) it decreases. */
 
				if (Chance16I(1, 22, r >> 16)) {
 
					new_prod += mult * (max(((RandomRange(50) + 10) * old_prod) >> 8, 1U));
 
				}
 

	
 
				/* Prevent production to overflow or Oil Rig passengers to be over-"produced" */
 
				new_prod = Clamp(new_prod, 1, 255);
 

	
 
				if (((indspec->behaviour & INDUSTRYBEH_BUILT_ONWATER) != 0) && j == 1)
 
					new_prod = Clamp(new_prod, 0, 16);
 

	
 
				/* Do not stop closing the industry when it has the lowest possible production rate */
 
				if (new_prod == old_prod && old_prod > 1) {
 
					closeit = false;
 
					continue;
 
				}
 

	
 
				percent = (old_prod == 0) ? 100 : (new_prod * 100 / old_prod - 100);
 
				i->production_rate[j] = new_prod;
 

	
 
				/* Close the industry when it has the lowest possible production rate */
 
				if (new_prod > 1) closeit = false;
 

	
 
				if (abs(percent) >= 10) {
 
					ReportNewsProductionChangeIndustry(i, i->produced_cargo[j], percent);
 
				}
 
			}
 
		} else {
 
			if (only_decrease || Chance16(1, 3)) {
 
				/* If more than 60% transported, 66% chance of increase, else 33% chance of increase */
 
				if (!only_decrease && (i->last_month_pct_transported[0] > PERCENT_TRANSPORTED_60) != Chance16(1, 3)) {
 
					mul = 1; // Increase production
 
				} else {
 
					div = 1; // Decrease production
 
				}
 
			}
 
		}
 
	}
 

	
 
	if (!callback_enabled && (indspec->life_type & INDUSTRYLIFE_PROCESSING)) {
 
		if ( (byte)(_cur_year - i->last_prod_year) >= 5 && Chance16(1, smooth_economy ? 180 : 2)) {
 
			closeit = true;
 
		}
 
	}
 

	
 
	/* Increase if needed */
 
	while (mul-- != 0 && i->prod_level < PRODLEVEL_MAXIMUM) {
 
		i->prod_level = min(i->prod_level * 2, PRODLEVEL_MAXIMUM);
 
		recalculate_multipliers = true;
 
		if (str == STR_NULL) str = indspec->production_up_text;
 
	}
 

	
 
	/* Decrease if needed */
 
	while (div-- != 0 && !closeit) {
 
		if (i->prod_level == PRODLEVEL_MINIMUM) {
 
			closeit = true;
 
		} else {
 
			i->prod_level = max(i->prod_level / 2, (int)PRODLEVEL_MINIMUM); // typecast to int required to please MSVC
 
			recalculate_multipliers = true;
 
			if (str == STR_NULL) str = indspec->production_down_text;
 
		}
 
	}
 

	
 
	/* Increase or Decreasing the production level if needed */
 
	if (increment != 0) {
 
		if (increment < 0 && i->prod_level == PRODLEVEL_MINIMUM) {
 
			closeit = true;
 
		} else {
 
			i->prod_level = ClampU(i->prod_level + increment, PRODLEVEL_MINIMUM, PRODLEVEL_MAXIMUM);
 
			recalculate_multipliers = true;
 
		}
 
	}
 

	
 
	/* Recalculate production_rate
 
	 * For non-smooth economy these should always be synchronized with prod_level */
 
	if (recalculate_multipliers) {
 
		/* Rates are rounded up, so e.g. oilrig always produces some passengers */
 
		i->production_rate[0] = min(CeilDiv(indspec->production_rate[0] * i->prod_level, PRODLEVEL_DEFAULT), 0xFF);
 
		i->production_rate[1] = min(CeilDiv(indspec->production_rate[1] * i->prod_level, PRODLEVEL_DEFAULT), 0xFF);
 
	}
 

	
 
	/* Close if needed and allowed */
 
	if (closeit && !CheckIndustryCloseDownProtection(i->type)) {
 
		i->prod_level = PRODLEVEL_CLOSURE;
 
		str = indspec->closure_text;
 
	}
 

	
 
	if (!suppress_message && str != STR_NULL) {
 
		NewsSubtype ns;
 
		/* Compute news category */
 
		if (closeit) {
 
			ns = NS_INDUSTRY_CLOSE;
 
			AI::BroadcastNewEvent(new AIEventIndustryClose(i->index));
 
		} else {
 
			switch (WhoCanServiceIndustry(i)) {
 
				case 0: ns = NS_INDUSTRY_NOBODY;  break;
 
				case 1: ns = NS_INDUSTRY_OTHER;   break;
 
				case 2: ns = NS_INDUSTRY_COMPANY; break;
 
				default: NOT_REACHED();
 
			}
 
		}
 
		/* Set parameters of news string */
 
		if (str > STR_LAST_STRINGID) {
 
			SetDParam(0, STR_TOWN_NAME);
 
			SetDParam(1, i->town->index);
 
			SetDParam(2, indspec->name);
 
		} else if (closeit) {
 
			SetDParam(0, STR_FORMAT_INDUSTRY_NAME);
 
			SetDParam(1, i->town->index);
 
			SetDParam(2, indspec->name);
 
		} else {
 
			SetDParam(0, i->index);
 
		}
 
		/* and report the news to the user */
 
		AddNewsItem(str,
 
			ns,
 
			closeit ? NR_TILE : NR_INDUSTRY,
 
			closeit ? i->location.tile + TileDiffXY(1, 1) : i->index);
 
	}
 
}
 

	
 
/** Daily handler for the industry changes
 
 * Taking the original map size of 256*256, the number of random changes was always of just one unit.
 
 * But it cannot be the same on smaller or bigger maps. That number has to be scaled up or down.
 
 * For small maps, it implies that less than one change per month is required, while on bigger maps,
 
 * it would be way more. The daily loop handles those changes. */
 
void IndustryDailyLoop()
 
{
 
	_economy.industry_daily_change_counter += _economy.industry_daily_increment;
 

	
 
	/* Bits 16-31 of industry_construction_counter contain the number of industries to change/create today,
 
	 * the lower 16 bit are a fractional part that might accumulate over several days until it
 
	 * is sufficient for an industry. */
 
	uint16 change_loop = _economy.industry_daily_change_counter >> 16;
 

	
 
	/* Reset the active part of the counter, just keeping the "factional part" */
 
	_economy.industry_daily_change_counter &= 0xFFFF;
 

	
 
	if (change_loop == 0) {
 
		return;  // Nothing to do? get out
 
	}
 

	
 
	CompanyID old_company = _current_company;
 
	_current_company = OWNER_NONE;
 

	
 
	/* perform the required industry changes for the day */
 
	for (uint16 j = 0; j < change_loop; j++) {
 
		/* 3% chance that we start a new industry */
 
		if (Chance16(3, 100)) {
 
			MaybeNewIndustry();
 
		} else {
 
			Industry *i = Industry::GetRandom();
 
			if (i != NULL) {
 
				ChangeIndustryProduction(i, false);
 
				SetWindowDirty(WC_INDUSTRY_VIEW, i->index);
 
			}
 
		}
 
	}
 

	
 
	_current_company = old_company;
 

	
 
	/* production-change */
 
	InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 1);
 
}
 

	
 
void IndustryMonthlyLoop()
 
{
 
	Industry *i;
 
	CompanyID old_company = _current_company;
 
	_current_company = OWNER_NONE;
 

	
 
	FOR_ALL_INDUSTRIES(i) {
 
		UpdateIndustryStatistics(i);
 
		if (i->prod_level == PRODLEVEL_CLOSURE) {
 
			delete i;
 
		} else {
 
			ChangeIndustryProduction(i, true);
 
			SetWindowDirty(WC_INDUSTRY_VIEW, i->index);
 
		}
 
	}
 

	
 
	_current_company = old_company;
 

	
 
	/* production-change */
 
	InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 1);
 
}
 

	
 

	
 
void InitializeIndustries()
 
{
 
	_industry_pool.CleanPool();
 

	
 
	Industry::ResetIndustryCounts();
 
	_industry_sound_tile = 0;
 
}
 

	
 
bool IndustrySpec::IsRawIndustry() const
 
{
 
	/* Lumber mills are extractive/organic, but can always be built like a non-raw industry */
 
	return (this->life_type & (INDUSTRYLIFE_EXTRACTIVE | INDUSTRYLIFE_ORGANIC)) != 0 &&
 
			(this->behaviour & INDUSTRYBEH_CUT_TREES) == 0;
 
}
 

	
 
Money IndustrySpec::GetConstructionCost() const
 
{
 
	/* Building raw industries like secondary uses different price base */
 
	return (_price[(_settings_game.construction.raw_industry_construction == 1 && this->IsRawIndustry()) ?
 
			PR_BUILD_INDUSTRY_RAW : PR_BUILD_INDUSTRY] * this->cost_multiplier) >> 8;
 
}
 

	
 
Money IndustrySpec::GetRemovalCost() const
 
{
 
	return (_price[PR_CLEAR_INDUSTRY] * this->removal_cost_multiplier) >> 8;
 
}
 

	
 
static CommandCost TerraformTile_Industry(TileIndex tile, DoCommandFlag flags, uint z_new, Slope tileh_new)
 
{
 
	if (AutoslopeEnabled()) {
 
		/* We imitate here TTDP's behaviour:
 
		 *  - Both new and old slope must not be steep.
 
		 *  - TileMaxZ must not be changed.
 
		 *  - Allow autoslope by default.
 
		 *  - Disallow autoslope if callback succeeds and returns non-zero.
 
		 */
 
		Slope tileh_old = GetTileSlope(tile, NULL);
 
		/* TileMaxZ must not be changed. Slopes must not be steep. */
 
		if (!IsSteepSlope(tileh_old) && !IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new))) {
 
			const IndustryGfx gfx = GetIndustryGfx(tile);
 
			const IndustryTileSpec *itspec = GetIndustryTileSpec(gfx);
 

	
 
			/* Call callback 3C 'disable autosloping for industry tiles'. */
 
			if (HasBit(itspec->callback_mask, CBM_INDT_AUTOSLOPE)) {
 
				/* If the callback fails, allow autoslope. */
 
				uint16 res = GetIndustryTileCallback(CBID_INDTILE_AUTOSLOPE, 0, 0, gfx, Industry::GetByTile(tile), tile);
 
				if ((res == 0) || (res == CALLBACK_FAILED)) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]);
 
			} else {
 
				/* allow autoslope */
 
				return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]);
 
			}
 
		}
 
	}
 
	return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
 
}
 

	
 
extern const TileTypeProcs _tile_type_industry_procs = {
 
	DrawTile_Industry,           // draw_tile_proc
 
	GetSlopeZ_Industry,          // get_slope_z_proc
 
	ClearTile_Industry,          // clear_tile_proc
 
	AddAcceptedCargo_Industry,   // add_accepted_cargo_proc
 
	GetTileDesc_Industry,        // get_tile_desc_proc
 
	GetTileTrackStatus_Industry, // get_tile_track_status_proc
 
	ClickTile_Industry,          // click_tile_proc
 
	AnimateTile_Industry,        // animate_tile_proc
 
	TileLoop_Industry,           // tile_loop_proc
 
	ChangeTileOwner_Industry,    // change_tile_owner_proc
 
	NULL,                        // add_produced_cargo_proc
 
	NULL,                        // vehicle_enter_tile_proc
 
	GetFoundation_Industry,      // get_foundation_proc
 
	TerraformTile_Industry,      // terraform_tile_proc
 
};
src/misc_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file misc_gui.cpp GUIs for a number of misc windows. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "landscape.h"
 
#include "newgrf_text.h"
 
#include "saveload/saveload.h"
 
#include "gui.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "command_func.h"
 
#include "company_func.h"
 
#include "town.h"
 
#include "network/network.h"
 
#include "network/network_content.h"
 
#include "company_base.h"
 
#include "texteff.hpp"
 
#include "cargotype.h"
 
#include "company_manager_face.h"
 
#include "strings_func.h"
 
#include "fileio_func.h"
 
#include "fios.h"
 
#include "zoom_func.h"
 
#include "window_func.h"
 
#include "tilehighlight_func.h"
 
#include "querystring_gui.h"
 
#include "console_func.h"
 
#include "core/geometry_func.hpp"
 
#include "newgrf_debug.h"
 

	
 
#include "table/strings.h"
 

	
 

	
 
/**
 
 * Try to retrive the current clipboard contents.
 
 *
 
 * @note OS-specific funtion.
 
 * @return True if some text could be retrived.
 
 */
 
bool GetClipboardContents(char *buffer, size_t buff_len);
 

	
 

	
 
/* Variables to display file lists */
 
SaveLoadDialogMode _saveload_mode;
 

	
 

	
 
static bool _fios_path_changed;
 
static bool _savegame_sort_dirty;
 
int _caret_timer;
 

	
 
/** Widgets for the land info window. */
 
enum LandInfoWidgets {
 
	LIW_BACKGROUND, ///< Background to draw on
 
};
 

	
 
static const NWidgetPart _nested_land_info_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
 
		NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_LAND_AREA_INFORMATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_DEBUGBOX, COLOUR_GREY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, LIW_BACKGROUND), EndContainer(),
 
};
 

	
 
static const WindowDesc _land_info_desc(
 
	WDP_AUTO, 0, 0,
 
	WC_LAND_INFO, WC_NONE,
 
	0,
 
	_nested_land_info_widgets, lengthof(_nested_land_info_widgets)
 
);
 

	
 
class LandInfoWindow : public Window {
 
	enum {
 
	enum LandInfoLines {
 
		LAND_INFO_CENTERED_LINES   = 12,                       ///< Up to 12 centered lines
 
		LAND_INFO_MULTICENTER_LINE = LAND_INFO_CENTERED_LINES, ///< One multicenter line
 
		LAND_INFO_LINE_END,
 
	};
 

	
 
		LAND_INFO_LINE_BUFF_SIZE = 512,
 
	};
 
	static const uint LAND_INFO_LINE_BUFF_SIZE = 512;
 

	
 
public:
 
	char landinfo_data[LAND_INFO_LINE_END][LAND_INFO_LINE_BUFF_SIZE];
 
	TileIndex tile;
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != LIW_BACKGROUND) return;
 

	
 
		uint y = r.top + WD_TEXTPANEL_TOP;
 
		for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
 
			if (StrEmpty(this->landinfo_data[i])) break;
 

	
 
			DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, this->landinfo_data[i], i == 0 ? TC_LIGHT_BLUE : TC_FROMSTRING, SA_CENTER);
 
			y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
			if (i == 0) y += 4;
 
		}
 

	
 
		if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
 
			SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
 
			DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, r.bottom - WD_TEXTPANEL_BOTTOM, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER);
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
 
	{
 
		if (widget != LIW_BACKGROUND) return;
 

	
 
		size->height = WD_TEXTPANEL_TOP + WD_TEXTPANEL_BOTTOM;
 
		for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
 
			if (StrEmpty(this->landinfo_data[i])) break;
 

	
 
			uint width = GetStringBoundingBox(this->landinfo_data[i]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
 
			size->width = max(size->width, width);
 

	
 
			size->height += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
			if (i == 0) size->height += 4;
 
		}
 

	
 
		if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
 
			uint width = GetStringBoundingBox(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
 
			size->width = max(size->width, min(300u, width));
 
			SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
 
			size->height += GetStringHeight(STR_JUST_RAW_STRING, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
 
		}
 
	}
 

	
 
	LandInfoWindow(TileIndex tile) : Window(), tile(tile) {
 
		Town *t = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
 

	
 
		/* Because build_date is not set yet in every TileDesc, we make sure it is empty */
 
		TileDesc td;
 

	
 
		td.build_date = INVALID_DATE;
 

	
 
		/* Most tiles have only one owner, but
 
		 *  - drivethrough roadstops can be build on town owned roads (up to 2 owners) and
 
		 *  - roads can have up to four owners (railroad, road, tram, 3rd-roadtype "highway").
 
		 */
 
		td.owner_type[0] = STR_LAND_AREA_INFORMATION_OWNER; // At least one owner is displayed, though it might be "N/A".
 
		td.owner_type[1] = STR_NULL;       // STR_NULL results in skipping the owner
 
		td.owner_type[2] = STR_NULL;
 
		td.owner_type[3] = STR_NULL;
 
		td.owner[0] = OWNER_NONE;
 
		td.owner[1] = OWNER_NONE;
 
		td.owner[2] = OWNER_NONE;
 
		td.owner[3] = OWNER_NONE;
 

	
 
		td.station_class = STR_NULL;
 
		td.station_name = STR_NULL;
 
		td.airport_tile_name = STR_NULL;
 
		td.rail_speed = 0;
 

	
 
		td.grf = NULL;
 

	
 
		CargoArray acceptance;
 
		AddAcceptedCargo(tile, acceptance, NULL);
 
		GetTileDesc(tile, &td);
 

	
 
		uint line_nr = 0;
 

	
 
		/* Tiletype */
 
		SetDParam(0, td.dparam[0]);
 
		GetString(this->landinfo_data[line_nr], td.str, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Up to four owners */
 
		for (uint i = 0; i < 4; i++) {
 
			if (td.owner_type[i] == STR_NULL) continue;
 

	
 
			SetDParam(0, STR_LAND_AREA_INFORMATION_OWNER_N_A);
 
			if (td.owner[i] != OWNER_NONE && td.owner[i] != OWNER_WATER) GetNameOfOwner(td.owner[i], tile);
 
			GetString(this->landinfo_data[line_nr], td.owner_type[i], lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Cost to clear/revenue when cleared */
 
		StringID str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR_N_A;
 
		Company *c = Company::GetIfValid(_local_company);
 
		if (c != NULL) {
 
			Money old_money = c->money;
 
			c->money = INT64_MAX;
 
			CommandCost costclear = DoCommand(tile, 0, 0, DC_NONE, CMD_LANDSCAPE_CLEAR);
 
			c->money = old_money;
 
			if (costclear.Succeeded()) {
 
				Money cost = costclear.GetCost();
 
				if (cost < 0) {
 
					cost = -cost; // Negate negative cost to a positive revenue
 
					str = STR_LAND_AREA_INFORMATION_REVENUE_WHEN_CLEARED;
 
				} else {
 
					str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR;
 
				}
 
				SetDParam(0, cost);
 
			}
 
		}
 
		GetString(this->landinfo_data[line_nr], str, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Location */
 
		char tmp[16];
 
		snprintf(tmp, lengthof(tmp), "0x%.4X", tile);
 
		SetDParam(0, TileX(tile));
 
		SetDParam(1, TileY(tile));
 
		SetDParam(2, TileHeight(tile));
 
		SetDParamStr(3, tmp);
 
		GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LANDINFO_COORDS, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Local authority */
 
		SetDParam(0, STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE);
 
		if (t != NULL) {
 
			SetDParam(0, STR_TOWN_NAME);
 
			SetDParam(1, t->index);
 
		}
 
		GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Build date */
 
		if (td.build_date != INVALID_DATE) {
 
			SetDParam(0, td.build_date);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_BUILD_DATE, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Station class */
 
		if (td.station_class != STR_NULL) {
 
			SetDParam(0, td.station_class);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_CLASS, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Station type name */
 
		if (td.station_name != STR_NULL) {
 
			SetDParam(0, td.station_name);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_TYPE, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Station type name */
 
		if (td.airport_tile_name != STR_NULL) {
 
			SetDParam(0, td.airport_tile_name);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_AIRPORTTILE_NAME, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Rail speed limit */
 
		if (td.rail_speed != 0) {
 
			SetDParam(0, td.rail_speed);
 
			GetString(this->landinfo_data[line_nr], STR_LANG_AREA_INFORMATION_RAIL_SPEED_LIMIT, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* NewGRF name */
 
		if (td.grf != NULL) {
 
			SetDParamStr(0, td.grf);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_NEWGRF_NAME, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		assert(line_nr < LAND_INFO_CENTERED_LINES);
 

	
 
		/* Mark last line empty */
 
		this->landinfo_data[line_nr][0] = '\0';
 

	
 
		/* Cargo acceptance is displayed in a extra multiline */
 
		char *strp = GetString(this->landinfo_data[LAND_INFO_MULTICENTER_LINE], STR_LAND_AREA_INFORMATION_CARGO_ACCEPTED, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
 
		bool found = false;
 

	
 
		for (CargoID i = 0; i < NUM_CARGO; ++i) {
 
			if (acceptance[i] > 0) {
 
				/* Add a comma between each item. */
 
				if (found) {
 
					*strp++ = ',';
 
					*strp++ = ' ';
 
				}
 
				found = true;
 

	
 
				/* If the accepted value is less than 8, show it in 1/8:ths */
 
				if (acceptance[i] < 8) {
 
					SetDParam(0, acceptance[i]);
 
					SetDParam(1, CargoSpec::Get(i)->name);
 
					strp = GetString(strp, STR_LAND_AREA_INFORMATION_CARGO_EIGHTS, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
 
				} else {
 
					strp = GetString(strp, CargoSpec::Get(i)->name, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
 
				}
 
			}
 
		}
 
		if (!found) this->landinfo_data[LAND_INFO_MULTICENTER_LINE][0] = '\0';
 

	
 
		this->InitNested(&_land_info_desc);
 

	
 
#if defined(_DEBUG)
 
#	define LANDINFOD_LEVEL 0
 
#else
 
#	define LANDINFOD_LEVEL 1
 
#endif
 
		DEBUG(misc, LANDINFOD_LEVEL, "TILE: %#x (%i,%i)", tile, TileX(tile), TileY(tile));
 
		DEBUG(misc, LANDINFOD_LEVEL, "type_height  = %#x", _m[tile].type_height);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m1           = %#x", _m[tile].m1);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m2           = %#x", _m[tile].m2);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m3           = %#x", _m[tile].m3);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m4           = %#x", _m[tile].m4);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m5           = %#x", _m[tile].m5);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m6           = %#x", _m[tile].m6);
 
		DEBUG(misc, LANDINFOD_LEVEL, "m7           = %#x", _me[tile].m7);
 
#undef LANDINFOD_LEVEL
 
	}
 

	
 
	virtual bool IsNewGRFInspectable() const
 
	{
 
		return ::IsNewGRFInspectable(GetGrfSpecFeature(this->tile), this->tile);
 
	}
 

	
 
	virtual void ShowNewGRFInspectWindow() const
 
	{
 
		::ShowNewGRFInspectWindow(GetGrfSpecFeature(this->tile), this->tile);
 
	}
 
};
 

	
 
static void Place_LandInfo(TileIndex tile)
 
{
 
	DeleteWindowById(WC_LAND_INFO, 0);
 
	new LandInfoWindow(tile);
 
}
 

	
 
void PlaceLandBlockInfo()
 
{
 
	if (_cursor.sprite == SPR_CURSOR_QUERY) {
 
		ResetObjectToPlace();
 
	} else {
 
		_place_proc = Place_LandInfo;
 
		SetObjectToPlace(SPR_CURSOR_QUERY, PAL_NONE, HT_RECT, WC_MAIN_TOOLBAR, 0);
 
	}
 
}
 

	
 
/** Widgets for the land info window. */
 
enum AboutWidgets {
 
	AW_SCROLLING_TEXT,       ///< The actually scrolling text
 
	AW_WEBSITE,              ///< URL of OpenTTD website
 
};
 

	
 
static const NWidgetPart _nested_about_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
 
		NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_ABOUT_OPENTTD, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(4, 2, 4),
 
		NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_ABOUT_ORIGINAL_COPYRIGHT, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_ABOUT_VERSION, STR_NULL),
 
		NWidget(WWT_FRAME, COLOUR_GREY), SetPadding(0, 5, 1, 5),
 
			NWidget(WWT_EMPTY, INVALID_COLOUR, AW_SCROLLING_TEXT),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_GREY, AW_WEBSITE), SetDataTip(STR_BLACK_RAW_STRING, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_ABOUT_COPYRIGHT_OPENTTD, STR_NULL),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _about_desc(
 
	WDP_CENTER, 0, 0,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	0,
 
	_nested_about_widgets, lengthof(_nested_about_widgets)
 
);
 

	
 
static const char * const _credits[] = {
 
	"Original design by Chris Sawyer",
 
	"Original graphics by Simon Foster",
 
	"",
 
	"The OpenTTD team (in alphabetical order):",
 
	"  Albert Hofkamp (Alberth) - GUI expert",
 
	"  Jean-Fran\xC3\xA7ois Claeys (Belugas) - GUI, newindustries and more",
 
	"  Matthijs Kooijman (blathijs) - Pathfinder-guru, pool rework",
 
	"  Christoph Elsenhans (frosch) - General coding",
 
	"  Lo\xC3\xAF""c Guilloux (glx) - Windows Expert",
 
	"  Michael Lutz (michi_cc) - Path based signals",
 
	"  Owen Rudge (orudge) - Forum host, OS/2 port",
 
	"  Peter Nelson (peter1138) - Spiritual descendant from NewGRF gods",
 
	"  Remko Bijker (Rubidium) - Lead coder and way more",
 
	"  Zden\xC4\x9Bk Sojka (SmatZ) - Bug finder and fixer",
 
	"  Jos\xC3\xA9 Soler (Terkhen) - General coding",
 
	"  Thijs Marinussen (Yexo) - AI Framework",
 
	"",
 
	"Inactive Developers:",
 
	"  Bjarni Corfitzen (Bjarni) - MacOSX port, coder and vehicles",
 
	"  Victor Fischer (Celestar) - Programming everywhere you need him to",
 
	"  Tam\xC3\xA1s Farag\xC3\xB3 (Darkvater) - Ex-Lead coder",
 
	"  Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;)",
 
	"  Jonathan Coome (Maedhros) - High priest of the NewGRF Temple",
 
	"  Attila B\xC3\xA1n (MiHaMiX) - Developer WebTranslator 1 and 2",
 
	"  Christoph Mallon (Tron) - Programmer, code correctness police",
 
	"",
 
	"Retired Developers:",
 
	"  Ludvig Strigeus (ludde) - OpenTTD author, main coder (0.1 - 0.3.3)",
 
	"  Serge Paquet (vurlix) - Assistant project manager, coder (0.1 - 0.3.3)",
 
	"  Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3.0 - 0.3.6)",
 
	"  Benedikt Br\xC3\xBCggemeier (skidd13) - Bug fixer and code reworker",
 
	"  Patric Stout (TrueLight) - Programmer (0.3 - pre0.7), sys op (active)",
 
	"",
 
	"Special thanks go out to:",
 
	"  Josef Drexler - For his great work on TTDPatch",
 
	"  Marcin Grzegorczyk - For his documentation of TTD internals",
 
	"  Petr Baudi\xC5\xA1 (pasky) - Many patches, newGRF support",
 
	"  Stefan Mei\xC3\x9Fner (sign_de) - For his work on the console",
 
	"  Simon Sasburg (HackyKid) - Many bugfixes he has blessed us with",
 
	"  Cian Duffy (MYOB) - BeOS port / manual writing",
 
	"  Christian Rosentreter (tokai) - MorphOS / AmigaOS port",
 
	"  Richard Kempton (richK) - additional airports, initial TGP implementation",
 
	"",
 
	"  Alberto Demichelis - Squirrel scripting language \xC2\xA9 2003-2008",
 
	"  L. Peter Deutsch - MD5 implementation \xC2\xA9 1999, 2000, 2002",
 
	"  Michael Blunck - Pre-Signals and Semaphores \xC2\xA9 2003",
 
	"  George - Canal/Lock graphics \xC2\xA9 2003-2004",
 
	"  David Dallaston - Tram tracks",
 
	"  Marcin Grzegorczyk - Foundations for Tracks on Slopes",
 
	"  All Translators - Who made OpenTTD a truly international game",
 
	"  Bug Reporters - Without whom OpenTTD would still be full of bugs!",
 
	"",
 
	"",
 
	"And last but not least:",
 
	"  Chris Sawyer - For an amazing game!"
 
};
 

	
 
struct AboutWindow : public Window {
 
	int text_position;                       ///< The top of the scrolling text
 
	byte counter;                            ///< Used to scroll the text every 5 ticks
 
	int line_height;                         ///< The height of a single line
 
	static const int num_visible_lines = 19; ///< The number of lines visible simultaneously
 

	
 
	AboutWindow() : Window()
 
	{
 
		this->InitNested(&_about_desc);
 

	
 
		this->counter = 5;
 
		this->text_position = this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y + this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->current_y;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == AW_WEBSITE) SetDParamStr(0, "Website: http://www.openttd.org");
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
 
	{
 
		if (widget != AW_SCROLLING_TEXT) return;
 

	
 
		this->line_height = FONT_HEIGHT_NORMAL;
 

	
 
		Dimension d;
 
		d.height = this->line_height * num_visible_lines;
 

	
 
		d.width = 0;
 
		for (uint i = 0; i < lengthof(_credits); i++) {
 
			d.width = max(d.width, GetStringBoundingBox(_credits[i]).width);
 
		}
 
		*size = maxdim(*size, d);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
src/music/qtmidi.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * @file qtmidi.cpp
 
 * @brief MIDI music player for MacOS X using QuickTime.
 
 *
 
 * This music player should work in all MacOS X releases starting from 10.0,
 
 * as QuickTime is an integral part of the system since the old days of the
 
 * Motorola 68k-based Macintoshes. The only extra dependency apart from
 
 * QuickTime itself is Carbon, which is included since 10.0 as well.
 
 *
 
 * QuickTime gets fooled with the MIDI files from Transport Tycoon Deluxe
 
 * because of the @c .gm suffix. To force QuickTime to load the MIDI files
 
 * without the need of dealing with the individual QuickTime components
 
 * needed to play music (data source, MIDI parser, note allocators,
 
 * synthesizers and the like) some Carbon functions are used to set the file
 
 * type as seen by QuickTime, using @c FSpSetFInfo() (which modifies the
 
 * file's resource fork).
 
 */
 

	
 

	
 
#ifndef NO_QUICKTIME
 

	
 
#include "../stdafx.h"
 
#include "qtmidi.h"
 
#include "../debug.h"
 

	
 
#define Rect  OTTDRect
 
#define Point OTTDPoint
 
#include <QuickTime/QuickTime.h>
 
#undef Rect
 
#undef Point
 

	
 
static FMusicDriver_QtMidi iFMusicDriver_QtMidi;
 

	
 

	
 
enum {
 
	midiType = 'Midi' ///< OSType code for MIDI songs.
 
};
 
static const uint MIDI_TYPE = 'Midi' ///< OSType code for MIDI songs.
 

	
 

	
 
/**
 
 * Sets the @c OSType of a given file to @c 'Midi', but only if it's not
 
 * already set.
 
 *
 
 * @param *ref A @c FSSpec structure referencing a file.
 
 */
 
static void SetMIDITypeIfNeeded(const FSRef *ref)
 
{
 
	FSCatalogInfo catalogInfo;
 

	
 
	assert(ref);
 

	
 
	if (noErr != FSGetCatalogInfo(ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL)) return;
 
	if (!(catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)) {
 
		FileInfo * const info = (FileInfo *) catalogInfo.finderInfo;
 
		if (info->fileType != midiType && !(info->finderFlags & kIsAlias)) {
 
		if (info->fileType != MIDI_TYPE && !(info->finderFlags & kIsAlias)) {
 
			OSErr e;
 
			info->fileType = midiType;
 
			info->fileType = MIDI_TYPE;
 
			e = FSSetCatalogInfo(ref, kFSCatInfoFinderInfo, &catalogInfo);
 
			if (e == noErr) {
 
				DEBUG(driver, 3, "qtmidi: changed filetype to 'Midi'");
 
			} else {
 
				DEBUG(driver, 0, "qtmidi: changing filetype to 'Midi' failed - error %d", e);
 
			}
 
		}
 
	}
 
}
 

	
 

	
 
/**
 
 * Loads a MIDI file and returns it as a QuickTime Movie structure.
 
 *
 
 * @param *path String with the path of an existing MIDI file.
 
 * @param *moov Pointer to a @c Movie where the result will be stored.
 
 * @return Wether the file was loaded and the @c Movie successfully created.
 
 */
 
static bool LoadMovieForMIDIFile(const char *path, Movie *moov)
 
{
 
	int fd;
 
	int ret;
 
	char magic[4];
 
	FSRef fsref;
 
	FSSpec fsspec;
 
	short refnum = 0;
 
	short resid  = 0;
 

	
 
	assert(path != NULL);
 
	assert(moov != NULL);
 

	
 
	DEBUG(driver, 2, "qtmidi: start loading '%s'...", path);
 

	
 
	/*
 
	 * XXX Manual check for MIDI header ('MThd'), as I don't know how to make
 
	 * QuickTime load MIDI files without a .mid suffix without knowing it's
 
	 * a MIDI file and setting the OSType of the file to the 'Midi' value.
 
	 * Perhahaps ugly, but it seems that it does the Right Thing(tm).
 
	 */
 
	fd = open(path, O_RDONLY, 0);
 
	if (fd == -1) return false;
 
	ret = read(fd, magic, 4);
 
	close(fd);
 
	if (ret < 4) return false;
 

	
 
	DEBUG(driver, 3, "qtmidi: header is '%.4s'", magic);
 
	if (magic[0] != 'M' || magic[1] != 'T' || magic[2] != 'h' || magic[3] != 'd') {
 
		return false;
 
	}
 

	
 
	if (noErr != FSPathMakeRef((const UInt8 *) path, &fsref, NULL)) return false;
 
	SetMIDITypeIfNeeded(&fsref);
 

	
 
	if (noErr != FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsspec, NULL)) return false;
 
	if (OpenMovieFile(&fsspec, &refnum, fsRdPerm) != noErr) return false;
 
	DEBUG(driver, 3, "qtmidi: '%s' successfully opened", path);
 

	
 
	if (noErr != NewMovieFromFile(moov, refnum, &resid, NULL,
 
				newMovieActive | newMovieDontAskUnresolvedDataRefs, NULL)) {
 
		CloseMovieFile(refnum);
 
		return false;
 
	}
 
	DEBUG(driver, 3, "qtmidi: movie container created");
 

	
 
	CloseMovieFile(refnum);
 
	return true;
 
}
 

	
 

	
 
/**
 
 * Flag which has the @c true value when QuickTime is available and
 
 * initialized.
 
 */
 
static bool _quicktime_started = false;
 

	
 

	
 
/**
 
 * Initialize QuickTime if needed. This function sets the
 
 * #_quicktime_started flag to @c true if QuickTime is present in the system
 
 * and it was initialized properly.
 
 */
 
static void InitQuickTimeIfNeeded()
 
{
 
	OSStatus dummy;
 

	
 
	if (_quicktime_started) return;
 

	
 
	DEBUG(driver, 2, "qtmidi: initializing Quicktime");
 
	/* Be polite: check wether QuickTime is available and initialize it. */
 
	_quicktime_started =
 
		(noErr == Gestalt(gestaltQuickTime, &dummy)) &&
 
		(noErr == EnterMovies());
 
	if (!_quicktime_started) DEBUG(driver, 0, "qtmidi: Quicktime initialization failed!");
 
}
 

	
 

	
 
/** Possible states of the QuickTime music driver. */
 
enum {
 
enum QTStates {
 
	QT_STATE_IDLE, ///< No file loaded.
 
	QT_STATE_PLAY, ///< File loaded, playing.
 
	QT_STATE_STOP, ///< File loaded, stopped.
 
};
 

	
 

	
 
static Movie _quicktime_movie;                  ///< Current QuickTime @c Movie.
 
static byte  _quicktime_volume = 127;           ///< Current volume.
 
static int   _quicktime_state  = QT_STATE_IDLE; ///< Current player state.
 

	
 

	
 
/**
 
 * Maps OpenTTD volume to QuickTime notion of volume.
 
 */
 
#define VOLUME  ((short)((0x00FF & _quicktime_volume) << 1))
 

	
 

	
 
/**
 
 * Initialized the MIDI player, including QuickTime initialization.
 
 *
 
 * @todo Give better error messages by inspecting error codes returned by
 
 * @c Gestalt() and @c EnterMovies(). Needs changes in
 
 * #InitQuickTimeIfNeeded.
 
 */
 
const char *MusicDriver_QtMidi::Start(const char * const *parm)
 
{
 
	InitQuickTimeIfNeeded();
 
	return (_quicktime_started) ? NULL : "can't initialize QuickTime";
 
}
 

	
 

	
 
/**
 
 * Checks wether the player is active.
 
 *
 
 * This function is called at regular intervals from OpenTTD's main loop, so
 
 * we call @c MoviesTask() from here to let QuickTime do its work.
 
 */
 
bool MusicDriver_QtMidi::IsSongPlaying()
 
{
 
	if (!_quicktime_started) return true;
 

	
 
	switch (_quicktime_state) {
 
		case QT_STATE_IDLE:
 
		case QT_STATE_STOP:
 
			/* Do nothing. */
 
			break;
 

	
 
		case QT_STATE_PLAY:
 
			MoviesTask(_quicktime_movie, 0);
 
			/* Check wether movie ended. */
 
			if (IsMovieDone(_quicktime_movie) ||
 
					(GetMovieTime(_quicktime_movie, NULL) >=
 
					 GetMovieDuration(_quicktime_movie))) {
 
				_quicktime_state = QT_STATE_STOP;
 
			}
 
	}
 

	
 
	return _quicktime_state == QT_STATE_PLAY;
 
}
 

	
 

	
 
/**
 
 * Stops the MIDI player.
 
 *
 
 * Stops playing and frees any used resources before returning. As it
 
 * deinitilizes QuickTime, the #_quicktime_started flag is set to @c false.
 
 */
 
void MusicDriver_QtMidi::Stop()
 
{
 
	if (!_quicktime_started) return;
 

	
 
	DEBUG(driver, 2, "qtmidi: stopping driver...");
 
	switch (_quicktime_state) {
 
		case QT_STATE_IDLE:
 
			DEBUG(driver, 3, "qtmidi: stopping not needed, already idle");
 
			/* Do nothing. */
 
			break;
 

	
 
		case QT_STATE_PLAY:
 
			StopSong();
 
			/* Fall-through */
 

	
 
		case QT_STATE_STOP:
 
			DisposeMovie(_quicktime_movie);
 
	}
 

	
 
	ExitMovies();
 
	_quicktime_started = false;
 
}
 

	
 

	
 
/**
 
 * Starts playing a new song.
 
 *
 
 * @param filename Path to a MIDI file.
 
 */
 
void MusicDriver_QtMidi::PlaySong(const char *filename)
 
{
 
	if (!_quicktime_started) return;
 

	
 
	DEBUG(driver, 2, "qtmidi: trying to play '%s'", filename);
 
	switch (_quicktime_state) {
 
		case QT_STATE_PLAY:
 
			StopSong();
 
			DEBUG(driver, 3, "qtmidi: previous tune stopped");
 
			/* Fall-through -- no break needed. */
 

	
 
		case QT_STATE_STOP:
 
			DisposeMovie(_quicktime_movie);
 
			DEBUG(driver, 3, "qtmidi: previous tune disposed");
 
			_quicktime_state = QT_STATE_IDLE;
 
			/* Fall-through -- no break needed. */
 

	
 
		case QT_STATE_IDLE:
 
			LoadMovieForMIDIFile(filename, &_quicktime_movie);
 
			SetMovieVolume(_quicktime_movie, VOLUME);
 
			StartMovie(_quicktime_movie);
 
			_quicktime_state = QT_STATE_PLAY;
 
	}
 
	DEBUG(driver, 3, "qtmidi: playing '%s'", filename);
 
}
 

	
 

	
 
/**
 
 * Stops playing the current song, if the player is active.
 
 */
 
void MusicDriver_QtMidi::StopSong()
 
{
 
	if (!_quicktime_started) return;
 

	
 
	switch (_quicktime_state) {
 
		case QT_STATE_IDLE:
 
			/* Fall-through -- no break needed. */
 

	
 
		case QT_STATE_STOP:
 
			DEBUG(driver, 3, "qtmidi: stop requested, but already idle");
 
			/* Do nothing. */
 
			break;
 

	
 
		case QT_STATE_PLAY:
 
			StopMovie(_quicktime_movie);
 
			_quicktime_state = QT_STATE_STOP;
 
			DEBUG(driver, 3, "qtmidi: player stopped");
 
	}
 
}
 

	
 

	
 
/**
 
 * Changes the playing volume of the MIDI player.
 
 *
 
 * As QuickTime controls volume in a per-movie basis, the desired volume is
 
 * stored in #_quicktime_volume, and the volume is set here using the
 
 * #VOLUME macro, @b and when loading new song in #PlaySong.
 
 *
 
 * @param vol The desired volume, range of the value is @c 0-127
 
 */
 
void MusicDriver_QtMidi::SetVolume(byte vol)
 
{
 
	if (!_quicktime_started) return;
 

	
 
	_quicktime_volume = vol;
 

	
 
	DEBUG(driver, 2, "qtmidi: set volume to %u (%hi)", vol, VOLUME);
 
	switch (_quicktime_state) {
 
		case QT_STATE_IDLE:
 
			/* Do nothing. */
 
			break;
 

	
 
		case QT_STATE_PLAY:
 
		case QT_STATE_STOP:
 
			SetMovieVolume(_quicktime_movie, VOLUME);
 
	}
 
}
 

	
 
#endif /* NO_QUICKTIME */
src/network/network_chat_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file network_chat_gui.cpp GUI for handling chat messages. */
 

	
 
#include <stdarg.h> /* va_list */
 

	
 
#ifdef ENABLE_NETWORK
 

	
 
#include "../stdafx.h"
 
#include "../date_func.h"
 
#include "../gfx_func.h"
 
#include "../strings_func.h"
 
#include "../blitter/factory.hpp"
 
#include "../console_func.h"
 
#include "../video/video_driver.hpp"
 
#include "../table/sprites.h"
 
#include "../querystring_gui.h"
 
#include "../town.h"
 
#include "../window_func.h"
 
#include "../core/geometry_func.hpp"
 
#include "network.h"
 
#include "network_client.h"
 
#include "network_base.h"
 

	
 
#include "table/strings.h"
 

	
 
/* The draw buffer must be able to contain the chat message, client name and the "[All]" message,
 
 * some spaces and possible translations of [All] to other languages. */
 
assert_compile((int)DRAW_STRING_BUFFER >= (int)NETWORK_CHAT_LENGTH + NETWORK_NAME_LENGTH + 40);
 

	
 
enum {
 
	NETWORK_CHAT_LINE_SPACING = 3,
 
};
 
static const uint NETWORK_CHAT_LINE_SPACING = 3;
 

	
 
struct ChatMessage {
 
	char message[DRAW_STRING_BUFFER];
 
	TextColour colour;
 
	Date end_date;
 
};
 

	
 
/* used for chat window */
 
static ChatMessage *_chatmsg_list = NULL;
 
static bool _chatmessage_dirty = false;
 
static bool _chatmessage_visible = false;
 
static bool _chat_tab_completion_active;
 
static uint MAX_CHAT_MESSAGES = 0;
 

	
 
/* The chatbox grows from the bottom so the coordinates are pixels from
 
 * the left and pixels from the bottom. The height is the maximum height */
 
static PointDimension _chatmsg_box;
 
static uint8 *_chatmessage_backup = NULL;
 

	
 
static inline uint GetChatMessageCount()
 
{
 
	uint i = 0;
 
	for (; i < MAX_CHAT_MESSAGES; i++) {
 
		if (_chatmsg_list[i].message[0] == '\0') break;
 
	}
 

	
 
	return i;
 
}
 

	
 
/**
 
 * Add a text message to the 'chat window' to be shown
 
 * @param colour The colour this message is to be shown in
 
 * @param duration The duration of the chat message in game-days
 
 * @param message message itself in printf() style
 
 */
 
void CDECL NetworkAddChatMessage(TextColour colour, uint8 duration, const char *message, ...)
 
{
 
	char buf[DRAW_STRING_BUFFER];
 
	const char *bufp;
 
	va_list va;
 
	uint msg_count;
 
	uint16 lines;
 

	
 
	va_start(va, message);
 
	vsnprintf(buf, lengthof(buf), message, va);
 
	va_end(va);
 

	
 
	Utf8TrimString(buf, DRAW_STRING_BUFFER);
 

	
 
	/* Force linebreaks for strings that are too long */
 
	lines = GB(FormatStringLinebreaks(buf, lastof(buf), _chatmsg_box.width - 8), 0, 16) + 1;
 
	if (lines >= MAX_CHAT_MESSAGES) return;
 

	
 
	msg_count = GetChatMessageCount();
 
	/* We want to add more chat messages than there is free space for, remove 'old' */
 
	if (lines > MAX_CHAT_MESSAGES - msg_count) {
 
		int i = lines - (MAX_CHAT_MESSAGES - msg_count);
 
		memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i));
 
		msg_count = MAX_CHAT_MESSAGES - lines;
 
	}
 

	
 
	for (bufp = buf; lines != 0; lines--) {
 
		ChatMessage *cmsg = &_chatmsg_list[msg_count++];
 
		strecpy(cmsg->message, bufp, lastof(cmsg->message));
 

	
 
		/* The default colour for a message is company colour. Replace this with
 
		 * white for any additional lines */
 
		cmsg->colour = (bufp == buf && (colour & IS_PALETTE_COLOUR)) ? colour : TC_WHITE;
 
		cmsg->end_date = _date + duration;
 

	
 
		bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string
 
	}
 

	
 
	_chatmessage_dirty = true;
 
}
 

	
 
void NetworkInitChatMessage()
 
{
 
	MAX_CHAT_MESSAGES    = _settings_client.gui.network_chat_box_height;
 

	
 
	_chatmsg_list        = ReallocT(_chatmsg_list, _settings_client.gui.network_chat_box_height);
 
	_chatmsg_box.x       = 10;
 
	_chatmsg_box.y       = 3 * FONT_HEIGHT_NORMAL;
 
	_chatmsg_box.width   = _settings_client.gui.network_chat_box_width;
 
	_chatmsg_box.height  = _settings_client.gui.network_chat_box_height * (FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING) + 2;
 
	_chatmessage_backup  = ReallocT(_chatmessage_backup, _chatmsg_box.width * _chatmsg_box.height * BlitterFactoryBase::GetCurrentBlitter()->GetBytesPerPixel());
 
	_chatmessage_visible = false;
 

	
 
	for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) {
 
		_chatmsg_list[i].message[0] = '\0';
 
	}
 
}
 

	
 
/** Hide the chatbox */
 
void NetworkUndrawChatMessage()
 
{
 
	/* Sometimes we also need to hide the cursor
 
	 *   This is because both textmessage and the cursor take a shot of the
 
	 *   screen before drawing.
 
	 *   Now the textmessage takes his shot and paints his data before the cursor
 
	 *   does, so in the shot of the cursor is the screen-data of the textmessage
 
	 *   included when the cursor hangs somewhere over the textmessage. To
 
	 *   avoid wrong repaints, we undraw the cursor in that case, and everything
 
	 *   looks nicely ;)
 
	 * (and now hope this story above makes sense to you ;))
 
	 */
 
	if (_cursor.visible &&
 
			_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x &&
 
			_cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width &&
 
			_cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height &&
 
			_cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) {
 
		UndrawMouseCursor();
 
	}
 

	
 
	if (_chatmessage_visible) {
 
		Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 
		int x      = _chatmsg_box.x;
 
		int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
 
		int width  = _chatmsg_box.width;
 
		int height = _chatmsg_box.height;
 
		if (y < 0) {
 
			height = max(height + y, min(_chatmsg_box.height, _screen.height));
 
			y = 0;
 
		}
 
		if (x + width >= _screen.width) {
 
			width = _screen.width - x;
 
		}
 
		if (width <= 0 || height <= 0) return;
 

	
 
		_chatmessage_visible = false;
 
		/* Put our 'shot' back to the screen */
 
		blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
 
		/* And make sure it is updated next time */
 
		_video_driver->MakeDirty(x, y, width, height);
 

	
 
		_chatmessage_dirty = true;
 
	}
 
}
 

	
 
/** Check if a message is expired every day */
 
void NetworkChatMessageDailyLoop()
 
{
 
	for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) {
 
		ChatMessage *cmsg = &_chatmsg_list[i];
 
		if (cmsg->message[0] == '\0') continue;
 

	
 
		/* Message has expired, remove from the list */
 
		if (cmsg->end_date < _date) {
 
			/* Move the remaining messages over the current message */
 
			if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1));
 

	
 
			/* Mark the last item as empty */
 
			_chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0';
 
			_chatmessage_dirty = true;
 

	
 
			/* Go one item back, because we moved the array 1 to the left */
 
			i--;
 
		}
 
	}
 
}
 

	
 
/** Draw the chat message-box */
 
void NetworkDrawChatMessage()
 
{
 
	Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 
	if (!_chatmessage_dirty) return;
 

	
 
	/* First undraw if needed */
 
	NetworkUndrawChatMessage();
 

	
 
	if (_iconsole_mode == ICONSOLE_FULL) return;
 

	
 
	/* Check if we have anything to draw at all */
 
	uint count = GetChatMessageCount();
 
	if (count == 0) return;
 

	
 
	int x      = _chatmsg_box.x;
 
	int y      = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
 
	int width  = _chatmsg_box.width;
 
	int height = _chatmsg_box.height;
 
	if (y < 0) {
 
		height = max(height + y, min(_chatmsg_box.height, _screen.height));
 
		y = 0;
 
	}
 
	if (x + width >= _screen.width) {
 
		width = _screen.width - x;
 
	}
 
	if (width <= 0 || height <= 0) return;
 

	
 
	assert(blitter->BufferSize(width, height) <= (int)(_chatmsg_box.width * _chatmsg_box.height * blitter->GetBytesPerPixel()));
 

	
 
	/* Make a copy of the screen as it is before painting (for undraw) */
 
	blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height);
 

	
 
	_cur_dpi = &_screen; // switch to _screen painting
 

	
 
	/* Paint a half-transparent box behind the chat messages */
 
	GfxFillRect(
 
			_chatmsg_box.x,
 
			_screen.height - _chatmsg_box.y - count * (FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING) - 2,
 
			_chatmsg_box.x + _chatmsg_box.width - 1,
 
			_screen.height - _chatmsg_box.y - 2,
 
			PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR // black, but with some alpha for background
 
		);
 

	
 
	/* Paint the chat messages starting with the lowest at the bottom */
 
	for (uint y = FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING; count-- != 0; y += (FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING)) {
 
		DrawString(_chatmsg_box.x + 3, _chatmsg_box.x + _chatmsg_box.width - 1, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].message, _chatmsg_list[count].colour);
 
	}
 

	
 
	/* Make sure the data is updated next flush */
 
	_video_driver->MakeDirty(x, y, width, height);
 

	
 
	_chatmessage_visible = true;
 
	_chatmessage_dirty = false;
 
}
 

	
 

	
 
static void SendChat(const char *buf, DestType type, int dest)
 
{
 
	if (StrEmpty(buf)) return;
 
	if (!_network_server) {
 
		SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, 0);
 
	} else {
 
		NetworkServerSendChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, CLIENT_ID_SERVER);
 
	}
 
}
 

	
 
/** Widget numbers of the chat window. */
 
enum NetWorkChatWidgets {
 
	NWCW_CLOSE,
 
	NWCW_BACKGROUND,
 
	NWCW_DESTINATION,
 
	NWCW_TEXTBOX,
 
	NWCW_SENDBUTTON,
 
};
 

	
 
struct NetworkChatWindow : public QueryStringBaseWindow {
 
	DestType dtype;
 
	StringID dest_string;
 
	int dest;
 

	
 
	NetworkChatWindow(const WindowDesc *desc, DestType type, int dest) : QueryStringBaseWindow(NETWORK_CHAT_LENGTH)
 
	{
 
		this->dtype   = type;
 
		this->dest    = dest;
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 0);
 

	
 
		static const StringID chat_captions[] = {
 
			STR_NETWORK_CHAT_ALL_CAPTION,
 
			STR_NETWORK_CHAT_COMPANY_CAPTION,
 
			STR_NETWORK_CHAT_CLIENT_CAPTION
 
		};
 
		assert((uint)this->dtype < lengthof(chat_captions));
 
		this->dest_string = chat_captions[this->dtype];
 

	
 
		this->InitNested(desc, type);
 

	
 
		this->SetFocusedWidget(NWCW_TEXTBOX);
 
		InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height);
 
		_chat_tab_completion_active = false;
 
	}
 

	
 
	~NetworkChatWindow()
 
	{
 
		InvalidateWindowData(WC_NEWS_WINDOW, 0, 0);
 
	}
 

	
 
	/**
 
	 * Find the next item of the list of things that can be auto-completed.
 
	 * @param item The current indexed item to return. This function can, and most
 
	 *     likely will, alter item, to skip empty items in the arrays.
 
	 * @return Returns the char that matched to the index.
 
	 */
 
	const char *ChatTabCompletionNextItem(uint *item)
 
	{
 
		static char chat_tab_temp_buffer[64];
 

	
 
		/* First, try clients */
 
		if (*item < MAX_CLIENT_SLOTS) {
 
			/* Skip inactive clients */
 
			NetworkClientInfo *ci;
 
			FOR_ALL_CLIENT_INFOS_FROM(ci, *item) {
 
				*item = ci->index;
 
				return ci->client_name;
 
			}
 
			*item = MAX_CLIENT_SLOTS;
 
		}
 

	
 
		/* Then, try townnames
 
		 * Not that the following assumes all town indices are adjacent, ie no
 
		 * towns have been deleted. */
 
		if (*item < (uint)MAX_CLIENT_SLOTS + Town::GetPoolSize()) {
 
			const Town *t;
 

	
 
			FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_SLOTS) {
 
				/* Get the town-name via the string-system */
 
				SetDParam(0, t->index);
 
				GetString(chat_tab_temp_buffer, STR_TOWN_NAME, lastof(chat_tab_temp_buffer));
 
				return &chat_tab_temp_buffer[0];
 
			}
 
		}
 

	
 
		return NULL;
 
	}
 

	
 
	/**
 
	 * Find what text to complete. It scans for a space from the left and marks
 
	 *  the word right from that as to complete. It also writes a \0 at the
 
	 *  position of the space (if any). If nothing found, buf is returned.
 
	 */
 
	static char *ChatTabCompletionFindText(char *buf)
 
	{
 
		char *p = strrchr(buf, ' ');
 
		if (p == NULL) return buf;
 

	
 
		*p = '\0';
 
		return p + 1;
 
	}
 

	
 
	/**
 
	 * See if we can auto-complete the current text of the user.
 
	 */
 
	void ChatTabCompletion()
 
	{
 
		static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH];
 
		assert(this->edit_str_size == lengthof(_chat_tab_completion_buf));
 

	
 
		Textbuf *tb = &this->text;
 
		size_t len, tb_len;
 
		uint item;
 
		char *tb_buf, *pre_buf;
 
		const char *cur_name;
 
		bool second_scan = false;
 

	
 
		item = 0;
 

	
 
		/* Copy the buffer so we can modify it without damaging the real data */
 
		pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf);
 

	
 
		tb_buf  = ChatTabCompletionFindText(pre_buf);
 
		tb_len  = strlen(tb_buf);
 

	
 
		while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) {
 
			item++;
 

	
 
			if (_chat_tab_completion_active) {
 
				/* We are pressing TAB again on the same name, is there another name
 
				 *  that starts with this? */
 
				if (!second_scan) {
 
					size_t offset;
 
					size_t length;
 

	
 
					/* If we are completing at the begin of the line, skip the ': ' we added */
 
					if (tb_buf == pre_buf) {
 
						offset = 0;
 
						length = (tb->size - 1) - 2;
 
					} else {
 
						/* Else, find the place we are completing at */
 
						offset = strlen(pre_buf) + 1;
 
						length = (tb->size - 1) - offset;
 
					}
 

	
 
					/* Compare if we have a match */
 
					if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
 

	
 
					continue;
 
				}
 

	
 
				/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
 
			}
 

	
 
			len = strlen(cur_name);
 
			if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) {
 
				/* Save the data it was before completion */
 
				if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf);
 
				_chat_tab_completion_active = true;
 

	
 
				/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
 
				if (pre_buf == tb_buf) {
 
					snprintf(tb->buf, this->edit_str_size, "%s: ", cur_name);
 
				} else {
 
					snprintf(tb->buf, this->edit_str_size, "%s %s", pre_buf, cur_name);
src/network/network_content_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file network_content_gui.cpp Implementation of the Network Content related GUIs. */
 

	
 
#if defined(ENABLE_NETWORK)
 
#include "../stdafx.h"
 
#include "../strings_func.h"
 
#include "../gfx_func.h"
 
#include "../window_func.h"
 
#include "../gui.h"
 
#include "../ai/ai.hpp"
 
#include "../base_media_base.h"
 
#include "../sortlist_type.h"
 
#include "../querystring_gui.h"
 
#include "../core/geometry_func.hpp"
 
#include  "network_content.h"
 

	
 
#include "table/strings.h"
 
#include "../table/sprites.h"
 

	
 
/** Widgets used by this window */
 
enum DownloadStatusWindowWidgets {
 
	NCDSWW_BACKGROUND, ///< Background
 
	NCDSWW_CANCELOK,   ///< Cancel/OK button
 
};
 

	
 
/** Nested widgets for the download window. */
 
static const NWidgetPart _nested_network_content_download_status_window_widgets[] = {
 
	NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	NWidget(WWT_PANEL, COLOUR_GREY, NCDSWW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 30),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(125, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCDSWW_CANCELOK), SetMinimalSize(101, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 4),
 
	EndContainer(),
 
};
 

	
 
/** Window description for the download window */
 
static const WindowDesc _network_content_download_status_window_desc(
 
	WDP_CENTER, 0, 0,
 
	WC_NETWORK_STATUS_WINDOW, WC_NONE,
 
	WDF_MODAL,
 
	_nested_network_content_download_status_window_widgets, lengthof(_nested_network_content_download_status_window_widgets)
 
);
 

	
 
/** Window for showing the download status of content */
 
struct NetworkContentDownloadStatusWindow : public Window, ContentCallback {
 
private:
 
	ClientNetworkContentSocketHandler *connection; ///< Our connection with the content server
 
	SmallVector<ContentType, 4> receivedTypes;     ///< Types we received so we can update their cache
 

	
 
	uint total_files;      ///< Number of files to download
 
	uint downloaded_files; ///< Number of files downloaded
 
	uint total_bytes;      ///< Number of bytes to download
 
	uint downloaded_bytes; ///< Number of bytes downloaded
 

	
 
	uint32 cur_id; ///< The current ID of the downloaded file
 
	char name[48]; ///< The current name of the downloaded file
 

	
 
public:
 
	/**
 
	 * Create a new download window based on a list of content information
 
	 * with flags whether to download them or not.
 
	 * @param infos the list to search in
 
	 */
 
	NetworkContentDownloadStatusWindow() :
 
		cur_id(UINT32_MAX)
 
	{
 
		this->parent = FindWindowById(WC_NETWORK_WINDOW, 1);
 

	
 
		_network_content_client.AddCallback(this);
 
		_network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes);
 

	
 
		this->InitNested(&_network_content_download_status_window_desc, 0);
 
	}
 

	
 
	/** Free whatever we've allocated */
 
	~NetworkContentDownloadStatusWindow()
 
	{
 
		/* Tell all the backends about what we've downloaded */
 
		for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
 
			switch (*iter) {
 
				case CONTENT_TYPE_AI:
 
				case CONTENT_TYPE_AI_LIBRARY:
 
					AI::Rescan();
 
					SetWindowClassesDirty(WC_AI_DEBUG);
 
					break;
 

	
 
				case CONTENT_TYPE_BASE_GRAPHICS:
 
					BaseGraphics::FindSets();
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
					break;
 

	
 
				case CONTENT_TYPE_BASE_SOUNDS:
 
					BaseSounds::FindSets();
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
					break;
 

	
 
				case CONTENT_TYPE_BASE_MUSIC:
 
					BaseMusic::FindSets();
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
					break;
 

	
 
				case CONTENT_TYPE_NEWGRF:
 
					ScanNewGRFFiles();
 
					/* Yes... these are the NewGRF windows */
 
					InvalidateWindowClassesData(WC_SAVELOAD);
 
					InvalidateWindowData(WC_GAME_OPTIONS, 0, 1);
 
					break;
 

	
 
				case CONTENT_TYPE_SCENARIO:
 
				case CONTENT_TYPE_HEIGHTMAP:
 
					extern void ScanScenarios();
 
					ScanScenarios();
 
					InvalidateWindowData(WC_SAVELOAD, 0, 0);
 
					break;
 

	
 
				default:
 
					break;
 
			}
 
		}
 

	
 
		/* Always invalidate the download window; tell it we are going to be gone */
 
		InvalidateWindowData(WC_NETWORK_WINDOW, 1, 2);
 
		_network_content_client.RemoveCallback(this);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != NCDSWW_BACKGROUND) return;
 

	
 
		/* Draw nice progress bar :) */
 
		DrawFrameRect(r.left + 20, r.top + 4, r.left + 20 + (int)((this->width - 40LL) * this->downloaded_bytes / this->total_bytes), r.top + 14, COLOUR_MAUVE, FR_NONE);
 

	
 
		int y = r.top + 20;
 
		SetDParam(0, this->downloaded_bytes);
 
		SetDParam(1, this->total_bytes);
 
		SetDParam(2, this->downloaded_bytes * 100LL / this->total_bytes);
 
		DrawString(r.left + 2, r.right - 2, y, STR_CONTENT_DOWNLOAD_PROGRESS_SIZE, TC_FROMSTRING, SA_CENTER);
 

	
 
		StringID str;
 
		if (this->downloaded_bytes == this->total_bytes) {
 
			str = STR_CONTENT_DOWNLOAD_COMPLETE;
 
		} else if (!StrEmpty(this->name)) {
 
			SetDParamStr(0, this->name);
 
			SetDParam(1, this->downloaded_files);
 
			SetDParam(2, this->total_files);
 
			str = STR_CONTENT_DOWNLOAD_FILE;
 
		} else {
 
			str = STR_CONTENT_DOWNLOAD_INITIALISE;
 
		}
 

	
 
		y += FONT_HEIGHT_NORMAL + 5;
 
		DrawStringMultiLine(r.left + 2, r.right - 2, y, y + FONT_HEIGHT_NORMAL * 2, str, TC_FROMSTRING, SA_CENTER);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget, int click_count)
 
	{
 
		if (widget == NCDSWW_CANCELOK) {
 
			if (this->downloaded_bytes != this->total_bytes) _network_content_client.Close();
 
			delete this;
 
		}
 
	}
 

	
 
	virtual void OnDownloadProgress(const ContentInfo *ci, uint bytes)
 
	{
 
		if (ci->id != this->cur_id) {
 
			strecpy(this->name, ci->filename, lastof(this->name));
 
			this->cur_id = ci->id;
 
			this->downloaded_files++;
 
			this->receivedTypes.Include(ci->type);
 
		}
 
		this->downloaded_bytes += bytes;
 

	
 
		/* When downloading is finished change cancel in ok */
 
		if (this->downloaded_bytes == this->total_bytes) {
 
			this->GetWidget<NWidgetCore>(NCDSWW_CANCELOK)->widget_data = STR_BUTTON_OK;
 
		}
 

	
 
		this->SetDirty();
 
	}
 
};
 

	
 
/** Widgets of the content list window. */
 
enum NetworkContentListWindowWidgets {
 
	NCLWW_BACKGROUND,    ///< Resize button
 

	
 
	NCLWW_FILTER_CAPT,   ///< Caption for the filter editbox
 
	NCLWW_FILTER,        ///< Filter editbox
 

	
 
	NCLWW_CHECKBOX,      ///< Button above checkboxes
 
	NCLWW_TYPE,          ///< 'Type' button
 
	NCLWW_NAME,          ///< 'Name' button
 

	
 
	NCLWW_MATRIX,        ///< Panel with list of content
 
	NCLWW_SCROLLBAR,     ///< Scrollbar of matrix
 

	
 
	NCLWW_DETAILS,       ///< Panel with content details
 

	
 
	NCLWW_SELECT_ALL,    ///< 'Select all' button
 
	NCLWW_SELECT_UPDATE, ///< 'Select updates' button
 
	NCLWW_UNSELECT,      ///< 'Unselect all' button
 
	NCLWW_CANCEL,        ///< 'Cancel' button
 
	NCLWW_DOWNLOAD,      ///< 'Download' button
 

	
 
	NCLWW_SEL_ALL_UPDATE, ///< #NWID_SELECTION widget for select all/update buttons.
 
};
 

	
 
/** Window that lists the content that's at the content server */
 
class NetworkContentListWindow : public QueryStringBaseWindow, ContentCallback {
 
	typedef GUIList<const ContentInfo*> GUIContentList;
 

	
 
	enum {
 
		EDITBOX_MAX_SIZE = 50,
 
		EDITBOX_MAX_LENGTH = 300,
 
	};
 
	static const uint EDITBOX_MAX_SIZE   =  50;
 
	static const uint EDITBOX_MAX_LENGTH = 300;
 

	
 
	/** Runtime saved values */
 
	static Listing last_sorting;
 
	static Filtering last_filtering;
 
	/** The sorter functions */
 
	static GUIContentList::SortFunction * const sorter_funcs[];
 
	static GUIContentList::FilterFunction * const filter_funcs[];
 
	GUIContentList content;      ///< List with content
 

	
 
	const ContentInfo *selected; ///< The selected content info
 
	int list_pos;                ///< Our position in the list
 
	uint filesize_sum;           ///< The sum of all selected file sizes
 

	
 
	/**
 
	 * (Re)build the network game list as its amount has changed because
 
	 * an item has been added or deleted for example
 
	 */
 
	void BuildContentList()
 
	{
 
		if (!this->content.NeedRebuild()) return;
 

	
 
		/* Create temporary array of games to use for listing */
 
		this->content.Clear();
 

	
 
		for (ConstContentIterator iter = _network_content_client.Begin(); iter != _network_content_client.End(); iter++) {
 
			*this->content.Append() = *iter;
 
		}
 

	
 
		this->FilterContentList();
 
		this->content.Compact();
 
		this->content.RebuildDone();
 
		this->SortContentList();
 

	
 
		this->vscroll.SetCount(this->content.Length()); // Update the scrollbar
 
		this->ScrollToSelected();
 
	}
 

	
 
	/** Sort content by name. */
 
	static int CDECL NameSorter(const ContentInfo * const *a, const ContentInfo * const *b)
 
	{
 
		return strcasecmp((*a)->name, (*b)->name);
 
	}
 

	
 
	/** Sort content by type. */
 
	static int CDECL TypeSorter(const ContentInfo * const *a, const ContentInfo * const *b)
 
	{
 
		int r = 0;
 
		if ((*a)->type != (*b)->type) {
 
			char a_str[64];
 
			char b_str[64];
 
			GetString(a_str, STR_CONTENT_TYPE_BASE_GRAPHICS + (*a)->type - CONTENT_TYPE_BASE_GRAPHICS, lastof(a_str));
 
			GetString(b_str, STR_CONTENT_TYPE_BASE_GRAPHICS + (*b)->type - CONTENT_TYPE_BASE_GRAPHICS, lastof(b_str));
 
			r = strcasecmp(a_str, b_str);
 
		}
 
		if (r == 0) r = NameSorter(a, b);
 
		return r;
 
	}
 

	
 
	/** Sort content by state. */
 
	static int CDECL StateSorter(const ContentInfo * const *a, const ContentInfo * const *b)
 
	{
 
		int r = (*a)->state - (*b)->state;
 
		if (r == 0) r = TypeSorter(a, b);
 
		return r;
 
	}
 

	
 
	/** Sort the content list */
 
	void SortContentList()
 
	{
 
		if (!this->content.Sort()) return;
 

	
 
		for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
 
			if (*iter == this->selected) {
 
				this->list_pos = iter - this->content.Begin();
 
				break;
 
			}
 
		}
 
	}
 

	
 
	/** Filter content by tags/name */
 
	static bool CDECL TagNameFilter(const ContentInfo * const *a, const char *filter_string)
 
	{
 
		for (int i = 0; i < (*a)->tag_count; i++) {
 
			if (strcasestr((*a)->tags[i], filter_string) != NULL) return true;
 
		}
 
		return strcasestr((*a)->name, filter_string) != NULL;
 
	}
 

	
 
	/** Filter the content list */
 
	void FilterContentList()
 
	{
 
		if (!this->content.Filter(this->edit_str_buf)) return;
 

	
 
		/* update list position */
 
		for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
 
			if (*iter == this->selected) {
 
				this->list_pos = iter - this->content.Begin();
 
				return;
 
			}
 
		}
 

	
 
		/* previously selected item not in list anymore */
 
		this->selected = NULL;
 
		this->list_pos = 0;
 
	}
 

	
 
	/** Make sure that the currently selected content info is within the visible part of the matrix */
 
	void ScrollToSelected()
 
	{
 
		if (this->selected == NULL) return;
 

	
 
		this->vscroll.ScrollTowards(this->list_pos);
 
	}
 

	
 
public:
 
	/**
 
	 * Create the content list window.
 
	 * @param desc the window description to pass to Window's constructor.
 
	 */
 
	NetworkContentListWindow(const WindowDesc *desc, bool select_all) :
 
			QueryStringBaseWindow(EDITBOX_MAX_SIZE),
 
			selected(NULL),
 
			list_pos(0)
 
	{
 
		this->InitNested(desc, 1);
 

	
 
		this->GetWidget<NWidgetStacked>(NCLWW_SEL_ALL_UPDATE)->SetDisplayedPlane(select_all);
 

	
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, EDITBOX_MAX_LENGTH);
 
		this->SetFocusedWidget(NCLWW_FILTER);
 

	
 
		_network_content_client.AddCallback(this);
 
		this->content.SetListing(this->last_sorting);
 
		this->content.SetFiltering(this->last_filtering);
 
		this->content.SetSortFuncs(this->sorter_funcs);
 
		this->content.SetFilterFuncs(this->filter_funcs);
 
		this->content.ForceRebuild();
 
		this->FilterContentList();
 
		this->SortContentList();
 
		this->InvalidateData();
 
	}
 

	
 
	/** Free everything we allocated */
 
	~NetworkContentListWindow()
 
	{
 
		_network_content_client.RemoveCallback(this);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case NCLWW_FILTER_CAPT:
 
				*size = maxdim(*size, GetStringBoundingBox(STR_CONTENT_FILTER_TITLE));
 
				break;
 

	
 
			case NCLWW_TYPE: {
 
				Dimension d = *size;
 
				for (int i = CONTENT_TYPE_BEGIN; i < CONTENT_TYPE_END; i++) {
 
					d = maxdim(d, GetStringBoundingBox(STR_CONTENT_TYPE_BASE_GRAPHICS + i - CONTENT_TYPE_BASE_GRAPHICS));
 
				}
 
				size->width = d.width + WD_MATRIX_RIGHT + WD_MATRIX_LEFT;
 
				break;
 
			}
 

	
 
			case NCLWW_MATRIX:
 
				resize->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				size->height = 10 * resize->height;
 
				break;
 
		}
 
	}
 

	
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case NCLWW_FILTER_CAPT:
 
				DrawString(r.left, r.right, r.top, STR_CONTENT_FILTER_TITLE, TC_FROMSTRING, SA_RIGHT);
 
				break;
 

	
 
			case NCLWW_DETAILS:
 
				this->DrawDetails(r);
 
				break;
 

	
 
			case NCLWW_MATRIX:
 
				this->DrawMatrix(r);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const SortButtonState arrow = this->content.IsDescSortOrder() ? SBS_DOWN : SBS_UP;
 

	
 
		if (this->content.NeedRebuild()) {
 
			this->BuildContentList();
 
		}
 

	
 
		this->DrawWidgets();
 

	
 
		/* Edit box to filter for keywords */
 
		this->DrawEditBox(NCLWW_FILTER);
 

	
 
		switch (this->content.SortType()) {
 
			case NCLWW_CHECKBOX - NCLWW_CHECKBOX: this->DrawSortButtonState(NCLWW_CHECKBOX, arrow); break;
 
			case NCLWW_TYPE     - NCLWW_CHECKBOX: this->DrawSortButtonState(NCLWW_TYPE,     arrow); break;
 
			case NCLWW_NAME     - NCLWW_CHECKBOX: this->DrawSortButtonState(NCLWW_NAME,     arrow); break;
 
		}
 
	}
 

	
 
	void DrawMatrix(const Rect &r) const
 
	{
 
		const NWidgetBase *nwi_checkbox = this->GetWidget<NWidgetBase>(NCLWW_CHECKBOX);
 
		const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(NCLWW_NAME);
 
		const NWidgetBase *nwi_type = this->GetWidget<NWidgetBase>(NCLWW_TYPE);
 

	
 

	
 
		/* Fill the matrix with the information */
 
		int sprite_y_offset = WD_MATRIX_TOP + (FONT_HEIGHT_NORMAL - 10) / 2;
 
		uint y = r.top;
 
		int cnt = 0;
 
		for (ConstContentIterator iter = this->content.Get(this->vscroll.GetPosition()); iter != this->content.End() && cnt < this->vscroll.GetCapacity(); iter++, cnt++) {
 
			const ContentInfo *ci = *iter;
 

	
 
			if (ci == this->selected) GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 1, 10);
 

	
 
			SpriteID sprite;
 
			SpriteID pal = PAL_NONE;
 
			switch (ci->state) {
 
				case ContentInfo::UNSELECTED:     sprite = SPR_BOX_EMPTY;   break;
 
				case ContentInfo::SELECTED:       sprite = SPR_BOX_CHECKED; break;
 
				case ContentInfo::AUTOSELECTED:   sprite = SPR_BOX_CHECKED; break;
 
				case ContentInfo::ALREADY_HERE:   sprite = SPR_BLOT; pal = PALETTE_TO_GREEN; break;
 
				case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED;   break;
 
				default: NOT_REACHED();
 
			}
 
			DrawSprite(sprite, pal, nwi_checkbox->pos_x + (pal == PAL_NONE ? 2 : 3), y + sprite_y_offset + (pal == PAL_NONE ? 1 : 0));
 

	
 
			StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
 
			DrawString(nwi_type->pos_x, nwi_type->pos_x + nwi_type->current_x - 1, y + WD_MATRIX_TOP, str, TC_BLACK, SA_CENTER);
 

	
 
			DrawString(nwi_name->pos_x + WD_FRAMERECT_LEFT, nwi_name->pos_x + nwi_name->current_x - WD_FRAMERECT_RIGHT, y + WD_MATRIX_TOP, ci->name, TC_BLACK);
 
			y += this->resize.step_height;
 
		}
 
	}
 

	
 
	/**
 
	 * Helper function to draw the details part of this window.
 
	 * @param r the rectangle to stay within while drawing
 
	 */
 
	void DrawDetails(const Rect &r) const
 
	{
 
		static const int DETAIL_LEFT         =  5; ///< Number of pixels at the left
 
		static const int DETAIL_RIGHT        =  5; ///< Number of pixels at the right
 
		static const int DETAIL_TOP          =  5; ///< Number of pixels at the top
 

	
 
		/* Height for the title banner */
 
		int DETAIL_TITLE_HEIGHT = 5 * FONT_HEIGHT_NORMAL;
 

	
 
		/* Create the nice grayish rectangle at the details top */
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + DETAIL_TITLE_HEIGHT, 157);
 
		DrawString(r.left + WD_INSET_LEFT, r.right - WD_INSET_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_INSET_TOP, STR_CONTENT_DETAIL_TITLE, TC_FROMSTRING, SA_CENTER);
 

	
 
		/* Draw the total download size */
 
		SetDParam(0, this->filesize_sum);
 
		DrawString(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_NORMAL, STR_CONTENT_TOTAL_DOWNLOAD_SIZE);
 

	
 
		if (this->selected == NULL) return;
 

	
 
		/* And fill the rest of the details when there's information to place there */
 
		DrawStringMultiLine(r.left + WD_INSET_LEFT, r.right - WD_INSET_RIGHT, r.top + DETAIL_TITLE_HEIGHT / 2, r.top + DETAIL_TITLE_HEIGHT, STR_CONTENT_DETAIL_SUBTITLE_UNSELECTED + this->selected->state, TC_FROMSTRING, SA_CENTER);
 

	
 
		/* Also show the total download size, so keep some space from the bottom */
 
		const uint max_y = r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_WIDE;
 
		int y = r.top + DETAIL_TITLE_HEIGHT + DETAIL_TOP;
 

	
 
		if (this->selected->upgrade) {
 
			SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_UPDATE);
 
			y += WD_PAR_VSEP_WIDE;
 
		}
 

	
 
		SetDParamStr(0, this->selected->name);
 
		y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_NAME);
 

	
 
		if (!StrEmpty(this->selected->version)) {
 
			SetDParamStr(0, this->selected->version);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_VERSION);
 
		}
 

	
 
		if (!StrEmpty(this->selected->description)) {
 
			SetDParamStr(0, this->selected->description);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DESCRIPTION);
 
		}
 

	
 
		if (!StrEmpty(this->selected->url)) {
 
			SetDParamStr(0, this->selected->url);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_URL);
 
		}
 

	
 
		SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
 
		y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TYPE);
 

	
 
		y += WD_PAR_VSEP_WIDE;
 
		SetDParam(0, this->selected->filesize);
 
		y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_FILESIZE);
 

	
 
		if (this->selected->dependency_count != 0) {
 
			/* List dependencies */
 
			char buf[DRAW_STRING_BUFFER] = "";
 
			char *p = buf;
 
			for (uint i = 0; i < this->selected->dependency_count; i++) {
 
				ContentID cid = this->selected->dependencies[i];
 

	
 
				/* Try to find the dependency */
 
				ConstContentIterator iter = _network_content_client.Begin();
 
				for (; iter != _network_content_client.End(); iter++) {
 
					const ContentInfo *ci = *iter;
 
					if (ci->id != cid) continue;
 

	
 
					p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name);
 
					break;
 
				}
 
			}
 
			SetDParamStr(0, buf);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DEPENDENCIES);
 
		}
 

	
 
		if (this->selected->tag_count != 0) {
 
			/* List all tags */
 
			char buf[DRAW_STRING_BUFFER] = "";
 
			char *p = buf;
 
			for (uint i = 0; i < this->selected->tag_count; i++) {
 
				p += seprintf(p, lastof(buf), i == 0 ? "%s" : ", %s", this->selected->tags[i]);
 
			}
 
			SetDParamStr(0, buf);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TAGS);
 
		}
 

	
 
		if (this->selected->IsSelected()) {
 
			/* When selected show all manually selected content that depends on this */
 
			ConstContentVector tree;
 
			_network_content_client.ReverseLookupTreeDependency(tree, this->selected);
 

	
 
			char buf[DRAW_STRING_BUFFER] = "";
 
			char *p = buf;
 
			for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
 
				const ContentInfo *ci = *iter;
 
				if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
 

	
 
				p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name);
 
			}
 
			if (p != buf) {
 
				SetDParamStr(0, buf);
 
				y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget, int click_count)
 
	{
 
		switch (widget) {
 
			case NCLWW_MATRIX: {
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(NCLWW_MATRIX)->pos_y) / this->resize.step_height;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->content.Length()) return; // click out of bounds
 

	
 
				this->selected = *this->content.Get(id_v);
 
				this->list_pos = id_v;
 

	
 
				const NWidgetBase *checkbox = this->GetWidget<NWidgetBase>(NCLWW_CHECKBOX);
 
				if (click_count > 1 || IsInsideBS(pt.x, checkbox->pos_x, checkbox->current_x)) {
 
					_network_content_client.ToggleSelectedState(this->selected);
 
					this->content.ForceResort();
 
				}
 

	
 
				this->InvalidateData();
 
			} break;
 

	
 
			case NCLWW_CHECKBOX:
 
			case NCLWW_TYPE:
src/network/network_gamelist.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * @file network_gamelist.cpp This file handles the GameList
 
 * Also, it handles the request to a server for data about the server
 
 */
 

	
 
#ifdef ENABLE_NETWORK
 

	
 
#include "../stdafx.h"
 
#include "../debug.h"
 
#include "../thread/thread.h"
 
#include "network_internal.h"
 
#include "network_udp.h"
 
#include "network_gamelist.h"
 

	
 
NetworkGameList *_network_game_list = NULL;
 

	
 
static ThreadMutex *_network_game_list_mutex = ThreadMutex::New();
 
static NetworkGameList *_network_game_delayed_insertion_list = NULL;
 

	
 
/** Add a new item to the linked gamelist, but do it delayed in the next tick
 
 * or so to prevent race conditions.
 
 * @param item the item to add. Will be freed once added.
 
 */
 
void NetworkGameListAddItemDelayed(NetworkGameList *item)
 
{
 
	_network_game_list_mutex->BeginCritical();
 
	item->next = _network_game_delayed_insertion_list;
 
	_network_game_delayed_insertion_list = item;
 
	_network_game_list_mutex->EndCritical();
 
}
 

	
 
/** Perform the delayed (thread safe) insertion into the game list */
 
static void NetworkGameListHandleDelayedInsert()
 
{
 
	_network_game_list_mutex->BeginCritical();
 
	while (_network_game_delayed_insertion_list != NULL) {
 
		NetworkGameList *ins_item = _network_game_delayed_insertion_list;
 
		_network_game_delayed_insertion_list = ins_item->next;
 

	
 
		NetworkGameList *item = NetworkGameListAddItem(ins_item->address);
 

	
 
		if (item != NULL) {
 
			if (StrEmpty(item->info.server_name)) {
 
				ClearGRFConfigList(&item->info.grfconfig);
 
				memset(&item->info, 0, sizeof(item->info));
 
				strecpy(item->info.server_name, ins_item->info.server_name, lastof(item->info.server_name));
 
				strecpy(item->info.hostname, ins_item->info.hostname, lastof(item->info.hostname));
 
				item->online = false;
 
			}
 
			item->manually |= ins_item->manually;
 
			if (item->manually) NetworkRebuildHostList();
 
			UpdateNetworkGameWindow(false);
 
		}
 
		free(ins_item);
 
	}
 
	_network_game_list_mutex->EndCritical();
 
}
 

	
 
/** Add a new item to the linked gamelist. If the IP and Port match
 
 * return the existing item instead of adding it again
 
 * @param address the address of the to-be added item
 
 * @param port the port the server is running on
 
 * @return a point to the newly added or already existing item */
 
NetworkGameList *NetworkGameListAddItem(NetworkAddress address)
 
{
 
	const char *hostname = address.GetHostname();
 

	
 
	/* Do not query the 'any' address. */
 
	if (StrEmpty(hostname) ||
 
			strcmp(hostname, "0.0.0.0") == 0 ||
 
			strcmp(hostname, "::") == 0) {
 
		return NULL;
 
	}
 

	
 
	NetworkGameList *item, *prev_item;
 

	
 
	prev_item = NULL;
 
	for (item = _network_game_list; item != NULL; item = item->next) {
 
		if (item->address == address) return item;
 
		prev_item = item;
 
	}
 

	
 
	item = CallocT<NetworkGameList>(1);
 
	item->next = NULL;
 
	item->address = address;
 

	
 
	if (prev_item == NULL) {
 
		_network_game_list = item;
 
	} else {
 
		prev_item->next = item;
 
	}
 
	DEBUG(net, 4, "[gamelist] added server to list");
 

	
 
	UpdateNetworkGameWindow(false);
 

	
 
	return item;
 
}
 

	
 
/** Remove an item from the gamelist linked list
 
 * @param remove pointer to the item to be removed */
 
void NetworkGameListRemoveItem(NetworkGameList *remove)
 
{
 
	NetworkGameList *prev_item = NULL;
 
	for (NetworkGameList *item = _network_game_list; item != NULL; item = item->next) {
 
		if (remove == item) {
 
			if (prev_item == NULL) {
 
				_network_game_list = remove->next;
 
			} else {
 
				prev_item->next = remove->next;
 
			}
 

	
 
			/* Remove GRFConfig information */
 
			ClearGRFConfigList(&remove->info.grfconfig);
 
			free(remove);
 
			remove = NULL;
 

	
 
			DEBUG(net, 4, "[gamelist] removed server from list");
 
			NetworkRebuildHostList();
 
			UpdateNetworkGameWindow(false);
 
			return;
 
		}
 
		prev_item = item;
 
	}
 
}
 

	
 
enum {
 
	MAX_GAME_LIST_REQUERY_COUNT  = 10, ///< How often do we requery in number of times per server?
 
	REQUERY_EVERY_X_GAMELOOPS    = 60, ///< How often do we requery in time?
 
	REFRESH_GAMEINFO_X_REQUERIES = 50, ///< Refresh the game info itself after REFRESH_GAMEINFO_X_REQUERIES * REQUERY_EVERY_X_GAMELOOPS game loops
 
};
 
static const uint MAX_GAME_LIST_REQUERY_COUNT  = 10; ///< How often do we requery in number of times per server?
 
static const uint REQUERY_EVERY_X_GAMELOOPS    = 60; ///< How often do we requery in time?
 
static const uint REFRESH_GAMEINFO_X_REQUERIES = 50; ///< Refresh the game info itself after REFRESH_GAMEINFO_X_REQUERIES * REQUERY_EVERY_X_GAMELOOPS game loops
 

	
 
/** Requeries the (game) servers we have not gotten a reply from */
 
void NetworkGameListRequery()
 
{
 
	NetworkGameListHandleDelayedInsert();
 

	
 
	static uint8 requery_cnt = 0;
 

	
 
	if (++requery_cnt < REQUERY_EVERY_X_GAMELOOPS) return;
 
	requery_cnt = 0;
 

	
 
	for (NetworkGameList *item = _network_game_list; item != NULL; item = item->next) {
 
		item->retries++;
 
		if (item->retries < REFRESH_GAMEINFO_X_REQUERIES && (item->online || item->retries >= MAX_GAME_LIST_REQUERY_COUNT)) continue;
 

	
 
		/* item gets mostly zeroed by NetworkUDPQueryServer */
 
		uint8 retries = item->retries;
 
		NetworkUDPQueryServer(NetworkAddress(item->address));
 
		item->retries = (retries >= REFRESH_GAMEINFO_X_REQUERIES) ? 0 : retries;
 
	}
 
}
 

	
 
/**
 
 * Rebuild the GRFConfig's of the servers in the game list as we did
 
 * a rescan and might have found new NewGRFs.
 
 */
 
void NetworkAfterNewGRFScan()
 
{
 
	for (NetworkGameList *item = _network_game_list; item != NULL; item = item->next) {
 
		/* Reset compatability state */
 
		item->info.compatible = item->info.version_compatible;
 

	
 
		for (GRFConfig *c = item->info.grfconfig; c != NULL; c = c->next) {
 
			assert(HasBit(c->flags, GCF_COPY));
 

	
 
			const GRFConfig *f = FindGRFConfig(c->ident.grfid, c->ident.md5sum);
 
			if (f == NULL) {
 
				/* Don't know the GRF, so mark game incompatible and the (possibly)
 
				 * already resolved name for this GRF (another server has sent the
 
				 * name of the GRF already */
 
				c->name   = FindUnknownGRFName(c->ident.grfid, c->ident.md5sum, true);
 
				c->status = GCS_NOT_FOUND;
 

	
 
				/* If we miss a file, we're obviously incompatible */
 
				item->info.compatible = false;
 
			} else {
 
				c->filename  = f->filename;
 
				c->name      = f->name;
 
				c->info      = f->info;
 
				c->status    = GCS_UNKNOWN;
 
			}
 
		}
 
	}
 
}
 

	
 
#endif /* ENABLE_NETWORK */
src/network/network_udp.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * @file network_udp.cpp This file handles the UDP related communication.
 
 *
 
 * This is the GameServer <-> MasterServer and GameServer <-> GameClient
 
 * communication before the game is being joined.
 
 */
 

	
 
#ifdef ENABLE_NETWORK
 

	
 
#include "../stdafx.h"
 
#include "../date_func.h"
 
#include "../map_func.h"
 
#include "../debug.h"
 
#include "network_gamelist.h"
 
#include "network_internal.h"
 
#include "network_udp.h"
 
#include "network.h"
 
#include "../core/endian_func.hpp"
 
#include "../company_base.h"
 
#include "../thread/thread.h"
 
#include "../rev.h"
 

	
 
#include "core/udp.h"
 

	
 
static ThreadMutex *_network_udp_mutex = ThreadMutex::New();
 

	
 
/** Session key to register ourselves to the master server */
 
static uint64 _session_key = 0;
 

	
 
enum {
 
	ADVERTISE_NORMAL_INTERVAL = 30000, // interval between advertising in ticks (15 minutes)
 
	ADVERTISE_RETRY_INTERVAL  =   300, // readvertise when no response after this many ticks (9 seconds)
 
	ADVERTISE_RETRY_TIMES     =     3  // give up readvertising after this much failed retries
 
};
 
static const uint ADVERTISE_NORMAL_INTERVAL = 30000; ///< interval between advertising in ticks (15 minutes)
 
static const uint ADVERTISE_RETRY_INTERVAL  =   300; ///< readvertise when no response after this many ticks (9 seconds)
 
static const uint ADVERTISE_RETRY_TIMES     =     3; ///< give up readvertising after this much failed retries
 

	
 
NetworkUDPSocketHandler *_udp_client_socket = NULL; ///< udp client socket
 
NetworkUDPSocketHandler *_udp_server_socket = NULL; ///< udp server socket
 
NetworkUDPSocketHandler *_udp_master_socket = NULL; ///< udp master socket
 

	
 
///*** Communication with the masterserver ***/
 

	
 
class MasterNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
 
protected:
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_MASTER_ACK_REGISTER);
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_MASTER_SESSION_KEY);
 
public:
 
	MasterNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {}
 
	virtual ~MasterNetworkUDPSocketHandler() {}
 
};
 

	
 
DEF_UDP_RECEIVE_COMMAND(Master, PACKET_UDP_MASTER_ACK_REGISTER)
 
{
 
	_network_advertise_retries = 0;
 
	DEBUG(net, 2, "[udp] advertising on master server successful (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family));
 

	
 
	/* We are advertised, but we don't want to! */
 
	if (!_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(false);
 
}
 

	
 
DEF_UDP_RECEIVE_COMMAND(Master, PACKET_UDP_MASTER_SESSION_KEY)
 
{
 
	_session_key = p->Recv_uint64();
 
	DEBUG(net, 2, "[udp] received new session key from master server (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family));
 
}
 

	
 
///*** Communication with clients (we are server) ***/
 

	
 
class ServerNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
 
protected:
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_CLIENT_FIND_SERVER);
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_CLIENT_DETAIL_INFO);
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_CLIENT_GET_NEWGRFS);
 
public:
 
	ServerNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {}
 
	virtual ~ServerNetworkUDPSocketHandler() {}
 
};
 

	
 
DEF_UDP_RECEIVE_COMMAND(Server, PACKET_UDP_CLIENT_FIND_SERVER)
 
{
 
	/* Just a fail-safe.. should never happen */
 
	if (!_network_udp_server) {
 
		return;
 
	}
 

	
 
	NetworkGameInfo ngi;
 

	
 
	/* Update some game_info */
 
	ngi.clients_on     = _network_game_info.clients_on;
 
	ngi.start_date     = ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1);
 

	
 
	ngi.server_lang    = _settings_client.network.server_lang;
 
	ngi.use_password   = !StrEmpty(_settings_client.network.server_password);
 
	ngi.clients_max    = _settings_client.network.max_clients;
 
	ngi.companies_on   = (byte)Company::GetNumItems();
 
	ngi.companies_max  = _settings_client.network.max_companies;
 
	ngi.spectators_on  = NetworkSpectatorCount();
 
	ngi.spectators_max = _settings_client.network.max_spectators;
 
	ngi.game_date      = _date;
 
	ngi.map_width      = MapSizeX();
 
	ngi.map_height     = MapSizeY();
 
	ngi.map_set        = _settings_game.game_creation.landscape;
 
	ngi.dedicated      = _network_dedicated;
 
	ngi.grfconfig      = _grfconfig;
 

	
 
	strecpy(ngi.map_name, _network_game_info.map_name, lastof(ngi.map_name));
 
	strecpy(ngi.server_name, _settings_client.network.server_name, lastof(ngi.server_name));
 
	strecpy(ngi.server_revision, _openttd_revision, lastof(ngi.server_revision));
 

	
 
	Packet packet(PACKET_UDP_SERVER_RESPONSE);
 
	this->Send_NetworkGameInfo(&packet, &ngi);
 

	
 
	/* Let the client know that we are here */
 
	this->SendPacket(&packet, client_addr);
 

	
 
	DEBUG(net, 2, "[udp] queried from %s", client_addr->GetHostname());
 
}
 

	
 
DEF_UDP_RECEIVE_COMMAND(Server, PACKET_UDP_CLIENT_DETAIL_INFO)
 
{
 
	/* Just a fail-safe.. should never happen */
 
	if (!_network_udp_server) return;
 

	
 
	Packet packet(PACKET_UDP_SERVER_DETAIL_INFO);
 

	
 
	/* Send the amount of active companies */
 
	packet.Send_uint8 (NETWORK_COMPANY_INFO_VERSION);
 
	packet.Send_uint8 ((uint8)Company::GetNumItems());
 

	
 
	/* Fetch the latest version of the stats */
 
	NetworkCompanyStats company_stats[MAX_COMPANIES];
 
	NetworkPopulateCompanyStats(company_stats);
 

	
 
	Company *company;
 
	/* Go through all the companies */
 
	FOR_ALL_COMPANIES(company) {
 
		/* Send the information */
 
		this->Send_CompanyInformation(&packet, company, &company_stats[company->index]);
 
	}
 

	
 
	this->SendPacket(&packet, client_addr);
 
}
 

	
 
/**
 
 * A client has requested the names of some NewGRFs.
 
 *
 
 * Replying this can be tricky as we have a limit of SEND_MTU bytes
 
 * in the reply packet and we can send up to 100 bytes per NewGRF
 
 * (GRF ID, MD5sum and NETWORK_GRF_NAME_LENGTH bytes for the name).
 
 * As SEND_MTU is _much_ less than 100 * NETWORK_MAX_GRF_COUNT, it
 
 * could be that a packet overflows. To stop this we only reply
 
 * with the first N NewGRFs so that if the first N + 1 NewGRFs
 
 * would be sent, the packet overflows.
 
 * in_reply and in_reply_count are used to keep a list of GRFs to
 
 * send in the reply.
 
 */
 
DEF_UDP_RECEIVE_COMMAND(Server, PACKET_UDP_CLIENT_GET_NEWGRFS)
 
{
 
	uint8 num_grfs;
 
	uint i;
 

	
 
	const GRFConfig *in_reply[NETWORK_MAX_GRF_COUNT];
 
	uint8 in_reply_count = 0;
 
	size_t packet_len = 0;
 

	
 
	DEBUG(net, 6, "[udp] newgrf data request from %s", client_addr->GetAddressAsString());
 

	
 
	num_grfs = p->Recv_uint8 ();
 
	if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
 

	
 
	for (i = 0; i < num_grfs; i++) {
 
		GRFIdentifier c;
 
		const GRFConfig *f;
 

	
 
		this->Recv_GRFIdentifier(p, &c);
 

	
 
		/* Find the matching GRF file */
 
		f = FindGRFConfig(c.grfid, c.md5sum);
 
		if (f == NULL) continue; // The GRF is unknown to this server
 

	
 
		/* If the reply might exceed the size of the packet, only reply
 
		 * the current list and do not send the other data.
 
		 * The name could be an empty string, if so take the filename. */
 
		packet_len += sizeof(c.grfid) + sizeof(c.md5sum) +
 
				min(strlen(f->GetName()) + 1, (size_t)NETWORK_GRF_NAME_LENGTH);
 
		if (packet_len > SEND_MTU - 4) { // 4 is 3 byte header + grf count in reply
 
			break;
 
		}
 
		in_reply[in_reply_count] = f;
 
		in_reply_count++;
 
	}
 

	
 
	if (in_reply_count == 0) return;
 

	
 
	Packet packet(PACKET_UDP_SERVER_NEWGRFS);
 
	packet.Send_uint8(in_reply_count);
 
	for (i = 0; i < in_reply_count; i++) {
 
		char name[NETWORK_GRF_NAME_LENGTH];
 

	
 
		/* The name could be an empty string, if so take the filename */
 
		strecpy(name, in_reply[i]->GetName(), lastof(name));
 
		this->Send_GRFIdentifier(&packet, &in_reply[i]->ident);
 
		packet.Send_string(name);
 
	}
 

	
 
	this->SendPacket(&packet, client_addr);
 
}
 

	
 
///*** Communication with servers (we are client) ***/
 

	
 
class ClientNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
 
protected:
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE);
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_MASTER_RESPONSE_LIST);
 
	DECLARE_UDP_RECEIVE_COMMAND(PACKET_UDP_SERVER_NEWGRFS);
 
	virtual void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config);
 
public:
 
	virtual ~ClientNetworkUDPSocketHandler() {}
 
};
 

	
 
DEF_UDP_RECEIVE_COMMAND(Client, PACKET_UDP_SERVER_RESPONSE)
 
{
 
	NetworkGameList *item;
 

	
 
	/* Just a fail-safe.. should never happen */
 
	if (_network_udp_server) return;
 

	
 
	DEBUG(net, 4, "[udp] server response from %s", client_addr->GetAddressAsString());
 

	
 
	/* Find next item */
 
	item = NetworkGameListAddItem(*client_addr);
 

	
 
	ClearGRFConfigList(&item->info.grfconfig);
 
	this->Recv_NetworkGameInfo(p, &item->info);
 

	
 
	item->info.compatible = true;
 
	{
 
		/* Checks whether there needs to be a request for names of GRFs and makes
 
		 * the request if necessary. GRFs that need to be requested are the GRFs
 
		 * that do not exist on the clients system and we do not have the name
 
		 * resolved of, i.e. the name is still UNKNOWN_GRF_NAME_PLACEHOLDER.
 
		 * The in_request array and in_request_count are used so there is no need
 
		 * to do a second loop over the GRF list, which can be relatively expensive
 
		 * due to the string comparisons. */
 
		const GRFConfig *in_request[NETWORK_MAX_GRF_COUNT];
 
		const GRFConfig *c;
 
		uint in_request_count = 0;
 

	
 
		for (c = item->info.grfconfig; c != NULL; c = c->next) {
 
			if (c->status == GCS_NOT_FOUND) item->info.compatible = false;
 
			if (c->status != GCS_NOT_FOUND || strcmp(c->GetName(), UNKNOWN_GRF_NAME_PLACEHOLDER) != 0) continue;
 
			in_request[in_request_count] = c;
 
			in_request_count++;
 
		}
 

	
 
		if (in_request_count > 0) {
 
			/* There are 'unknown' GRFs, now send a request for them */
 
			uint i;
 
			Packet packet(PACKET_UDP_CLIENT_GET_NEWGRFS);
 

	
 
			packet.Send_uint8(in_request_count);
 
			for (i = 0; i < in_request_count; i++) {
 
				this->Send_GRFIdentifier(&packet, &in_request[i]->ident);
 
			}
 

	
 
			this->SendPacket(&packet, &item->address);
 
		}
 
	}
 

	
 
	if (item->info.hostname[0] == '\0') {
 
		snprintf(item->info.hostname, sizeof(item->info.hostname), "%s", client_addr->GetHostname());
 
	}
 

	
 
	if (client_addr->GetAddress()->ss_family == AF_INET6) {
 
		strecat(item->info.server_name, " (IPv6)", lastof(item->info.server_name));
 
	}
 

	
 
	/* Check if we are allowed on this server based on the revision-match */
 
	item->info.version_compatible = IsNetworkCompatibleVersion(item->info.server_revision);
 
	item->info.compatible &= item->info.version_compatible; // Already contains match for GRFs
 

	
 
	item->online = true;
 

	
 
	UpdateNetworkGameWindow(false);
 
}
 

	
 
DEF_UDP_RECEIVE_COMMAND(Client, PACKET_UDP_MASTER_RESPONSE_LIST)
 
{
 
	/* packet begins with the protocol version (uint8)
 
	 * then an uint16 which indicates how many
 
	 * ip:port pairs are in this packet, after that
 
	 * an uint32 (ip) and an uint16 (port) for each pair.
 
	 */
 

	
 
	ServerListType type = (ServerListType)(p->Recv_uint8() - 1);
 

	
 
	if (type < SLT_END) {
 
		for (int i = p->Recv_uint16(); i != 0 ; i--) {
 
			sockaddr_storage addr_storage;
 
			memset(&addr_storage, 0, sizeof(addr_storage));
 

	
 
			if (type == SLT_IPv4) {
 
				addr_storage.ss_family = AF_INET;
 
				((sockaddr_in*)&addr_storage)->sin_addr.s_addr = TO_LE32(p->Recv_uint32());
 
			} else {
 
				assert(type == SLT_IPv6);
 
				addr_storage.ss_family = AF_INET6;
 
				byte *addr = (byte*)&((sockaddr_in6*)&addr_storage)->sin6_addr;
 
				for (uint i = 0; i < sizeof(in6_addr); i++) *addr++ = p->Recv_uint8();
 
			}
 
			NetworkAddress addr(addr_storage, type == SLT_IPv4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
 
			addr.SetPort(p->Recv_uint16());
 

	
 
			/* Somehow we reached the end of the packet */
 
			if (this->HasClientQuit()) return;
 

	
 
			NetworkUDPQueryServer(addr);
 
		}
 
	}
 
}
 

	
 
/** The return of the client's request of the names of some NewGRFs */
 
DEF_UDP_RECEIVE_COMMAND(Client, PACKET_UDP_SERVER_NEWGRFS)
 
{
 
	uint8 num_grfs;
 
	uint i;
 

	
 
	DEBUG(net, 6, "[udp] newgrf data reply from %s", client_addr->GetAddressAsString());
 

	
 
	num_grfs = p->Recv_uint8 ();
 
	if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
 

	
 
	for (i = 0; i < num_grfs; i++) {
 
		char *unknown_name;
 
		char name[NETWORK_GRF_NAME_LENGTH];
 
		GRFIdentifier c;
 

	
 
		this->Recv_GRFIdentifier(p, &c);
 
		p->Recv_string(name, sizeof(name));
 

	
 
		/* An empty name is not possible under normal circumstances
 
		 * and causes problems when showing the NewGRF list. */
 
		if (StrEmpty(name)) continue;
 

	
 
		/* Finds the fake GRFConfig for the just read GRF ID and MD5sum tuple.
 
		 * If it exists and not resolved yet, then name of the fake GRF is
 
		 * overwritten with the name from the reply. */
 
		unknown_name = FindUnknownGRFName(c.grfid, c.md5sum, false);
 
		if (unknown_name != NULL && strcmp(unknown_name, UNKNOWN_GRF_NAME_PLACEHOLDER) == 0) {
 
			ttd_strlcpy(unknown_name, name, NETWORK_GRF_NAME_LENGTH);
 
		}
 
	}
 
}
 

	
 
void ClientNetworkUDPSocketHandler::HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config)
 
{
 
	/* Find the matching GRF file */
 
	const GRFConfig *f = FindGRFConfig(config->ident.grfid, config->ident.md5sum);
 
	if (f == NULL) {
 
		/* Don't know the GRF, so mark game incompatible and the (possibly)
 
		 * already resolved name for this GRF (another server has sent the
 
		 * name of the GRF already */
 
		config->name   = FindUnknownGRFName(config->ident.grfid, config->ident.md5sum, true);
 
		config->status = GCS_NOT_FOUND;
 
	} else {
 
		config->filename  = f->filename;
 
		config->name      = f->name;
 
		config->info      = f->info;
 
	}
 
	SetBit(config->flags, GCF_COPY);
 
}
 

	
 
/* Broadcast to all ips */
 
static void NetworkUDPBroadCast(NetworkUDPSocketHandler *socket)
 
{
 
	for (NetworkAddress *addr = _broadcast_list.Begin(); addr != _broadcast_list.End(); addr++) {
 
		Packet p(PACKET_UDP_CLIENT_FIND_SERVER);
 

	
 
		DEBUG(net, 4, "[udp] broadcasting to %s", addr->GetHostname());
 

	
 
		socket->SendPacket(&p, addr, true, true);
 
	}
 
}
 

	
 

	
 
/* Request the the server-list from the master server */
 
void NetworkUDPQueryMasterServer()
 
{
 
	Packet p(PACKET_UDP_CLIENT_GET_LIST);
 
	NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
 

	
 
	/* packet only contains protocol version */
 
	p.Send_uint8(NETWORK_MASTER_SERVER_VERSION);
 
	p.Send_uint8(SLT_AUTODETECT);
 

	
 
	_udp_client_socket->SendPacket(&p, &out_addr, true);
 

	
 
	DEBUG(net, 2, "[udp] master server queried at %s", out_addr.GetAddressAsString());
 
}
 

	
 
/* Find all servers */
 
void NetworkUDPSearchGame()
 
{
 
	/* We are still searching.. */
 
	if (_network_udp_broadcast > 0) return;
 

	
 
	DEBUG(net, 0, "[udp] searching server");
 

	
 
	NetworkUDPBroadCast(_udp_client_socket);
 
	_network_udp_broadcast = 300; // Stay searching for 300 ticks
 
}
 

	
 
/** Simpler wrapper struct for NetworkUDPQueryServerThread */
 
struct NetworkUDPQueryServerInfo : NetworkAddress {
 
	bool manually; ///< Did we connect manually or not?
 
	NetworkUDPQueryServerInfo(const NetworkAddress &address, bool manually) :
 
		NetworkAddress(address),
 
		manually(manually)
 
	{

Changeset was too big and was cut off... Show full diff anyway

0 comments (0 inline, 0 general)