int CArmorClass::GetMaxHP (CItemCtx &ItemCtx)

//	GetMaxHP
//
//	Returns the max HP for this kind of armor

	{
	//	Start with hit points defined by the class

	int iHP = m_iHitPoints;

	//	Fire event to compute HP, if necessary

	iHP = FireGetMaxHP(ItemCtx, iHP);

	//	Add mods

	const CItemEnhancement &Mods = ItemCtx.GetMods();
	if (Mods.IsNotEmpty())
		iHP = iHP * Mods.GetHPAdj() / 100;

	//	Add complete bonus

	CInstalledArmor *pSect = ItemCtx.GetArmor();
	if (pSect && pSect->IsComplete())
		iHP += m_iArmorCompleteBonus;

	//	Done

	return iHP;
	}
Exemple #2
0
void CArmorClass::CalcAdjustedDamage (CItemCtx &ItemCtx, SDamageCtx &Ctx)

//	CalcAdjustedDamage
//
//	Modifies Ctx.iDamage to account for damage type adjustments, etc.

	{
	CInstalledArmor *pArmor = ItemCtx.GetArmor();

	//	Adjust for special armor damage:
	//
	//	<0	=	2.5x damage
	//	0	=	2x damage
	//	1	=	1.5x damage
	//	2	=	1.25x damage
	//	>2	=	1x damage

	int iDamageLevel = Ctx.Damage.GetArmorDamageLevel();
	if (iDamageLevel > 0)
		Ctx.iDamage = (CalcArmorDamageAdj(Ctx.Damage) * Ctx.iDamage + 50) / 100;

	//	Adjust for damage type

	int iDamageAdj = GetDamageAdj((pArmor ? pArmor->GetMods() : CItemEnhancement()), Ctx.Damage);
	Ctx.iDamage = (iDamageAdj * Ctx.iDamage + 50) / 100;
	}
void CArmorClass::CalcAdjustedDamage (CItemCtx &ItemCtx, SDamageCtx &Ctx)

//	CalcAdjustedDamage
//
//	Modifies Ctx.iDamage to account for damage type adjustments, etc.

	{
	CInstalledArmor *pArmor = ItemCtx.GetArmor();

	//	Adjust for special armor damage:
	//
	//	<0	=	2.5x damage
	//	0	=	2x damage
	//	1	=	1.5x damage
	//	2	=	1.25x damage
	//	>2	=	1x damage

	int iDamageLevel = Ctx.Damage.GetArmorDamageLevel();
	if (iDamageLevel > 0)
		{
		int iDiff = m_pItemType->GetLevel() - iDamageLevel;
		int iAdj;

		switch (iDiff)
			{
			case 0:
				iAdj = 200;
				break;

			case 1:
				iAdj = 150;
				break;

			case 2:
				iAdj = 125;
				break;

			default:
				if (iDiff < 0)
					iAdj = 250;
				else
					iAdj = 100;
			}

		Ctx.iDamage = (iAdj * Ctx.iDamage + 50) / 100;
		}

	//	Adjust for damage type

	int iDamageAdj = GetDamageAdj((pArmor ? pArmor->GetMods() : CItemEnhancement()), Ctx.Damage);
	Ctx.iDamage = (iDamageAdj * Ctx.iDamage + 50) / 100;
	}
bool CArmorClass::IsReflective (CItemCtx &ItemCtx, const DamageDesc &Damage)

//	IsReflective
//
//	Returns TRUE if the armor reflects this damage

	{
	const CItemEnhancement &Mods = ItemCtx.GetMods();

	int iReflectChance = 0;

	//	Base armor chance

	if (m_Reflective.InSet(Damage.GetDamageType()))
		iReflectChance = MAX_REFLECTION_CHANCE;

	//	Mods

	int iModReflect;
	if (Mods.IsNotEmpty() && Mods.IsReflective(Damage, &iModReflect))
		iReflectChance = Max(iReflectChance, iModReflect);

	//	Done

	if (iReflectChance)
		{
		CInstalledArmor *pSect = ItemCtx.GetArmor();

		int iMaxHP = GetMaxHP(ItemCtx);
		int iHP = (pSect ? pSect->GetHitPoints() : iMaxHP);

		//	Adjust based on how damaged the armor is

		iReflectChance = (iMaxHP > 0 ? iHP * iReflectChance / iMaxHP : iReflectChance);

		return (mathRandom(1, 100) <= iReflectChance);
		}
	else
		return false;
	}
void CGSelectorArea::PaintInstalledItem (CG32bitImage &Dest, const RECT &rcRect, const SEntry &Entry)

