/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "StdAfx.h" #include "LCDDrawing.h" #include "CompMessage.h" extern CGame *_pGame; static const FLOAT tmComputerFade = 1.0f; // how many seconds it takes computer to fade in/out static FLOAT fComputerFadeValue = 0.0f; // faded value of computer (0..1) static CTimerValue tvComputerLast; static CTimerValue _tvMessageAppear; static CPlayer *_ppenPlayer = NULL; extern FLOAT _fMsgAppearFade = 0.0f; extern FLOAT _fMsgAppearDelta = 0.0f; // player statistics are set here extern CTString _strStatsDetails = ""; // mouse cursor position static PIX2D _vpixMouse; static PIX2D _vpixExternMouse; static PIX _pixSliderDragJ = -1; static PIX _iSliderDragLine = -1; static PIX _bSliderDragText = FALSE; // font metrics static PIX _pixCharSizeI = 1; static PIX _pixCharSizeJ = 1; static PIX _pixCharSize2I = 1; static PIX _pixCharSize2J = 1; static PIX _pixMarginI = 1; static PIX _pixMarginJ = 1; // general geometry data static FLOAT _fScaling = 1; static FLOAT _fScaling2 = 1; static PIX _pixSizeI=0; static PIX _pixSizeJ=0; static PIXaabbox2D _boxTitle; static PIXaabbox2D _boxExit; static PIXaabbox2D _boxMsgList; static PIXaabbox2D _boxMsgText; static PIXaabbox2D _boxMsgImage; static PIXaabbox2D _boxButton[CMT_COUNT]; static INDEX _ctMessagesOnScreen = 5; static INDEX _ctTextLinesOnScreen = 20; static INDEX _ctTextCharsPerRow = 20; // position of the message list static INDEX _iFirstMessageOnScreen = -1; static INDEX _iWantedFirstMessageOnScreen = 0; static INDEX _iLastActiveMessage = -1; static INDEX _iActiveMessage = 0; // message type selected in the buttons list static enum CompMsgType _cmtCurrentType = (enum CompMsgType)-1; static enum CompMsgType _cmtWantedType = CMT_INFORMATION; // current scroll position of message text static INDEX _iTextLineOnScreen = 0; // message list cache for messages of current type static CStaticStackArray _acmMessages; // message image data static CTextureObject _toPicture; // text/graphics colors static COLOR _colLight; static COLOR _colMedium; static COLOR _colDark; static COLOR _colBoxes; static void SetFont1(CDrawPort *pdp) { pdp->SetFont(_pfdConsoleFont); pdp->SetTextScaling(_fScaling); pdp->SetTextAspect(1.0f); } static void SetFont2(CDrawPort *pdp) { pdp->SetFont(_pfdDisplayFont); pdp->SetTextScaling(_fScaling2); pdp->SetTextAspect(1.0f); } static COLOR MouseOverColor(const PIXaabbox2D &box, COLOR colNone, COLOR colOff, COLOR colOn) { if (box>=_vpixMouse) { return LCDBlinkingColor(colOff, colOn); } else { return colNone; } } static PIXaabbox2D GetMsgListBox(INDEX i) { PIX pixI0 = _boxMsgList.Min()(1)+_pixMarginI; PIX pixI1 = _boxMsgList.Max()(1)-_pixMarginI*3; PIX pixJ0 = _boxMsgList.Min()(2)+_pixMarginJ; PIX pixDJ = _pixCharSizeJ; return PIXaabbox2D( PIX2D(pixI0, pixJ0+pixDJ*i), PIX2D(pixI1, pixJ0+pixDJ*(i+1)-1)); } static PIXaabbox2D GetSliderBox(INDEX iFirst, INDEX iVisible, INDEX iTotal, PIXaabbox2D boxFull) { FLOAT fSize = ClampUp(FLOAT(iVisible)/iTotal, 1.0f); PIX pixFull = boxFull.Size()(2); PIX pixSize = PIX(pixFull*fSize); pixSize = ClampDn(pixSize, boxFull.Size()(1)); PIX pixTop = pixFull*(FLOAT(iFirst)/iTotal)+boxFull.Min()(2); PIX pixI0 = boxFull.Min()(1); PIX pixI1 = boxFull.Max()(1); return PIXaabbox2D(PIX2D(pixI0, pixTop), PIX2D(pixI1, pixTop+pixSize)); } static INDEX SliderPixToIndex(PIX pixOffset, INDEX iVisible, INDEX iTotal, PIXaabbox2D boxFull) { FLOAT fSize = ClampUp(FLOAT(iVisible)/iTotal, 1.0f); PIX pixFull = boxFull.Size()(2); PIX pixSize = PIX(pixFull*fSize); if (pixSize>=boxFull.Size()(2)) { return 0; } return (iTotal*pixOffset)/pixFull; } static PIXaabbox2D GetTextSliderSpace(void) { PIX pixSizeI = _boxMsgText.Size()(1); PIX pixSizeJ = _boxMsgText.Size()(2); PIX pixSliderSizeI = _pixMarginI*2; if (pixSliderSizeI<5) { pixSliderSizeI=5; } return PIXaabbox2D( PIX2D(pixSizeI-pixSliderSizeI, _pixMarginJ*4), PIX2D(pixSizeI, pixSizeJ)); } static PIXaabbox2D GetMsgSliderSpace(void) { PIX pixSizeI = _boxMsgList.Size()(1); PIX pixSizeJ = _boxMsgList.Size()(2); PIX pixSliderSizeI = _pixMarginI*2; if (pixSliderSizeI<5) { pixSliderSizeI=5; } return PIXaabbox2D( PIX2D(pixSizeI-pixSliderSizeI, 0), PIX2D(pixSizeI, pixSizeJ)); } static PIXaabbox2D GetTextSliderBox(void) { if (_iActiveMessage>=_acmMessages.Count()) { return PIXaabbox2D(); } INDEX ctTextLines = _acmMessages[_iActiveMessage].cm_ctFormattedLines; PIX pixSizeI = _boxMsgText.Size()(1); PIX pixSizeJ = _boxMsgText.Size()(2); return GetSliderBox( _iTextLineOnScreen, _ctTextLinesOnScreen, ctTextLines, GetTextSliderSpace()); } static PIXaabbox2D GetMsgSliderBox(void) { INDEX ctLines = _acmMessages.Count(); PIX pixSizeI = _boxMsgList.Size()(1); PIX pixSizeJ = _boxMsgList.Size()(2); return GetSliderBox( _iFirstMessageOnScreen, _ctMessagesOnScreen, ctLines, GetMsgSliderSpace()); } // syncronize message list scrolling to show active message void SyncScrollWithActive(void) { if (_iActiveMessage<_iFirstMessageOnScreen) { _iWantedFirstMessageOnScreen = _iActiveMessage; } if (_iActiveMessage>_iFirstMessageOnScreen+_ctMessagesOnScreen-1) { _iWantedFirstMessageOnScreen = _iActiveMessage-_ctMessagesOnScreen+1; } } // select next unread message static void NextUnreadMessage(void) { INDEX i=_iActiveMessage; FOREVER { i++; if (i>=_acmMessages.Count()) { i = 0; } if (i==_iActiveMessage) { return; } if (!_acmMessages[i].cm_bRead) { _iActiveMessage = i; SyncScrollWithActive(); return; } } } // select last unread message, or last message if all read void LastUnreadMessage(void) { BOOL bFound = FALSE; for(_iActiveMessage=_acmMessages.Count()-1; _iActiveMessage>=0; _iActiveMessage--) { if (!_acmMessages[_iActiveMessage].cm_bRead) { bFound = TRUE; break; } } if (!bFound) { _iActiveMessage = ClampDn(_acmMessages.Count()-1, 0); } SyncScrollWithActive(); } // go to next/previous message void PrevMessage(void) { if (_iActiveMessage<_acmMessages.Count()==0) { return; } _iActiveMessage--; if (_iActiveMessage<0) { _iActiveMessage = 0; } SyncScrollWithActive(); } void NextMessage(void) { if (_iActiveMessage<_acmMessages.Count()==0) { return; } _iActiveMessage++; if (_iActiveMessage>=_acmMessages.Count()) { _iActiveMessage = _acmMessages.Count()-1; } SyncScrollWithActive(); } void MessagesUpDn(INDEX ctLines) { INDEX ctMessages = _acmMessages.Count(); _iWantedFirstMessageOnScreen += ctLines; INDEX iMaxFirst = ClampDn(0L, ctMessages-_ctMessagesOnScreen); _iWantedFirstMessageOnScreen = Clamp(_iWantedFirstMessageOnScreen, 0L, iMaxFirst); _iActiveMessage = Clamp(_iActiveMessage, _iWantedFirstMessageOnScreen, _iWantedFirstMessageOnScreen+_ctMessagesOnScreen-1L); } void SelectMessage(INDEX i) { if (_acmMessages.Count()==0) { return; } _iActiveMessage = i; if (_iActiveMessage<0) { _iActiveMessage = 0; } if (_iActiveMessage>=_acmMessages.Count()) { _iActiveMessage = _acmMessages.Count()-1; } SyncScrollWithActive(); } // scroll message text void MessageTextUp(INDEX ctLines) { _iTextLineOnScreen-=ctLines; if (_iTextLineOnScreen<0) { _iTextLineOnScreen = 0; } } void MessageTextDn(INDEX ctLines) { // if no message do nothing if (_iActiveMessage<_acmMessages.Count()==0) { return; } // find text lines count _acmMessages[_iActiveMessage].PrepareMessage(_ctTextCharsPerRow); INDEX ctTextLines = _acmMessages[_iActiveMessage].cm_ctFormattedLines; // calculate maximum value for first visible line INDEX iFirstLine = ctTextLines-_ctTextLinesOnScreen; if (iFirstLine<0) { iFirstLine = 0; } // increment _iTextLineOnScreen+=ctLines; if (_iTextLineOnScreen>iFirstLine) { _iTextLineOnScreen = iFirstLine; } } void MessageTextUpDn(INDEX ctLines) { if (ctLines>0) { MessageTextDn(ctLines); } else if (ctLines<0) { MessageTextUp(-ctLines); } } // mark current message as read void MarkCurrentRead(void) { if (_iActiveMessage>=_acmMessages.Count()) { return; } // if running in background if (_pGame->gm_csComputerState == CS_ONINBACKGROUND) { // do nothing return; } ASSERT(_ppenPlayer!=NULL); if (_ppenPlayer==NULL) { return; } // if already read if (_acmMessages[_iActiveMessage].cm_bRead) { // do nothing return; } // mark as read _ppenPlayer->m_ctUnreadMessages--; _acmMessages[_iActiveMessage].MarkRead(); } // update scroll position for message list static void UpdateFirstOnScreen(void) { if (_iFirstMessageOnScreen==_iWantedFirstMessageOnScreen) { return; } _iFirstMessageOnScreen=_iWantedFirstMessageOnScreen; ASSERT( _iFirstMessageOnScreen>=0&& _iFirstMessageOnScreen<=_acmMessages.Count()); _iFirstMessageOnScreen = Clamp(_iFirstMessageOnScreen, INDEX(0), _acmMessages.Count()); // for each message for(INDEX i=0; i<_acmMessages.Count(); i++) { CCompMessage &cm = _acmMessages[i]; // if on screen if (i>=_iWantedFirstMessageOnScreen &&i<_iWantedFirstMessageOnScreen+_ctMessagesOnScreen) { // load cm.PrepareMessage(_ctTextCharsPerRow); // if not on screen } else { // unload cm.UnprepareMessage(); } } } // update current active message category static void UpdateType(BOOL bForce=FALSE) { if (_cmtCurrentType==_cmtWantedType && !bForce) { return; } // cleare message cache _acmMessages.Clear(); // for each player's message CDynamicStackArray &acmiMsgs = _ppenPlayer->m_acmiMessages; for(INDEX i=0; iExecute("FreeUnusedStock();"); // make sure user doesn't overflow memory _iTextLineOnScreen = 0; _iLastActiveMessage=_iActiveMessage; _tvMessageAppear = _pTimer->GetHighPrecisionTimer(); } CTimerValue tvNow = _pTimer->GetHighPrecisionTimer(); _fMsgAppearDelta = (tvNow-_tvMessageAppear).GetSeconds(); if (fComputerFadeValue<0.99f) { _tvMessageAppear = _pTimer->GetHighPrecisionTimer(); _fMsgAppearDelta = 0.0f; } _fMsgAppearFade = Clamp(_fMsgAppearDelta/0.5f, 0.0f,1.0f); } // update screen geometry static void UpdateSize(CDrawPort *pdp) { // get screen size PIX pixSizeI = pdp->GetWidth(); PIX pixSizeJ = pdp->GetHeight(); // remember new size _pixSizeI = pixSizeI; _pixSizeJ = pixSizeJ; // determine scaling _fScaling = 1.0f; _fScaling2 = 1.0f; if (pixSizeJ<384) { _fScaling = 1.0f; _fScaling2 = pixSizeJ/480.0f; } // remember font size CFontData *pfd = _pfdConsoleFont; _pixCharSizeI = pfd->fd_pixCharWidth + pfd->fd_pixCharSpacing; _pixCharSizeJ = pfd->fd_pixCharHeight + pfd->fd_pixLineSpacing; _pixCharSize2I = _pixCharSizeI*_fScaling2; _pixCharSize2J = _pixCharSizeJ*_fScaling2; _pixCharSizeI = _pixCharSizeI*_fScaling; _pixCharSizeJ = _pixCharSizeJ*_fScaling; _pixMarginI = 5*_fScaling2; _pixMarginJ = 5*_fScaling2; PIX pixBoxMarginI = 10*_fScaling2; PIX pixBoxMarginJ = 10*_fScaling2; PIX pixJ0Dn = pixBoxMarginJ; PIX pixJ1Up = pixJ0Dn+_pixCharSize2J+_pixMarginI*2; PIX pixJ1Dn = pixJ1Up+pixBoxMarginJ; PIX pixJ2Up = pixJ1Dn+_pixCharSize2J*6*2+pixBoxMarginJ; PIX pixJ2Dn = pixJ2Up+pixBoxMarginJ; PIX pixJ3Up = _pixSizeJ-pixBoxMarginJ; PIX pixI0Rt = pixBoxMarginI; PIX pixI1Lt = pixI0Rt+_pixCharSize2I*20+pixBoxMarginI; PIX pixI1Rt = pixI1Lt+pixBoxMarginI; PIX pixI2Lt = _pixSizeI/2-pixBoxMarginI/2; PIX pixI2Rt = _pixSizeI/2+pixBoxMarginI/2; PIX pixI4Lt = _pixSizeI-pixBoxMarginI; PIX pixI3Rt = pixI4Lt-pixBoxMarginI*2-_pixCharSize2I*10; PIX pixI3Lt = pixI3Rt-pixBoxMarginI; // calculate box sizes _boxTitle = PIXaabbox2D( PIX2D(0, pixJ0Dn-1), PIX2D(pixI3Lt, pixJ1Up)); _boxExit = PIXaabbox2D( PIX2D( pixI3Rt, pixJ0Dn-1), PIX2D(_pixSizeI, pixJ1Up)); PIX pixD = 5; PIX pixH = (pixJ2Up-pixJ1Dn-pixD*(CMT_COUNT-1))/CMT_COUNT; INDEX i; for( i=0; isp_bCooperative) { _boxMsgText = PIXaabbox2D( PIX2D(pixI2Rt, pixJ2Dn), PIX2D(pixI4Lt, pixJ3Up)); _boxMsgImage= PIXaabbox2D( PIX2D(pixI0Rt, pixJ2Dn), PIX2D(pixI2Lt, pixJ3Up)); } else { _boxMsgText = PIXaabbox2D( PIX2D(pixI0Rt, pixJ2Dn), PIX2D(pixI4Lt, pixJ3Up)); _boxMsgImage= PIXaabbox2D(); } FLOAT fSlideSpeed = Max(_pixSizeI, _pixSizeJ*2L); FLOAT fGroup0 = ClampDn((1-fComputerFadeValue)*fSlideSpeed-_pixSizeJ, 0.0f); FLOAT fGroup1 = (1-fComputerFadeValue)*fSlideSpeed; // animate box positions _boxTitle -= PIX2D( fGroup1, 0); _boxExit += PIX2D( fGroup1, 0); for( i=0; iLCDRenderCompGrid(); LCDRenderClouds2(); LCDScreenBoxOpenLeft(_colBoxes); SetFont2(&dpButton); // count messages INDEX ctTotal=0; INDEX ctRead=0; CDynamicStackArray &acmiMsgs = _ppenPlayer->m_acmiMessages; {for(INDEX i=0; iGetPlayerName()); pdp->PutText( strTitle, _pixMarginI*3, _pixMarginJ-2*_fScaling2+1, _colMedium); } // print exit button void PrintExit(CDrawPort *pdp) { SetFont2(pdp); pdp->PutTextR( TRANS("Exit"), _boxExit.Size()(1)-_pixMarginI*3, _pixMarginJ-2*_fScaling2+1, MouseOverColor(_boxExit, _colMedium, _colDark, _colLight)); } // print list of messages void PrintMessageList(CDrawPort *pdp) { PIX pixTextX = _pixMarginI; PIX pixYLine = _pixMarginJ; SetFont1(pdp); INDEX iFirst = _iFirstMessageOnScreen; INDEX iLast = Min(INDEX(_iFirstMessageOnScreen+_ctMessagesOnScreen), _acmMessages.Count())-1; if (iFirst>iLast) { pdp->PutText( TRANS("no messages"), pixTextX, pixYLine, _colDark); } for(INDEX i=iFirst; i<=iLast; i++) { COLOR col = _colMedium; if (_acmMessages[i].cm_bRead) { col = _colDark; } if (i==_iActiveMessage) { col = _colLight; } if (GetMsgListBox(i-_iFirstMessageOnScreen)>=_vpixMouse) { col = LCDBlinkingColor(_colLight, _colMedium); } pdp->PutText( _acmMessages[i].cm_strSubject, pixTextX, pixYLine, col); pixYLine+=_pixCharSizeJ; } PIXaabbox2D boxSliderSpace = GetMsgSliderSpace(); LCDDrawBox(0,0,boxSliderSpace, _colBoxes); PIXaabbox2D boxSlider = GetMsgSliderBox(); COLOR col = _colBoxes; PIXaabbox2D boxSliderTrans = boxSlider; boxSliderTrans+=_boxMsgList.Min(); if (boxSliderTrans>=_vpixMouse) { col = LCDBlinkingColor(_colLight, _colDark); } pdp->Fill( boxSlider.Min()(1)+2, boxSlider.Min()(2)+2, boxSlider.Size()(1)-4, boxSlider.Size()(2)-4, col); } // print text of current message void PrintMessageText(CDrawPort *pdp) { if (_acmMessages.Count()==0 || _iActiveMessage>=_acmMessages.Count()|| fComputerFadeValue<0.99f) { return; } SetFont2(pdp); // print subject CTString strSubject0; CTString strSubject1; CTString strSubject2; //strSubject.PrintF("%g", _fMsgAppearFade); const char *strSubject = _acmMessages[_iActiveMessage].cm_strSubject; INDEX ctSubjectLen = strlen(strSubject); INDEX ctToPrint = int(_fMsgAppearDelta*20.0f); for (INDEX iChar=0; iCharctToPrint) { NOTHING; } else if (iChar==ctToPrint) { strSubject2+=strChar; } else if (iChar==ctToPrint-1) { strSubject1+=strChar; } else { strSubject0+=strChar; } } PIX pixWidth0 = pdp->GetTextWidth(strSubject0); PIX pixWidth1 = pdp->GetTextWidth(strSubject1); pdp->PutText(strSubject0, _pixMarginI, _pixMarginJ-1, _colMedium); pdp->PutText(strSubject1, _pixMarginI+pixWidth0, _pixMarginJ-1, LerpColor( _colLight, _colMedium, 0.5f)); pdp->PutText(strSubject2, _pixMarginI+pixWidth0+pixWidth1, _pixMarginJ-1, _colLight); pdp->DrawLine(0, PIX(_pixMarginJ*4), _boxMsgText.Size()(1), PIX(_pixMarginJ*4), _colBoxes); // fill in fresh player statistics if (strncmp(_acmMessages[_iActiveMessage].cm_strText, "$STAT", 5)==0) { _ppenPlayer->GetStats(_strStatsDetails, CST_DETAIL, _ctTextCharsPerRow); _acmMessages[_iActiveMessage].cm_ctFormattedWidth = 0; } // format text _acmMessages[_iActiveMessage].PrepareMessage(_ctTextCharsPerRow); SetFont1(pdp); INDEX ctLineToPrint = int(_fMsgAppearDelta*20.0f); // print it PIX pixJ = _pixMarginJ*4; for (INDEX iLine = _iTextLineOnScreen; iLine<_iTextLineOnScreen+_ctTextLinesOnScreen; iLine++) { INDEX iPrintLine = iLine-_iTextLineOnScreen; if (iPrintLine>ctLineToPrint) { continue; } COLOR col = LerpColor( _colLight, _colMedium, Clamp( FLOAT(ctLineToPrint-iPrintLine)/3, 0.0f, 1.0f)); pdp->PutText(_acmMessages[_iActiveMessage].GetLine(iLine), _pixMarginI, pixJ, col); pixJ+=_pixCharSizeJ; } PIXaabbox2D boxSliderSpace = GetTextSliderSpace(); LCDDrawBox(0,0,boxSliderSpace, _colBoxes); PIXaabbox2D boxSlider = GetTextSliderBox(); COLOR col = _colBoxes; PIXaabbox2D boxSliderTrans = boxSlider; boxSliderTrans+=_boxMsgText.Min(); if (boxSliderTrans>=_vpixMouse) { col = LCDBlinkingColor(_colLight, _colDark); } pdp->Fill( boxSlider.Min()(1)+2, boxSlider.Min()(2)+2, boxSlider.Size()(1)-4, boxSlider.Size()(2)-4, col); } void RenderMessagePicture(CDrawPort *pdp) { CCompMessage &cm = _acmMessages[_iActiveMessage]; // try to try { // load image _toPicture.SetData_t(cm.cm_fnmPicture); ((CTextureData*)_toPicture.GetData())->Force(TEX_CONSTANT); // if failed } catch(char *strError) { // report error CPrintF("Cannot load '%s':\n%s\n", (CTString&)cm.cm_fnmPicture, strError); // do nothing return; } // get image and box sizes PIX pixImgSizeI = _toPicture.GetWidth(); PIX pixImgSizeJ = _toPicture.GetHeight(); PIXaabbox2D boxPic(PIX2D(_pixMarginI, _pixMarginJ), PIX2D(_boxMsgImage.Size()(1)-_pixMarginI, _boxMsgImage.Size()(2)-_pixMarginJ)); PIX pixBoxSizeI = boxPic.Size()(1); PIX pixBoxSizeJ = boxPic.Size()(2); PIX pixCenterI = _boxMsgImage.Size()(1)/2; PIX pixCenterJ = _boxMsgImage.Size()(2)/2; // find image stretch to fit in box FLOAT fStretch = Min(FLOAT(pixBoxSizeI)/pixImgSizeI, FLOAT(pixBoxSizeJ)/pixImgSizeJ); // draw the image pdp->PutTexture(&_toPicture, PIXaabbox2D( PIX2D(pixCenterI-pixImgSizeI*fStretch/2, pixCenterJ-pixImgSizeJ*fStretch/2), PIX2D(pixCenterI+pixImgSizeI*fStretch/2, pixCenterJ+pixImgSizeJ*fStretch/2))); } void RenderMessageStats(CDrawPort *pdp) { CSessionProperties *psp = (CSessionProperties *)_pNetwork->GetSessionProperties(); ULONG ulLevelMask = psp->sp_ulLevelsMask; INDEX iLevel = -1; if (psp->sp_bCooperative) { extern void RenderMap( CDrawPort *pdp, ULONG ulLevelMask, CProgressHookInfo *pphi); if (pdp->Lock()) { // get sizes PIX pixSizeI = pdp->GetWidth(); PIX pixSizeJ = pdp->GetHeight(); // clear bcg pdp->Fill( 1, 1, pixSizeI-2, pixSizeJ-2, C_BLACK|CT_OPAQUE); // render the map if not fading COLOR colFade = LCDFadedColor(C_WHITE|255); if( (colFade&255) == 255) { RenderMap( pdp, ulLevelMask, NULL); } pdp->Unlock(); } } } extern void RenderMessageModel(CDrawPort *pdp, const CTString &strModel); // draw image of current message void RenderMessageImage(CDrawPort *pdp) { if (!GetSP()->sp_bCooperative) { return; } // if no message if (_acmMessages.Count()==0 || fComputerFadeValue<0.99f) { // render empty LCDRenderClouds2(); LCDScreenBox(_colBoxes); return; } CCompMessage &cm = _acmMessages[_iActiveMessage]; if (cm.cm_itImage == CCompMessage::IT_STATISTICS) { _pGame->LCDRenderCompGrid(); } LCDRenderClouds2(); LCDScreenBox(_colBoxes); // if no image if (cm.cm_itImage == CCompMessage::IT_NONE) { // do nothing return; } else if (cm.cm_itImage == CCompMessage::IT_PICTURE) { RenderMessagePicture(pdp); } else if (cm.cm_itImage == CCompMessage::IT_STATISTICS) { RenderMessageStats(pdp); } else if (cm.cm_itImage == CCompMessage::IT_MODEL) { RenderMessageModel(pdp, cm.cm_strModel); } else { ASSERT(FALSE); } } // find first group with some unread message static BOOL FindGroupWithUnread(void) { CDynamicStackArray &acmiMsgs = _ppenPlayer->m_acmiMessages; for(INDEX i=acmiMsgs.Count()-1; i>=0; i--) { CCompMessageID &cmi = acmiMsgs[i]; // if it unread if (!cmi.cmi_bRead) { _cmtWantedType = cmi.cmi_cmtType; return TRUE; } } // if none found, select statistics _cmtWantedType = CMT_STATISTICS; return FALSE; } static void ComputerOn(void) { // init button names _astrButtonTexts[CMT_INFORMATION ] = TRANS("tactical data"); _astrButtonTexts[CMT_BACKGROUND ] = TRANS("strategic data"); _astrButtonTexts[CMT_WEAPONS ] = TRANS("weapons"); _astrButtonTexts[CMT_ENEMIES ] = TRANS("enemies"); _astrButtonTexts[CMT_STATISTICS ] = TRANS("statistics"); _iFirstMessageOnScreen = -1; _iWantedFirstMessageOnScreen = 0; _iActiveMessage = 0; _cmtCurrentType = (enum CompMsgType)-1; _cmtWantedType = CMT_INFORMATION; _acmMessages.Clear(); ASSERT(_ppenPlayer!=NULL); if (_ppenPlayer==NULL) { return; } // fill in player statistics _ppenPlayer->GetStats(_strStatsDetails, CST_DETAIL, _ctTextCharsPerRow); // if end of level if (_ppenPlayer->m_bEndOfLevel || _pNetwork->IsGameFinished()) { // select statistics _cmtWantedType = CMT_STATISTICS; // if not end of level } else { // find group with some unread messages FindGroupWithUnread(); } } static void ComputerOff(void) { _acmMessages.Clear(); _pShell->Execute("FreeUnusedStock();"); } static void ExitRequested(void) { // if end of game if (_ppenPlayer!=NULL && _ppenPlayer->m_bEndOfGame || _pNetwork->IsGameFinished()) { // if in single player if (GetSP()->sp_bSinglePlayer) { // request app to show high score _pShell->Execute("sam_bMenuHiScore=1;"); } // hard turn off _pGame->gm_csComputerState = CS_OFF; fComputerFadeValue = 0.0f; ComputerOff(); cmp_ppenPlayer = NULL; // stop current game _pGame->StopGame(); // if not end of level } else { // if can be rendered on second display if (cmp_ppenDHPlayer!=NULL) { // clear pressed keys _pInput->ClearInput(); // just switch to background fast _pGame->gm_csComputerState = CS_ONINBACKGROUND; cmp_ppenPlayer = NULL; // if no second display } else { // start turning off _pGame->gm_csComputerState = CS_TURNINGOFF; } } // turn off end of level for player if (_ppenPlayer!=NULL) { _ppenPlayer->m_bEndOfLevel = FALSE; } } void CGame::ComputerMouseMove(PIX pixX, PIX pixY) { _vpixMouse(1) += pixX-_vpixExternMouse(1); _vpixMouse(2) += pixY-_vpixExternMouse(2); _vpixExternMouse(1) = pixX; _vpixExternMouse(2) = pixY; // if dragging if (_pixSliderDragJ>=0) { PIX pixDelta = _vpixMouse(2)-_pixSliderDragJ; if (_bSliderDragText) { if (_iActiveMessage<_acmMessages.Count()) { INDEX ctTextLines = _acmMessages[_iActiveMessage].cm_ctFormattedLines; INDEX iWantedLine = _iSliderDragLine+ SliderPixToIndex(pixDelta, _ctTextLinesOnScreen, ctTextLines, GetTextSliderSpace()); MessageTextUpDn(iWantedLine-_iTextLineOnScreen); } } else { INDEX ctLines = _acmMessages.Count(); INDEX iWantedLine = _iSliderDragLine+ SliderPixToIndex(pixDelta, _ctMessagesOnScreen, ctLines, GetMsgSliderSpace()); MessagesUpDn(iWantedLine-_iFirstMessageOnScreen); } } } void CGame::ComputerKeyDown(MSG msg) { static BOOL bRDown = FALSE; // if computer is not active if (_pGame->gm_csComputerState!=CS_ON && _pGame->gm_csComputerState!=CS_TURNINGON) { // do nothing return; } // if escape pressed if (msg.message==WM_KEYDOWN && msg.wParam==VK_ESCAPE) { ExitRequested(); } // if right mouse pressed if (msg.message==WM_RBUTTONDOWN || msg.message==WM_RBUTTONDBLCLK) { bRDown = TRUE; } // if right mouse released if (bRDown && msg.message==WM_RBUTTONUP) { bRDown = FALSE; // mark current message as read MarkCurrentRead(); // find a group with first unread message BOOL bHasUnread = FindGroupWithUnread(); // if some if (bHasUnread) { // select first unread message in it NextUnreadMessage(); } else { ExitRequested(); } } if (msg.message==WM_KEYDOWN) { switch (msg.wParam) { // change message types on number keys case '1': _cmtWantedType = CMT_INFORMATION ; return; case '2': _cmtWantedType = CMT_WEAPONS ; return; case '3': _cmtWantedType = CMT_ENEMIES ; return; case '4': _cmtWantedType = CMT_BACKGROUND ; return; case '5': _cmtWantedType = CMT_STATISTICS ; return; // go to next unread case 'U': case VK_SPACE: NextUnreadMessage(); return; // scroll message list case 219: PrevMessage(); return; case 221: NextMessage(); return; // mark current message as read and go to next case VK_RETURN: MarkCurrentRead(); NextUnreadMessage(); return; // scroll message text case VK_UP: MessageTextUp(1); return; case VK_DOWN: MessageTextDn(1); return; case VK_PRIOR:MessageTextUp(_ctTextLinesOnScreen-1); return; case VK_NEXT: MessageTextDn(_ctTextLinesOnScreen-1); return; }; } // if left mouse pressed if (msg.message==WM_LBUTTONDOWN || msg.message==WM_LBUTTONDBLCLK) { BOOL bOverMsgSlider = FALSE; // if over slider {PIXaabbox2D boxSlider = GetTextSliderBox(); PIXaabbox2D boxSliderTrans = boxSlider; boxSliderTrans+=_boxMsgText.Min(); if (boxSliderTrans>=_vpixMouse) { bOverMsgSlider = TRUE; // start dragging _bSliderDragText = TRUE; _pixSliderDragJ=_vpixMouse(2); _iSliderDragLine = _iTextLineOnScreen; }} // if over slider {PIXaabbox2D boxSlider = GetMsgSliderBox(); PIXaabbox2D boxSliderTrans = boxSlider; boxSliderTrans+=_boxMsgList.Min(); if (boxSliderTrans>=_vpixMouse) { // start dragging _bSliderDragText = FALSE; _pixSliderDragJ=_vpixMouse(2); _iSliderDragLine = _iFirstMessageOnScreen; }} // if over some button {for(INDEX i=0; i=_vpixMouse) { // switch to that message type _cmtWantedType = (CompMsgType)i; } }} // if over some message {for(INDEX i=0; i<_ctMessagesOnScreen; i++) { if (GetMsgListBox(i)>=_vpixMouse && !bOverMsgSlider) { // switch to that message SelectMessage(_iFirstMessageOnScreen+i); } }} } // if left mouse released if (msg.message==WM_LBUTTONUP) { // stop dragging _pixSliderDragJ=-1; // if over exit if (_boxExit>=_vpixMouse) { // exit ExitRequested(); } } } void CGame::ComputerRender(CDrawPort *pdp) { // if playing a demo if (_pNetwork->IsPlayingDemo()) { // never call computer cmp_ppenPlayer = NULL; } // disable netricsa for non-local players if (cmp_ppenPlayer!=NULL && !_pNetwork->IsPlayerLocal(cmp_ppenPlayer)) { cmp_ppenPlayer = NULL; } if (cmp_ppenDHPlayer!=NULL && !_pNetwork->IsPlayerLocal(cmp_ppenDHPlayer)) { cmp_ppenDHPlayer = NULL; } if (cmp_ppenDHPlayer!=NULL && !pdp->IsDualHead()) { cmp_ppenDHPlayer = NULL; } // initially - no player _ppenPlayer=NULL; // if player calls computer if (cmp_ppenPlayer!=NULL) { // use that player _ppenPlayer = cmp_ppenPlayer; // if computer is on in background if (_pGame->gm_csComputerState==CS_ONINBACKGROUND) { // just toggle to on _pGame->gm_csComputerState=CS_ON; // find group with some unread messages FindGroupWithUnread(); // force reinit _cmtCurrentType = (enum CompMsgType)-1; } // if using dualhead to render computer on second display } else if (cmp_ppenDHPlayer!=NULL) { // use that player _ppenPlayer = cmp_ppenDHPlayer; // clear dualhead request - it has to be reinitialized every frame cmp_ppenDHPlayer = NULL; // if viewing statistics if (_cmtWantedType == CMT_STATISTICS) { // fill in fresh player statistics _ppenPlayer->GetStats(_strStatsDetails, CST_DETAIL, _ctTextCharsPerRow); // force updating UpdateType(TRUE); } // if computer is not on or on in background if (_pGame->gm_csComputerState!=CS_ON && _pGame->gm_csComputerState!=CS_ONINBACKGROUND) { // switch on fast ComputerOn(); fComputerFadeValue = 1.0f; _pGame->gm_csComputerState = CS_ONINBACKGROUND; cmp_bInitialStart = FALSE; // end of eventual initial start } // if should update to new message if (cmp_bUpdateInBackground) { cmp_bUpdateInBackground = FALSE; FindGroupWithUnread(); // force reinit _cmtCurrentType = (enum CompMsgType)-1; } } // if no player if (_ppenPlayer==NULL) { // make sure computer is off _pGame->gm_csComputerState=CS_OFF; // do nothing more return; } // if computer is not active if (_pGame->gm_csComputerState==CS_OFF) { // just remember time tvComputerLast = _pTimer->GetHighPrecisionTimer(); // if a player wants computer if (_ppenPlayer!=NULL) { // start turning on _pGame->gm_csComputerState=CS_TURNINGON; ComputerOn(); } else { return; } } // calculate up-down speed to be independent of refresh speed CTimerValue tvNow = _pTimer->GetHighPrecisionTimer(); CTimerValue tvDelta = tvNow - tvComputerLast; tvComputerLast = tvNow; FLOAT fFadeSpeed = (FLOAT)(tvDelta.GetSeconds() / tmComputerFade); // if computer is dropping down if( _pGame->gm_csComputerState==CS_TURNINGON) { // move it down fComputerFadeValue += fFadeSpeed; // if finished moving if( fComputerFadeValue>1.0f) { // stop fComputerFadeValue = 1.0f; _pGame->gm_csComputerState = CS_ON; cmp_bInitialStart = FALSE; // end of eventual initial start } } // if computer is pulling up if( _pGame->gm_csComputerState==CS_TURNINGOFF) { // move it up fComputerFadeValue -= fFadeSpeed; // if finished moving if( fComputerFadeValue<0.0f) { // stop fComputerFadeValue = 0.0f; _pGame->gm_csComputerState = CS_OFF; ComputerOff(); cmp_ppenPlayer = NULL; // exit computer return; } } // safety check -> do not proceed if no player if (_ppenPlayer==NULL) { return; } // lock drawport CDrawPort dpComp(pdp, FALSE); if(!dpComp.Lock()) { // do nothing return; } // if in fullscreen CDisplayMode dmCurrent; _pGfx->GetCurrentDisplayMode(dmCurrent); if (dmCurrent.IsFullScreen() && dmCurrent.IsDualHead()) { // clamp mouse pointer _vpixMouse(1) = Clamp(_vpixMouse(1), 0L, dpComp.GetWidth()); _vpixMouse(2) = Clamp(_vpixMouse(2), 0L, dpComp.GetHeight()); // if in window } else { // use same mouse pointer as windows _vpixMouse = _vpixExternMouse; // if dualhead if (dpComp.dp_MinI>0) { // offset by half screen _vpixMouse(1) -= dpComp.GetWidth(); } // if widescreen if (dpComp.dp_MinJ>0) { // offset by screen top _vpixMouse(2) -= dpComp.dp_MinJ; } } TIME tmOld = _pTimer->CurrentTick(); FLOAT fLerpOld = _pTimer->GetLerpFactor(); FLOAT fSec = tvNow.GetSeconds(); TIME tmTick = floor(fSec/_pTimer->TickQuantum)*_pTimer->TickQuantum; FLOAT fLerp = (fSec-tmTick)/_pTimer->TickQuantum; _pTimer->SetCurrentTick(tmTick); _pTimer->SetLerp(fLerp); LCDPrepare(1.0f);//ClampUp(fComputerFadeValue*10,1.0f)); LCDSetDrawport(&dpComp); // if initial start if (cmp_bInitialStart) { // do not allow game to show through dpComp.Fill(C_BLACK|255); // if normal start } else { // fade over game view dpComp.Fill(LCDFadedColor(C_BLACK|255)); } dpComp.FillZBuffer(1.0f); // update screen geometry UpdateSize(&dpComp); // update scroll positions UpdateType(); UpdateFirstOnScreen(); // check for message change UpdateMessageAppearing(); // mark current message as read MarkCurrentRead(); // get current time and alpha value FLOAT tmNow = (FLOAT)tvNow.GetSeconds(); ULONG ulA = NormFloatToByte(fComputerFadeValue); _colLight = LCDFadedColor(C_WHITE|255); _colMedium = LCDFadedColor(SE_COL_BLUE_LIGHT|255); _colDark = LCDFadedColor(LerpColor(SE_COL_BLUE_DARK, SE_COL_BLUE_LIGHT, 0.5f)|255); _colBoxes = LCDFadedColor(LerpColor(SE_COL_BLUE_DARK, SE_COL_BLUE_LIGHT, 0.5f)|255); // background LCDRenderCloudsForComp(); // dpComp.DrawLine( 0, pixSizeJ-1, pixSizeI, pixSizeJ-1, C_GREEN|ulA); // all done dpComp.Unlock(); // print title CDrawPort dpTitle(&dpComp, _boxTitle); if (dpTitle.Lock()) { LCDSetDrawport(&dpTitle); LCDRenderCompGrid(); LCDRenderClouds2(); LCDScreenBoxOpenLeft(_colBoxes); PrintTitle(&dpTitle); dpTitle.Unlock(); } // print exit button CDrawPort dpExit(&dpComp, _boxExit); if (dpExit.Lock()) { LCDSetDrawport(&dpExit); LCDRenderCompGrid(); LCDRenderClouds2(); LCDScreenBoxOpenRight(_colBoxes); PrintExit(&dpExit); dpExit.Unlock(); } // print buttons for (INDEX i=0; igm_csComputerState != CS_ONINBACKGROUND) { if (dpComp.Lock()) { LCDSetDrawport(&dpComp); LCDDrawPointer(_vpixMouse(1), _vpixMouse(2)); dpComp.Unlock(); } } _pTimer->SetCurrentTick(tmOld); _pTimer->SetLerp(fLerpOld); } void CGame::ComputerForceOff() { cmp_ppenPlayer=NULL; cmp_ppenDHPlayer=NULL; _pGame->gm_csComputerState = CS_OFF; fComputerFadeValue = 0.0f; _ppenPlayer = NULL; }