Beispiel #1
0
/**
 * @brief Console command binding for save function
 * @sa SAV_GameSave
 * @note called via 'game_save' command
 */
static void SAV_GameSave_f (void)
{
	char comment[MAX_VAR] = "";
	char *error = NULL;
	bool result;

	/* get argument */
	if (Cmd_Argc() < 2) {
		Com_Printf("Usage: %s <filename> <comment|*cvar>\n", Cmd_Argv(0));
		return;
	}

	if (!CP_IsRunning()) {
		Com_Printf("No running game - no saving...\n");
		return;
	}

	/* get comment */
	if (Cmd_Argc() > 2) {
		const char *arg = Cmd_Argv(2);
		Q_strncpyz(comment, arg, sizeof(comment));
	}

	result = SAV_GameSave(Cmd_Argv(1), comment, &error);
	if (!result) {
		if (error)
			CP_Popup(_("Note"), "%s\n%s", _("Error saving game."), error);
		else
			CP_Popup(_("Note"), "%s\n%s", "%s\n", _("Error saving game."));
	}
}
/**
 * @brief Sells aircraft or craftitem.
 * @sa BS_BuyAircraft_f
 */
static void BS_SellAircraft_f (void)
{
	int num;
	base_t *base = B_GetCurrentSelectedBase();

	if (Cmd_Argc() < 2) {
		Com_Printf("Usage: %s <num>\n", Cmd_Argv(0));
		return;
	}

	if (!base)
		return;

	num = atoi(Cmd_Argv(1));
	if (num < 0 || num >= buyList.length)
		return;

	if (buyCat == FILTER_AIRCRAFT) {
		const aircraft_t *aircraftTemplate = buyList.l[num].aircraft;
		qboolean aircraftOutNote = qfalse;
		qboolean teamNote = qfalse;
		aircraft_t *aircraft = NULL;

		if (!aircraftTemplate)
			return;

		AIR_ForeachFromBase(a, base) {
			if (Q_streq(a->id, aircraftTemplate->id)) {
				if (AIR_GetTeamSize(a) > 0) {
					teamNote = qtrue;
					continue;
				}
				if (!AIR_IsAircraftInBase(a)) {
					/* aircraft is not in base */
					aircraftOutNote = qtrue;
					continue;
				}
				aircraft = a;
				break;
			}
		}

		if (aircraft) {
			BS_SellAircraft(aircraft);

			/* reinit the menu */
			BS_BuyType(base);
		} else {
			if (teamNote)
				CP_Popup(_("Note"), _("You can't sell an aircraft if it still has a team assigned"));
			else if (aircraftOutNote)
				CP_Popup(_("Note"), _("You can't sell an aircraft that is not in base"));
			else
				Com_DPrintf(DEBUG_CLIENT, "BS_SellAircraft_f: There are no aircraft available (with no team assigned) for selling\n");
		}
	}
}
/**
 * @brief Starts an aircraft or stops the current mission and let the aircraft idle around.
 */
static void AIM_AircraftStart_f (void)
{
	aircraft_t *aircraft;
	base_t *base = B_GetCurrentSelectedBase();

	if (!base)
		return;

	if (!base->aircraftCurrent) {
		Com_DPrintf(DEBUG_CLIENT, "Error - there is no current aircraft in this base\n");
		return;
	}

	/* Aircraft cannot start without Command Centre operational. */
	if (!B_GetBuildingStatus(base, B_COMMAND)) {
		CP_Popup(_("Notice"), _("No operational Command Centre in this base.\n\nAircraft can not start.\n"));
		return;
	}

	aircraft = base->aircraftCurrent;

	/* Aircraft cannot start without a pilot. */
	if (!AIR_GetPilot(aircraft)) {
		CP_Popup(_("Notice"), _("There is no pilot assigned to this aircraft.\n\nAircraft can not start.\n"));
		return;
	}

	if (AIR_IsAircraftInBase(aircraft)) {
		/* reload its ammunition */
		AII_ReloadAircraftWeapons(aircraft);
	}
	MS_AddNewMessage(_("Notice"), _("Aircraft started"), qfalse, MSG_STANDARD, NULL);
	aircraft->status = AIR_IDLE;

	MAP_SelectAircraft(aircraft);
	/* Return to geoscape. */
	UI_PopWindow(qfalse);
	UI_PopWindow(qfalse);
}
/**
 * @brief Constructs a new installation.
 */