//	PaintInstalledItem
//
//	Paints the installed item.

	{
	const CItem &Item = Entry.pItemCtx->GetItem();
	if (Item.GetType() == NULL)
		return;

	CSpaceObject *pSource = Entry.pItemCtx->GetSource();
	CInstalledArmor *pArmor = Entry.pItemCtx->GetArmor();
	CInstalledDevice *pDevice = Entry.pItemCtx->GetDevice();
	CDeviceClass *pDeviceClass;

	//	Paint the item icon

	bool bGrayed = (pDevice && !pDevice->IsEnabled());
	int xIcon = rcRect.left + (RectWidth(rcRect) - ITEM_ICON_WIDTH) / 2;
	int yIcon = rcRect.top + ITEM_ENTRY_PADDING_TOP;
	DrawItemTypeIcon(Dest, xIcon, yIcon, Item.GetType(), ITEM_ICON_WIDTH, ITEM_ICON_HEIGHT, bGrayed);

	//	Paint the name of the item below.

	RECT rcText;
	rcText.left = rcRect.left + ITEM_ENTRY_PADDING_LEFT;
	rcText.right = rcRect.right - ITEM_ENTRY_PADDING_RIGHT;
	rcText.top = yIcon + ITEM_ICON_HEIGHT;
	rcText.bottom = rcRect.bottom;

	m_VI.GetFont(fontMedium).DrawText(Dest, 
			rcText,
			m_rgbTextColor,
			Item.GetNounPhrase(nounShort | nounNoModifiers),
			0,
			CG16bitFont::AlignCenter);


	//	If this is an armor segment, then paint HP, etc.

	if (pArmor)
		{
		int x = rcRect.right - ITEM_ENTRY_PADDING_RIGHT;
		int y = rcRect.top + ITEM_ENTRY_PADDING_TOP;

		//	HP

		CString sHP = strFromInt(pArmor->GetHitPoints());
		m_VI.GetFont(fontLarge).DrawText(Dest,
				x,
				y,
				m_rgbTextColor,
				sHP,
				CG16bitFont::AlignRight);
		y += m_VI.GetFont(fontLarge).GetHeight();

		//	Damage

		int iMaxHP = pArmor->GetMaxHP(pSource);
		if (iMaxHP != pArmor->GetHitPoints() && iMaxHP > 0)
			{
			int iPercent = ((1000 * pArmor->GetHitPoints() / iMaxHP) + 5) / 10;
			CString sPercent = strPatternSubst(CONSTLIT("%d%%"), iPercent);

			m_VI.GetFont(fontMedium).DrawText(Dest,
					x,
					y,
					m_VI.GetColor(colorTextDockWarning),
					sPercent,
					CG16bitFont::AlignRight);
			y += m_VI.GetFont(fontMedium).GetHeight();
			}

		//	Modifiers

		if (pArmor->GetMods().IsNotEmpty())
			{
			CString sMods = Item.GetEnhancedDesc(pSource);
			if (!sMods.IsBlank())
				{
				bool bIsDisadvantage = *sMods.GetASCIIZPointer() == '-';
				CG32bitPixel rgbBackColor = (bIsDisadvantage ? m_VI.GetColor(colorAreaDisadvantage) : m_VI.GetColor(colorAreaAdvantage));
				CG32bitPixel rgbTextColor = (bIsDisadvantage ? m_VI.GetColor(colorTextDisadvantage) : m_VI.GetColor(colorTextAdvantage));

				PaintModifier(Dest, x, y, sMods, rgbTextColor, rgbBackColor, &y);
				}
			}
		}

	//	If this is a device, then paint device-specific stuff

	else if (pDevice
				&& (pDeviceClass = pDevice->GetClass()))
		{
		int x = rcRect.right - ITEM_ENTRY_PADDING_RIGHT;
		int y = rcRect.top + ITEM_ENTRY_PADDING_TOP;

		//	HP

		if (pDevice->IsEnabled())
			{
			if (pDevice->GetCategory() == itemcatShields)
				{
				int iHP;
				int iMaxHP;
				pDevice->GetStatus(pSource, &iHP, &iMaxHP);

				CString sHP = strFromInt(iHP);
				m_VI.GetFont(fontLarge).DrawText(Dest,
						x,
						y,
						m_rgbTextColor,
						sHP,
						CG16bitFont::AlignRight);
				y += m_VI.GetFont(fontLarge).GetHeight();

				//	Shield level

				if (iMaxHP != iHP && iMaxHP > 0)
					{
					int iPercent = ((1000 * iHP / iMaxHP) + 5) / 10;
					CString sPercent = strPatternSubst(CONSTLIT("%d%%"), iPercent);

					m_VI.GetFont(fontMedium).DrawText(Dest,
							x,
							y,
							m_VI.GetColor(colorTextShields),
							sPercent,
							CG16bitFont::AlignRight);
					y += m_VI.GetFont(fontMedium).GetHeight();
					}
				}
			}
		else
			PaintModifier(Dest, x, y, CONSTLIT("disabled"), m_VI.GetColor(colorTextNormal), CG32bitPixel::Null(), &y);

		//	External

		if (pDevice->IsExternal() || pDeviceClass->IsExternal())
			PaintModifier(Dest, x, y, CONSTLIT("external"), m_VI.GetColor(colorTextNormal), CG32bitPixel::Null(), &y);

		//	Damaged

		if (pDevice->IsDamaged())
			PaintModifier(Dest, x, y, CONSTLIT("damaged"), m_VI.GetColor(colorTextDisadvantage), m_VI.GetColor(colorAreaDisadvantage), &y);

		if (pDevice->IsDisrupted())
			PaintModifier(Dest, x, y, CONSTLIT("ionized"), m_VI.GetColor(colorTextDisadvantage), m_VI.GetColor(colorAreaDisadvantage), &y);

		//	Modifiers

		if (pDevice->GetEnhancements() != NULL)
			{
			CString sMods = pDevice->GetEnhancedDesc(pSource, &Item);
			if (!sMods.IsBlank())
				{
				bool bIsDisadvantage = *sMods.GetASCIIZPointer() == '-';
				CG32bitPixel rgbBackColor = (bIsDisadvantage ? m_VI.GetColor(colorAreaDisadvantage) : m_VI.GetColor(colorAreaAdvantage));
				CG32bitPixel rgbTextColor = (bIsDisadvantage ? m_VI.GetColor(colorTextDisadvantage) : m_VI.GetColor(colorTextAdvantage));

				PaintModifier(Dest, x, y, sMods, rgbTextColor, rgbBackColor, &y);
				}
			}
		}
	}
