mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-23 06:57:23 +00:00
1114 lines
30 KiB
C++
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
|