hostile-takeover/game/Headquarters.cpp
2016-08-24 21:34:48 -04:00

1114 lines
30 KiB
C++

#include "ht.h"
namespace wi {
class StructureBuildForm : public Form
{
public:
BuilderGob *GetOwner() {
return m_pgobOwner;
}
void SetOwner(BuilderGob *pgobOwner) secStructures;
void UpdateStructureInfo(ListItem *pli) secStructures;
// Form overrides
void EndForm(int nResult = kidcCancel) secStructures;
virtual void OnControlSelected(word idc) secStructures;
virtual void OnControlNotify(word idc, int nNotify);
virtual void OnPaintBackground(DibBitmap *pbm, UpdateMap *pupd) secStructures;
private:
BuilderGob *m_pgobOwner;
bool m_fLimitReached;
};
//
// HqGob implementation
//
BuilderConsts gbldcHq;
StructureBuildForm *HqGob::s_pfrmBuild = NULL;
PlaceStructureForm *gpfrmPlace = NULL;
static TBitmap *s_ptbmPlacementGood = NULL;
static TBitmap *s_ptbmPlacementBad = NULL;
//
// Gob methods
//
bool HqGob::InitClass(IniReader *pini)
{
gbldcHq.gt = kgtHeadquarters;
gbldcHq.ut = kutHeadquarters;
gbldcHq.umCanBuild = kumStructures & ~(kumHeadquarters | kumReplicator);
gbldcHq.wf |= kfUntcStructureBuilder;
// Preload the HQ's build form
s_pfrmBuild = new StructureBuildForm();
if (s_pfrmBuild == NULL) {
Assert("fatal error");
return false;
}
if (!s_pfrmBuild->Init(gpmfrmm, gpiniForms, kidfBuildStructure)) {
Assert("fatal error");
return false;
}
gpmfrmm->RemoveForm(s_pfrmBuild);
// Preload the HQ's place form
gpfrmPlace = new PlaceStructureForm();
if (gpfrmPlace == NULL) {
Assert("fatal error");
return false;
}
if (!gpfrmPlace->Init(gpfrmmSim, gpiniForms, kidfPlaceStructure)) {
Assert("fatal error");
return false;
}
gpfrmmSim->RemoveForm(gpfrmPlace);
// Preload the placement tile bitmaps
s_ptbmPlacementGood = LoadTBitmap("placementGood.tbm");
s_ptbmPlacementBad = LoadTBitmap("placementBad.tbm");
// Sound effects
gbldcHq.sfxUnitBuildAbort = ksfxHeadquartersAbortConstruction;
gbldcHq.sfxUnitBuild = ksfxHeadquartersConstruct;
gbldcHq.sfxUnitReady = ksfxHeadquartersStructureReady;
gbldcHq.sfxAbortRepair = ksfxHeadquartersAbortRepair;
gbldcHq.sfxRepair = ksfxHeadquartersRepair;
gbldcHq.sfxDamaged = ksfxHeadquartersDamaged;
gbldcHq.sfxDestroyed = ksfxHeadquartersDestroyed;
gbldcHq.sfxSelect = ksfxHeadquartersSelect;
return BuilderGob::InitClass(&gbldcHq, pini);
}
void HqGob::ExitClass()
{
delete s_ptbmPlacementGood;
s_ptbmPlacementGood = NULL;
delete s_ptbmPlacementBad;
s_ptbmPlacementBad = NULL;
delete gpfrmPlace;
gpfrmPlace = NULL;
delete s_pfrmBuild;
s_pfrmBuild = NULL;
BuilderGob::ExitClass(&gbldcHq);
}
HqGob::HqGob() : BuilderGob(&gbldcHq)
{
m_bq.SetSize(1); // no queuing
}
HqGob::~HqGob()
{
// if the structure placement form is up, pull it down
if (gpfrmPlace->GetOwner() != NULL)
gpfrmPlace->OnControlSelected(kidcCancel);
}
void HqGob::InitMenu(Form *pfrm)
{
// toggle on/off the right stuff
Control *pctl = pfrm->GetControlPtr(kidcBuild);
pctl->Show(!m_bq.IsFull());
pctl = pfrm->GetControlPtr(kidcAbortBuild);
pctl->Show(!m_bq.IsEmpty());
BuilderGob::InitMenu(pfrm);
}
void HqGob::OnMenuItemSelected(int idc)
{
switch (idc) {
case kidcBuild:
{
gpmfrmm->AddForm(s_pfrmBuild);
s_pfrmBuild->SetOwner(this);
int idc;
s_pfrmBuild->DoModal(&idc);
gpmfrmm->RemoveForm(s_pfrmBuild);
if (idc == kidcCancel)
break;
gpfrmPlace->SetOwner(this, GetUnitConsts(idc)->ut);
m_unvl.MinSkip();
gpfrmmSim->AddForm(gpfrmPlace);
}
break;
case kidcAbortBuild:
if (PopupConfirmation("ABORT BUILD")) {
gcmdq.Enqueue(kmidAbortBuildOtherCommand, m_gid);
if (m_pplr == gpplrLocal)
gsndm.PlaySfx(m_pbldrc->sfxUnitBuildAbort);
}
break;
default:
BuilderGob::OnMenuItemSelected(idc);
break;
}
}
// take down the build and place forms, HQ is no longer
// in working order
void HqGob::Deactivate()
{
BuilderGob::Deactivate();
if (gpfrmPlace->GetOwner() == this)
gpfrmPlace->OnControlSelected(kidcCancel);
if (s_pfrmBuild->GetOwner() == this)
s_pfrmBuild->EndForm();
}
//
// StateMachine methods
//
#if defined(DEBUG_HELPERS)
char *HqGob::GetName()
{
return "Headquarters";
}
#endif
void HqGob::RemoveScorch(StructGob *pstru)
{
// Get surrounding tile rect
TRect trc;
pstru->GetTileRect(&trc);
// Inflate by one tile
trc.Inflate(1, 1);
// Clip to map
if (trc.left < 0)
trc.left = 0;
if (trc.top < 0)
trc.top = 0;
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
if (trc.right > ctx)
trc.right = ctx;
if (trc.bottom > cty)
trc.bottom = cty;
// Remove scorch gobs that'll be underneath this structure
for (int ty = trc.top; ty < trc.bottom; ty++) {
for (int tx = trc.left; tx < trc.right; tx++) {
Gid gid = ggobm.GetFirstGid(tx, ty);
while (gid != kgidNull) {
Gid gidNext = ggobm.GetNextGid(gid);
Gob *pgob = ggobm.GetGob(gid, false);
if (pgob != NULL && pgob->GetType() == kgtScorch) {
ggobm.RemoveGob(pgob);
delete pgob;
}
gid = gidNext;
}
}
}
}
int HqGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnMsg(kmidBuildOtherCommand)
// Only if the queue is empty since unit only knows how to build one at a time
if (m_bq.IsEmpty()) {
// Counts will already have been checked in the order UI. However since the order is queued, the UI only
// guesses if it is possible to build based on current state when the button is pressed. Here we make a
// bedrock decision. This ensures the limits are enforced and that the same decision gets made on all
// clients in a multiplayer game.
if (ggobm.IsBelowLimit(knLimitStruct, m_pplr)) {
// BUGBUG: due to network lag we can't assume the destination position
// hasn't become occupied between when this command was issued and when
// it is received.
StructGob *pstru = (StructGob *)CreateGob(gapuntc[pmsg->BuildOtherCommand.ut]->gt);
if (pstru == NULL)
return knHandled;
pstru->Init(pmsg->BuildOtherCommand.wpt.wx, pmsg->BuildOtherCommand.wpt.wy, m_pplr, 0, kfGobBeingBuilt, NULL);
pstru->SetHealth(0);
// Clear out scorch marks that are here
RemoveScorch(pstru);
Build(pmsg->BuildOtherCommand.ut, pstru->GetId());
}
}
OnMsg(kmidAbortBuildOtherCommand)
AbortBuild(true);
OnMsg(kmidSelfDestructCommand)
// override and call abort build here so we can tell it to refund the value.
// AbortBuild is also in BuilderGob::Deactivate but will do nothing if called
// a 2nd time, and there it wouldnt refund if we left it
AbortBuild(true);
SelfDestruct();
State(kstBuildOtherCompleting)
OnEnter
// it's now too late to decide to abort
TakedownConfirmation();
gsmm.SendMsg(kmidBuildComplete, m_gidBuildVisible);
// Notify the BuildMgr that this Unit is complete
gsim.GetBuildMgr()->OnBuilt(GetBuiltGob(), this);
ClearBuiltGob();
SetState(kstIdle);
// These are here to keep the message from routing up to BuilderGob's message handler
OnUpdate
OnExit
#if 0
EndStateMachineInherit(BuilderGob)
#else
return knHandled;
}
} else {
return (int)BuilderGob::ProcessStateMachineMessage(st, pmsg);
}
return (int)BuilderGob::ProcessStateMachineMessage(st, pmsg);
#endif
}
//===========================================================================
// StructureBuildForm implementation
void StructureBuildForm::SetOwner(BuilderGob *pgobOwner)
{
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_pgobOwner = pgobOwner;
BuildListControl *plstc = (BuildListControl *)GetControlPtr(kidcList);
plstc->Clear();
// Initialize list with available structure types.
// UNDONE: need to link list to dynamic build capability
Player *pplr = pgobOwner->GetOwner();
UnitMask umOwned = (pplr->GetUnitMask() & kumStructures) | pplr->GetUpgrades();
BuilderConsts *pbldrc = (BuilderConsts *)pgobOwner->GetConsts();
UnitMask umAllowed = pbldrc->umCanBuild & (gfGodMode ? kumAll : pplr->GetAllowedUnits());
UpgradeMask upgmOwned = pplr->GetUpgradeMask();
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, fDisabled);
}
}
}
// Select the first unit by default
plstc->Select(m_pgobOwner->GetLastSelection());
plstc->SetQueueInfo(NULL, NULL);
// If we're at the structure limit, hide the order button, show the limit reached text
// Otherwise hide the limit reached text
if (ggobm.IsBelowLimit(knLimitStruct, m_pgobOwner->GetOwner())) {
m_fLimitReached = false;
} else {
m_fLimitReached = true;
GetControlPtr(kidcOk)->Show(false);
}
GetControlPtr(kidcLimitReached)->Show(m_fLimitReached);
if (!m_fLimitReached && plstc->GetSelectedItemIndex() == -1) {
GetControlPtr(kidcOk)->Show(false);
}
}
void StructureBuildForm::UpdateStructureInfo(ListItem *pli)
{
StructConsts *pstruc = (StructConsts *)gapuntc[(long)pli->pvData];
// Update Cost
LabelControl *plbl = (LabelControl *)GetControlPtr(kidcCost);
char szT[kcbStructUnitName];
itoa(pstruc->GetCost(), szT, 10);
plbl->SetText(szT);
PipMeterControl *pmtr = (PipMeterControl *)GetControlPtr(kidcCostMeter);
pmtr->SetValue((pstruc->GetCost() * 100) / GetUnitCostMax());
// Update Name
plbl = (LabelControl *)GetControlPtr(kidcName);
strcpy(szT, pstruc->szLongName);
HtStrupr(szT);
plbl->SetText(szT);
// Update Power Demand
plbl = (LabelControl *)GetControlPtr(kidcPowerDemand);
itoa(pstruc->nPowerDemand, szT, 10);
plbl->SetText(szT);
pmtr = (PipMeterControl *)GetControlPtr(kidcPowerDemandMeter);
// Use gnPowerSupplyMax as the scaler instead of gnPowerDemandMax so
// both supply and demand can be compared on the same scale
pmtr->SetValue((pstruc->nPowerDemand * 100) / gnPowerSupplyMax);
// Update Power Supply
plbl = (LabelControl *)GetControlPtr(kidcPowerSupply);
itoa(pstruc->nPowerSupply, szT, 10);
plbl->SetText(szT);
pmtr = (PipMeterControl *)GetControlPtr(kidcPowerSupplyMeter);
pmtr->SetValue((pstruc->nPowerSupply * 100) / gnPowerSupplyMax);
// Update Firepower
DamageMeterControl *pdmtr = (DamageMeterControl *)GetControlPtr(kidcWeaponStrengthMeter);
pdmtr->SetUnitConsts(pstruc);
// Update Armor
char *pszT;
fix fxOneThirdRange = (fix)divfx(subfx(gfxStructureArmorStrengthMax, gfxStructureArmorStrengthMin), itofx(3));
plbl = (LabelControl *)GetControlPtr(kidcArmorStrength);
pszT = "MEDIUM";
if (pstruc->GetArmorStrength() == 0)
pszT = "NONE";
else if (pstruc->GetArmorStrength() < addfx(gfxStructureArmorStrengthMin, fxOneThirdRange))
pszT = "LIGHT";
else if (pstruc->GetArmorStrength() >= subfx(gfxStructureArmorStrengthMax, fxOneThirdRange))
pszT = "HEAVY";
plbl->SetText(pszT);
pmtr = (PipMeterControl *)GetControlPtr(kidcArmorStrengthMeter);
pmtr->SetValue(((int)fxtoi(pstruc->GetArmorStrength()) * 100) / fxtoi(gfxStructureArmorStrengthMax));
// Update Description. Substitute the list of prerequisites if the
// item is disabled.
plbl = (LabelControl *)GetControlPtr(kidcDescription);
if (pli->fDisabled) {
char szT[120];
GetPrerequisiteString(szT, pstruc);
char szT2[120];
sprintf(szT2, "This building requires: %s.", szT);
plbl->SetText(szT2);
} else {
plbl->SetText(pstruc->szDescription);
}
// Hide the "Build" button if this structure is disabled
if (!m_fLimitReached) {
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcOk);
pbtn->Show(!pli->fDisabled);
}
}
void StructureBuildForm::EndForm(int nResult)
{
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
m_pgobOwner->SetLastSelection(plstc->GetSelectedItemIndex());
m_pgobOwner = NULL;
Form::EndForm(nResult);
}
void StructureBuildForm::OnControlSelected(word idc)
{
switch (idc) {
case kidcCancel:
EndForm(kidcCancel);
return;
case kidcHelp:
Help("buildings", !ggame.IsMultiplayer());
break;
case kidcList:
{
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
if (plstc->GetSelectedItem() == NULL)
return;
UpdateStructureInfo(plstc->GetSelectedItem());
}
break;
case kidcOk:
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
if (plstc->GetSelectedItem() != NULL)
EndForm(gapuntc[UnitTypeFromPVoid(plstc->GetSelectedItemData())]->gt);
return;
}
}
void StructureBuildForm::OnControlNotify(word idc, int nNotify)
{
if (idc != kidcList) {
return;
}
if (nNotify != knNotifySelectionChange && nNotify != knNotifySelectionTap) {
return;
}
ListControl *plstc = (ListControl *)GetControlPtr(kidcList);
if (plstc->GetSelectedItemIndex() == -1) {
GetControlPtr(kidcOk)->Show(false);
}
}
void StructureBuildForm::OnPaintBackground(DibBitmap *pbm, UpdateMap *pupd)
{
RawBitmap *prbm = LoadRawBitmap("buildformbkgd.rbm");
BltHelper(pbm, prbm, pupd, m_rc.left, m_rc.top);
delete prbm;
}
//===========================================================================
// PlaceStructureForm implementation
//
PlaceStructureForm::PlaceStructureForm()
{
m_pgobOwner = NULL;
m_fDragging = false;
m_fPassOnInput = false;
m_wf |= kfFrmTranslucent;
}
bool PlaceStructureForm::OnHitTest(Event *pevt)
{
// If not passing on input to the form below, do normal form
// processing
if (!m_fPassOnInput) {
return Form::OnHitTest(pevt);
}
// Nothing goes to an invisible form
if (!(m_wf & kfFrmVisible)) {
return false;
}
// If the form has the mouse captured it should get input
if (HasCapture()) {
return true;
}
// Otherwise, input not on the placement form is passed on. This
// is because the SimUI form moves the map with finger input.
for (int n = 0; n < m_cctl; n++) {
Control *pctl = m_apctl[n];
if (pctl->OnHitTest(pevt) >= 0) {
return true;
}
}
// On the placement form somewhere else?
WCoord wxViewOrigin, wyViewOrigin;
gsim.GetViewPos(&wxViewOrigin, &wyViewOrigin);
Rect rcInside, rcOutside;
GetSubRects(wxViewOrigin, wyViewOrigin, &rcInside, &rcOutside);
if (rcOutside.PtIn(pevt->x, pevt->y)) {
return true;
}
// Pass it on to next form
return false;
}
void PlaceStructureForm::OnControlSelected(word idc)
{
gtimm.RemoveTimer(this);
if (idc == kidcCancel) {
gpfrmmSim->RemoveForm(this);
m_pgobOwner = NULL;
return;
}
Message msg;
memset(&msg, 0, sizeof(msg));
msg.smidSender = ksmidNull;
msg.mid = kmidBuildOtherCommand;
msg.BuildOtherCommand.ut = m_pstruc->ut;
msg.BuildOtherCommand.wpt.wx = WcFromTc(m_tx);
msg.BuildOtherCommand.wpt.wy = WcFromTc(m_ty);
msg.smidReceiver = m_pgobOwner->GetId();
gcmdq.Enqueue(&msg);
if (m_pgobOwner->GetOwner() == gpplrLocal)
gsndm.PlaySfx(((BuilderConsts *)m_pgobOwner->GetConsts())->sfxUnitBuild);
gpfrmmSim->RemoveForm(this);
m_pgobOwner = NULL;
}
void PlaceStructureForm::SetOwner(BuilderGob *pgobOwner, UnitType ut)
{
gtimm.AddTimer(this, kctMapScrollRate);
// form is loaded as the size of the whole playfield
// draw the structure placement indicator near the HQ
m_pgobOwner = pgobOwner;
m_pstruc = (StructConsts *)gapuntc[ut];
WPoint wptT;
m_pgobOwner->GetPosition(&wptT);
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
Size sizPlayfield;
ggame.GetPlayfieldSize(&sizPlayfield);
WCoord wxViewMax, wyViewMax;
wxViewMax = WcFromPc(sizPlayfield.cx) + wxView;
wyViewMax = WcFromPc(sizPlayfield.cy) + wyView;
// be consistent about appearing below the HQ unless we will be hidden
StructConsts *pstrucOwner = (StructConsts *)pgobOwner->GetConsts();
WCoord wxTarget = wptT.wx;
WCoord wyTarget = wptT.wy + WcFromTc(pstrucOwner->ctyReserve);
// now make sure we're onscreen
wxViewMax -= WcFromTc(m_pstruc->ctxReserve);
if (wxTarget < wxView)
wxTarget = wxView;
else if (wxTarget > wxViewMax)
wxTarget = wxViewMax;
m_tx = m_txStart = TcFromWc(wxTarget);
if (wyTarget < wyView) {
wyTarget = wyView;
} else {
// watch out for being behind the minimap as well on the screen
Form *pfrmMiniMap = gpmfrmm->GetFormPtr(kidfMiniMap);
Rect rc;
pfrmMiniMap->GetRect(&rc);
WCoord wyMiniMap = WcFromUpc(rc.top) + wyView;
if (wyTarget > wyMiniMap - WcFromTc(m_pstruc->ctyReserve) ) {
// below the top of the mini map - check the horizontal
WCoord wxMiniMap = WcFromUpc(rc.left) + wxView;
if (wxTarget + WcFromTc(m_pstruc->ctxReserve) > wxMiniMap) {
// behind the mini map, go above
wyTarget = wyMiniMap - WcFromTc(m_pstruc->ctyReserve);
} else {
// not behind minimap, just watch screen bottom
wyViewMax -= WcFromTc(m_pstruc->ctyReserve);
if (wyTarget > wyViewMax)
wyTarget = wyViewMax;
}
}
}
m_ty = m_tyStart = TcFromWc(wyTarget);
m_wxPen = m_wxDragStart = 0;
m_wyPen = m_wyDragStart = 0;
// use UpdatePlacementIndicator to get the buttons initialized to the correct location
UpdatePlacementIndicator(wxView, wyView, wxView, wyView);
}
#define kwcButtonInset 56
#define kwcButtonWidth (50 + kwcButtonInset)
void PlaceStructureForm::GetSubRects(WCoord wx, WCoord wy, Rect *prcInside,
Rect *prcOutside)
{
WRect wrcInside;
wrcInside.left = WcFromTc(m_tx);
wrcInside.top = WcFromTc(m_ty);
wrcInside.right = WcFromTc(m_tx + m_pstruc->ctxReserve);
wrcInside.bottom = WcFromTc(m_ty + m_pstruc->ctyReserve);
WRect wrcOutside = wrcInside;
#if defined(IPHONE) || defined(SDL)
if (wrcOutside.Width() < WcFromTc(3)) {
wrcOutside.Inflate((WcFromTc(3) - wrcOutside.Width()) / 2, 0);
}
#endif
Size siz;
gsim.GetLevel()->GetTileMap()->GetMapSize(&siz);
if (wrcOutside.left < kwcButtonWidth) {
wrcOutside.left = kwcButtonWidth;
}
if (wrcOutside.right > WcFromUpc(siz.cx) - kwcButtonWidth) {
wrcOutside.right = WcFromUpc(siz.cx) - kwcButtonWidth;
}
wrcInside.Offset(-wx, -wy);
prcInside->FromWorldRect(&wrcInside);
wrcOutside.Offset(-wx, -wy);
prcOutside->FromWorldRect(&wrcOutside);
}
// do the math to move the hash marks that denote where your building might go
// but keep it tile-aligned. We make sure the hash marks stay on the map and
// stay close to where the pen is and in penmove we make sure the pen location
// we remember is always on the screen so in theory the structure placement
// indicator will always be at at least partially on screen.
void PlaceStructureForm::UpdatePlacementIndicator(WCoord wxViewStart, WCoord wyViewStart,
WCoord wxView, WCoord wyView)
{
// Invalidate our old location
Rect rcInside, rcOutside;
GetSubRects(wxViewStart, wyViewStart, &rcInside, &rcOutside);
InvalidateRect(&rcOutside);
// always use pen movement from start, incremental pen
// movements get rounded down to zero when converted to
// tiles
m_tx = m_txStart + TcFromWc(m_wxPen) - TcFromWc(m_wxDragStart);
m_ty = m_tyStart + TcFromWc(m_wyPen) - TcFromWc(m_wyDragStart);
// don't let the structure outline go off map at all
if (m_tx < 0)
m_tx = 0;
if (m_ty < 0)
m_ty = 0;
Size siz;
gsim.GetLevel()->GetTileMap()->GetMapSize(&siz);
siz.cx /= gcxTile;
siz.cy /= gcyTile;
if (m_tx + m_pstruc->ctxReserve >= siz.cx)
m_tx = siz.cx - m_pstruc->ctxReserve;
if (m_ty + m_pstruc->ctyReserve >= siz.cy)
m_ty = siz.cy - m_pstruc->ctyReserve;
// Invalidate our new location
GetSubRects(wxView, wyView, &rcInside, &rcOutside);
InvalidateRect(&rcOutside);
// Draw 'x', 'check' buttons
// check gets shown/hidden in DrawPlacementTiles because other gob movement
// affects whether or not it shows
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancel);
Rect rcCtl;
pbtn->GetRect(&rcCtl);
int y = (PcFromWc(WcFromTc(m_ty) - wyView) + (PcFromTc(m_pstruc->ctyReserve) - rcCtl.Height()) / 2);
pbtn->SetPosition(rcOutside.right - PcFromWc(kwcButtonInset), y);
pbtn = (ButtonControl *)GetControlPtr(kidcOk);
pbtn->SetPosition(rcOutside.left - rcCtl.Width() +
PcFromWc(kwcButtonInset), y);
// See if it's valid
bool fValid = true;
bool fPlacementValid = ggobm.IsStructurePlacementValid(m_pstruc, m_tx, m_ty, m_pgobOwner->GetOwner());
// Show / hide ok button?
TerrainMap *ptmap = gsim.GetLevel()->GetTerrainMap();
FogMap *pfogm = gsim.GetLevel()->GetFogMap();
for (int ty = m_ty; ty < m_ty + m_pstruc->ctyReserve; ty++) {
for (int tx = m_tx; tx < m_tx + m_pstruc->ctxReserve; tx++) {
if (fPlacementValid && IsTileFree(tx, ty, kbfReserved | kbfStructure) && pfogm->GetGalaxite(tx, ty) == 0) {
continue;
} else {
fValid = false;
break;
}
}
}
// Show ok button if valid
pbtn = (ButtonControl *)GetControlPtr(kidcOk);
pbtn->Show(fValid || gfGodMode);
}
bool PlaceStructureForm::FingerHitTest(Event *pevt) {
// Check to see if the input is over a control, while making sure
// the control doesn't steal input from the hash mark area.
Rect rcFinger;
Rect rcT;
// kidcOk is on the left
Control *pctl = GetControlPtr(kidcOk);
pctl->GetFingerRect(&rcFinger);
pctl->GetRect(&rcT);
rcFinger.right = rcT.right;
if (rcFinger.PtIn(pevt->x - m_rc.left, pevt->y - m_rc.top)) {
return true;
}
// kidcCancel is on the right
pctl = GetControlPtr(kidcCancel);
pctl->GetFingerRect(&rcFinger);
pctl->GetRect(&rcT);
rcFinger.left = rcT.left;
if (rcFinger.PtIn(pevt->x - m_rc.left, pevt->y - m_rc.top)) {
return true;
}
return false;
}
bool PlaceStructureForm::OnPenEvent(Event *pevt)
{
// If captured, do regular processing
if (m_pctlCapture != NULL) {
return Form::OnPenEvent(pevt);
}
// If this is finger input, hit test the buttons specially.
// This is so the controls don't take input meant for the hash mark
// part of the form (the controls have extra wide finger hit test
// rects).
if (pevt->ff & kfEvtFinger) {
if (pevt->eType == penDownEvent && FingerHitTest(pevt)) {
if (Form::OnPenEvent(pevt)) {
return true;
}
}
} else {
// Give the form controls first crack at handling the event. Form
// controls include: soft menu button, mode cancel button, status
// label)
if (Form::OnPenEvent(pevt)) {
return true;
}
}
// Handle drag-scrolling mode (shift-drag or graffiti-scroll)
if (m_fDragging) {
switch (pevt->eType) {
case penDownEvent:
// Something strange is going on, fall through to penUpEvent to
// clear dragging flag and complete in-progress drag operation.
// ...FALL THROUGH...
case penUpEvent:
m_fDragging = false;
// ...FALL THROUGH...
case penMoveEvent:
{
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
// remember where the pen is for scrolling on update
// do everything in world coordinates so we don't get confused
// when the view scrolls
m_wxPen = WcFromPc(pevt->x) + wxView;
m_wyPen = WcFromPc(pevt->y) + wyView;
// Pen may move into a graffiti area, but we'll go no farther
// than the playfield to keep the Placement Indicator at least
// partly on screen. This won't mess up scrolling because it is
// triggered by pen position, not by a pen move event.
Size sizPlayfield;
ggame.GetPlayfieldSize(&sizPlayfield);
WCoord wcxPlayfield = WcFromUpc(sizPlayfield.cx);
WCoord wcyPlayfield = WcFromUpc(sizPlayfield.cy);
if (m_wxPen > wxView + wcxPlayfield)
m_wxPen = wxView + wcxPlayfield;
if (m_wxPen < wxView)
m_wxPen = wxView;
if (m_wyPen > wyView + wcyPlayfield)
m_wyPen = wyView + wcyPlayfield;
if (m_wyPen < wyView)
m_wyPen = wyView;
// move structure outline, but keep it within the map boundry
// no change in view
UpdatePlacementIndicator(wxView, wyView, wxView, wyView);
}
break;
}
} else {
// do our regular thing
if (pevt->eType == penDownEvent) {
UpdatePosition(pevt);
m_fDragging = true;
}
}
return true;
}
void PlaceStructureForm::UpdatePosition(Event *pevt)
{
// If pen down in placement form, drag it without offsetting
// If pen down outside placement form, center placement for under pen
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
m_wxPen = WcFromPc(pevt->x) + wxView;
m_wyPen = WcFromPc(pevt->y) + wyView;
m_wxDragStart = m_wxPen;
m_wyDragStart = m_wyPen;
TRect trcT;
trcT.Set(m_tx, m_ty, m_tx + m_pstruc->ctxReserve,
m_ty + m_pstruc->ctyReserve);
if (!trcT.PtIn(TcFromWc(m_wxPen), TcFromWc(m_wyPen))) {
// New position for the placement indicator. Center indicator underneath
// pen
m_txStart = TcFromWc(m_wxPen) - (m_pstruc->ctxReserve - 1) / 2;
if (m_txStart < 0)
m_txStart = 0;
m_tyStart = TcFromWc(m_wyPen) - (m_pstruc->ctyReserve - 1) / 2;
if (m_tyStart < 0)
m_tyStart = 0;
} else {
// Start from current position
m_txStart = m_tx;
m_tyStart = m_ty;
}
}
// UNDONE: we should probably create an update on the form that gets called
// after all the gobs have updated their state and use that for the placement
// indicator testing too.
// A real-time (not simulation time) timer is started when the structure
// placement form is shown so we can scroll when the form is dragged
// off-screen.
void PlaceStructureForm::OnTimer(long tCurrent)
{
// Something may have moved under it.
// NOTE: this could be faster by remembering during paint
// get fancy with scrolling. World Coordinates, please
// imitated from SimUI.cpp::Update
WCoord wx, wy;
wx = m_wxPen; // where the pen last moved
wy = m_wyPen;
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
Size sizPlayfield;
ggame.GetPlayfieldSize(&sizPlayfield);
WCoord wcxPlayfield = WcFromUpc(sizPlayfield.cx);
WCoord wcyPlayfield = WcFromUpc(sizPlayfield.cy);
WCoord wxViewNew = wxView;
WCoord wyViewNew = wyView;
// is the pen near an edge? if so, scroll
if (m_fDragging) {
// Screen edge
if (wx < wxView + kwcScrollBorderSize) {
wxViewNew -= kwcScrollStepSize;
} else if (wx > wxView + wcxPlayfield - kwcScrollBorderSize) {
wxViewNew += kwcScrollStepSize;
}
if (wy < wyView + kwcScrollBorderSize) {
wyViewNew -= kwcScrollStepSize;
} else if (wy > wyView + wcyPlayfield - kwcScrollBorderSize) {
wyViewNew += kwcScrollStepSize;
}
}
// if we should scroll: update the view, the position of the pen
// and the structure outline by the amount we scrolled.
// setting the viewpos and re-getting it lets the sim
// deal with map edges for us
if (wxViewNew != wxView || wyViewNew != wyView)
gsim.SetViewPos(wxViewNew, wyViewNew);
WCoord wxViewActual, wyViewActual;
gsim.GetViewPos(&wxViewActual, &wyViewActual);
m_wxPen += wxViewActual - wxView;
m_wyPen += wyViewActual - wyView;
UpdatePlacementIndicator(wxView, wyView, wxViewActual, wyViewActual);
// Cause a redraw to drag independently from the game rate
gevm.SetRedrawFlags(kfRedrawDirty | kfRedrawBeforeTimer);
}
void PlaceStructureForm::OnScroll(int dx, int dy)
{
// we're scrolling - don't let the buttons get left behind
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcCancel);
Rect rcCtl;
// let it invalidate, as long as the PlacementTiles draw on every paint the
// controls will need to as well.
pbtn->GetRect(&rcCtl);
rcCtl.Offset(dx,dy);
pbtn->SetRect(&rcCtl);
pbtn = (ButtonControl *)GetControlPtr(kidcOk);
pbtn->GetRect(&rcCtl);
rcCtl.Offset(dx,dy);
pbtn->SetRect(&rcCtl);
}
void PlaceStructureForm::OnPaintSimUI(DibBitmap *pbm)
{
if (m_pgobOwner == NULL)
return;
WCoord wxViewOrigin, wyViewOrigin;
gsim.GetViewPos(&wxViewOrigin, &wyViewOrigin);
Rect rcInside, rcOutside;
GetSubRects(wxViewOrigin, wyViewOrigin, &rcInside, &rcOutside);
// If the outside is larger than the inside, draw leader lines from
// the inside to the edge of the controls, if they are visible.
// Ok is on the left.
if (!rcOutside.Equal(&rcInside)){
// Ok is on the left, Cancel is on the right
Control *pctl = GetControlPtr(kidcOk);
if (pctl->GetFlags() & kfCtlVisible) {
Rect rcT;
pctl->GetRect(&rcT);
int x = m_rc.left + rcT.right;
int y = m_rc.top + rcT.top + rcT.Height() / 2;
pbm->Fill(x, y, rcInside.left - x, 1, GetColor(kiclrWhite));
}
// Cancel is on the right
pctl = GetControlPtr(kidcCancel);
if (pctl->GetFlags() & kfCtlVisible) {
Rect rcT;
pctl->GetRect(&rcT);
int x = rcInside.right;
int y = m_rc.top + rcT.top + rcT.Height() / 2;
pbm->Fill(x, y, m_rc.left + rcT.left - x, 1, GetColor(kiclrWhite));
}
}
bool fPlacementValid = ggobm.IsStructurePlacementValid(m_pstruc, m_tx, m_ty, m_pgobOwner->GetOwner());
// Draw placement tiles, properly colored
TerrainMap *ptmap = gsim.GetLevel()->GetTerrainMap();
FogMap *pfogm = gsim.GetLevel()->GetFogMap();
for (int ty = m_ty; ty < m_ty + m_pstruc->ctyReserve; ty++) {
for (int tx = m_tx; tx < m_tx + m_pstruc->ctxReserve; tx++) {
TBitmap *ptbm;
if (fPlacementValid && IsTileFree(tx, ty, kbfReserved | kbfStructure) && pfogm->GetGalaxite(tx, ty) == 0) {
ptbm = s_ptbmPlacementGood;
} else {
ptbm = s_ptbmPlacementBad;
}
ptbm->BltTo(pbm, (tx * gcxTile) - PcFromUwc(wxViewOrigin), (ty * gcyTile) - PcFromUwc(wyViewOrigin));
}
}
}
void PlaceStructureForm::OnPaintControlsSimUI(DibBitmap *pbm, UpdateMap *pupd)
{
if (m_pgobOwner == NULL)
return;
Form::OnPaintControls(pbm, pupd);
}
} // namespace wi