void CArmorClass::CalcDamageEffects (CItemCtx &ItemCtx, SDamageCtx &Ctx)

//	CalcDamageEffects
//
//	Initialize the damage effects based on the damage and on this armor type.

	{
	CSpaceObject *pSource = ItemCtx.GetSource();
	CInstalledArmor *pArmor = ItemCtx.GetArmor();

	//	Compute all the effects (if we don't have installed armor, then the 
	//	caller is responsible for setting this).

	if (pArmor)
		Ctx.iHPLeft = pArmor->GetHitPoints();

	//	Reflect

	Ctx.bReflect = (IsReflective(ItemCtx, Ctx.Damage) && Ctx.iDamage > 0);

	//	Disintegration

	int iDisintegration = Ctx.Damage.GetDisintegrationDamage();
	Ctx.bDisintegrate = (iDisintegration > 0 && !IsDisintegrationImmune(ItemCtx));

	//	Shatter

	int iShatter = Ctx.Damage.GetShatterDamage();
	if (iShatter)
		{
		//	Compute the threshold mass. Below this size, we shatter the object

		int iMassLimit = 10 * mathPower(5, iShatter);
		Ctx.bShatter = (pSource && pSource->GetMass() < iMassLimit);
		}
	else
		Ctx.bShatter = false;

	//	Blinding

	int iBlinding = Ctx.Damage.GetBlindingDamage();
	if (iBlinding && !IsBlindingDamageImmune(ItemCtx))
		{
		//	The chance of being blinded is dependent
		//	on the rating.

		int iChance = 4 * iBlinding * iBlinding * GetBlindingDamageAdj() / 100;
		Ctx.bBlind = (mathRandom(1, 100) <= iChance);
		Ctx.iBlindTime = Ctx.iDamage * g_TicksPerSecond / 2;
		}
	else
		Ctx.bBlind = false;

	//	EMP

	int iEMP = Ctx.Damage.GetEMPDamage();
	if (iEMP && !IsEMPDamageImmune(ItemCtx))
		{
		//	The chance of being paralyzed is dependent
		//	on the EMP rating.

		int iChance = 4 * iEMP * iEMP * GetEMPDamageAdj() / 100;
		Ctx.bParalyze = (mathRandom(1, 100) <= iChance);
		Ctx.iParalyzeTime = Ctx.iDamage * g_TicksPerSecond / 2;
		}
	else
		Ctx.bParalyze = false;

	//	Device disrupt

	int iDeviceDisrupt = Ctx.Damage.GetDeviceDisruptDamage();
	if (iDeviceDisrupt && !IsDeviceDamageImmune(ItemCtx))
		{
		//	The chance of damaging a device depends on the rating.

		int iChance = 4 * iDeviceDisrupt * iDeviceDisrupt * GetDeviceDamageAdj() / 100;
		Ctx.bDeviceDisrupt = (mathRandom(1, 100) <= iChance);
		Ctx.iDisruptTime = 2 * Ctx.iDamage * g_TicksPerSecond;
		}
	else
		Ctx.bDeviceDisrupt = false;

	//	Device damage

	int iDeviceDamage = Ctx.Damage.GetDeviceDamage();
	if (iDeviceDamage && !IsDeviceDamageImmune(ItemCtx))
		{
		//	The chance of damaging a device depends on the rating.

		int iChance = 4 * iDeviceDamage * iDeviceDamage * GetDeviceDamageAdj() / 100;
		Ctx.bDeviceDamage = (mathRandom(1, 100) <= iChance);
		}
	else
		Ctx.bDeviceDamage = false;

	//	Radiation

	int iRadioactive = Ctx.Damage.GetRadiationDamage();
	Ctx.bRadioactive = (iRadioactive > 0 && !IsRadiationImmune(ItemCtx));

	//	Some effects decrease damage

	if (iBlinding || iEMP)
		Ctx.iDamage = 0;
	else if (iDeviceDamage)
		Ctx.iDamage = Ctx.iDamage / 2;
	}
