mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2026-02-19 00:24:31 -07:00
1200 lines
33 KiB
C++
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
|