static void INS_BuildInstallation_f (void)
{
	const installationTemplate_t* installationTemplate;

	if (cgi->Cmd_Argc() < 1) {
		Com_Printf("Usage: %s <installationType>\n", cgi->Cmd_Argv(0));
		return;
	}

	/* We shouldn't build more installations than the actual limit */
	if (B_GetInstallationLimit() <= INS_GetCount())
		return;

	installationTemplate = INS_GetInstallationTemplateByID(cgi->Cmd_Argv(1));
	if (!installationTemplate) {
		Com_Printf("The installation type %s passed for %s is not valid.\n", cgi->Cmd_Argv(1), cgi->Cmd_Argv(0));
		return;
	}

	assert(installationTemplate->cost >= 0);

	if (ccs.credits - installationTemplate->cost > 0) {
		/* set up the installation */
		installation_t* installation = INS_Build(installationTemplate, ccs.newBasePos, cgi->Cvar_GetString("mn_installation_title"));

		CP_UpdateCredits(ccs.credits - installationTemplate->cost);
		/* this cvar is used for disabling the installation build button on geoscape if MAX_INSTALLATIONS was reached */
		cgi->Cvar_SetValue("mn_installation_count", INS_GetCount());

		const nation_t* nation = GEO_GetNation(installation->pos);
		if (nation)
			Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new installation has been built: %s (nation: %s)"), installation->name, _(nation->name));
		else
			Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new installation has been built: %s"), installation->name);
		MSO_CheckAddNewMessage(NT_INSTALLATION_BUILDSTART, _("Installation building"), cp_messageBuffer, MSG_CONSTRUCTION);
	} else {
		if (installationTemplate->type == INSTALLATION_RADAR) {
			if (GEO_IsRadarOverlayActivated())
					GEO_SetOverlay("radar");
		}
		if (ccs.mapAction == MA_NEWINSTALLATION)
			ccs.mapAction = MA_NONE;

		CP_Popup(_("Notice"), _("Not enough credits to set up a new installation."));
	}
	ccs.mapAction = MA_NONE;
}
Beispiel #5
0
/**
 * @brief Loads the quick save slot
 * @sa SAV_GameQuickSave_f
 */
static void SAV_GameQuickLoad_f (void)
{
	const char *error = NULL;

	if (cgi->CL_OnBattlescape()) {
		Com_Printf("Could not load the campaign while you are on the battlefield\n");
		return;
	}

	if (!SAV_GameLoad("slotquick", &error)) {
		Cbuf_Execute(); /* wipe outstanding campaign commands */
		CP_Popup(_("Error"), "%s\n%s", _("Error loading game."), error ? error : "");
	} else {
		MS_AddNewMessage(_("Campaign loaded"), _("Quicksave campaign was successfully loaded."), MSG_INFO);
		CP_CheckBaseAttacks();
	}
}
/**
 * @brief Checks why a button in base menu is disabled, and create a popup to inform player
 */
