hostile-takeover/game/Builder.cpp
Nathan Fulton f0b60db710 Fix builder bug that allowed ordering disabled units
If a unit was ordered while it was enabled, and then the order was
canceled after the unit was disabled, the order button would appear and
allow the disabled unit to be ordered more. This would continue to work
as long as there was at least one order of the disabled unit in the
queue to cancel.
2016-08-31 23:54:48 -04:00

1203 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;
ListItem *pli = plstc->GetSelectedItem();
if (pli != NULL)
m_fOrderValid = !pli->fDisabled;
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