hostile-takeover/game/Ecom.cpp
2016-08-31 23:55:30 -04:00

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