static void B_CheckBuildingStatusForMenu_f (void)
{
	int num;
	const char *buildingID;
	const building_t *building;
	const base_t *base = B_GetCurrentSelectedBase();

	if (cgi->Cmd_Argc() != 2) {
		Com_Printf("Usage: %s <buildingID>\n", cgi->Cmd_Argv(0));
		return;
	}

	buildingID = cgi->Cmd_Argv(1);
	building = B_GetBuildingTemplate(buildingID);

	if (!building || !base)
		return;

	/* Maybe base is under attack ? */
	if (B_IsUnderAttack(base)) {
		CP_Popup(_("Notice"), _("Base is under attack, you can't access this building !"));
		return;
	}

	if (building->buildingType == B_HANGAR) {
		/* this is an exception because you must have a small or large hangar to enter aircraft menu */
		CP_Popup(_("Notice"), _("You need at least one Hangar (and its dependencies) to use aircraft."));
		return;
	}

	num = B_GetNumberOfBuildingsInBaseByBuildingType(base, building->buildingType);
	if (num > 0) {
		int numUnderConstruction;
		/* maybe all buildings of this type are under construction ? */
		B_CheckBuildingTypeStatus(base, building->buildingType, B_STATUS_UNDER_CONSTRUCTION, &numUnderConstruction);
		if (numUnderConstruction == num) {
			int minDay = 99999;
			building_t *b = NULL;

			while ((b = B_GetNextBuildingByType(base, b, building->buildingType))) {
				if (b->buildingStatus == B_STATUS_UNDER_CONSTRUCTION) {
					const float remaining = B_GetConstructionTimeRemain(b);
					minDay = std::min(minDay, (int)std::max(0.0f, remaining));
				}
			}

			CP_Popup(_("Notice"), ngettext("Construction of building will be over in %i day.\nPlease wait to enter.", "Construction of building will be over in %i days.\nPlease wait to enter.",
					minDay), minDay);
			return;
		}

		if (!B_CheckBuildingDependencesStatus(building)) {
			const building_t *dependenceBuilding = building->dependsBuilding;
			assert(building->dependsBuilding);
			if (B_GetNumberOfBuildingsInBaseByBuildingType(base, dependenceBuilding->buildingType) <= 0) {
				/* the dependence of the building is not built */
				CP_Popup(_("Notice"), _("You need a building %s to make building %s functional."), _(dependenceBuilding->name), _(building->name));
				return;
			} else {
				/* maybe the dependence of the building is under construction
				 * note that we can't use B_STATUS_UNDER_CONSTRUCTION here, because this value
				 * is not use for every building (for exemple Command Centre) */
				building_t *b = NULL;

				while ((b = B_GetNextBuildingByType(base, b, dependenceBuilding->buildingType))) {
					if (!B_IsBuildingBuiltUp(b)) {
						CP_Popup(_("Notice"), _("Building %s is not finished yet, and is needed to use building %s."),
								_(dependenceBuilding->name), _(building->name));
						return;
					}
				}
				/* the dependence is built but doesn't work - must be because of their dependendes */
				CP_Popup(_("Notice"), _("Make sure that the dependencies of building %s (%s) are operational, so that building %s may be used."),
						_(dependenceBuilding->name), _(dependenceBuilding->dependsBuilding->name), _(building->name));
				return;
			}
		}
		/* all buildings are OK: employees must be missing */
		if (building->buildingType == B_WORKSHOP && E_CountHired(base, EMPL_WORKER) <= 0) {
			CP_Popup(_("Notice"), _("You need to recruit %s to use building %s."),
					E_GetEmployeeString(EMPL_WORKER, 2), _(building->name));
			return;
		} else if (building->buildingType == B_LAB && E_CountHired(base, EMPL_SCIENTIST) <= 0) {
			CP_Popup(_("Notice"), _("You need to recruit %s to use building %s."),
					E_GetEmployeeString(EMPL_SCIENTIST, 2), _(building->name));
			return;
		}
	} else {
		CP_Popup(_("Notice"), _("Build a %s first."), _(building->name));
		return;
	}
}
/**
 * @brief Mark a building for destruction - you only have to confirm it now
 * @param[in] building Pointer to the base to destroy
 */