EDamageResults CArmorClass::AbsorbDamage (CItemCtx &ItemCtx, SDamageCtx &Ctx)

//	AbsorbDamage
//
//	Handles getting hit by damage.
//
//	Returns damageNoDamage if all the damage was absorbed and no further processing is necessary
//	Returns damageDestroyed if the source was destroyed
//	Returns damageArmorHit if source was damage and further processing (destroy check) is needed
//
//	Sets Ctx.iDamage to the amount of hit points left after damage absorption.

	{
	CSpaceObject *pSource = ItemCtx.GetSource();
	CInstalledArmor *pArmor = ItemCtx.GetArmor();
	if (pSource == NULL || pArmor == NULL)
		return damageNoDamage;

	//	Compute all the effects (this initializes elements in Ctx).

	CalcDamageEffects(ItemCtx, Ctx);

	//	First give custom weapons a chance

	bool bCustomDamage = Ctx.pDesc->FireOnDamageArmor(Ctx);
	if (pSource->IsDestroyed())
		return damageDestroyed;

	//	Damage adjustment

	CalcAdjustedDamage(ItemCtx, Ctx);

	//	If the armor has custom code to deal with damage, handle it here.

	FireOnArmorDamage(ItemCtx, Ctx);
	if (pSource->IsDestroyed())
		return damageDestroyed;

	//	If this armor section reflects this kind of damage then
	//	send the damage on

	if (Ctx.bReflect)
		{
		if (Ctx.pCause)
			Ctx.pCause->CreateReflection(Ctx.vHitPos, (Ctx.iDirection + 120 + mathRandom(0, 120)) % 360);
		return damageNoDamage;
		}

	//	If this is a disintegration attack, then disintegrate the ship

	if (Ctx.bDisintegrate)
		{
		if (!pSource->OnDestroyCheck(killedByDisintegration, Ctx.Attacker))
			return damageNoDamage;

		pSource->Destroy(killedByDisintegration, Ctx.Attacker);
		return damageDestroyed;
		}

	//	If this is a shatter attack, see if the ship is destroyed

	if (Ctx.bShatter)
		{
		if (!pSource->OnDestroyCheck(killedByShatter, Ctx.Attacker))
			return damageNoDamage;

		pSource->Destroy(killedByShatter, Ctx.Attacker);
		return damageDestroyed;
		}

	//	If this is a paralysis attack and we've gotten past the shields
	//	then freeze the ship.

	if (Ctx.bParalyze)
		pSource->MakeParalyzed(Ctx.iParalyzeTime);

	//	If this is blinding damage then our sensors are disabled

	if (Ctx.bBlind)
		pSource->MakeBlind(Ctx.iBlindTime);

	//	If this attack is radioactive, then contaminate the ship

	if (Ctx.bRadioactive)
		pSource->OnHitByRadioactiveDamage(Ctx);

	//	If this is device damage, then see if any device is damaged

	if (Ctx.bDeviceDamage)
		pSource->OnHitByDeviceDamage();

	if (Ctx.bDeviceDisrupt)
		pSource->OnHitByDeviceDisruptDamage(Ctx.iDisruptTime);

	//	Create a hit effect. (Many weapons show an effect even if no damage was
	//	done.)

	Ctx.pDesc->CreateHitEffect(pSource->GetSystem(), Ctx);

	//	If no damage has reached us, then we're done

	if (Ctx.iDamage == 0 && !bCustomDamage)
		return damageNoDamage;

	//	Give source events a chance to change the damage before we
	//	subtract from armor.

	if (pSource->HasOnDamageEvent())
		{
		pSource->FireOnDamage(Ctx);
		if (pSource->IsDestroyed())
			return damageDestroyed;
		}

	//	Take damage

	if (Ctx.iDamage <= pArmor->GetHitPoints())
		{
		pArmor->IncHitPoints(-Ctx.iDamage);
		Ctx.iDamage = 0;
		}
	else
		{
		Ctx.iDamage -= pArmor->GetHitPoints();
		pArmor->SetHitPoints(0);
		}

	return damageArmorHit;
	}
