mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
551 lines
14 KiB
C++
551 lines
14 KiB
C++
#include "ht.h"
|
|
|
|
namespace wi {
|
|
|
|
// font colors Jana, Andy, Olstrom, Fox
|
|
|
|
#define kiaiclrJana 0
|
|
#define kiaiclrAndy 1
|
|
#define kiaiclrOlstrom 2
|
|
#define kiaiclrFox 3
|
|
|
|
#define kcchPerSec 40 // TUNE: how many characters per second output during typed dialog
|
|
#define kctSpeechDelay 50 // TUNE: hsecs before a new speech
|
|
|
|
#define kfEcomAutoTakedown 1
|
|
#define kfEcomMore 2
|
|
|
|
class EcomForm : public Form, public Timer
|
|
{
|
|
public:
|
|
EcomForm() secEcom;
|
|
virtual ~EcomForm() secEcom;
|
|
void SetAutoTakedown() {m_wfEcom |= kfEcomAutoTakedown;}
|
|
bool DoModal(char *pszMessage, int *pnResult = NULL, Sfx sfxShow = ksfxGuiFormShow, Sfx sfxHide = ksfxGuiFormHide) secEcom;
|
|
|
|
// Form overrides
|
|
|
|
virtual void OnPaintBackground(DibBitmap *pbm, UpdateMap *pupd) secEcom;
|
|
virtual void OnControlSelected(word idc) secEcom;
|
|
virtual bool OnPenEvent(Event *pevt) secEcom;
|
|
|
|
// Timer Overrides
|
|
|
|
virtual void OnTimer(long tCurrent) secEcom;
|
|
|
|
private:
|
|
void More() secEcom;
|
|
|
|
word m_wfEcom;
|
|
char *m_pszText;
|
|
char *m_pszNext;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static char *s_aszEmailAddrs[] = {
|
|
"Nobody", "Andy@@ACME", "Jana@@ACME", "Olstrom@@ACME", "Fox@@OMNI", "ACME Security", "OMNI Security", "Anonymous", ""
|
|
};
|
|
static char *s_aszNames[] = {\
|
|
"", "Andy: ", "Jana: ", "Olstrom: ", "Fox: ", "ACME Security: ", "OMNI Security: ", "Anonymous: ", ""
|
|
};
|
|
static char *s_aszPortraits[] = {
|
|
NULL, "andyportrait.tbm", "jana.tbm", "olstrom.tbm", "fox.tbm", NULL, NULL, NULL, NULL
|
|
};
|
|
|
|
void Ecom(int nCharFrom, int nCharTo, char *pszMessage, int nBackground, bool fMore)
|
|
{
|
|
// Don't show ecoms in recording stress mode
|
|
|
|
#ifdef STRESS
|
|
if (gfStress)
|
|
return;
|
|
#endif
|
|
|
|
EcomForm *pfrm = (EcomForm *)gpmfrmm->LoadForm(gpiniForms, nBackground != knSmallLargeTypeLarge ? kidfEcomSmall : kidfEcomLarge, new EcomForm());
|
|
if (pfrm == NULL)
|
|
return;
|
|
|
|
#if 0
|
|
if (nBackground != knSmallLargeTypeLarge && !fMore)
|
|
pfrm->SetAutoTakedown();
|
|
#else
|
|
// Don't special case type large. Some resolutions the large ecom doesn't
|
|
// fill the whole screen, and autotakedown is desireable.
|
|
// Don't special case fMore since that is now automatically handled.
|
|
|
|
pfrm->SetAutoTakedown();
|
|
#endif
|
|
|
|
Size sizPlayfield;
|
|
ggame.GetPlayfieldSize(&sizPlayfield);
|
|
|
|
if (nBackground == knSmallLargeTypeSmallTop) {
|
|
Rect rc;
|
|
pfrm->GetRect(&rc);
|
|
Font *pfnt = gapfnt[kifntDefault];
|
|
rc.Offset(0, -rc.top);
|
|
pfrm->SetRect(&rc);
|
|
} else if (nBackground == knSmallLargeTypeSmallBottom) {
|
|
|
|
// position on bottom of screen. Different on 240 vs 160 screens
|
|
Rect rc;
|
|
pfrm->GetRect(&rc);
|
|
rc.Offset(0, sizPlayfield.cy - rc.Height() - rc.top);
|
|
pfrm->SetRect(&rc);
|
|
}
|
|
|
|
// For all ecoms, horizontally center. Usually fills the screen horizontally unless
|
|
// we're in a weird data / screen mode combo.
|
|
// TUNE: May want to vertically adjust as well but I'm not standing on my head (at the moment)
|
|
|
|
Rect rcForm;
|
|
pfrm->GetRect(&rcForm);
|
|
rcForm.Offset((sizPlayfield.cx - rcForm.Width()) / 2, 0);
|
|
if ((rcForm.left & 1) != 0)
|
|
rcForm.Offset(-1, 0);
|
|
pfrm->SetRect(&rcForm);
|
|
|
|
// Initialize controls
|
|
|
|
LabelControl *plbl = (LabelControl *)pfrm->GetControlPtr(kidcFrom);
|
|
plbl->SetText(s_aszEmailAddrs[nCharFrom]);
|
|
plbl = (LabelControl *)pfrm->GetControlPtr(kidcTo);
|
|
plbl->SetText(s_aszEmailAddrs[nCharTo]);
|
|
|
|
// The label self-adjusts its height
|
|
|
|
if (nBackground == knSmallLargeTypeLarge) {
|
|
BitmapControl *pbmc = (BitmapControl *)pfrm->GetControlPtr(kidcFromBitmap);
|
|
char *pszBitmap = s_aszPortraits[nCharFrom];
|
|
if (pszBitmap != NULL) {
|
|
pbmc->SetBitmap(LoadTBitmap(pszBitmap));
|
|
pbmc->Show(true);
|
|
} else {
|
|
pbmc->Show(false);
|
|
}
|
|
|
|
pbmc = (BitmapControl *)pfrm->GetControlPtr(kidcToBitmap);
|
|
pszBitmap = s_aszPortraits[nCharTo];
|
|
if (pszBitmap != NULL) {
|
|
pbmc->SetBitmap(LoadTBitmap(pszBitmap));
|
|
pbmc->Show(true);
|
|
} else {
|
|
pbmc->Show(false);
|
|
}
|
|
}
|
|
|
|
pfrm->DoModal(pszMessage);
|
|
delete pfrm;
|
|
}
|
|
|
|
//===========================================================================
|
|
// EcomForm implementation
|
|
|
|
// This is just here so we can be explicit about what section it ends up in
|
|
EcomForm::EcomForm()
|
|
{
|
|
m_wfEcom = 0;
|
|
m_pszText = NULL;
|
|
}
|
|
|
|
EcomForm::~EcomForm()
|
|
{
|
|
if (m_pszText != NULL)
|
|
delete[] m_pszText;
|
|
}
|
|
|
|
bool EcomForm::DoModal(char *pszMessage, int *pnResult, Sfx sfxShow, Sfx sfxHide)
|
|
{
|
|
// Expand the text first thing so subsequent operations like calcing the number
|
|
// of lines that will fit on the ecom are based on the expanded text.
|
|
// Use the tail end of the scratch buffer because ExpandVars (potentially) calls
|
|
// StringTable::GetString which reads from the database, decompressing to
|
|
// the front of the scratch buffer as part of the process.
|
|
|
|
char *pszT = (char *)gpbScratch + (gcbScratch / 2);
|
|
ExpandVars(pszMessage, pszT, gcbScratch / 2);
|
|
m_pszText = new char[strlen(pszT) + 1];
|
|
if (m_pszText == NULL) {
|
|
pszT = "ECom text too long! Out of memory.";
|
|
m_pszText = new char[strlen(pszT) + 1];
|
|
if (m_pszText == NULL)
|
|
return false;
|
|
}
|
|
strcpy(m_pszText, pszT);
|
|
m_pszNext = m_pszText;
|
|
|
|
More();
|
|
|
|
gtimm.AddTimer(this, kctEcomOutputInterval);
|
|
bool f = Form::DoModal(pnResult, sfxShow, sfxHide);
|
|
gtimm.RemoveTimer(this);
|
|
return f;
|
|
}
|
|
|
|
void EcomForm::More()
|
|
{
|
|
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcOk);
|
|
pbtn->Show(false);
|
|
|
|
EcomTextControl *pect = (EcomTextControl *)GetControlPtr(kidcMessage);
|
|
char *pszT = new char[strlen(m_pszNext) + 1];
|
|
strcpy(pszT, m_pszNext);
|
|
|
|
// How much of the text will fit?
|
|
|
|
Font *pfnt = gapfnt[kifntDefault];
|
|
Rect rcT;
|
|
pect->GetRect(&rcT);
|
|
|
|
int cLines = rcT.Height() / pfnt->GetHeight();
|
|
char *pchBreak = pszT;
|
|
while (cLines-- > 0 && pchBreak != NULL)
|
|
pfnt->CalcBreak(rcT.Width(), &pchBreak);
|
|
|
|
// If it all fits, scan to the end.
|
|
|
|
if (pchBreak == NULL) {
|
|
pchBreak = pszT;
|
|
while (*pchBreak != 0)
|
|
pchBreak++;
|
|
}
|
|
*pchBreak = 0;
|
|
|
|
pect->SetText(pszT);
|
|
|
|
// Start from the break next time around
|
|
|
|
m_pszNext += pchBreak - pszT;
|
|
delete[] pszT;
|
|
|
|
bool fMore = (*m_pszNext != 0);
|
|
if (fMore) {
|
|
m_wfEcom |= kfEcomMore;
|
|
} else {
|
|
m_wfEcom &= ~kfEcomMore;
|
|
}
|
|
|
|
pbtn->SetText((char *)(fMore ? "More..." : "OK"));
|
|
}
|
|
|
|
bool EcomForm::OnPenEvent(Event *pevt)
|
|
{
|
|
if (pevt->eType == penDownEvent) {
|
|
for (int n = m_cctl - 1; n >= 0; n--) {
|
|
// Is it on this control?
|
|
|
|
Control *pctl = m_apctl[n];
|
|
if (pctl->OnHitTest(pevt) >= 0) {
|
|
return Form::OnPenEvent(pevt);
|
|
}
|
|
}
|
|
|
|
// Not on a control
|
|
|
|
EcomTextControl *pect = (EcomTextControl *)GetControlPtr(kidcMessage);
|
|
pect->ShowAll();
|
|
}
|
|
return Form::OnPenEvent(pevt);
|
|
}
|
|
|
|
void EcomForm::OnControlSelected(word idc)
|
|
{
|
|
switch (idc) {
|
|
case kidcCancel:
|
|
EndForm(kidcCancel);
|
|
return;
|
|
|
|
case kidcMessage:
|
|
{
|
|
EcomTextControl *pect = (EcomTextControl *)GetControlPtr(kidcMessage);
|
|
pect->ShowAll();
|
|
}
|
|
break;
|
|
|
|
case kidcOk:
|
|
if (*m_pszNext == 0)
|
|
EndForm(kidcOk);
|
|
else
|
|
More();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void EcomForm::OnPaintBackground(DibBitmap *pbm, UpdateMap *pupd)
|
|
{
|
|
RawBitmap *prbm = LoadRawBitmap(GetId() == kidfEcomLarge ? (char *)"ecomlargebkgd.rbm" : (char *)"ecomsmallbkgd.rbm");
|
|
BltHelper(pbm, prbm, pupd, m_rc.left, m_rc.top);
|
|
delete prbm;
|
|
}
|
|
|
|
void EcomForm::OnTimer(long tCurrent)
|
|
{
|
|
if (m_wf & kfFrmDoModal) {
|
|
|
|
// invalidate the space for the next character. if it's done,
|
|
// make sure the More/Close button shows
|
|
|
|
EcomTextControl *pect = (EcomTextControl *)GetControlPtr(kidcMessage);
|
|
if (pect->ShowMoreText()) {
|
|
ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcOk);
|
|
pbtn->Show(true);
|
|
if ((m_wfEcom & (kfEcomMore | kfEcomAutoTakedown)) == kfEcomAutoTakedown)
|
|
m_wf |= kfFrmAutoTakedown;
|
|
}
|
|
}
|
|
}
|
|
|
|
// EcomTextControl
|
|
|
|
EcomTextControl::EcomTextControl()
|
|
{
|
|
m_cchCur = 0;
|
|
m_ctPrevTime = HostGetTickCount();
|
|
}
|
|
|
|
EcomTextControl::~EcomTextControl()
|
|
{
|
|
}
|
|
|
|
bool EcomTextControl::Init(Form *pfrm, IniReader *pini, FindProp *pfind)
|
|
{
|
|
// Base initialization
|
|
|
|
if (!Control::Init(pfrm, pini, pfind))
|
|
return false;
|
|
|
|
// idc (x y cx cy) "label" nfnt
|
|
|
|
char szLabel[256];
|
|
char szFlag1[32];
|
|
szLabel[sizeof(szLabel) - 1] = 0;
|
|
szFlag1[sizeof(szFlag1) - 1] = 0;
|
|
|
|
int cArgs = pini->GetPropertyValue(pfind, "%*d (%*d %*d %*d %*d) \"%s\" %d %s",
|
|
szLabel, &m_nfnt, szFlag1);
|
|
Assert(szLabel[sizeof(szLabel) - 1] == 0);
|
|
Assert(szFlag1[sizeof(szFlag1) - 1] == 0);
|
|
|
|
if (cArgs < 2 || cArgs > 3)
|
|
return false;
|
|
|
|
m_szLabel = (char *)gmmgr.AllocPtr(strlen(szLabel) + 1);
|
|
gmmgr.WritePtr(m_szLabel, 0, szLabel, strlen(szLabel) + 1);
|
|
|
|
if (cArgs == 3) {
|
|
if (strcmp(szFlag1, "center") == 0)
|
|
m_wf |= kfLblCenterText;
|
|
else if (strcmp(szFlag1, "right") == 0)
|
|
m_wf |= kfLblRightText;
|
|
}
|
|
|
|
// always multiline
|
|
|
|
m_wf |= kfLblMultiLine;
|
|
|
|
m_cchCur = 0;
|
|
byte biclr;
|
|
biclr = GetColor(kiclrJana) & 0xff;
|
|
m_aiclrEcom[kiaiclrJana] = MAKEDWORD(biclr);
|
|
biclr = GetColor(kiclrAndy) & 0xff;
|
|
m_aiclrEcom[kiaiclrAndy] = MAKEDWORD(biclr);
|
|
biclr = GetColor(kiclrOlstrom) & 0xff;
|
|
m_aiclrEcom[kiaiclrOlstrom] = MAKEDWORD(biclr);
|
|
biclr = GetColor(kiclrFox) & 0xff;
|
|
m_aiclrEcom[kiaiclrFox] = MAKEDWORD(biclr);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EcomTextControl::ShowMoreText()
|
|
{
|
|
// for now invalidate the whole control. Can we do better?
|
|
|
|
// decide how many characters to output based on how much time has
|
|
// gone by (if handheld was slow to paint it can be longer than the timer interval)
|
|
// and paint what we need to to meet our chars per sec. At speech starts
|
|
// we'll delay a specified number of hsecs
|
|
// time is in hundredths of a sec
|
|
|
|
if (m_szLabel[m_cchCur] == 0)
|
|
return true;
|
|
|
|
long ctCurTime = HostGetTickCount();
|
|
long ctDeltaTime = ctCurTime - m_ctPrevTime;
|
|
|
|
// pause before a new character speaks. Make sure we don't count
|
|
// our pause time as part of our character output time when pause ends.
|
|
|
|
if (m_szLabel[m_cchCur] == '@') {
|
|
if (ctDeltaTime < kctSpeechDelay)
|
|
return false;
|
|
else
|
|
ctDeltaTime -= kctSpeechDelay;
|
|
}
|
|
|
|
// output the needed characters to get our output rate
|
|
|
|
int cch = (int)((kcchPerSec * ctDeltaTime)/100);
|
|
if (cch <= 0)
|
|
return false;
|
|
m_ctPrevTime = ctCurTime;
|
|
|
|
// make sure we advance at least one character so we don't get stuck on
|
|
// speech starts.
|
|
|
|
do {
|
|
m_cchCur++;
|
|
cch--;
|
|
} while ( (m_szLabel[m_cchCur] != 0) && (m_szLabel[m_cchCur] != '@') && cch > 0);
|
|
Invalidate();
|
|
|
|
return m_szLabel[m_cchCur] == 0;
|
|
}
|
|
|
|
void EcomTextControl::ShowAll()
|
|
{
|
|
// for now invalidate the whole control. Can we do better?
|
|
|
|
m_cchCur = (int)strlen(m_szLabel);
|
|
Invalidate();
|
|
}
|
|
|
|
void EcomTextControl::OnPaint(DibBitmap *pbm)
|
|
{
|
|
if (m_szLabel == 0)
|
|
return;
|
|
|
|
Rect rcForm;
|
|
m_pfrm->GetRect(&rcForm);
|
|
Font *pfnt = gapfnt[m_nfnt];
|
|
DrawText(pbm, pfnt, m_szLabel, m_rc.left + rcForm.left, m_rc.top + rcForm.top, m_rc.Width(), m_cchCur);
|
|
}
|
|
|
|
int EcomTextControl::OnHitTest(Event *pevt)
|
|
{
|
|
// Label Control stubs this out since they're not usually selectable
|
|
|
|
return Control::OnHitTest(pevt);
|
|
}
|
|
|
|
void EcomTextControl::CalcRect()
|
|
{
|
|
// override the label version of this. Our rect stays whatever it is in forms.pp.ini
|
|
}
|
|
|
|
void EcomTextControl::SetText(char *psz)
|
|
{
|
|
LabelControl::SetText(psz);
|
|
m_cchCur = 0;
|
|
m_ctPrevTime = HostGetTickCount();
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
void EcomTextControl::DrawText(DibBitmap *pbm, Font *pfnt, char *psz, int x, int y, int cx, int cchMax)
|
|
{
|
|
// this draws the string up to the point of cchMax. The string is broken up into character speeches
|
|
// seperated by an '@'. Scan forward to the next '@', set the color appropriately, then
|
|
// draw that speech with the multiline drawing, repeat until we reach cchMax or null
|
|
// null is possible because we don't count the @X that signify color changes, but it does.
|
|
|
|
char *pszNextSpeech = psz;
|
|
int cchSpeech;
|
|
byte biclr = GetColor(kiclrWhite) & 0xff; // make into a dword
|
|
dword dwiclr = MAKEDWORD(biclr); // default color
|
|
dword dwiColorName = dwiclr; // save for doing names
|
|
int xStart = x;
|
|
int cxStart = cx;
|
|
|
|
while ((*pszNextSpeech != 0) && (cchMax > 0)) {
|
|
|
|
if (*pszNextSpeech != '@') {
|
|
|
|
// FYI this data file's speeches are formatted wrong.
|
|
// the should have @[A | J | O | F] at the start of the text line.
|
|
//feel free to continue
|
|
|
|
//Assert(false);
|
|
cchSpeech = 0;
|
|
}else {
|
|
|
|
Assert(*pszNextSpeech == '@');
|
|
pszNextSpeech++;
|
|
int iszNames = 0;
|
|
cchSpeech = 0;
|
|
|
|
// set the color for a new speech
|
|
switch (*pszNextSpeech) {
|
|
case 'A':
|
|
dwiclr = m_aiclrEcom[kiaiclrAndy];
|
|
iszNames = 1;
|
|
break;
|
|
case 'J':
|
|
dwiclr = m_aiclrEcom[kiaiclrJana];
|
|
iszNames = 2;
|
|
break;
|
|
case 'O':
|
|
dwiclr = m_aiclrEcom[kiaiclrOlstrom];
|
|
iszNames = 3;
|
|
break;
|
|
case 'F':
|
|
dwiclr = m_aiclrEcom[kiaiclrFox];
|
|
iszNames = 4;
|
|
break;
|
|
default:
|
|
Assert(false);
|
|
}
|
|
pszNextSpeech++;
|
|
|
|
// we don't count the @format characters in the length - so adjust cchMax.
|
|
cchMax -= 2;
|
|
|
|
// output the character's Name:
|
|
// seems wonky to use both dwWhite and dwiscColor but palm compiler
|
|
// messes it up if I don't use the intermediate variable
|
|
|
|
pfnt->DrawText(pbm, s_aszNames[iszNames], x, y, -1, &dwiColorName );
|
|
int cxName = pfnt->GetTextExtent(s_aszNames[iszNames]);
|
|
x += cxName;
|
|
cx -= cxName;
|
|
|
|
}//endif
|
|
|
|
char *pszNextLine = pszNextSpeech;
|
|
|
|
// point at the next speech and count length of this one
|
|
|
|
while ((*pszNextSpeech != '@') && (*pszNextSpeech != 0)){
|
|
cchSpeech++;
|
|
pszNextSpeech++;
|
|
}
|
|
|
|
if (cchSpeech > cchMax)
|
|
cchSpeech = cchMax;
|
|
cchMax -= cchSpeech;
|
|
|
|
// output this speech in a multiline way
|
|
|
|
while (cchSpeech > 0 && pszNextLine != NULL) {
|
|
Assert(pszNextLine != 0);
|
|
char *pszStart = pszNextLine;
|
|
int cch = pfnt->CalcBreak(cx, &pszNextLine);
|
|
if (cch >= cchSpeech)
|
|
cch = cchSpeech;
|
|
cchSpeech -= cch;
|
|
int iret = pfnt->DrawText(pbm, pszStart, x, y, cch, &dwiclr);
|
|
|
|
// cch does not include the whitespace char being used
|
|
// to break the line!
|
|
|
|
cchSpeech--;
|
|
y += pfnt->GetHeight() - pfnt->GetLineOverlap();
|
|
x = xStart;
|
|
cx = cxStart;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace wi
|