static void B_MarkBuildingDestroy (building_t* building)
{
	baseCapacities_t cap;
	base_t *base = building->base;

	/* you can't destroy buildings if base is under attack */
	if (B_IsUnderAttack(base)) {
		CP_Popup(_("Notice"), _("Base is under attack, you can't destroy buildings!"));
		return;
	}

	cap = B_GetCapacityFromBuildingType(building->buildingType);
	/* store the pointer to the building you wanna destroy */
	base->buildingCurrent = building;

	/** @todo: make base destroyable by destroying entrance */
	if (building->buildingType == B_ENTRANCE) {
		CP_Popup(_("Destroy Entrance"), _("You can't destroy the entrance of the base!"));
		return;
	}

	if (!B_IsBuildingDestroyable(building)) {
			CP_Popup(_("Notice"), _("You can't destroy this building! It is the only connection to other buildings!"));
			return;
	}

	if (building->buildingStatus == B_STATUS_WORKING) {
		const bool hasMoreBases = B_GetCount() > 1;
		switch (building->buildingType) {
		case B_HANGAR:
		case B_SMALL_HANGAR:
			if (CAP_GetFreeCapacity(base, cap) <= 0) {
				cgi->UI_PopupButton(_("Destroy Hangar"), _("If you destroy this hangar, you will also destroy the aircraft inside.\nAre you sure you want to destroy this building?"),
					"ui_pop;ui_push aircraft;aircraft_select;", _("Go to hangar"), _("Go to hangar without destroying building"),
					va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
					hasMoreBases ? "ui_pop;ui_push transfer;" : NULL, hasMoreBases ? _("Transfer") : NULL,
					_("Go to transfer menu without destroying the building"));
				return;
			}
			break;
		case B_QUARTERS:
			if (CAP_GetFreeCapacity(base, cap) < building->capacity) {
				cgi->UI_PopupButton(_("Destroy Quarter"), _("If you destroy this Quarters, every employee inside will be killed.\nAre you sure you want to destroy this building?"),
					"ui_pop;ui_push employees;employee_list 0;", _("Dismiss"), _("Go to hiring menu without destroying building"),
					va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
					hasMoreBases ? "ui_pop;ui_push transfer;" : NULL, hasMoreBases ? _("Transfer") : NULL,
					_("Go to transfer menu without destroying the building"));
				return;
			}
			break;
		case B_STORAGE:
			if (CAP_GetFreeCapacity(base, cap) < building->capacity) {
				cgi->UI_PopupButton(_("Destroy Storage"), _("If you destroy this Storage, every items inside will be destroyed.\nAre you sure you want to destroy this building?"),
					"ui_pop;ui_push market;buy_type *mn_itemtype", _("Go to storage"), _("Go to buy/sell menu without destroying building"),
					va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
					hasMoreBases ? "ui_pop;ui_push transfer;" : NULL, hasMoreBases ? _("Transfer") : NULL,
					_("Go to transfer menu without destroying the building"));
				return;
			}
			break;
		default:
			break;
		}
	}

	cgi->UI_PopupButton(_("Destroy building"), _("Are you sure you want to destroy this building?"),
		NULL, NULL, NULL,
		va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"),
		NULL, NULL, NULL);
}
Beispiel #8
0
/**
 * @brief Buy/Sell item/aircraft/ugv on the market
 */