void CArmorDisplay::Update (void)

//	Update
//
//	Updates buffer from data

	{
	int i;

	if (m_pPlayer == NULL)
		return;

	CShip *pShip = m_pPlayer->GetShip();
	const CPlayerSettings *pSettings = pShip->GetClass()->GetPlayerSettings();
	CItemListManipulator ItemList(pShip->GetItemList());
	const CG16bitFont &SmallFont = m_pPlayer->GetTrans()->GetFonts().Small;
	m_Text.DeleteAll();

	//	If we've changed ships then we need to delete the painters

	if (m_dwCachedShipID != pShip->GetID())
		{
		if (m_pShieldPainter)
			{
			m_pShieldPainter->Delete();
			m_pShieldPainter = NULL;
			}

		m_dwCachedShipID = pShip->GetID();
		}

	//	Erase everything

	m_Buffer.Fill(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, DEFAULT_TRANSPARENT_COLOR);

	//	Figure out the status of the shields

	int iHP = 0;
	int iMaxHP = 10;
	CInstalledDevice *pShield = pShip->GetNamedDevice(devShields);
	if (pShield)
		pShield->GetStatus(pShip, &iHP, &iMaxHP);

	//	Draw the base ship image, if we have it

	const SArmorImageDesc &ArmorDesc = pSettings->GetArmorDesc();
	if (!ArmorDesc.ShipImage.IsEmpty())
		{
		const RECT &rcShip = ArmorDesc.ShipImage.GetImageRect();

		m_Buffer.ColorTransBlt(rcShip.left, 
				rcShip.top, 
				RectWidth(rcShip), 
				RectHeight(rcShip), 
				255,
				ArmorDesc.ShipImage.GetImage(NULL_STR), 
				DESCRIPTION_WIDTH + ((SHIELD_IMAGE_WIDTH - RectWidth(rcShip)) / 2),
				(SHIELD_IMAGE_HEIGHT - RectHeight(rcShip)) / 2);
		}

	//	Draw the old-style shields

	const SShieldImageDesc &ShieldDesc = pSettings->GetShieldDesc();
	if (!ShieldDesc.pShieldEffect)
		{
		int iWhole = (iMaxHP > 0 ? (iHP * 100) / iMaxHP : 100);
		int iIndex = (100 - iWhole) / 20;

		const RECT &rcShield = ShieldDesc.Image.GetImageRect();
		m_Buffer.ColorTransBlt(rcShield.left, 
				rcShield.top + (RectHeight(rcShield) * iIndex), 
				RectWidth(rcShield), 
				RectHeight(rcShield), 
				255,
				ShieldDesc.Image.GetImage(NULL_STR), 
				DESCRIPTION_WIDTH + ((SHIELD_IMAGE_WIDTH - RectWidth(rcShield)) / 2),
				(SHIELD_IMAGE_HEIGHT - RectHeight(rcShield)) / 2);
		}

	if (pShield)
		{
		m_Buffer.Fill(SHIELD_HP_DISPLAY_X,
				SHIELD_HP_DISPLAY_Y, 
				SHIELD_HP_DISPLAY_WIDTH, 
				SHIELD_HP_DISPLAY_HEIGHT,
				SHIELD_HP_DISPLAY_BACK_COLOR);
		
		CString sHP = strFromInt(iHP);
		int cxWidth = m_pFonts->Medium.MeasureText(sHP, NULL);
		m_pFonts->Medium.DrawText(m_Buffer,
				SHIELD_HP_DISPLAY_X + (SHIELD_HP_DISPLAY_WIDTH - cxWidth) / 2,
				SHIELD_HP_DISPLAY_Y - 1,
				CG16bitImage::LightenPixel(m_pFonts->wAltGreenColor, 60),
				sHP);

		DrawBrokenLine(m_Buffer,
				0,
				SHIELD_HP_DISPLAY_Y,
				SHIELD_HP_DISPLAY_X,
				SHIELD_HP_DISPLAY_Y,
				0,
				SHIELD_HP_DISPLAY_LINE_COLOR);

		WORD wColor;
		if (pShield->IsEnabled() && !pShield->IsDamaged() && !pShield->IsDisrupted())
			wColor = m_pFonts->wAltGreenColor;
		else
			wColor = DISABLED_TEXT_COLOR;

		CString sShieldName = pShield->GetClass()->GetName();
		int cyHeight;
		cxWidth = m_pFonts->Medium.MeasureText(sShieldName, &cyHeight);

		//	Add the shield name to list of text to paint

		STextPaint *pPaint = m_Text.Insert();
		pPaint->sText = sShieldName;
		pPaint->x = 0;
		pPaint->y = SHIELD_HP_DISPLAY_Y;
		pPaint->pFont = &m_pFonts->Medium;
		pPaint->wColor = wColor;

		//	Paint the modifiers

		if (pShield->GetMods().IsNotEmpty() || pShield->GetBonus() != 0)
			{
			pShip->SetCursorAtNamedDevice(ItemList, devShields);
			CString sMods = pShield->GetEnhancedDesc(pShip, &ItemList.GetItemAtCursor());
			if (!sMods.IsBlank())
				{
				bool bDisadvantage = (*(sMods.GetASCIIZPointer()) == '-');

				int cx = SmallFont.MeasureText(sMods);
				m_Buffer.Fill(SHIELD_HP_DISPLAY_X - cx - 8,
						SHIELD_HP_DISPLAY_Y,
						cx + 8,
						SHIELD_HP_DISPLAY_HEIGHT,
						(bDisadvantage ? ARMOR_DAMAGED_BACK_COLOR : ARMOR_ENHANCE_BACK_COLOR));

				SmallFont.DrawText(m_Buffer,
						SHIELD_HP_DISPLAY_X - cx - 4,
						SHIELD_HP_DISPLAY_Y + (SHIELD_HP_DISPLAY_HEIGHT - SmallFont.GetHeight()) / 2,
						(bDisadvantage ? ARMOR_DAMAGED_TEXT_COLOR : ARMOR_ENHANCE_TEXT_COLOR),
						sMods);
				}
			}
		}

	//	Draw armor

	int iArmorCount = Min(pShip->GetArmorSectionCount(), pSettings->GetArmorDescCount());
	for (i = 0; i < iArmorCount; i++)
		{
		const SArmorSegmentImageDesc *pImage = &pSettings->GetArmorDesc(i);

		CInstalledArmor *pArmor = pShip->GetArmorSection(i);
		int iMaxHP = pArmor->GetMaxHP(pShip);
		int iWhole = (iMaxHP == 0 ? 100 : (pArmor->GetHitPoints() * 100) / iMaxHP);
		int iIndex = (100 - iWhole) / 20;
		
		if (iIndex < 5)
			{
			const RECT &rcImage = pImage->Image.GetImageRect();
			m_Buffer.ColorTransBlt(rcImage.left,
					rcImage.top + iIndex * RectHeight(rcImage),
					RectWidth(rcImage),
					RectHeight(rcImage),
					255,
					pImage->Image.GetImage(NULL_STR),
					DESCRIPTION_WIDTH + pImage->xDest,
					pImage->yDest);
			}
		}

	//	Draw the new style shields on top

	if (ShieldDesc.pShieldEffect)
		{
		int x = DESCRIPTION_WIDTH + SHIELD_IMAGE_WIDTH / 2;
		int y = SHIELD_IMAGE_HEIGHT / 2;

		SViewportPaintCtx Ctx;
		Ctx.iTick = g_pUniverse->GetTicks();
		Ctx.iVariant = (iMaxHP > 0 ? (iHP * 100) / iMaxHP : 0);
		Ctx.iDestiny = pShip->GetDestiny();
		Ctx.iRotation = 90;

		if (m_pShieldPainter == NULL)
			m_pShieldPainter = ShieldDesc.pShieldEffect->CreatePainter();

		m_pShieldPainter->Paint(m_Buffer, x, y, Ctx);
		}

	//	Draw armor names

	for (i = 0; i < iArmorCount; i++)
		{
		const SArmorSegmentImageDesc *pImage = &pSettings->GetArmorDesc(i);
		CInstalledArmor *pArmor = pShip->GetArmorSection(i);

		//	Paint the HPs

		if (i == m_iSelection)
			{
			m_Buffer.Fill(DESCRIPTION_WIDTH + pImage->xHP - 1, 
					pImage->yHP - 1, 
					HP_DISPLAY_WIDTH + 2, 
					HP_DISPLAY_HEIGHT + 2,
					CG16bitImage::DarkenPixel(m_pFonts->wSelectBackground, 128));
			}
		else
			{
			m_Buffer.Fill(DESCRIPTION_WIDTH + pImage->xHP, 
					pImage->yHP, 
					HP_DISPLAY_WIDTH, 
					HP_DISPLAY_HEIGHT,
					HP_DISPLAY_BACK_COLOR);
			}

		CString sHP = strFromInt(pArmor->GetHitPoints());
		int cxWidth = m_pFonts->Medium.MeasureText(sHP, NULL);
		m_pFonts->Medium.DrawText(m_Buffer,
				DESCRIPTION_WIDTH + pImage->xHP + (HP_DISPLAY_WIDTH - cxWidth) / 2,
				pImage->yHP - 1,
				m_pFonts->wTitleColor,
				sHP);

		//	Paint the armor name line

		DrawBrokenLine(m_Buffer,
				0,
				pImage->yName + m_pFonts->Medium.GetHeight(),
				DESCRIPTION_WIDTH + pImage->xHP + pImage->xNameDestOffset,
				pImage->yHP + pImage->yNameDestOffset,
				pImage->cxNameBreak,
				(i == m_iSelection ? CG16bitImage::DarkenPixel(m_pFonts->wSelectBackground, 128) : ARMOR_LINE_COLOR));

		//	Paint the armor names

		CString sName = pArmor->GetClass()->GetShortName();
		int cy;
		int cx = m_pFonts->Medium.MeasureText(sName, &cy) + 4;
		if (i == m_iSelection)
			{
			m_Buffer.Fill(0, 
					pImage->yName, 
					cx, 
					cy,
					CG16bitImage::DarkenPixel(m_pFonts->wSelectBackground, 128));
			}

		STextPaint *pPaint = m_Text.Insert();
		pPaint->sText = sName;
		pPaint->x = 2;
		pPaint->y = pImage->yName;
		pPaint->pFont = &m_pFonts->Medium;
		pPaint->wColor = m_pFonts->wTitleColor;

		//	Paint the modifiers

		if (pArmor->GetMods().IsNotEmpty())
			{
			pShip->SetCursorAtArmor(ItemList, i);
			CString sMods = ItemList.GetItemAtCursor().GetEnhancedDesc(pShip);
			if (!sMods.IsBlank())
				{
				int cx = SmallFont.MeasureText(sMods);
				m_Buffer.Fill(ARMOR_ENHANCE_X - cx - 4,
						pImage->yName + m_pFonts->Medium.GetHeight() - HP_DISPLAY_HEIGHT,
						cx + 8,
						HP_DISPLAY_HEIGHT,
						ARMOR_ENHANCE_BACK_COLOR);

				SmallFont.DrawText(m_Buffer,
						ARMOR_ENHANCE_X - cx,
						pImage->yName + 3,
						ARMOR_ENHANCE_TEXT_COLOR,
						sMods);
				}
			}
		}
	}
