hostile-takeover/game/Builder.cpp
2016-01-03 23:19:26 -08:00

1200 lines
33 KiB
C++

#include "ht.h"
namespace wi {
const short kcyBuildProgress = 3;
//===========================================================================
// BuilderGob implementation
//
bool BuilderGob::InitClass(BuilderConsts *pbldrc, IniReader *pini)
{
if (!StructGob::InitClass(pbldrc, pini))
return false;
char szTemplate[10];
itoa(pbldrc->gt, szTemplate, 10);
// Required properties
if (pini->GetPropertyValue(szTemplate, "BuildRate", "%d", &pbldrc->nBuildRate) != 1)
return false;
return true;
}
void BuilderGob::ExitClass(BuilderConsts *pbldrc)
{
StructGob::ExitClass(pbldrc);
}
BuilderGob::BuilderGob(BuilderConsts *pbldrc) : StructGob(pbldrc)
{
m_gidBuildVisible = kgidNull;
m_fxHealthBuilding = itofx(0);
m_nCreditsSpentOnBuilding = 0;
m_nCostRemainder = 0;
m_iLastSelection = 0;
m_tptRally.tx = m_tptRally.ty = ktxInvalid;
}
BuilderGob::~BuilderGob()
{
}
bool BuilderGob::Init(WCoord wx, WCoord wy, Player *pplr, fix fxHealth, dword ff, const char *pszName)
{
if (!StructGob::Init(wx, wy, pplr, fxHealth, ff, pszName))
return false;
// Initalize the rally point for units built by this Builder
WRect wrc;
GetTilePaddedWRect(&wrc);
// Center spot "at the bottom" middle
m_tptRally.tx = TcFromWc(wrc.left + wrc.Width() / 3);
m_tptRally.ty = TcFromWc(wrc.bottom);
return true;
}
#define knVerBuilderGobState 4
bool BuilderGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerBuilderGobState)
return false;
m_bq.LoadState(pstm);
if (!m_bq.IsEmpty()) {
m_gidBuildVisible = pstm->ReadWord();
m_fxHealthBuilding = pstm->ReadWord();
m_nCreditsSpentOnBuilding = pstm->ReadWord();
m_nCostRemainder = pstm->ReadWord();
m_tptRally.tx = pstm->ReadWord();
m_tptRally.ty = pstm->ReadWord();
}
return StructGob::LoadState(pstm);
}
bool BuilderGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerBuilderGobState);
m_bq.SaveState(pstm);
if (!m_bq.IsEmpty()) {
pstm->WriteWord(m_gidBuildVisible);
pstm->WriteWord(m_fxHealthBuilding);
pstm->WriteWord(m_nCreditsSpentOnBuilding);
pstm->WriteWord(m_nCostRemainder);
pstm->WriteWord(m_tptRally.tx);
pstm->WriteWord(m_tptRally.ty);
}
return StructGob::SaveState(pstm);
}
void BuilderGob::Deactivate()
{
// half-baked buildings blow when builder goes bye-bye
// either through takeover or being destroyed
// on Deactivate is the only way to respond to being destroyed
// by the enemy
AbortBuild();
StructGob::Deactivate();
}
int BuilderGob::GetGlobalQueuedCount(Player *pplr, word wfTest)
{
int cQueued = 0;
word wfPlr = (pplr->GetFlags() & kfPlrComputer);
for (Gob *pgobT = ggobm.GetFirstGob(); pgobT != NULL; pgobT = ggobm.GetNextGob(pgobT)) {
if (!(pgobT->GetFlags() & kfGobUnit))
continue;
UnitGob *puntT = (UnitGob *)pgobT;
if ((puntT->GetOwner()->GetFlags() & kfPlrComputer) != wfPlr)
continue;
if (!(puntT->GetConsts()->wf & wfTest))
continue;
BuilderGob *pbldr = (BuilderGob *)puntT;
cQueued += pbldr->m_bq.GetUnitCount(kutNone);
}
return cQueued;
}
int BuilderGob::GetQueuedCount(Player *pplr, word wfTest)
{
int cQueued = 0;
for (Gob *pgobT = ggobm.GetFirstGob(); pgobT != NULL; pgobT = ggobm.GetNextGob(pgobT)) {
if (!(pgobT->GetFlags() & kfGobUnit))
continue;
UnitGob *puntT = (UnitGob *)pgobT;
if (puntT->GetOwner() != pplr)
continue;
if (!(puntT->GetConsts()->wf & wfTest))
continue;
BuilderGob *pbldr = (BuilderGob *)puntT;
cQueued += pbldr->m_bq.GetUnitCount(kutNone);
}
return cQueued;
}
void BuilderGob::Build(UnitType ut, Gid gid)
{
// put it on the public build queue
m_bq.Enqueue(ut);
m_gidBuildVisible = gid;
// The build queue is acted on during Update
m_unvl.MinSkip();
}
void BuilderGob::AbortBuild(bool fRefundCreditsSpentOnBuilding, UnitType ut)
{
// this needs to be called by a queued message if triggered by the player
// because it affects credits, especially if they're recycling called by
// kmidAbortBuildOtherCommand and by deactivate & takeover refunds
// CreditsSpent by parameter, and tells self-destruct to not refund half
// the value of the building
// we're either aborting the build of a specific unit (chosen by the user)
// or we're flushing the queue (due to deactivate, takeover or structure
// build abort)
// empty queue case
if (m_bq.IsEmpty())
return;
bool fCurrentConstruction = true;
if (ut == kutNone) {
// flush the queue. If we were building a structure, clean it up
m_bq.Clear();
StructGob *pgobBuild = (StructGob *)ggobm.GetGob(m_gidBuildVisible, false);
if (pgobBuild != NULL) {
pgobBuild->SetFlags(pgobBuild->GetFlags() & ~kfGobBeingBuilt);
pgobBuild->SelfDestruct(false);
}
} else {
Assert(m_gidBuildVisible == kgidNull);
fCurrentConstruction = m_bq.RemoveUnit(ut);
}
if (fCurrentConstruction) {
// we just aborted the current construction
if (fRefundCreditsSpentOnBuilding) {
m_pplr->SetCredits(m_pplr->GetCredits() + m_nCreditsSpentOnBuilding, false);
m_pplr->ModifyTotalCreditsConsumed(-m_nCreditsSpentOnBuilding);
}
m_nCreditsSpentOnBuilding = 0;
m_nCostRemainder = 0;
m_fxHealthBuilding = itofx(0);
WaitingForCredits(false);
}
}
void BuilderGob::DefUpdate()
{
StructGob::DefUpdate();
if (m_bq.IsEmpty())
return;
if (m_ff & kfGobBeingUpgraded) {
// we'll pause building, but make sure we're showing credits correctly
// if we were building when the upgrade started
long nCredits = m_pplr->GetCredits();
WaitingForCredits(nCredits==0);
return;
}
UnitConsts *puntc = gapuntc[m_bq.Peek()];
UnitGob *pgobBuild = (UnitGob *)ggobm.GetGob(m_gidBuildVisible, false);
// God can build things instantly and for free
bool fBuild = false;
if (gfGodMode && !ggame.IsMultiplayer()) {
fBuild = true;
WaitingForCredits(false);
} else {
// CostPerUpdate is a fraction. We track its remainder and add it
// in the next update so we charge the right amount overall.
int nCost = puntc->GetCost() + m_nCostRemainder;
int cupdTimeToBuild = puntc->GetTimeToBuild();
if (m_pplr->GetHandicap() & kfHcapDecreasedTimeToBuild)
cupdTimeToBuild = ((cupdTimeToBuild * (100 + knDecreasedTimeToBuildPercent)) + 50) / 100; // +50 for rounding
// if power is low and this building is so influenced, slow production
// by inflating TimeToBuild
if ((m_puntc->wf & kfUntcNotifyPowerLowHigh) && m_pplr->IsPowerLow()) {
cupdTimeToBuild *= 2; //TUNE:
}
int nCostPerUpdate = nCost / cupdTimeToBuild;
int nCredits = m_pplr->GetCredits() - nCostPerUpdate;
if (nCredits >= 0) {
WaitingForCredits(false);
m_pplr->SetCredits(nCredits, true);
m_nCreditsSpentOnBuilding += nCostPerUpdate;
m_nCostRemainder = nCost % cupdTimeToBuild;
fix fxHealthPerUpdate = puntc->GetArmorStrength() / cupdTimeToBuild;
fix fxHealth = addfx(m_fxHealthBuilding, fxHealthPerUpdate);
if (m_nCreditsSpentOnBuilding >= puntc->GetCost()) {
// power changes during building can get us off by a credit
//Assert(m_nCreditsSpentOnBuilding == puntc->GetCost());
fxHealth = puntc->GetArmorStrength();
fBuild = true;
}
m_fxHealthBuilding = fxHealth;
if (pgobBuild != NULL)
pgobBuild->SetHealth(m_fxHealthBuilding);
// This gob needs to redraw its build status indicator
MarkRedraw();
} else {
if (!(m_wfUnit & kfUnitRepairing)) {
// we'll only manage this if there is not repair code doing so
WaitingForCredits(true);
}
// pgobBuild still animates even when credits have run out
if (pgobBuild != NULL)
pgobBuild->MarkRedraw();
}
// to skip more intervals while not building would mean either A) it takes
// up to kcupdSymbolFlashRate-1 updates for building to resume with a next update
// or B) we need some kind of notification system to force an update. Notification system
// is complex because builders don't drain credits to zero so can't tell just
// from credits whether or not buildings need notifications.
m_unvl.MinSkip();
}
if (fBuild) {
if (pgobBuild != NULL) {
pgobBuild->SetHealth(puntc->GetArmorStrength());
pgobBuild->SetFlags(pgobBuild->GetFlags() & ~kfGobBeingBuilt);
}
// Kick off delivery of the new build
if (m_pplr == gpplrLocal)
gsndm.PlaySfx(m_pbldrc->sfxUnitReady);
SetState(kstBuildOtherCompleting);
}
}
bool BuilderGob::ShowingBuildProgress() {
if (m_bq.IsEmpty()) {
return false;
}
if (IsAlly(gpplrLocal->GetSide())) {
return true;
}
if (gpplrLocal->GetHandicap() & kfHcapShowEnemyBuildProgress) {
return true;
}
return false;
}
void BuilderGob::GetClippingBounds(Rect *prc) {
StructGob::GetClippingBounds(prc);
// Make sure build progress is in this, if it is visible
if (ShowingBuildProgress()) {
Rect rcT;
rcT.FromWorldRect(&m_puntc->wrcUIBounds);
rcT.Offset(PcFromWc(m_wx), PcFromWc(m_wy));
prc->left = _min(prc->left, rcT.left);
prc->right = _max(prc->right, rcT.right);
}
}
void BuilderGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
StructGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
if (nLayer == knLayerDepthSorted) {
if (ShowingBuildProgress()) {
Rect rcT;
rcT.FromWorldRect(&m_puntc->wrcUIBounds);
rcT.Offset(-xViewOrigin + PcFromWc(m_wx), -yViewOrigin + PcFromWc(m_wy));
DrawBuildProgressIndicator(pbm, &rcT, m_fxHealthBuilding, gapuntc[m_bq.Peek()]->GetArmorStrength());
}
}
}
void BuilderGob::DrawUpgradeEffect(DibBitmap *pbm, int xViewOrigin, int yViewOrigin)
{
if (!(m_ff & kfGobActive))
return;
if (!IsAlly(gpplrLocal->GetSide()) && ((gpplrLocal->GetHandicap() & kfHcapShowEnemyBuildProgress) == 0))
return;
// UNDONE: hack-effect
WRect wrcBounds;
GetTilePaddedWRect(&wrcBounds);
Rect rcBounds;
rcBounds.FromWorldRect(&wrcBounds);
rcBounds.Offset(-xViewOrigin, -yViewOrigin);
int nT = _min(rcBounds.Width(), rcBounds.Height()) / 3;
int cupd = gsim.GetUpdateCount();
int n = (cupd % 4);
Color clr = GetColor(kiclrGalaxite);
if (n < 2) {
Rect rcT = rcBounds;
rcT.Inflate(-nT, -nT);
DrawBorder(pbm, &rcT, 2, clr);
} else {
DrawBorder(pbm, &rcBounds, 2, clr);
}
if (!(m_ff & kfGobSelected)) {
Rect rcT;
rcT.FromWorldRect(&m_puntc->wrcUIBounds);
rcT.Offset(-xViewOrigin + PcFromWc(m_wx), -yViewOrigin + PcFromWc(m_wy));
DrawHealthIndicator(pbm, &rcT, m_fxHealth, m_puntc->GetArmorStrength());
}
}
UnitGob *BuilderGob::GetBuiltGob()
{
// Buildings are visible and added to the gob mgr when building. Vehicles
// are not until the build is done.
Assert(!m_bq.IsEmpty());
UnitGob *pgobBuild = (UnitGob *)ggobm.GetGob(m_gidBuildVisible);
if (pgobBuild != NULL)
return pgobBuild;
return (UnitGob *)CreateGob(gapuntc[m_bq.Peek()]->gt);
}
void BuilderGob::ClearBuiltGob()
{
// if this assert goes off then an AbortBuild may have somehow snuck in
// between setting kstBuildOtherCompleting and the derived class calling this
Assert((m_nCreditsSpentOnBuilding > 0) || gfGodMode);
m_bq.Dequeue();
m_gidBuildVisible = kgidNull;
m_nCostRemainder = 0;
m_nCreditsSpentOnBuilding = 0;
m_fxHealthBuilding = itofx(0);
}
void BuilderGob::DrawBuildProgress(DibBitmap *pbm, Rect *prc)
{
if (m_bq.IsEmpty())
return;
Color clr = GetColor(kiclrYellow);
int cxWidth = prc->right - prc->left;
int nLength = (cxWidth * (int)m_fxHealthBuilding) / gapuntc[m_bq.Peek()]->GetArmorStrength();
pbm->Fill(prc->left, prc->top, nLength, kcyBuildProgress, clr);
}
void BuilderGob::SyncBuildQueue(BuildQueue *pbq)
{
// operator is overloaded
*pbq = m_bq;
}
// Note: s_aptInit has been fixed to not have a structure sized-hole
static char s_aptInit[] = {
0, 0, 0, -1, -1, 0, 1, 0, 0, 1, -1, -1, 1, -1, -1, 1,
1, 1, 0, -2, -2, 0, 2, 0, 0, 2, -1, -2, 1, -2, -2, -1,
2, -1, -2, 1, 2, 1, -1, 2, 1, 2, -2, -2, 2, -2, -2, 2,
2, 2, 0, -3, -3, 0, 3, 0, 0, 3, -1, -3, 1, -3, -3, -1,
3, -1, -3, 1, 3, 1, -1, 3, 1, 3, -2, -3, 2, -3, -3, -2,
3, -2, -3, 2, 3, 2, -2, 3, 2, 3, 0, -4, -4, 0, 4, 0,
0, 4, -1, -4, 1, -4, -4, -1, 4, -1, -4, 1, 4, 1, -1, 4,
1, 4, -3, -3, 3, -3, -3, 3, 3, 3, -2, -4, 2, -4, -4, -2,
4, -2, -4, 2, 4, 2, -2, 4, 2, 4, 0, -5, -3, -4, 3, -4,
-4, -3, 4, -3, -5, 0, 5, 0, -4, 3, 4, 3, -3, 4, 3, 4,
0, 5, -1, -5, 1, -5, -5, -1, 5, -1, -5, 1, 5, 1, -1, 5,
1, 5, -2, -5, 2, -5, -5, -2, 5, -2, -5, 2, 5, 2, -2, 5,
2, 5, -4, -4, 4, -4, -4, 4, 4, 4, -3, -5, 3, -5, -5, -3,
5, -3, -5, 3, 5, 3, -3, 5, 3, 5, 0, -6, -6, 0, 6, 0,
0, 6, -1, -6, 1, -6, -6, -1, 6, -1, -6, 1, 6, 1, -1, 6,
1, 6, -2, -6, 2, -6, -6, -2, 6, -2, -6, 2, 6, 2, -2, 6,
};
void FindNearestFreeTile(TCoord tx, TCoord ty, WPoint *pwpt, byte bf)
{
int cpt = sizeof(s_aptInit) / 2;
for (int npt = 0; npt < cpt; npt++) {
int txT = tx + s_aptInit[npt * 2];
int tyT = ty + s_aptInit[npt * 2 + 1];
if (IsTileFree(txT, tyT, bf)) {
pwpt->wx = WcFromTc(txT) + kwcTileHalf;
pwpt->wy = WcFromTc(tyT) + kwcTileHalf;
return;
}
}
// Couldn't find anything good
pwpt->wx = WcFromTc(tx) + kwcTileHalf;
pwpt->wy = WcFromTc(ty) + kwcTileHalf;
}
void BuilderGob::FindInitPosition(WPoint *pwpt)
{
WRect wrc;
GetTilePaddedWRect(&wrc);
// Center spot "at the bottom" middle
TCoord tx = TcFromWc(wrc.left + wrc.Width() / 3);
TCoord ty = TcFromWc(wrc.bottom);
FindNearestFreeTile(tx, ty, pwpt);
}
//===========================================================================
// MobileUnitBuildForm implementation (used by both VTS and HRC but HQ has its own)
void MobileUnitBuildForm::SetOwner(BuilderGob *pbldr)
{
// Position
Size sizDib;
m_pfrmm->GetDib()->GetSize(&sizDib);
Rect rc;
rc.left = ((sizDib.cx - m_rc.Width()) / 2) & ~1;
rc.top = 0; // (sizDib.cy - m_rc.Height()) / 2;
rc.right = rc.left + m_rc.Width();
rc.bottom = rc.top + m_rc.Height();
SetRect(&rc);
m_wf |= kfFrmAutoTakedown | kfFrmNoEcom;
m_pbldr = pbldr;
m_pbldr->SyncBuildQueue(&m_bqPrivate);
BuildListControl *plstc = (BuildListControl *)GetControlPtr(kidcList);
plstc->Clear();
// Initialize list with available unit types.
// UNDONE: need to link list to dynamic build capability
Player *pplr = pbldr->GetOwner();
UnitMask umOwned = (pplr->GetUnitMask() & kumStructures) | pplr->GetUpgrades();
UnitMask umAllowed = ((BuilderConsts *)pbldr->GetConsts())->umCanBuild & (gfGodMode ? kumAll : pplr->GetAllowedUnits());
UpgradeMask upgmOwned = pplr->GetUpgradeMask();
ButtonControl *pbtn;
m_fOrderValid = !m_bqPrivate.IsFull();
int j = 0;
for (int i = 0; i < kutMax; i++) {
UnitConsts *puntc = gapuntc[i];
// Does this player have what it takes to build this structure/unit?
bool fDisabled = true;
if (gfGodMode || ((umOwned & puntc->umPrerequisites) == puntc->umPrerequisites &&
(upgmOwned & puntc->upgmPrerequisites) == puntc->upgmPrerequisites))
fDisabled = false;
// Yep, is this one of the types this build form allows?
if (puntc->um & umAllowed) {
// Yep, add it to the list
int nStripIcon = puntc->panid->GetStripIndex("icon");
if (nStripIcon != -1) {
plstc->Add(puntc->panid, nStripIcon, 0, (void *)(pword)puntc->ut, (pword)fDisabled);
j++;
if (j == m_pbldr->GetLastSelection()) {
// If disabled, orders are not valid
if (fDisabled)
m_fOrderValid = false;
// Disable Cancel Order button if nothing queued for that unit.
pbtn = (ButtonControl *)GetControlPtr(kidcCancelOrder);
pbtn->Show(m_bqPrivate.GetUnitCount(puntc->ut) > 0);
}
}
}
}
// If nothing added to the list, the order button is invalid
if (j == 0) {
m_fOrderValid = false;
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancelOrder);
pbtn->Show(false);
}
// Select the last selected unit by default
// Will be set to the current selection by OnControlSelected via plstc->Select
s_iliCurrent = -1;
plstc->Select(m_pbldr->GetLastSelection());
plstc->SetQueueInfo(m_pbldr, &m_bqPrivate);
// Calc what control needs to be visible
if (plstc->GetSelectedItemIndex() == -1) {
s_iliCurrent = -1;
m_fOrderValid = false;
UpdateOrderButton(false);
} else {
UpdateOrderButton(true);
}
}
void MobileUnitBuildForm::UpdateUnitInfo(ListItem *pli)
{
MobileUnitConsts *pmuntc = (MobileUnitConsts *)gapuntc[(long)pli->pvData];
// Update Cost
LabelControl *plbl = (LabelControl *)GetControlPtr(kidcCost);
char szT[kcbStructUnitName];
itoa(pmuntc->GetCost(), szT, 10);
plbl->SetText(szT);
PipMeterControl *pmtr = (PipMeterControl *)GetControlPtr(kidcCostMeter);
Assert(pmuntc->GetCost() <= GetUnitCostMax()); // make sure we're scaling correctly
pmtr->SetValue(((int)pmuntc->GetCost() * 100) / GetUnitCostMax());
// Update Name
plbl = (LabelControl *)GetControlPtr(kidcName);
strcpy(szT, pmuntc->szLongName);
HtStrupr(szT);
plbl->SetText(szT);
// Update Speed
WCoord wcOneThirdRange = (gwcMoveDistPerUpdateMax - gwcMoveDistPerUpdateMin) / 3;
plbl = (LabelControl *)GetControlPtr(kidcMoveRate);
char *pszT = "MEDIUM";
if (pmuntc->GetMoveDistPerUpdate() < gwcMoveDistPerUpdateMin + wcOneThirdRange)
pszT = "SLOW";
else if (pmuntc->GetMoveDistPerUpdate() >= gwcMoveDistPerUpdateMax - wcOneThirdRange)
pszT = "FAST";
plbl->SetText(pszT);
pmtr = (PipMeterControl *)GetControlPtr(kidcMoveRateMeter);
Assert(pmuntc->GetMoveDistPerUpdate() <= gwcMoveDistPerUpdateMax);
pmtr->SetValue((pmuntc->GetMoveDistPerUpdate() * 100) / gwcMoveDistPerUpdateMax);
// Update Firepower
DamageMeterControl *pdmtr = (DamageMeterControl *)GetControlPtr(kidcWeaponStrengthMeter);
pdmtr->SetUnitConsts(pmuntc);
// Update Armor
fix fxOneThirdRange = (fix)divfx(subfx(gfxMobileUnitArmorStrengthMax, gfxMobileUnitArmorStrengthMin), itofx(3));
plbl = (LabelControl *)GetControlPtr(kidcArmorStrength);
pszT = "MEDIUM";
if (pmuntc->GetArmorStrength() == 0)
pszT = "NONE";
else if (pmuntc->GetArmorStrength() < addfx(gfxMobileUnitArmorStrengthMin, fxOneThirdRange))
pszT = "LIGHT";
else if (pmuntc->GetArmorStrength() >= subfx(gfxMobileUnitArmorStrengthMax, fxOneThirdRange))
pszT = "HEAVY";
plbl->SetText(pszT);
pmtr = (PipMeterControl *)GetControlPtr(kidcArmorStrengthMeter);
Assert(pmuntc->GetArmorStrength() <= gfxMobileUnitArmorStrengthMax);
pmtr->SetValue(((int)fxtoi(pmuntc->GetArmorStrength()) * 100) / fxtoi(gfxMobileUnitArmorStrengthMax));
// Update Range
TCoord tcOneThirdRange = ((gtcFiringRangeMax - gtcFiringRangeMin) * 100) / 3;
plbl = (LabelControl *)GetControlPtr(kidcWeaponRange);
pszT = "MEDIUM";
if (pmuntc->tcFiringRange == 0)
pszT = "NONE";
else if (pmuntc->tcFiringRange * 100 <= tcOneThirdRange)
pszT = "LOW";
else if (pmuntc->tcFiringRange * 100 >= (gtcFiringRangeMax * 100) - tcOneThirdRange)
pszT = "HIGH";
plbl->SetText(pszT);
pmtr = (PipMeterControl *)GetControlPtr(kidcWeaponRangeMeter);
Assert(pmuntc->tcFiringRange-1 <= gtcFiringRangeMax);
pmtr->SetValue(((pmuntc->tcFiringRange - 1) * 100) / gtcFiringRangeMax);
// Update Description. Substitute the list of prerequisites if the
// item is disabled.
plbl = (LabelControl *)GetControlPtr(kidcDescription);
if (pli->fDisabled) {
char szT[120];
GetPrerequisiteString(szT, pmuntc);
char szT2[120];
sprintf(szT2, "This unit requires: %s.", szT);
plbl->SetText(szT2);
} else {
plbl->SetText(pmuntc->szDescription);
}
// Hide the "Order"/"Recruit" button if this structure is disabled
m_fOrderValid = !m_bqPrivate.IsFull() && !pli->fDisabled;
UpdateOrderButton(false);
}
void MobileUnitBuildForm::EndForm(int nResult)
{
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
m_pbldr->SetLastSelection(plstc->GetSelectedItemIndex());
m_pbldr = NULL;
Form::EndForm(nResult);
}
int MobileUnitBuildForm::s_iliCurrent = -1;
#define kcupdLimit 6
void MobileUnitBuildForm::OnControlSelected(word idc)
{
switch (idc) {
case kidcLimitReached:
break;
case kidcCancel:
EndForm(kidcCancel);
return;
//CRM
case kidcHelp:
Help(m_idf == kidfBuildInfantry ? "personnel" : "vehicles", !ggame.IsMultiplayer());
break;
case kidcOrder:
{
m_fOrderValid = !m_bqPrivate.IsFull();
UpdateOrderButton(true);
if (m_fOrderValid && !m_fLimitReached) {
Message msg;
memset(&msg, 0, sizeof(msg));
msg.smidSender = ksmidNull;
msg.mid = kmidBuildOtherCommand;
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
msg.BuildOtherCommand.ut = UnitTypeFromPVoid(plstc->GetSelectedItemData());
msg.smidReceiver = m_pbldr->GetId();
gcmdq.Enqueue(&msg);
if (m_pbldr->GetOwner() == gpplrLocal)
gsndm.PlaySfx(((BuilderConsts *)m_pbldr->GetConsts())->sfxUnitBuild);
// update our private copy of the queue and see if we filled it
m_bqPrivate.Enqueue(UnitTypeFromPVoid(plstc->GetSelectedItemData()));
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancelOrder);
pbtn->Show(true);
// Need to update the order button / show the limit reached label.
// Since the order was queued we don't really know if the limit has been
// reached. We'll just update it asap.
m_cupdLast = gsim.GetUpdateCount() - kcupdLimit + 1;
m_fOrderValid = !m_bqPrivate.IsFull();
UpdateOrderButton(false);
// repaint the number showing the unit count
plstc->Invalidate();
}
}
break;
case kidcCancelOrder:
{
// decrement the appropriate count
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
if (m_bqPrivate.GetUnitCount(UnitTypeFromPVoid(plstc->GetSelectedItemData())) == 0) {
Assert(false); // if my input lags this might be possible...
return;
}
Message msg;
memset(&msg, 0, sizeof(msg));
msg.smidSender = ksmidNull;
msg.mid = kmidAbortBuildOtherCommand;
msg.AbortBuildOtherCommand.ut= UnitTypeFromPVoid(plstc->GetSelectedItemData());
msg.smidReceiver = m_pbldr->GetId();
gcmdq.Enqueue(&msg);
if (m_pbldr->GetOwner() == gpplrLocal)
gsndm.PlaySfx(((BuilderConsts *)m_pbldr->GetConsts())->sfxUnitBuildAbort);
// decrement the the queue count. private queue for immediate response
UnitType ut = UnitTypeFromPVoid(plstc->GetSelectedItemData());
m_bqPrivate.RemoveUnit(ut);
plstc->Invalidate();
// Queue is not full by definition but gob count may still be at limit
m_fOrderValid = true;
m_fLimitReached = false;
UpdateOrderButton(false);
if (m_bqPrivate.GetUnitCount(ut) == 0) {
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancelOrder);
pbtn->Show(false);
}
}
break;
case kidcOk:
EndForm();
return;
}
}
void MobileUnitBuildForm::OnControlNotify(word idc, int nNotify)
{
if (idc != kidcList)
return;
if (nNotify != knNotifySelectionChange && nNotify != knNotifySelectionTap) {
return;
}
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
ListItem *pli = plstc->GetSelectedItem();
if (pli == NULL) {
s_iliCurrent = -1;
m_fOrderValid = false;
UpdateOrderButton(false);
return;
}
UpdateUnitInfo(pli);
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancelOrder);
pbtn->Show(m_bqPrivate.GetUnitCount(UnitTypeFromPVoid(plstc->GetSelectedItemData())) > 0);
// Can we order?
m_fOrderValid = !m_bqPrivate.IsFull() && !pli->fDisabled;
UpdateOrderButton(false);
if (m_fOrderValid && !m_fLimitReached) {
// Tap on a selected item orders it
int ili = plstc->GetSelectedItemIndex();
if (s_iliCurrent == ili && !pli->fDisabled) {
OnControlSelected(kidcOrder);
} else {
s_iliCurrent = ili;
}
}
}
void MobileUnitBuildForm::UpdateOrderButton(bool fCalcLimit)
{
Control *pctlLimit = GetControlPtr(kidcLimitReached);
Control *pctlOrder = GetControlPtr(kidcOrder);
// Limit only gets calced
if (fCalcLimit) {
m_fLimitReached = !ggobm.IsBelowLimit(knLimitMobileUnit,
m_pbldr->GetOwner());
}
if (m_fLimitReached) {
pctlLimit->Show(true);
pctlLimit->SetFlags(pctlLimit->GetFlags() | kfLblHitTest);
pctlOrder->Show(false);
} else {
pctlLimit->Show(false);
pctlOrder->Show(m_fOrderValid);
}
}
void MobileUnitBuildForm::DefUpdate(BuilderGob *pbldr, bool fBuildInProgress)
{
// assumes this is only called if a build is in progress
if (!(m_wf & kfFrmVisible) || pbldr != m_pbldr)
return;
// May have multiple builders that have caused the limit to be reached.
// Poll to figure this out. This takes into account the units already queued too.
// Don't do it every update. This gets rechecked at actual order time so it'll be
// up to date.
int cupdCurrent = gsim.GetUpdateCount();
if (abs(cupdCurrent - m_cupdLast) > kcupdLimit) {
m_cupdLast = cupdCurrent;
UpdateOrderButton(true);
}
// if we're showing during building, we have a progress bar we're
// advancing. Make it update.
if (fBuildInProgress) {
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
plstc->Invalidate();
}
}
void MobileUnitBuildForm::OnPaintBackground(DibBitmap *pbm, UpdateMap *pupd)
{
RawBitmap *prbm = LoadRawBitmap("buildformbkgd.rbm");
BltHelper(pbm, prbm, pupd, m_rc.left, m_rc.top);
delete prbm;
}
void MobileUnitBuildForm::OnUnitCompleted(BuilderGob *pbldr, UnitType ut)
{
if (!(m_wf & kfFrmVisible) || pbldr != m_pbldr)
return;
m_bqPrivate.RemoveUnit(ut);
// fix up button state
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
ListItem *pli = plstc->GetSelectedItem();
m_fOrderValid = pli == NULL || !pli->fDisabled;
UpdateOrderButton(true);
if (m_bqPrivate.GetUnitCount(ut) == 0) {
if (UnitTypeFromPVoid(plstc->GetSelectedItemData()) == ut) {
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancelOrder);
pbtn->Show(false);
}
}
}
//
// BuildListControl
//
bool BuildListControl::Init(Form *pfrm, IniReader *pini, FindProp *pfind)
{
// Base initialization
if (!ListControl::Init(pfrm, pini, pfind))
return false;
// idc (x y cx cy) nfnt cyItem
int cyItem;
int cArgs = pini->GetPropertyValue(pfind, "%*d (%*d %*d %*d %*d) %*d %d",
&cyItem);
if (cArgs != 1)
return false;
m_cyItem = PcFromFc(cyItem);
m_pbldr = NULL;
m_pbq = NULL;
SetScrollPosColorIndex(kiclrListBorder);
return true;
}
bool BuildListControl::Add(AnimationData *panid, int nStrip, int nFrame, void *pvData, bool fDisabled)
{
ListItem *pli = new ListItem;
if (pli == NULL)
return false;
pli->Anim.panid = panid;
pli->Anim.nStrip = nStrip;
pli->Anim.nFrame = nFrame;
pli->pvData = pvData;
pli->fDisabled = fDisabled;
return ListControl::Add(pli);
}
void BuildListControl::GetSubRects(Rect *prcInterior, Rect *prcUpArrow,
Rect *prcDownArrow, Rect *prcScrollPosition)
{
Size sizArrow;
s_ptbmScrollUpUp->GetSize(&sizArrow);
int l = m_rc.left + gcxyBorder;
int r = m_rc.right - gcxyBorder;
int t = m_rc.top + 1;
int b = m_rc.bottom - 1;
if (m_wf & kfLstcScrollPosition) {
if (prcUpArrow != NULL) {
prcUpArrow->SetEmpty();
}
if (prcDownArrow != NULL) {
prcDownArrow->SetEmpty();
}
prcInterior->Set(l, t + sizArrow.cy, r - gcxyBorder * 2,
b - sizArrow.cy);
if (prcScrollPosition != NULL) {
prcScrollPosition->Set(r - gcxyBorder, prcInterior->top, r,
prcInterior->bottom - prcInterior->Height() % m_cyItem);
}
} else {
if (prcUpArrow != NULL) {
prcUpArrow->Set(l, t, r, t + sizArrow.cy);
}
if (prcDownArrow != NULL) {
prcDownArrow->Set(l, b - sizArrow.cy, r, b);
}
prcInterior->Set(l, t + sizArrow.cy, r, b - sizArrow.cy);
if (prcScrollPosition != NULL) {
prcScrollPosition->SetEmpty();
}
}
}
void BuildListControl::OnPaint(DibBitmap *pbm)
{
Rect rcForm;
m_pfrm->GetRect(&rcForm);
Rect rc;
GetRect(&rc);
rc.Offset(rcForm.left, rcForm.top);
DrawBorder(pbm, &rc, 1, GetColor(kiclrListBorder));
pbm->Fill(rc.left + 1, rc.top + 1, rc.Width() - 2, rc.Height() -2, GetColor(kiclrListBackground));
ListControl::OnPaint(pbm);
}
void BuildListControl::DrawItem(DibBitmap *pbm, ListItem *pli, int x, int y, int cx, int cy)
{
HostSoundServiceProc();
Rect rc;
if (pli->fSelected) {
rc.Set(x, y, x + cx, y + cy);
DrawBorder(pbm, &rc, 2, GetColor(kiclrWhite));
} else {
rc.Set(x + 1, y + 1, x + cx - 1, y + cy - 1);
DrawBorder(pbm, &rc, 1, GetColor(kiclrListBorder));
}
AnimationData *panid = pli->Anim.panid;
int nStrip = pli->Anim.nStrip;
int nFrame = pli->Anim.nFrame;
// Center the image within the item area
Rect rcAnim;
pli->Anim.panid->GetBounds(nStrip, nFrame, &rcAnim);
int yOff = (cy - rcAnim.Height()) / 2;
int xOff = (cx - rcAnim.Width()) / 2;
panid->DrawFrame(nStrip, nFrame, pbm, x + xOff, y + yOff, gpplrLocal->GetSide());
if (pli->fDisabled)
pbm->Shadow(rc.left, rc.top, rc.Width(), rc.Height());
// if we have a builder pointer, draw building information
if ((m_pbq != NULL) && (m_pbq->GetUnitCount(UnitTypeFromPVoid(pli->pvData)) > 0)) {
// draw a test health bar. offset by two pixels - either the width of the selected
// rect or the width & offset of an unselected rect
Assert(m_pbldr != NULL);
if (m_pbldr->UnitBuildInProgress() == UnitTypeFromPVoid(pli->pvData)) {
rc.top = y + cy - (2 + kcyBuildProgress);
rc.left = x + 2;
rc.bottom = y + cy - 2;
rc.right = x + cx - 2;
m_pbldr->DrawBuildProgress(pbm, &rc);
}
Font *pfnt = gapfnt[kifntButton];
char szCount[3];
sprintf(szCount,"%d", m_pbq->GetUnitCount(UnitTypeFromPVoid(pli->pvData)));
int cxQ = pfnt->GetTextExtent(szCount);
pfnt->DrawText(pbm, szCount, x+cx-2-cxQ, y+cy-2-pfnt->GetHeight());
}
}
// ----------------------------------------------------------------------------------
// BuildQueue - helper class to ensure the public and private queues behave the same
//
// m_achBuildQueue is a signed byte array of unit types. as items are pulled off everything
// is scooted up with a memcopy. That way units can be canceled in a different order than
// they were ordered and gaps can be closed with memcopy while preserving the overall order
// m_achBuildQueue is bytes for size consideratons but UnitTypes are actually larger so be aware
// ----------------------------------------------------------------------------------
// follow the versioning for the build gob
bool BuildQueue::LoadState(Stream *pstm)
{
m_cchQueueMax = pstm->ReadWord();
if (m_cchQueueMax > 0)
pstm->ReadBytesRLE((byte *)m_achBuildQueue, m_cchQueueMax);
return true;
}
bool BuildQueue::SaveState(Stream *pstm)
{
pstm->WriteWord(m_cchQueueMax);
if (m_cchQueueMax > 0)
pstm->WriteBytesRLE((byte *)m_achBuildQueue, m_cchQueueMax);
return true;
}
BuildQueue::BuildQueue()
{
Assert(kutMax < 128);
m_cchQueueMax = kcBuildQueueMax;
memset(&m_achBuildQueue, (char)kutNone, sizeof(m_achBuildQueue));
}
void BuildQueue::SetSize(word cutMax)
{
Assert(cutMax <= kcBuildQueueMax);
m_cchQueueMax = cutMax;
}
int BuildQueue::GetRemainingCapacity()
{
int cSlotsOpen = 0;
for (int i = 0; i < m_cchQueueMax; i++) {
if (m_achBuildQueue[i] == (char)kutNone)
cSlotsOpen++;
}
return cSlotsOpen;
}
void BuildQueue::Enqueue(UnitType ut)
{
int i = 0;
while ((m_achBuildQueue[i] != (char)kutNone) && i < m_cchQueueMax)
i++;
// we should have made sure there would be room before we get here, we've already
// told the user it's on the queue by this point
Assert(i < m_cchQueueMax);
m_achBuildQueue[i] = (char)ut;
}
void BuildQueue::Dequeue()
{
// normally Dequeue would return the thing it's dequeuing, but I don't really care
memmove(&(m_achBuildQueue[0]), &(m_achBuildQueue[1]), (kcBuildQueueMax - 1));
m_achBuildQueue[kcBuildQueueMax - 1] = (char) kutNone;
}
bool BuildQueue::RemoveUnit(UnitType ut)
{
// find something of that unit type and pull it out of the queue
// search from back to front so we remove a queued item before
// re-setting what's currently building.
int i;
for(i = m_cchQueueMax - 1; i > -1; i--) {
if (m_achBuildQueue[i] == (char) ut)
break;
}
Assert(i > -1);
// it's possible we will encounter things being removed that aren't there because
// of a lag in state. As long as it happens once in each queue we'll be ok. (IE one queue
// will fail canceling and the other will fail removing a completed unit) Since only one
// queue actually reflects game state it's ok for them to get to the same state different ways
memmove(&(m_achBuildQueue[i]), &(m_achBuildQueue[i+1]), sizeof(char)*(m_cchQueueMax - i));
m_achBuildQueue[m_cchQueueMax - 1] = kutNone;
// return true if we removed the currently building item
return i == 0;
}
UnitType BuildQueue::Peek()
{
return (UnitType) m_achBuildQueue[0];
}
bool BuildQueue::IsEmpty()
{
return m_achBuildQueue[0] == (char) kutNone;
}
bool BuildQueue::IsFull()
{
return m_achBuildQueue[m_cchQueueMax-1] != (char) kutNone;
}
int BuildQueue::GetUnitCount(UnitType ut)
{
int cut = 0;
for (int i = 0; i < m_cchQueueMax; i++) {
if (ut == kutNone) {
if (m_achBuildQueue[i] != (char)kutNone)
cut++;
} else {
if (m_achBuildQueue[i] == (char)ut)
cut++;
}
}
return cut;
}
void BuildQueue::Clear()
{
memset(&m_achBuildQueue, (char)kutNone, kcBuildQueueMax);
}
BuildQueue &BuildQueue::operator=( BuildQueue &bqRHS )
{
m_cchQueueMax = bqRHS.m_cchQueueMax;
memcpy(&m_achBuildQueue, bqRHS.m_achBuildQueue, m_cchQueueMax);
return *this;
}
} // namespace wi