static void BS_Buy_f (void)
{
	const char* itemid;
	int count;
	base_t* base = B_GetCurrentSelectedBase();
	const aircraft_t* aircraft;
	const ugv_t* ugv;
	const objDef_t* od;

	if (cgi->Cmd_Argc() < 2) {
		cgi->Com_Printf("Usage: %s <item-id> <count> [base-idx] \nNegative count means selling. If base index is omitted buys on the currently selected base.\n",
				cgi->Cmd_Argv(0));
		return;
	}

	itemid = cgi->Cmd_Argv(1);
	count = atoi(cgi->Cmd_Argv(2));

	if (cgi->Cmd_Argc() >= 4)
		base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(3)));

	if (char const* const rest = Q_strstart(itemid, "aircraft_")) {
		/* aircraft sell - with aircraft golbal idx */
		int idx = atoi(rest);
		aircraft_t* aircraft = AIR_AircraftGetFromIDX(idx);

		if (!aircraft) {
			cgi->Com_Printf("Invalid aircraft index!\n");
			return;
		}
		AIR_RemoveEmployees(*aircraft);
		BS_SellAircraft(aircraft);
		return;
	}

	if (char const* const rest = Q_strstart(itemid, "ugv-")) {
		/* ugv sell - with unique character number index */
		int ucn = atoi(rest);
		Employee* robot = E_GetEmployeeByTypeFromChrUCN(EMPL_ROBOT, ucn);

		if (!robot) {
			cgi->Com_Printf("Invalid UCN for UGV!\n");
			return;
		}

		BS_SellUGV(robot);
		return;
	}

	if (!base) {
		cgi->Com_Printf("No/invalid base selected.\n");
		return;
	}

	aircraft = AIR_GetAircraftSilent(itemid);
	if (aircraft) {
		if (!B_GetBuildingStatus(base, B_COMMAND)) {
			CP_Popup(_("Note"), _("No Command Centre in this base.\nHangars are not functional.\n"));
			return;
		}
		/* We cannot buy aircraft if there is no power in our base. */
		if (!B_GetBuildingStatus(base, B_POWER)) {
			CP_Popup(_("Note"), _("No power supplies in this base.\nHangars are not functional."));
			return;
		}
		/* We cannot buy aircraft without any hangar. */
		if (!AIR_AircraftAllowed(base)) {
			CP_Popup(_("Note"), _("Build a hangar first."));
			return;
		}
		/* Check free space in hangars. */
		if (CAP_GetFreeCapacity(base, AIR_GetHangarCapacityType(aircraft)) <= 0) {
			CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough space in hangars.\n"));
			return;
		}

		if (ccs.credits < BS_GetAircraftBuyingPrice(aircraft)) {
			CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough credits.\n"));
			return;
		}

		BS_BuyAircraft(aircraft, base);
		return;
	}

	ugv = cgi->Com_GetUGVByIDSilent(itemid);
	if (ugv) {
		const objDef_t* ugvWeapon = INVSH_GetItemByID(ugv->weapon);
		if (!ugvWeapon)
			cgi->Com_Error(ERR_DROP, "BS_BuyItem_f: Could not get weapon '%s' for ugv/tank '%s'.", ugv->weapon, ugv->id);

		if (E_CountUnhiredRobotsByType(ugv) < 1)
			return;
		if (ccs.eMarket.numItems[ugvWeapon->idx] < 1)
			return;

		if (ccs.credits < ugv->price) {
			CP_Popup(_("Not enough money"), _("You cannot buy this item as you don't have enough credits."));
			return;
		}

		if (CAP_GetFreeCapacity(base, CAP_ITEMS) < UGV_SIZE + ugvWeapon->size) {
			CP_Popup(_("Not enough storage space"), _("You cannot buy this item.\nNot enough space in storage.\nBuild more storage facilities."));
			return;
		}

		BS_BuyUGV(ugv, base);
		return;
	}

	if (count == 0) {
		cgi->Com_Printf("Invalid number of items to buy/sell: %s\n", cgi->Cmd_Argv(2));
		return;
	}

	/* item */
	od = INVSH_GetItemByID(cgi->Cmd_Argv(1));
	if (od) {
		if (!BS_IsOnMarket(od))
			return;

		if (count > 0) {
			/* buy */
			const int price = BS_GetItemBuyingPrice(od);
			count = std::min(count, BS_GetItemOnMarket(od));

			/* no items available on market */
			if (count <= 0)
				return;

			if (price <= 0) {
				cgi->Com_Printf("Item on market with invalid buying price: %s (%d)\n", od->id, BS_GetItemBuyingPrice(od));
				return;
			}
			/** @todo warn if player can buy less item due to available credits? */
			count = std::min(count, ccs.credits / price);
			/* not enough money for a single item */
			if (count <= 0) {
				CP_Popup(_("Not enough money"), _("You cannot buy this item as you don't have enough credits."));
				return;
			}

			if (od->size <= 0) {
				cgi->Com_Printf("Item on market with invalid size: %s (%d)\n", od->id, od->size);
				return;
			}
			count = std::min(count, CAP_GetFreeCapacity(base, CAP_ITEMS) / od->size);
			if (count <= 0) {
				CP_Popup(_("Not enough storage space"), _("You cannot buy this item.\nNot enough space in storage.\nBuild more storage facilities."));
				return;
			}

			BS_BuyItem(od, base, count);
		} else {
			/* sell */
			count = std::min(-1 * count, B_ItemInBase(od, base));
			/* no items in storage */
			if (count <= 0)
				return;
			BS_SellItem(od, base, count);
		}
		return;
	}
	cgi->Com_Printf("Invalid item ID\n");
}
/**
 * @brief Buys aircraft or craftitem.
 * @sa BS_SellAircraft_f
 */