void CTranscendenceWnd::CreateShipDescAnimation (CShip *pShip, IAnimatron **retpAnimatron)

//	CreateShipDescAnimation
//
//	Creates animation describing the given ship

	{
	int i, j;
	int iDuration = 600;
	int iInterLineDelay = 1;
	int iDelay = 0;
	int x = m_rcIntroMain.left + (RectWidth(m_rcIntroMain) / 2) + (RectWidth(m_rcIntroMain) / 6);
	int y = m_rcIntroMain.bottom - RectHeight(m_rcIntroMain) / 3;

	//	Create sequencer to hold everything.

	CAniSequencer *pSeq = new CAniSequencer;

	//	Show the ship class

	CString sClassName = strToLower(pShip->GetName());
	int cyClassName;
	int cxClassName = m_Fonts.SubTitle.MeasureText(sClassName, &cyClassName);
	int cySectionSpacing = cyClassName / 6;

	IAnimatron *pText;
	CAniText::Create(sClassName,
			CVector((Metric)x, (Metric)y),
			&m_Fonts.SubTitle,
			CG16bitFont::AlignCenter,
			m_Fonts.rgbTitleColor,
			&pText);
	pText->AnimateLinearFade(iDuration, 0, 30);
	pSeq->AddTrack(pText, 0);

	y += cyClassName + cySectionSpacing;
	iDelay += iInterLineDelay * 3;

	//	Weapons label

	CAniText::Create(CONSTLIT("WEAPONS:"),
			CVector((Metric)x - cyClassName / 4, (Metric)(y + m_Fonts.Medium.GetAscent() - m_Fonts.Small.GetAscent())),
			&m_Fonts.Small,
			CG16bitFont::AlignRight,
			m_Fonts.rgbLightTitleColor,
			&pText);
	pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
	pSeq->AddTrack(pText, iDelay);

	//	Collect duplicate weapons

	struct SWeaponDesc
		{
		CString sWeaponName;
		int iCount;
		};

	TArray<SWeaponDesc> WeaponList;

	for (i = 0; i < pShip->GetDeviceCount(); i++)
		{
		CInstalledDevice *pDevice = pShip->GetDevice(i);
		if (pDevice->IsEmpty())
			continue;

		if (pDevice->GetCategory() == itemcatWeapon || pDevice->GetCategory() == itemcatLauncher)
			{
			CString sName = pDevice->GetClass()->GetItemType()->GetNounPhrase(nounActual | nounCapitalize);

			//	Look for the weapon in the list

			bool bFound = false;
			for (j = 0; j < WeaponList.GetCount() && !bFound; j++)
				if (strEquals(WeaponList[j].sWeaponName, sName))
					{
					WeaponList[j].iCount++;
					bFound = true;
					}

			//	Add if necessary

			if (!bFound)
				{
				SWeaponDesc *pWeapon = WeaponList.Insert();
				pWeapon->sWeaponName = sName;
				pWeapon->iCount = 1;
				}
			}
		}

	//	Output weapon list

	if (WeaponList.GetCount() == 0)
		{
		CAniText::Create(CONSTLIT("None"),
				CVector((Metric)x + cyClassName / 4, (Metric)y),
				&m_Fonts.Medium,
				0,
				m_Fonts.rgbTitleColor,
				&pText);
		pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
		pSeq->AddTrack(pText, iDelay);

		y += m_Fonts.Medium.GetHeight();
		iDelay += iInterLineDelay;
		}
	else
		{
		for (i = 0; i < WeaponList.GetCount() && i < 6; i++)
			{
			CAniText::Create((WeaponList[i].iCount == 1 ? WeaponList[i].sWeaponName
						: strPatternSubst(CONSTLIT("%s (x%d)"), WeaponList[i].sWeaponName, WeaponList[i].iCount)),
					CVector((Metric)x + cyClassName / 4, (Metric)y),
					&m_Fonts.Medium,
					0,
					m_Fonts.rgbTitleColor,
					&pText);
			pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
			pSeq->AddTrack(pText, iDelay);

			y += m_Fonts.Medium.GetHeight();
			iDelay += iInterLineDelay;
			}
		}

	//	Shields

	y += cySectionSpacing;

	CAniText::Create(CONSTLIT("SHIELDS:"),
			CVector((Metric)x - cyClassName / 4, (Metric)(y + m_Fonts.Medium.GetAscent() - m_Fonts.Small.GetAscent())),
			&m_Fonts.Small,
			CG16bitFont::AlignRight,
			m_Fonts.rgbLightTitleColor,
			&pText);
	pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
	pSeq->AddTrack(pText, iDelay);

	CInstalledDevice *pShields = pShip->GetNamedDevice(devShields);

	CAniText::Create((pShields ? pShields->GetClass()->GetItemType()->GetNounPhrase(nounActual | nounCapitalize) : CONSTLIT("None")),
			CVector((Metric)x + cyClassName / 4, (Metric)y),
			&m_Fonts.Medium,
			0,
			m_Fonts.rgbTitleColor,
			&pText);
	pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
	pSeq->AddTrack(pText, iDelay);

	y += m_Fonts.Medium.GetHeight();
	iDelay += iInterLineDelay;

	//	Armor

	y += cySectionSpacing;

	CAniText::Create(CONSTLIT("ARMOR:"),
			CVector((Metric)x - cyClassName / 4, (Metric)(y + m_Fonts.Medium.GetAscent() - m_Fonts.Small.GetAscent())),
			&m_Fonts.Small,
			CG16bitFont::AlignRight,
			m_Fonts.rgbLightTitleColor,
			&pText);
	pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
	pSeq->AddTrack(pText, iDelay);

	int iCount = pShip->GetArmorSectionCount();
	CInstalledArmor *pArmor = (iCount > 0 ? pShip->GetArmorSection(0) : NULL);
	CString sArmor = (pArmor ? pArmor->GetClass()->GetItemType()->GetNounPhrase(nounActual | nounCapitalize | nounShort) : NULL_STR);

	CAniText::Create((pArmor ? strPatternSubst(CONSTLIT("%s (x%d)"), sArmor, iCount) : CONSTLIT("None")),
			CVector((Metric)x + cyClassName / 4, (Metric)y),
			&m_Fonts.Medium,
			0,
			m_Fonts.rgbTitleColor,
			&pText);
	pText->AnimateLinearFade(iDuration - iDelay, 15, 30);
	pSeq->AddTrack(pText, iDelay);

	y += m_Fonts.Medium.GetHeight();
	iDelay += iInterLineDelay;

	//	Done

	*retpAnimatron = pSeq;
	}