static void BS_BuyAircraft_f (void)
{
	int num;
	const aircraft_t *aircraftTemplate;
	base_t *base = B_GetCurrentSelectedBase();

	if (Cmd_Argc() < 2) {
		Com_Printf("Usage: %s <num>\n", Cmd_Argv(0));
		return;
	}

	if (!base)
		return;

	num = atoi(Cmd_Argv(1));
	if (num < 0 || num >= buyList.length)
		return;

	if (buyCat == FILTER_AIRCRAFT) {
		int freeSpace;
		if (!B_GetBuildingStatus(base, B_COMMAND)) {
			CP_Popup(_("Note"), _("No command centre in this base.\nHangars are not functional.\n"));
			return;
		}
		/* We cannot buy aircraft if there is no power in our base. */
		if (!B_GetBuildingStatus(base, B_POWER)) {
			CP_Popup(_("Note"), _("No power supplies in this base.\nHangars are not functional."));
			return;
		}
		/* We cannot buy aircraft without any hangar. */
		if (!AIR_AircraftAllowed(base)) {
			CP_Popup(_("Note"), _("Build a hangar first."));
			return;
		}
		aircraftTemplate = buyList.l[num].aircraft;
		freeSpace = AIR_CalculateHangarStorage(aircraftTemplate, base, 0);

		/* Check free space in hangars. */
		if (freeSpace < 0) {
			Com_Printf("BS_BuyAircraft_f: something bad happened, AIR_CalculateHangarStorage returned -1!\n");
			return;
		}

		if (freeSpace == 0) {
			CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough space in hangars.\n"));
			return;
		} else {
			const int price = BS_GetAircraftBuyingPrice(aircraftTemplate);
			if (ccs.credits < price) {
				CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough credits.\n"));
				return;
			} else {
				/* Hangar capacities are being updated in AIR_NewAircraft().*/
				BS_RemoveAircraftFromMarket(aircraftTemplate, 1);
				CP_UpdateCredits(ccs.credits - price);
				AIR_NewAircraft(base, aircraftTemplate);
				Cmd_ExecuteString(va("buy_type %s", INV_GetFilterType(FILTER_AIRCRAFT)));
			}
		}
	}
}
Beispiel #10
0
/**
 * @brief Adds a new message to message stack
 * @note These are the messages that are displayed at geoscape
 * @param[in] title Already translated message/mail title
 * @param[in] text Already translated message/mail body
 * @param[in] popup Show this as a popup, too?
 * @param[in] type The message type
 * @param[in] pedia Pointer to technology (only if needed)
 * @param[in] playSound Whether the sound associated with the message type should be played
 * @return message_t pointer
 * @sa UP_OpenMail_f
 * @sa CL_EventAddMail_f
 * @note this method forwards to @c MS_AddNewMessageSound with @code playSound = true @endcode
 */
uiMessageListNodeMessage_t* MS_AddNewMessage (const char* title, const char* text, messageType_t type, technology_t* pedia, bool popup, bool playSound)
{
	assert(type < MSG_MAX);

	/* allocate memory for new message - delete this with every new game */
	uiMessageListNodeMessage_t* const mess = Mem_PoolAllocType(uiMessageListNodeMessage_t, cp_campaignPool);

	switch (type) {
	case MSG_DEBUG: /**< only save them in debug mode */
		mess->iconName = "icons/message_debug";
		break;
	case MSG_INFO: /**< don't save these messages */
		mess->iconName = "icons/message_info";
		break;
	case MSG_STANDARD:
		mess->iconName = "icons/message_info";
		break;
	case MSG_RESEARCH_PROPOSAL:
		mess->iconName = "icons/message_research";
		break;
	case MSG_RESEARCH_HALTED:
		mess->iconName = "icons/message_research";
		break;
	case MSG_RESEARCH_FINISHED:
		mess->iconName = "icons/message_research";
		break;
	case MSG_CONSTRUCTION:
		mess->iconName = "icons/message_construction";
		break;
	case MSG_UFOSPOTTED:
		mess->iconName = "icons/message_ufo";
		break;
	case MSG_TERRORSITE:
		mess->iconName = "icons/message_ufo";
		break;
	case MSG_BASEATTACK:
		mess->iconName = "icons/message_ufo";
		break;
	case MSG_TRANSFERFINISHED:
		mess->iconName = "icons/message_transfer";
		break;
	case MSG_PROMOTION:
		mess->iconName = "icons/message_promotion";
		break;
	case MSG_PRODUCTION:
		mess->iconName = "icons/message_production";
		break;
	case MSG_DEATH:
		mess->iconName = "icons/message_death";
		break;
	case MSG_CRASHSITE:
		mess->iconName = "icons/message_ufo";
		break;
	case MSG_EVENT:
		mess->iconName = "icons/message_info";
		break;
	default:
		mess->iconName = "icons/message_info";
		break;
	}

	/* push the new message at the beginning of the stack */
	cgi->UI_MessageAddStack(mess);

	mess->type = type;
	mess->pedia = pedia;		/* pointer to UFOpaedia entry */

	mess->date = ccs.date;

	Q_strncpyz(mess->title, title, sizeof(mess->title));
	mess->text = cgi->PoolStrDup(text, cp_campaignPool, 0);

	/* get formatted date text */
	MS_TimestampedText(mess->timestamp, mess, sizeof(mess->timestamp));

	/* they need to be translated already */
	if (popup) {
		CP_GameTimeStop();
		CP_Popup(mess->title, "%s", mess->text);
	}

	if (playSound) {
		const char* sound = nullptr;
		switch (type) {
		case MSG_DEBUG:
			break;
		case MSG_STANDARD:
			sound = "geoscape/standard";
			break;
		case MSG_INFO:
		case MSG_TRANSFERFINISHED:
		case MSG_DEATH:
		case MSG_CONSTRUCTION:
		case MSG_PRODUCTION:
			sound = "geoscape/info";
			break;
		case MSG_RESEARCH_PROPOSAL:
		case MSG_RESEARCH_FINISHED:
			assert(pedia);
		case MSG_RESEARCH_HALTED:
		case MSG_EVENT:
		case MSG_NEWS:
			/* reread the new mails in UP_GetUnreadMails */
			ccs.numUnreadMails = -1;
			sound = "geoscape/mail";
			break;
		case MSG_UFOLOST:
			sound = "geoscape/ufolost";
			break;
		case MSG_UFOSPOTTED:
			sound = "geoscape/ufospotted";
			break;
		case MSG_BASEATTACK:
			sound = "geoscape/basealert";
			break;
		case MSG_TERRORSITE:
			sound = "geoscape/alien-activity";
			break;
		case MSG_CRASHSITE:
			sound = "geoscape/newmission";
			break;
		case MSG_PROMOTION:
			sound = "geoscape/promotion";
			break;
		case MSG_MAX:
			break;
		}

		cgi->S_StartLocalSample(sound, 1.0f);
	}

	return mess;
}