Files
2026-02-02 04:50:13 +01:00

2891 lines
67 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "bagel/afxwin.h"
#include "bagel/hodjnpodj/hnplibs/dibdoc.h"
#include "bagel/hodjnpodj/hnplibs/stdinc.h"
#include "bagel/hodjnpodj/hnplibs/text.h"
#include "bagel/hodjnpodj/globals.h"
#include "bagel/hodjnpodj/hnplibs/sprite.h"
#include "bagel/hodjnpodj/hnplibs/mainmenu.h"
#include "bagel/hodjnpodj/hnplibs/cmessbox.h"
#include "bagel/boflib/misc.h"
#include "bagel/hodjnpodj/hnplibs/rules.h"
#include "bagel/boflib/error.h"
#include "bagel/hodjnpodj/hnplibs/button.h"
#include "bagel/hodjnpodj/hnplibs/gamedll.h"
#include "bagel/boflib/sound.h"
#include "bagel/hodjnpodj/fuge/fuge.h"
#include "bagel/hodjnpodj/fuge/usercfg.h"
#include "bagel/hodjnpodj/hodjnpodj.h"
namespace Bagel {
namespace HodjNPodj {
namespace Fuge {
extern CWnd *ghParentWnd;
#define CSOUND 0
//
// This mini-game's main screen bitmap
//
#define MINI_GAME_MAP ".\\ART\\FUGE6.BMP"
// Fuge sprite bitmaps
//
#define IDB_BALL 103
#define N_PADDLE_CELS 31
#define PADDLE_START_X 285
#define PADDLE_START_Y 205
#define PADDLE_CEL_JUMP 1
#define FACE_ANGLE (double)0.977322
#define PADDLE0_ANGLE (PI/4) // 45 degrees
#define PADDLE1_ANGLE (PI/3) // 60 degrees
#define PADDLE2_ANGLE (PI/2) // 90 degrees
#define MOUSE_SENS 32
#define BLACKHOLE_RADIUS 14
#define PADDLE_RADIUS 34
#define INNER_BRICK_RADIUS 102
#define ROW6_RADIUS 114
#define ROW5_RADIUS 129
#define ROW4_RADIUS 145
#define ROW3_RADIUS 160
#define ROW2_RADIUS 175
#define ROW1_RADIUS 190
#define WHEEL_RADIUS 191
#define BRICK0_ANGLE 0.000000
#define BRICK1_ANGLE 0.392699
#define BRICK2_ANGLE 0.785398
#define BRICK3_ANGLE 1.178097
#define BRICK4_ANGLE 1.570796
#define BRICK5_ANGLE 1.963495
#define BRICK6_ANGLE 2.356194
#define BRICK7_ANGLE 2.748894
#define BRICK8_ANGLE 3.141593
#define BRICK9_ANGLE 3.534291
#define BRICK10_ANGLE 3.926990
#define BRICK11_ANGLE 4.319690
#define BRICK12_ANGLE 4.712389
#define BRICK13_ANGLE 5.105088
#define BRICK14_ANGLE 5.497787
#define BRICK15_ANGLE 5.890486
#define N_BLACKHOLE_CELS 5
#define N_BRICK_POINTS 21
#define N_BALL_MOVES 3
#define BALL_START_X 309
#define BALL_START_Y 180
#define BALL_SIZE_X 20
#define BALL_SIZE_Y 20
#define BALL_RADIUS (BALL_SIZE_X/2)
#define CENTER_X 319
#define CENTER_Y 239
#define G_FORCE 0.009000
#define TURBO_MIN 0
#define TURBO_DEF 10
#define TURBO_MAX 10
#define EXTRA_LIFE_SCORE 100
//
// Button ID constants
//
#define IDC_MENU 100
#define TIMER_ID 10
#define TIMER_INTERVAL 50
// Sounds
//
#define WAV_PADDLE ".\\SOUND\\PADDLE.WAV"
#define WAV_WALL ".\\SOUND\\WALL.WAV"
#define WAV_WINWAVE ".\\SOUND\\WINWAVE.WAV"
#define WAV_GAMEOVER ".\\SOUND\\SOSORRY.WAV" //GAMEOVER.WAV"
#define WAV_LOSEBALL ".\\SOUND\\TOILET.WAV" //LOSEBALL.WAV"
#define WAV_NARRATION ".\\SOUND\\FUGE.WAV"
#define MID_SOUNDTRACK ".\\SOUND\\FUGE.MID"
#define WAV_CAR1 ".\\SOUND\\SICK.WAV"
#define WAV_CAR2 ".\\SOUND\\FERRIS.WAV"
#define WAV_TENT ".\\SOUND\\LAWYER.WAV"
#define WAV_BOOTH ".\\SOUND\\STEPUP.WAV"
#define WAV_PEOPLE1 ".\\SOUND\\BALLOON.WAV"
#define WAV_PEOPLE2 ".\\SOUND\\AUNTEDNA.WAV"
#define N_PADDLE_SIZES (PSIZE_MAX+1)
STATIC const char *pszPaddles[N_PADDLE_SIZES] = {
".\\ART\\PADCEL45.BMP",
".\\ART\\PADCEL60.BMP",
".\\ART\\PADCEL90.BMP"
};
STATIC const double fPaddleAngles[N_PADDLE_SIZES] = {
PADDLE0_ANGLE,
PADDLE1_ANGLE,
PADDLE2_ANGLE
};
#define NUM_WAVS 2 //There are two people and two car sounds
// Audio Easter Egg areas
//
#define TENT_X 16
#define TENT_Y 320
#define TENT_DX 75
#define TENT_DY 47
#define BOOTH_X 503
#define BOOTH_Y 377
#define BOOTH_DX 93
#define BOOTH_DY 53
#define PEOPLE_X 27
#define PEOPLE_Y 370
#define PEOPLE_DX 102
#define PEOPLE_DY 60
#define CAR1_X 131
#define CAR1_Y 385
#define CAR_DX 41
#define CAR_DY 49
#define CAR2_X 84
#define CAR2_Y 231
#define CAR3_X 95
#define CAR3_Y 149
#define CAR4_X 133
#define CAR4_Y 73
#define CAR5_X 465
#define CAR5_Y 75
#define CAR6_X 503
#define CAR6_Y 150
#define CAR7_X 514
#define CAR7_Y 230
#define CAR8_X 500
#define CAR8_Y 315
#define CAR9_X 218
#define CAR9_Y 23
#define CAR9_DX 37
#define CAR9_DY 39
#define CAR10_X 384
#define CAR10_Y 23
#define CAR10_DX 39
#define CAR10_DY 39
//
// Globals
//
STATIC const char *pszFugeArt[N_ROWS + 1] = {
".\\ART\\FUGE6.BMP",
".\\ART\\FUGE1.BMP",
".\\ART\\FUGE2.BMP",
".\\ART\\FUGE3.BMP",
".\\ART\\FUGE4.BMP",
".\\ART\\FUGE5.BMP",
".\\ART\\FUGE6.BMP"
};
STATIC CPalette *pGamePalette;
const char *INI_SECTION = "Fuge";
LPGAMESTRUCT pGameParams;
extern HWND ghParentWnd;
typedef struct {
VECTOR v1, v2;
} BRICK_VECTORS;
STATIC const BRICK_VECTORS aBrickVectors[BRICKS_PER_ROW] = {
{{-1, 1, 0}, { 5, -2, 0}},
{{-5, 2, 0}, { 1, 0, 0}},
{{-1, 0, 0}, { 5, 2, 0}},
{{-5, -2, 0}, { 1, 1, 0}},
{{-1, -1, 0}, { 2, 5, 0}},
{{-2, -5, 0}, { 0, 1, 0}},
{{ 0, -1, 0}, {-2, 5, 0}},
{{ 2, -5, 0}, {-1, 1, 0}},
{{ 1, -1, 0}, {-5, 2, 0}},
{{ 5, -2, 0}, {-1, 0, 0}},
{{ 1, 0, 0}, {-5, -2, 0}},
{{ 5, 2, 0}, {-1, -1, 0}},
{{ 1, 1, 0}, {-2, -5, 0}},
{{ 2, 5, 0}, { 0, -1, 0}},
{{ 0, 1, 0}, { 2, -5, 0}},
{{-2, 5, 0}, { 1, -1, 0}}
};
STATIC const VECTOR BRICK_CRIT_POINTS[N_ROWS][N_BRICK_POINTS] = {
{ { 0, -191, 0}, { 8, -191, 0}, { 15, -190, 0}, { 23, -190, 0}, { 31, -188, 0},
{ 39, -187, 0}, { 47, -185, 0}, { 55, -183, 0}, { 62, -181, 0}, { 69, -178, 0},
{ 67, -172, 0}, { 64, -165, 0}, { 56, -168, 0}, { 48, -171, 0}, { 40, -173, 0},
{ 32, -174, 0}, { 24, -175, 0}, { 16, -176, 0}, { 8, -177, 0}, { 0, -177, 0},
{ 0, -184, 0}
},
{ { 0, -175, 0}, { 7, -175, 0}, { 14, -175, 0}, { 21, -174, 0}, { 28, -173, 0},
{ 35, -172, 0}, { 42, -170, 0}, { 49, -168, 0}, { 56, -166, 0}, { 63, -164, 0},
{ 61, -158, 0}, { 59, -152, 0}, { 52, -154, 0}, { 44, -157, 0}, { 36, -159, 0},
{ 29, -160, 0}, { 21, -162, 0}, { 14, -162, 0}, { 7, -163, 0}, { 0, -163, 0},
{ 0, -169, 0}
},
{ { 0, -161, 0}, { 7, -161, 0}, { 13, -160, 0}, { 19, -160, 0}, { 26, -159, 0},
{ 33, -158, 0}, { 39, -156, 0}, { 45, -155, 0}, { 51, -153, 0}, { 58, -150, 0},
{ 56, -143, 0}, { 54, -137, 0}, { 48, -139, 0}, { 41, -141, 0}, { 35, -143, 0},
{ 28, -144, 0}, { 21, -146, 0}, { 14, -146, 0}, { 7, -147, 0}, { 0, -147, 0},
{ 0, -154, 0}
},
{ { 0, -145, 0}, { 7, -145, 0}, { 13, -145, 0}, { 19, -144, 0}, { 25, -143, 0},
{ 31, -142, 0}, { 36, -141, 0}, { 41, -139, 0}, { 47, -138, 0}, { 53, -135, 0},
{ 51, -129, 0}, { 48, -122, 0}, { 42, -124, 0}, { 37, -126, 0}, { 31, -127, 0},
{ 26, -129, 0}, { 19, -130, 0}, { 13, -130, 0}, { 7, -131, 0}, { 0, -131, 0},
{ 0, -138, 0}
},
{ { 0, -129, 0}, { 6, -129, 0}, { 11, -129, 0}, { 16, -128, 0}, { 21, -127, 0},
{ 26, -127, 0}, { 31, -125, 0}, { 36, -124, 0}, { 41, -123, 0}, { 47, -121, 0},
{ 45, -115, 0}, { 43, -109, 0}, { 37, -111, 0}, { 32, -112, 0}, { 27, -114, 0},
{ 22, -115, 0}, { 17, -116, 0}, { 11, -116, 0}, { 6, -117, 0}, { 0, -117, 0},
{ 0, -123, 0}
},
{ { 0, -115, 0}, { 6, -115, 0}, { 11, -115, 0}, { 16, -114, 0}, { 20, -113, 0},
{ 25, -112, 0}, { 29, -111, 0}, { 33, -110, 0}, { 37, -109, 0}, { 42, -107, 0},
{ 40, -102, 0}, { 38, -96, 0}, { 33, -98, 0}, { 29, -99, 0}, { 25, -100, 0},
{ 21, -101, 0}, { 16, -102, 0}, { 11, -102, 0}, { 6, -103, 0}, { 0, -103, 0},
{ 0, -109, 0}
}
};
STATIC VECTOR vBrickCritPoints[N_ROWS][N_BRICK_POINTS];
STATIC const POINT ptBrickPos[N_BRICKS] = {
{184, 62}, {250, 48}, {319, 48}, {385, 62}, // Purple Bricks
{445, 104}, {483, 165}, {483, 240}, {445, 309},
{385, 365}, {321, 404}, {250, 404}, {184, 365},
{144, 309}, {128, 240}, {128, 164}, {144, 105},
{195, 76}, {256, 64}, {319, 64}, {380, 76}, // Blue Bricks
{435, 115}, {469, 171}, {469, 240}, {435, 304},
{380, 355}, {321, 391}, {256, 391}, {195, 355},
{158, 304}, {143, 240}, {143, 171}, {158, 115},
{206, 90}, {261, 78}, {319, 78}, {375, 90}, // Green Bricks
{424, 126}, {455, 177}, {455, 240}, {424, 298},
{375, 344}, {321, 376}, {261, 376}, {206, 344},
{171, 298}, {158, 240}, {158, 177}, {171, 126},
{217, 104}, {266, 94}, {319, 94}, {369, 104}, // Yellow Bricks
{413, 137}, {441, 183}, {441, 240}, {413, 292},
{369, 333}, {321, 361}, {266, 361}, {217, 333},
{185, 292}, {173, 240}, {173, 183}, {185, 137},
{228, 119}, {272, 110}, {319, 110}, {364, 119}, // Orange Bricks
{402, 148}, {427, 189}, {427, 240}, {402, 286},
{364, 322}, {321, 348}, {272, 348}, {228, 322},
{200, 286}, {189, 240}, {189, 189}, {200, 148},
{238, 133}, {277, 124}, {319, 124}, {359, 133}, // Red Bricks
{392, 158}, {414, 195}, {414, 240}, {392, 280},
{359, 312}, {321, 335}, {277, 335}, {238, 312},
{214, 280}, {204, 240}, {204, 195}, {214, 158}
};
STATIC const SIZE ptBrickSize[N_BRICKS] = {
{70, 52}, {68, 27}, {70, 27}, {70, 52}, {50, 66}, {28, 74},
{28, 75}, {50, 66}, {70, 52}, {68, 27}, {70, 27}, {69, 52},
{50, 66}, {28, 74}, {28, 75}, {50, 65}, {64, 48}, {62, 24},
{64, 24}, {64, 48}, {46, 60}, {27, 68}, {27, 68}, {46, 60},
{64, 48}, {62, 24}, {64, 24}, {64, 48}, {46, 60}, {27, 68},
{27, 68}, {46, 60}, {58, 45}, {57, 25}, {59, 25}, {58, 45},
{44, 55}, {26, 62}, {26, 62}, {44, 55}, {58, 45}, {57, 25},
{59, 25}, {58, 45}, {44, 55}, {26, 62}, {26, 62}, {44, 55},
{53, 42}, {52, 24}, {54, 24}, {53, 42}, {41, 50}, {25, 56},
{25, 56}, {41, 50}, {53, 42}, {52, 24}, {54, 24}, {53, 42},
{41, 50}, {25, 56}, {25, 56}, {41, 50}, {47, 38}, {46, 21},
{48, 21}, {47, 38}, {37, 45}, {23, 50}, {23, 50}, {37, 45},
{47, 38}, {46, 21}, {48, 21}, {47, 38}, {37, 45}, {23, 50},
{23, 50}, {37, 45}, {42, 34}, {41, 20}, {43, 20}, {42, 34},
{33, 41}, {21, 44}, {21, 44}, {33, 41}, {42, 34}, {41, 20},
{43, 20}, {42, 34}, {33, 41}, {21, 44}, {21, 44}, {33, 41}
};
// Local Prototypes
//
void CALLBACK GetGameParams(CWnd *);
CFugeWindow::CFugeWindow() : gvCenter(CENTER_X, CENTER_Y) {
CString WndClass;
CRect tmpRect;
CDC *pDC;
CBitmap *pBmp;
ERROR_CODE errCode;
bool bSuccess;
// assume no error
errCode = ERR_NONE;
// Initialize fields
initMembers();
initStatics();
// Set the coordinates for the "Start New Game" button
//
m_rNewGameButton.SetRect(NEWGAME_LOCATION_X, NEWGAME_LOCATION_Y, NEWGAME_LOCATION_X + NEWGAME_WIDTH, NEWGAME_LOCATION_Y + NEWGAME_HEIGHT);
// Define a special window class which traps double-clicks, is byte aligned
// to maximize BITBLT performance, and creates "owned" DCs rather than sharing
// the five system defined DCs which are not guaranteed to be available;
// this adds a bit to our app size but avoids hangs/freezes/lockups.
WndClass = AfxRegisterWndClass(CS_DBLCLKS | CS_BYTEALIGNWINDOW | CS_OWNDC, nullptr, nullptr, nullptr);
// can't play this game if the background art is not available
//
if (FileExists(MINI_GAME_MAP)) {
// get this game's palette
//
if ((pDC = GetDC()) != nullptr) {
if ((pBmp = FetchBitmap(pDC, &m_pGamePalette, MINI_GAME_MAP)) != nullptr) {
delete pBmp;
pBmp = nullptr;
pGamePalette = m_pGamePalette;
}
ReleaseDC(pDC);
}
} else {
errCode = ERR_FFIND;
}
// Center our window on the screen
//
tmpRect.SetRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
#ifndef DEBUG
if ((pDC = GetDC()) != nullptr) {
tmpRect.left = (pDC->GetDeviceCaps(HORZRES) - GAME_WIDTH) >> 1;
tmpRect.top = (pDC->GetDeviceCaps(VERTRES) - GAME_HEIGHT) >> 1;
tmpRect.right = tmpRect.left + GAME_WIDTH;
tmpRect.bottom = tmpRect.top + GAME_HEIGHT;
ReleaseDC(pDC);
} else {
errCode = ERR_UNKNOWN;
}
#endif
// set mouse anchor point as center or game window
//
m_ptOrigin.x = GAME_WIDTH / 2 + tmpRect.left;
m_ptOrigin.y = GAME_HEIGHT / 2 + tmpRect.top;
// Create the window as a POPUP so no boarders, title, or menu are present;
// this is because the game's background art will fill the entire 640x480 area.
//
Create(WndClass, "Boffo Games -- Fuge", WS_POPUP, tmpRect, nullptr, 0);
BeginWaitCursor();
ShowWindow(SW_SHOWNORMAL);
PaintScreen();
EndWaitCursor();
ClipCursor(&tmpRect);
// only continue if there was no error
//
if (errCode == ERR_NONE) {
if ((m_pScrollButton = new CBmpButton) != nullptr) {
m_bIgnoreScrollClick = false;
tmpRect.SetRect(SCROLL_BUTTON_X, SCROLL_BUTTON_Y, SCROLL_BUTTON_X + SCROLL_BUTTON_DX, SCROLL_BUTTON_Y + SCROLL_BUTTON_DY);
bSuccess = m_pScrollButton->Create(nullptr, BS_OWNERDRAW | WS_CHILD | WS_VISIBLE, tmpRect, this, IDC_MENU);
assert(bSuccess);
if (bSuccess) {
bSuccess = m_pScrollButton->LoadBitmaps(SCROLLUP, SCROLLDOWN, SCROLLUP, SCROLLUP);
assert(bSuccess);
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_MEMORY;
}
// only continue if there was no error
//
if (errCode == ERR_NONE) {
BeginWaitCursor();
// Start the Fuge soundtrack
//
if (pGameParams->bMusicEnabled) {
if ((m_pSoundTrack = new CSound) != nullptr) {
m_pSoundTrack->initialize(this, MID_SOUNDTRACK, SOUND_MIDI | SOUND_LOOP | SOUND_DONT_LOOP_TO_END);
m_pSoundTrack->midiLoopPlaySegment(5390, 32280, 0, FMT_MILLISEC);
} else {
errCode = ERR_MEMORY;
}
}
// seed the random number generator
//srand((unsigned int)time(nullptr));
//
// The vector table is rotated by 11 or so degrees, because it
// was easier to type in (125, 320) as opposed to (114.452865, 300.526372).
// So I do the conversion here.
//
RealignVectors();
if (!errCode)
errCode = LoadMasterSprites();
if (!errCode)
errCode = LoadMasterSounds();
InitializeJoystick();
EndWaitCursor();
// as long as there was no error
//
if (!errCode) {
// if we are not playing from the metagame
//
if (!pGameParams->bPlayingMetagame) {
// Automatically bring up the main menu
PostMessage(WM_COMMAND, IDC_MENU, BN_CLICKED);
}
}
}
}
HandleError(errCode);
}
void CFugeWindow::initMembers() {
m_nInitNumBalls = BALLS_DEF;
m_nInitStartLevel = LEVEL_DEF;
m_nInitBallSpeed = SPEED_DEF;
m_nInitPaddleSize = PSIZE_DEF;
m_nGForceFactor = GFORCE_DEF;
m_pScrollButton = nullptr;
m_pGamePalette = nullptr;
m_pSoundTrack = nullptr;
m_bPause = false;
m_bGameActive = false;
m_bIgnoreScrollClick = false;
m_bBallOnPaddle = false;
m_nPaddleCelIndex = 29;
m_pPaddle = nullptr;
m_pBall = nullptr;
m_bMovingPaddle = false;
m_lScore = 0;
m_pBrickSound = nullptr;
m_pWallSound = nullptr;
m_pPaddleSound = nullptr;
m_pExtraLifeSound = nullptr;
m_hBrickRes = nullptr;
m_hWallRes = nullptr;
m_hPaddleRes = nullptr;
m_hExtraLifeRes = nullptr;
pGameParams->lScore = 0;
m_nNumRows = 0;
m_bJoyActive = false;
memset(m_bBrickVisible, 0, sizeof(bool) * N_BRICKS);
}
void CFugeWindow::initStatics() {
pGamePalette = nullptr;
// Set up the brick critical points from defaults
Common::copy(&BRICK_CRIT_POINTS[0][0], &BRICK_CRIT_POINTS[0][0] + N_ROWS * N_BRICK_POINTS,
&vBrickCritPoints[0][0]);
}
void CFugeWindow::InitializeJoystick() {
JOYINFO joyInfo;
if (joySetCapture(m_hWnd, JOYSTICKID1, 10000, true) == JOYERR_NOERROR) {
//
// Calibrate the joystick
//
joySetThreshold(JOYSTICKID1, 5000);
joyGetPos(JOYSTICKID1, &joyInfo);
m_nJoyOrgX = joyInfo.wXpos;
m_nJoyOrgY = joyInfo.wYpos;
m_bJoyActive = true;
} else {
//CMessageBox dlgNoJoystick((CWnd *)this, m_pGamePalette, "Warning! No Joystick", "Driver Installed");
}
}
void CFugeWindow::RealignVectors() {
CVector vTmp;
int i, j;
for (i = 0; i < N_ROWS; i++) {
for (j = 0; j < N_BRICK_POINTS; j++) {
vTmp = vBrickCritPoints[i][j];
vTmp.Rotate(2 * PI / BRICKS_PER_ROW * -2);
vBrickCritPoints[i][j] = vTmp;
}
}
}
ERROR_CODE CFugeWindow::LoadMasterSprites() {
CDC *pDC;
ERROR_CODE errCode;
errCode = LoadNewPaddle(m_nInitPaddleSize);
if (errCode == ERR_NONE) {
if ((pDC = GetDC()) != nullptr) {
if ((m_pBall = new CSprite) != nullptr) {
if (m_pBall->SharePalette(m_pGamePalette) != false) {
if (m_pBall->LoadResourceSprite(pDC, IDB_BALL) != false) {
m_pBall->SetMasked(true);
m_pBall->SetMobile(true);
// uncomment this if we decide to animate the ball as
// it moves
//if (m_pBall->LoadResourceCels(pDC, IDB_BALLSTRIP, N_BALLS) == false)
// errCode = ERR_UNKNOWN;
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_MEMORY;
}
ReleaseDC(pDC);
} else {
errCode = ERR_MEMORY;
}
}
return errCode;
}
void CFugeWindow::ReleaseMasterSprites() {
assert(m_pBall != nullptr);
if (m_pBall != nullptr) {
delete m_pBall;
m_pBall = nullptr;
}
assert(m_pPaddle != nullptr);
if (m_pPaddle != nullptr) {
delete m_pPaddle;
m_pPaddle = nullptr;
}
}
void CFugeWindow::HandleError(ERROR_CODE errCode) {
//
// Exit this application on fatal errors
//
if (errCode != ERR_NONE) {
// pause the current game (if any)
GamePause();
// Display Error Message to the user
MessageBox(errList[errCode], "Fatal Error!", MB_OK | MB_ICONSTOP);
// Force this application to terminate
PostMessage(WM_CLOSE, 0, 0);
// Don't allow a repaint (remove all WM_PAINT messages)
ValidateRect(nullptr);
}
}
void CFugeWindow::OnPaint() {
PAINTSTRUCT lpPaint;
Invalidate(false);
BeginPaint(&lpPaint);
PaintScreen();
EndPaint(&lpPaint);
}
void CFugeWindow::PaintScreen() {
CDC *pDC;
//
// Paint the background art and upadate any sprites called by OnPaint
//
if ((pDC = GetDC()) != nullptr) {
// painting the bricks will paint the screen
//
PaintBricks(pDC);
RepaintSpriteList(pDC);
ReleaseDC(pDC);
}
}
/*****************************************************************************
*
* RepaintSpriteList -
*
* DESCRIPTION: Longer description of this function. Continued onto next
* line like this.
*
* SAMPLE USAGE:
* errCode = RepaintSpriteList(pDC);
* CDC *pDC; pointer to current device context
*
* RETURNS: ERROR_CODE = error return code
*
*****************************************************************************/
void CFugeWindow::RepaintSpriteList(CDC *pDC) {
CSprite *pSprite;
// can't use a null pointer
assert(pDC != nullptr);
//
// Paint each sprite
//
pSprite = CSprite::GetSpriteChain();
while (pSprite) {
pSprite->ClearBackground();
pSprite = pSprite->GetNextSprite();
}
pSprite = CSprite::GetSpriteChain();
while (pSprite) {
pSprite->RefreshSprite(pDC);
pSprite = pSprite->GetNextSprite();
}
}
bool CFugeWindow::OnCommand(WPARAM wParam, LPARAM lParam) {
CMainMenu COptionsWind((CWnd *)this,
m_pGamePalette,
(pGameParams->bPlayingMetagame ? (NO_NEWGAME | NO_OPTIONS) : 0) | (m_bGameActive ? 0 : NO_RETURN),
GetGameParams, "fuge.txt", (pGameParams->bSoundEffectsEnabled ? WAV_NARRATION : nullptr), pGameParams);
if (HIWORD(lParam) == BN_CLICKED) {
switch (wParam) {
//
// must bring up our menu of controls
//
case IDC_MENU:
// hide the command scroll
//
m_pScrollButton->SendMessage(BM_SETSTATE, true, 0);
if (!m_bIgnoreScrollClick) {
m_bIgnoreScrollClick = true;
GamePause();
CSound::waitWaveSounds();
// Get users choice from command menu
//
switch (COptionsWind.DoModal()) {
// User has chosen to play a new game
//
case IDC_OPTIONS_NEWGAME:
PlayGame();
break;
// User has chosen to quit this mini-game
//
case IDC_OPTIONS_QUIT:
PostMessage(WM_CLOSE, 0, 0);
break;
default:
break;
}
// show the command scroll
//
m_pScrollButton->SendMessage(BM_SETSTATE, false, 0);
m_bIgnoreScrollClick = false;
if (!pGameParams->bMusicEnabled && (m_pSoundTrack != nullptr)) {
m_pSoundTrack->stop();
delete m_pSoundTrack;
m_pSoundTrack = nullptr;
} else if (pGameParams->bMusicEnabled && (m_pSoundTrack == nullptr)) {
if ((m_pSoundTrack = new CSound) != nullptr) {
m_pSoundTrack->initialize(this, MID_SOUNDTRACK, SOUND_MIDI | SOUND_LOOP | SOUND_DONT_LOOP_TO_END);
m_pSoundTrack->midiLoopPlaySegment(5390, 32280, 0, FMT_MILLISEC);
}
}
GameResume();
}
return true;
}
}
return false;
}
void CFugeWindow::GamePause() {
m_bPause = true;
}
void CFugeWindow::GameResume() {
if (!m_bBallOnPaddle && m_bMovingPaddle)
m_bPause = false;
}
void CFugeWindow::PlayGame() {
ERROR_CODE errCode;
// load the .INI settings
//
LoadIniSettings();
// reset all game parameters
//
GameReset();
errCode = LoadNewPaddle(m_nInitPaddleSize);
if (errCode == ERR_NONE) {
//
// Start game
//
// link
StartBricks();
StartBall();
StartPaddle();
// game starts paused
m_bPause = true;
m_bGameActive = true;
SetTimer(TIMER_ID, TIMER_INTERVAL, nullptr);
}
HandleError(errCode);
}
ERROR_CODE CFugeWindow::LoadNewPaddle(int nNewSize) {
CDC *pDC;
ERROR_CODE errCode;
assert(nNewSize >= PSIZE_MIN && nNewSize <= PSIZE_MAX);
// assume no error
errCode = ERR_NONE;
if ((pDC = GetDC()) != nullptr) {
// don't try to load the same paddle
//
if (m_nOldSize != nNewSize) {
if (m_pPaddle != nullptr) {
m_pPaddle->EraseSprite(pDC);
m_pPaddle->UnlinkSprite();
delete m_pPaddle;
m_pPaddle = nullptr;
}
if ((m_pPaddle = new CSprite) != nullptr) {
if (m_pPaddle->SharePalette(m_pGamePalette) != false) {
if (m_pPaddle->LoadCels(pDC, pszPaddles[nNewSize], N_PADDLE_CELS) != false) {
m_nOldSize = nNewSize;
m_pPaddle->SetMasked(true);
m_pPaddle->SetMobile(true);
m_pPaddle->SetAnimated(true);
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_MEMORY;
}
}
ReleaseDC(pDC);
} else {
errCode = ERR_MEMORY;
}
return errCode;
}
ERROR_CODE CFugeWindow::LoadMasterSounds() {
HANDLE hResInfo;
HINSTANCE hInst;
ERROR_CODE errCode;
// assume no error
errCode = ERR_NONE;
hInst = (HINSTANCE)GetWindowWord(m_hWnd, GWW_HINSTANCE);
// Load and lock the Brick "ping" into memory
//
if ((hResInfo = FindResource(hInst, "brickSound", "WAVE")) != nullptr) {
if ((m_hBrickRes = LoadResource(hInst, (HRSRC)hResInfo)) != nullptr) {
if ((m_pBrickSound = (char *)LockResource((HGLOBAL)m_hBrickRes)) != nullptr) {
// we have now loaded at least one of the master sounds
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
if (errCode == ERR_NONE) {
// Load and lock the wall "ping" into memory
//
if ((hResInfo = FindResource(hInst, "wallSound", "WAVE")) != nullptr) {
if ((m_hWallRes = LoadResource(hInst, (HRSRC)hResInfo)) != nullptr) {
if ((m_pWallSound = (char *)LockResource((HGLOBAL)m_hWallRes)) == nullptr)
errCode = ERR_UNKNOWN;
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
}
if (errCode == ERR_NONE) {
// Load and lock the paddle "ping" into memory
//
if ((hResInfo = FindResource(hInst, "paddleSound", "WAVE")) != nullptr) {
if ((m_hPaddleRes = LoadResource(hInst, (HRSRC)hResInfo)) != nullptr) {
if ((m_pPaddleSound = (char *)LockResource((HGLOBAL)m_hPaddleRes)) == nullptr)
errCode = ERR_UNKNOWN;
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
}
if (errCode == ERR_NONE) {
// Load and lock the extra life sound into memory
//
if ((hResInfo = FindResource(hInst, "NewLife", "WAVE")) != nullptr) {
if ((m_hExtraLifeRes = LoadResource(hInst, (HRSRC)hResInfo)) != nullptr) {
if ((m_pExtraLifeSound = (char *)LockResource((HGLOBAL)m_hExtraLifeRes)) == nullptr)
errCode = ERR_UNKNOWN;
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
}
return errCode;
}
void CFugeWindow::ReleaseMasterSounds() {
if (m_hExtraLifeRes != nullptr) {
FreeResource(m_hExtraLifeRes);
m_hExtraLifeRes = nullptr;
}
if (m_pPaddleSound != nullptr) {
FreeResource(m_hPaddleRes);
m_pPaddleSound = nullptr;
}
if (m_pWallSound != nullptr) {
FreeResource(m_hWallRes);
m_pWallSound = nullptr;
}
if (m_pBrickSound != nullptr) {
FreeResource(m_hBrickRes);
m_pBrickSound = nullptr;
}
}
void CFugeWindow::EndBall() {
CDC *pDC;
assert(m_pBall != nullptr);
if ((pDC = GetDC()) != nullptr) {
if (m_pBall->IsLinked()) {
m_pBall->EraseSprite(pDC);
m_pBall->UnlinkSprite();
}
ReleaseDC(pDC);
}
}
void CFugeWindow::StartBall() {
CDC *pDC;
assert(m_pBall != nullptr);
// have the ball start on the paddle like in arkinoids
//
m_ptBallLocation = BallOnPaddle();
m_pBall->LinkSprite();
if ((pDC = GetDC()) != nullptr) {
m_pBall->PaintSprite(pDC, (int)m_ptBallLocation.x, (int)m_ptBallLocation.y);
ReleaseDC(pDC);
}
}
CVector CFugeWindow::BallOnPaddle() {
CVector vBall(0, -(PADDLE_RADIUS + BALL_RADIUS));
vBall.Rotate(fPaddleAngles[m_nInitPaddleSize] / 2);
vBall.Rotate(m_nPaddleCelIndex * ((2 * PI) / N_PADDLE_CELS));
vBall -= CVector(BALL_RADIUS, BALL_RADIUS);
// this vector was relative to center so now make it a real point
vBall += gvCenter;
return vBall;
}
void CFugeWindow::PaintBall() {
CPoint ptLast;
CVector vBall, vGravity;
double length;
CDC *pDC;
if (m_bGameActive && !m_bPause && !m_bBallOnPaddle) {
assert(m_pBall != nullptr);
ptLast.x = (int)m_ptBallLocation.x;
ptLast.y = (int)m_ptBallLocation.y;
vGravity = gvCenter - (m_ptBallLocation + BALL_RADIUS);
vGravity.Unitize();
// calc new ball location
//
if (m_nGForceFactor != 0) {
vGravity *= G_FORCE * m_nGForceFactor;
m_vBallVector += vGravity;
m_ptBallLocation += m_vBallVector * (m_fTurboBoost + m_nBallSpeed);
} else {
vGravity *= G_FORCE * 10;
m_ptBallLocation += (m_vBallVector + vGravity) * (m_fTurboBoost + m_nBallSpeed);
}
assert(m_fTurboBoost <= TURBO_MAX);
m_fTurboBoost -= 0.05;
if (m_fTurboBoost < 0.0)
m_fTurboBoost = 0.0;
m_vBallVector.Unitize();
// get radius of the ball from the center of the screen
//
vBall = m_ptBallLocation + BALL_RADIUS - gvCenter;
length = vBall.Length() + BALL_RADIUS;
// check to see if ball has entered the balck hole
//
if (length <= BLACKHOLE_RADIUS + BALL_RADIUS * 2) {
// Play the ball-gets-sucked-into-black-hole animation
//
LoseBall();
m_bPaddleHit = false;
// or has ball hit the paddle?
//
} else if (length <= PADDLE_RADIUS + BALL_RADIUS * 2) {
BallvsPaddle();
// or has ball hit a brick?
//
} else if ((length >= INNER_BRICK_RADIUS) && (length < WHEEL_RADIUS + (m_bOutterWall ? BALL_RADIUS * 2 : 0))) {
//
// determine which row ball is in
//
BallvsBrick(length);
//
// determine if a ball actually hit a brick
//
m_bPaddleHit = false;
// or did ball hit edge of ferris wheel
//
} else if (length >= WHEEL_RADIUS) {
if (m_bOutterWall) {
// has ball hit right border
//
if (m_ptBallLocation.x >= GAME_WIDTH - GAME_RIGHT_BORDER_WIDTH - BALL_SIZE_X) {
m_ptBallLocation.x = GAME_WIDTH - GAME_RIGHT_BORDER_WIDTH - BALL_SIZE_X;
m_vBallVector.x = -m_vBallVector.x;
// randomly rotate 1 or -1 degrees
m_vBallVector.Rotate(Deg2Rad((brand() & 1) ? 0.125 : 0));
m_fTurboBoost = (double)12 - m_nBallSpeed;
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pWallSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
// has ball hit left border
//
} else if (m_ptBallLocation.x <= 0 + GAME_LEFT_BORDER_WIDTH) {
m_ptBallLocation.x = 0 + GAME_LEFT_BORDER_WIDTH;
m_vBallVector.x = -m_vBallVector.x;
// randomly rotate 1 or -1 degrees
m_vBallVector.Rotate(Deg2Rad((brand() & 1) ? 0.125 : 0));
m_fTurboBoost = (double)12 - m_nBallSpeed;
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pWallSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
// has ball hit bottom of screen
//
} else if (m_ptBallLocation.y >= GAME_HEIGHT - GAME_BOTTOM_BORDER_WIDTH - BALL_SIZE_Y) {
m_ptBallLocation.y = GAME_HEIGHT - GAME_BOTTOM_BORDER_WIDTH - BALL_SIZE_Y;
m_vBallVector.y = -m_vBallVector.y;
// randomly rotate 1 or -1 degrees
m_vBallVector.Rotate(Deg2Rad((brand() & 1) ? 0.125 : 0));
m_fTurboBoost = (double)12 - m_nBallSpeed;
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pWallSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
// has ball hit top of screen
//
} else if (m_ptBallLocation.y <= 0 + GAME_TOP_BORDER_WIDTH) {
m_ptBallLocation.y = 0 + GAME_TOP_BORDER_WIDTH;
m_vBallVector.y = -m_vBallVector.y;
// randomly rotate 1 or -1 degrees
m_vBallVector.Rotate(Deg2Rad((brand() & 1) ? 0.125 : 0));
m_fTurboBoost = (double)12 - m_nBallSpeed;
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pWallSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
}
} else {
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pWallSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
// pull ball back to just on the border
//
m_ptBallLocation -= m_vBallVector * (length - WHEEL_RADIUS + 1);
m_vBallVector.x = -m_vBallVector.x;
m_vBallVector.y = -m_vBallVector.y;
m_vBallVector.Reflect(vBall);
}
m_bPaddleHit = false;
}
// only paint the ball if it actually moved
//
if ((ptLast.x != (int)m_ptBallLocation.x) || (ptLast.y != (int)m_ptBallLocation.y)) {
if (m_pBall->IsLinked()) {
// paint the ball to it's new location
//
if ((pDC = GetDC()) != nullptr) {
m_pBall->PaintSprite(pDC, (int)m_ptBallLocation.x, (int)m_ptBallLocation.y);
ReleaseDC(pDC);
}
}
}
}
}
#define N_CRIT_POINTS 7
void CFugeWindow::BallvsPaddle() {
CVector vPoints[N_CRIT_POINTS];
CVector vTmp, vPaddle, vFace, vBallCenter, vBallEdge;
double a1, a2;
double fLen1, fLen2, fLen3, fLen4, fLen5, fLen6, fMin, length;
int i, j, k;
int nRollBack = 0;
bool bHit;
// calculate the 7 critical points for the paddle
//
vTmp.SetVector(0, -PADDLE_RADIUS);
// cel index determines paddle angle
//
vTmp.Rotate((2 * PI / N_PADDLE_CELS) * m_nPaddleCelIndex);
vPoints[1] = gvCenter + vTmp;
vPoints[0] = vTmp;
vPoints[0].Rotate(fPaddleAngles[m_nInitPaddleSize] / 2);
vPoints[0] += gvCenter;
vPoints[2] = vTmp;
vPoints[2].Rotate(fPaddleAngles[m_nInitPaddleSize]);
vPoints[2] += gvCenter;
vTmp.Unitize();
vTmp *= 25;
vPoints[3] = gvCenter + vTmp;
vPoints[4] = vTmp;
vPoints[4].Rotate(fPaddleAngles[m_nInitPaddleSize]);
vPoints[4] += gvCenter;
vPoints[5] = vPoints[1];
vPoints[6] = vPoints[2];
if (m_nInitPaddleSize > PSIZE_MIN) {
vTmp = vPoints[0] - vPoints[1];
vTmp.Unitize();
vTmp *= 9; // paddle width
vPoints[5] = vPoints[1] + vTmp;
vTmp = vPoints[0] - vPoints[2];
vTmp.Unitize();
vTmp *= 9; // paddle width
vPoints[6] = vPoints[2] - vTmp;
}
// get center of the ball
vBallCenter = m_ptBallLocation + BALL_RADIUS;
// if any of those points are less than the radius distance
// away from the center of the ball, then the ball has hit
// the paddle
//
bHit = false;
for (i = 0; i < N_CRIT_POINTS - 1; i++) {
switch (i) {
case 0:
j = 0;
k = 5;
break;
case 1:
j = 0;
k = 6;
break;
case 2:
j = 1;
k = 5;
break;
case 3:
j = 2;
k = 6;
break;
case 4:
j = 1;
k = 3;
break;
default:
j = 2;
k = 4;
break;
}
length = distanceBetweenPoints(vPoints[j], vPoints[k]) / 2;
length = sqrt(BALL_RADIUS * BALL_RADIUS + length * length) * 2;
fLen1 = distanceBetweenPoints(vPoints[j], vBallCenter);
fLen2 = distanceBetweenPoints(vPoints[k], vBallCenter);
nRollBack = 0;
if (fLen1 <= BALL_RADIUS) {
nRollBack = BALL_RADIUS - (int)fLen1;
bHit = true;
break;
} else if (fLen2 <= BALL_RADIUS) {
nRollBack = (BALL_RADIUS - (int)fLen2);
bHit = true;
break;
} else if (fLen1 + fLen2 <= length) {
nRollBack = (int)(length - (fLen1 + fLen2)) + 2;
bHit = true;
break;
}
}
if (bHit) {
nRollBack = MIN(m_nInitBallSpeed, nRollBack);
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pPaddleSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
// if we hit the ball twice in a row
//
if (m_bPaddleHit) {
TRACE("DoubleHit\n");
// then shoot the ball away from the paddle
//
m_vBallVector = vBallCenter - gvCenter;
m_vBallVector.Unitize();
m_vBallVector *= 2;
} else {
// role the ball back to the exact point that it hit the paddle
//
vTmp = m_vBallVector;
vTmp.Unitize();
vTmp *= nRollBack;
m_ptBallLocation -= vTmp;
// get center of ball
vBallCenter = m_ptBallLocation + BALL_RADIUS;
fLen1 = distanceBetweenPoints(vBallCenter, vPoints[0]);
fLen1 += distanceBetweenPoints(vBallCenter, vPoints[1]);
fMin = fLen1;
fLen2 = distanceBetweenPoints(vBallCenter, vPoints[0]);
fLen2 += distanceBetweenPoints(vBallCenter, vPoints[2]);
fMin = MIN(fMin, fLen2);
fLen3 = distanceBetweenPoints(vBallCenter, vPoints[3]);
fLen3 += distanceBetweenPoints(vBallCenter, vPoints[1]);
fMin = MIN(fMin, fLen3);
fLen4 = distanceBetweenPoints(vBallCenter, vPoints[4]);
fLen4 += distanceBetweenPoints(vBallCenter, vPoints[2]);
fMin = MIN(fMin, fLen4);
fLen5 = fLen1;
fLen6 = fLen2;
if (m_nInitPaddleSize > PSIZE_MIN) {
fLen5 = distanceBetweenPoints(vBallCenter, vPoints[1]);
fLen5 += distanceBetweenPoints(vBallCenter, vPoints[5]);
fMin = MIN(fMin, fLen5);
fLen6 = distanceBetweenPoints(vBallCenter, vPoints[2]);
fLen6 += distanceBetweenPoints(vBallCenter, vPoints[6]);
fMin = MIN(fMin, fLen6);
}
vTmp = m_vBallVector;
m_vBallVector.x = -m_vBallVector.x;
m_vBallVector.y = -m_vBallVector.y;
vFace = vPoints[0] - gvCenter;
if ((fMin == fLen1) || (fMin == fLen5) || (fMin == fLen2) || (fMin == fLen6)) {
if (fMin == fLen5) {
vFace = vBallCenter - gvCenter;
vFace.Rotate(-Deg2Rad(10));
} else if (fMin == fLen6) {
vFace = vBallCenter - gvCenter;
vFace.Rotate(Deg2Rad(10));
}
// get center of ball
vBallCenter = m_ptBallLocation + BALL_RADIUS;
// roll ball back to edge of paddle
//
while (distanceBetweenPoints(vBallCenter, gvCenter) < PADDLE_RADIUS + BALL_RADIUS) {
m_ptBallLocation -= vTmp;
// get center of ball
vBallCenter = m_ptBallLocation + BALL_RADIUS;
}
vPaddle = vFace;
} else if (fMin == fLen3) {
vPaddle.SetVector(vPoints[3].x - CENTER_X, vPoints[3].y - CENTER_Y);
vPaddle.Rotate(-PI / 2);
a1 = vPaddle.AngleBetween(m_vBallVector);
a2 = vFace.AngleBetween(m_vBallVector);
// kludge to compensate for when angle is too big
//
if (a1 > a2) {
vPaddle = vFace;
}
} else if (fMin == fLen4) {
vPaddle.SetVector(vPoints[4].x - CENTER_X, vPoints[4].y - CENTER_Y);
vPaddle.Rotate(PI / 2);
a1 = vPaddle.AngleBetween(m_vBallVector);
a2 = vFace.AngleBetween(m_vBallVector);
// kludge to compensate for when angle is too big
//
if (a1 > a2) {
vPaddle = vFace;
}
}
// reflect the ball vector around the final paddle (mirror) vector
//
m_vBallVector.Reflect(vPaddle);
//
// one final check to make sure the ball is bouncing in the
// correct direction.
//
if (m_vBallVector.AngleBetween(gvCenter - vBallCenter) <= Deg2Rad(15)) {
//TRACE("RealAjusting the Vector\n");
m_vBallVector.Rotate(Deg2Rad(180));
}
}
m_bPaddleHit = true;
}
}
#define MAX_BRICK_HITS 6
void CFugeWindow::BallvsBrick(double length) {
char buf1[32], buf2[32];
CVector vPoints[N_BRICK_POINTS];
CVector vBrick, vBallCenter, vOrigin, vTmp;
CDC *pDC;
CRect rTmpRect, rBall, cRect;
CPoint ptTmp;
double fMin, fLast, fLen[N_BRICK_POINTS];
double angle;
int i, j, nIndex, nLastIndex, nBrickIndex, nMaxHits;
int nBrick0, nBrick1, nRow0, nRow1, nRow2, nUse[MAX_BRICK_HITS];
bool bHit, bStillHit;
// get bounding rectangle of the ball
//
rBall.SetRect((int)m_ptBallLocation.x, (int)m_ptBallLocation.y, (int)m_ptBallLocation.x + BALL_SIZE_X, (int)m_ptBallLocation.y + BALL_SIZE_Y);
// get center of the ball
vBallCenter.SetVector(m_ptBallLocation.x + BALL_RADIUS, m_ptBallLocation.y + BALL_RADIUS);
vOrigin.SetVector(-1, -1);
vOrigin.Rotate(PI / BRICKS_PER_ROW);
vTmp = vBallCenter - gvCenter;
angle = vTmp.RealAngle(vOrigin);
//TRACE("RealAngle: %f\n", angle);
nMaxHits = MAX_BRICK_HITS;
if (length <= ROW6_RADIUS) {
nRow0 = 5;
nRow1 = 5;
nRow2 = 5;
nMaxHits = 2;
} else if (length <= ROW5_RADIUS) {
nRow0 = 5;
nRow1 = 4;
nRow2 = 4;
nMaxHits = 4;
} else if (length <= ROW4_RADIUS - BALL_RADIUS) {
nRow0 = 5;
nRow1 = 4;
nRow2 = 3;
} else if (length <= ROW3_RADIUS - BALL_RADIUS) {
nRow0 = 4;
nRow1 = 3;
nRow2 = 2;
} else if (length <= ROW2_RADIUS - BALL_RADIUS) {
nRow0 = 3;
nRow1 = 2;
nRow2 = 1;
} else {
nRow0 = 2;
nRow1 = 1;
nRow2 = 0;
}
if (angle >= BRICK15_ANGLE) {
nBrick0 = 0;
nBrick1 = 15;
} else if (angle >= BRICK14_ANGLE) {
nBrick0 = 15;
nBrick1 = 14;
} else if (angle >= BRICK13_ANGLE) {
nBrick0 = 14;
nBrick1 = 13;
} else if (angle >= BRICK12_ANGLE) {
nBrick0 = 13;
nBrick1 = 12;
} else if (angle >= BRICK11_ANGLE) {
nBrick0 = 12;
nBrick1 = 11;
} else if (angle >= BRICK10_ANGLE) {
nBrick0 = 11;
nBrick1 = 10;
} else if (angle >= BRICK9_ANGLE) {
nBrick0 = 10;
nBrick1 = 9;
} else if (angle >= BRICK8_ANGLE) {
nBrick0 = 9;
nBrick1 = 8;
} else if (angle >= BRICK7_ANGLE) {
nBrick0 = 8;
nBrick1 = 7;
} else if (angle >= BRICK6_ANGLE) {
nBrick0 = 7;
nBrick1 = 6;
} else if (angle >= BRICK5_ANGLE) {
nBrick0 = 6;
nBrick1 = 5;
} else if (angle >= BRICK4_ANGLE) {
nBrick0 = 5;
nBrick1 = 4;
} else if (angle >= BRICK3_ANGLE) {
nBrick0 = 4;
nBrick1 = 3;
} else if (angle >= BRICK2_ANGLE) {
nBrick0 = 3;
nBrick1 = 2;
} else if (angle >= BRICK1_ANGLE) {
nBrick0 = 2;
nBrick1 = 1;
} else {
nBrick0 = 1;
nBrick1 = 0;
}
nUse[0] = (nRow0 * BRICKS_PER_ROW) + nBrick0;
nUse[1] = (nRow0 * BRICKS_PER_ROW) + nBrick1;
nUse[2] = (nRow1 * BRICKS_PER_ROW) + nBrick0;
nUse[3] = (nRow1 * BRICKS_PER_ROW) + nBrick1;
nUse[4] = (nRow2 * BRICKS_PER_ROW) + nBrick0;
nUse[5] = (nRow2 * BRICKS_PER_ROW) + nBrick1;
// which brick did we hit?
//
bHit = false;
for (i = 0; i < nMaxHits; i++) {
nBrickIndex = nUse[i];
assert(nBrickIndex >= 0 && nBrickIndex < N_BRICKS);
if (m_bBrickVisible[nBrickIndex]) {
//TRACE("Checking %d\n", nBrickIndex);
// if ball's rectange intersects this brick's rectangle
//
cRect.SetRect(ptBrickPos[nBrickIndex].x, ptBrickPos[nBrickIndex].y, ptBrickPos[nBrickIndex].x + ptBrickSize[nBrickIndex].cx, ptBrickPos[nBrickIndex].y + ptBrickSize[nBrickIndex].cy);
if (rTmpRect.IntersectRect(rBall, cRect)) {
// calculate the 21 points for this brick
//
for (j = 0; j < N_BRICK_POINTS; j++) {
vPoints[j] = vBrickCritPoints[nBrickIndex / BRICKS_PER_ROW][j];
vPoints[j].Rotate(((2 * PI) / BRICKS_PER_ROW) * (nBrickIndex % BRICKS_PER_ROW));
vPoints[j] += gvCenter;
if (distanceBetweenPoints(vBallCenter, vPoints[j]) < 11.0) {
bHit = true;
}
}
if (bHit) {
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pBrickSound, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
if ((pDC = GetDC()) != nullptr) {
m_pBall->EraseSprite(pDC);
EraseBrick(pDC, nBrickIndex);
m_pBall->PaintSprite(pDC, m_pBall->GetPosition());
ReleaseDC(pDC);
}
// on less brick
--m_nBricks;
// Score: 1 point for each brick regardless of color
m_lScore += 1;
// did user earn an extra ball?
//
if (m_lScore >= m_lExtraLifeScore) {
if (pGameParams->bSoundEffectsEnabled) {
sndPlaySound(m_pExtraLifeSound, SND_MEMORY | SND_SYNC | SND_NODEFAULT);
}
// double the ammount the user needs for their next extra life
m_lExtraLifeScore += m_lExtraLifeScore;
// extra ball
m_nBalls++;
}
// if no bricks left
//
if (m_nBricks == 0) {
GamePause();
// reset turbo
m_fTurboBoost = 0.0;
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_WINWAVE,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_WINWAVE, SND_ASYNC);
#endif
}
// 5 point bonus for clearing all bricks
m_lScore += 5;
//
// User wins this round
//
Common::sprintf_s(buf1, "Round complete.");
Common::sprintf_s(buf2, "Score: %ld", m_lScore);
CMessageBox dlgYouWin((CWnd *)this, m_pGamePalette, buf1, buf2);
// stop all sounds
//
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
#else
sndPlaySound(nullptr, SND_ASYNC);
#endif
}
m_nNumRows++;
if (m_nNumRows > N_ROWS) {
m_nNumRows = 1;
m_nBallSpeed++;
//if (m_nBallSpeed > SPEED_MAX)
// m_nBallSpeed = SPEED_MAX;
}
// get new brick count
m_nBricks = m_nNumRows * BRICKS_PER_ROW;
// if user is playing the metagame
//
if (pGameParams->bPlayingMetagame) {
// return to the metagame
pGameParams->lScore = m_lScore;
PostMessage(WM_CLOSE, 0, 0);
} else {
// reset the ball position
//
EndPaddle();
EndBall();
StartBricks();
StartBall();
StartPaddle();
GameResume();
}
//
// there are more bricks left,
// so just calc a new vector for the ball
//
} else {
bStillHit = true;
while (bStillHit) {
// roll ball back to point of contact with brick
m_ptBallLocation -= m_vBallVector;
// get new center of ball
vBallCenter = m_ptBallLocation + BALL_RADIUS;
bStillHit = false;
for (j = 0; j < N_BRICK_POINTS; j++) {
if ((fLen[j] = distanceBetweenPoints(vBallCenter, vPoints[j])) < 11.0) {
bStillHit = true;
break;
}
}
}
// find the 2 closest points to the center of the ball
//
nIndex = nLastIndex = -1;
fMin = fLast = 9999;
for (j = 0; j < N_BRICK_POINTS; j++) {
if (fLen[j] < fMin) {
fLast = fMin;
fMin = fLen[j];
nLastIndex = nIndex;
nIndex = j;
} else if (fLen[j] < fLast) {
fLast = fLen[j];
nLastIndex = j;
}
}
// make sure we actually found an intersect point
assert((nIndex != -1) && (nLastIndex != -1));
// if ball hit a corner, then use next best point to
// determine which side the ball actually hit
//
switch (nIndex) {
case 0:
case 9:
case 11:
case 19:
nIndex = nLastIndex;
break;
default:
break;
}
// if hit back face of brick
//
if (nIndex >= 1 && nIndex <= 8) {
vBrick = vPoints[nIndex] - gvCenter;
// if hit inner face of brick
//
} else if (nIndex >= 12 && nIndex <= 18) {
vBrick = gvCenter - vPoints[nIndex];
// hit clockwise side of brick
//
} else if (nIndex == 10) {
vBrick = aBrickVectors[nBrickIndex % BRICKS_PER_ROW].v2;
// hit counter-clockwise side of brick
//
} else if (nIndex == 20) {
vBrick = aBrickVectors[nBrickIndex % BRICKS_PER_ROW].v1;
// invalid index
//
} else {
warning("Invalid Index (%d)", nIndex);
assert(0);
}
// determine the vector of the brick (using ptHit as the intersect point)
//
m_vBallVector.x = -m_vBallVector.x;
m_vBallVector.y = -m_vBallVector.y;
m_vBallVector.Reflect(vBrick);
}
break;
}
}
}
}
}
void CFugeWindow::EraseBrick(CDC *pDC, int nBrickIndex) {
assert(pDC != nullptr);
assert((nBrickIndex >= 0) && (nBrickIndex < N_BRICKS));
// remove this brick from the screen
//
pDC->FloodFill(ptBrickPos[nBrickIndex].x + ptBrickSize[nBrickIndex].cx / 2, ptBrickPos[nBrickIndex].y + ptBrickSize[nBrickIndex].cy / 2, RGB(255, 255, 255));
m_bBrickVisible[nBrickIndex] = false;
}
void CFugeWindow::LoseBall() {
char buf1[32], buf2[32];
CDC *pDC;
ERROR_CODE errCode;
// assume no error
errCode = ERR_NONE;
// pause the game
GamePause();
// reset turbo
m_fTurboBoost = 0.0;
if ((pDC = GetDC()) != nullptr) {
m_pBall->EraseSprite(pDC);
m_pBall->UnlinkSprite();
assert(m_nBalls > 0);
// on less ball
m_nBalls--;
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_LOSEBALL,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_LOSEBALL, SND_ASYNC);
#endif
}
/* BLACK HOLE ANIMATION REMOVED DUE TO LACK OF ARTWORK
CSprite *pSprite;
int i;
// start the black hole animation
//
if ((pSprite = new CSprite) != nullptr) {
if (pSprite->SharePalette(m_pGamePalette) != false) {
if (pSprite->LoadCels(pDC, ".\\ART\\LOSEBALL.BMP", N_BLACKHOLE_CELS) != false) {
pSprite->SetMasked(true);
pSprite->SetMobile(true);
pSprite->LinkSprite();
for (i = 0; i < N_BLACKHOLE_CELS * 2; i++) {
pSprite->PaintSprite(pDC, 296, 216);
Sleep(300);
}
pSprite->EraseSprite(pDC);
pSprite->UnlinkSprite();
delete pSprite;
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_UNKNOWN;
}
} else {
errCode = ERR_MEMORY;
}
*/
// if no more balls left - user has lost
//
if (m_nBalls == 0) {
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_GAMEOVER,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_GAMEOVER, SND_ASYNC);
#endif
}
Common::sprintf_s(buf1, "Score: %ld", m_lScore);
CMessageBox dlgLoseBall((CWnd *)this, m_pGamePalette, "Game over.", buf1);
if (pGameParams->bPlayingMetagame) {
// return the final score
//
pGameParams->lScore = m_lScore;
PostMessage(WM_CLOSE, 0, 0);
}
GameReset();
} else {
// display score, and start a new ball
//
Common::sprintf_s(buf1, "Score: %ld", m_lScore);
Common::sprintf_s(buf2, "Balls Left: %d", m_nBalls);
CMessageBox dlgLoseBall((CWnd *)this, m_pGamePalette, buf1, buf2);
//
// reset the ball position
//
EndPaddle();
StartPaddle();
StartBall();
}
ReleaseDC(pDC);
} else {
errCode = ERR_MEMORY;
}
HandleError(errCode);
}
void CFugeWindow::StartPaddle() {
assert(m_pPaddle != nullptr);
if (m_pPaddle != nullptr)
m_pPaddle->LinkSprite();
m_bBallOnPaddle = true;
PaintPaddle(true);
}
void CFugeWindow::EndPaddle() {
CDC *pDC;
assert(m_pPaddle != nullptr);
if ((m_pPaddle != nullptr) && (m_pPaddle->IsLinked())) {
if ((pDC = GetDC()) != nullptr) {
m_pPaddle->EraseSprite(pDC);
m_pPaddle->UnlinkSprite();
ReleaseDC(pDC);
}
}
}
void CFugeWindow::LaunchBall() {
assert(m_bGameActive);
assert(m_bBallOnPaddle);
m_bPause = false;
m_bBallOnPaddle = false;
// starting ball vector is determined by the location of the paddle
m_vBallVector = gvCenter - (m_ptBallLocation + BALL_RADIUS);
// add a slight randomness to the balls vector
//
m_vBallVector.Rotate(Deg2Rad((brand() % 2) * ((brand() & 1) ? -1 : 1)));
m_vBallVector.Unitize();
PaintBall();
}
void CFugeWindow::PaintPaddle(bool bPaint) {
CVector vPaddle;
CDC *pDC;
int nOldIndex;
bool bSuccess;
// verify that the input was not tainted
assert(m_nPaddleCelIndex < N_PADDLE_CELS * 2);
assert(m_nPaddleCelIndex > -N_PADDLE_CELS);
// can't access a null pointer
assert(m_pPaddle != nullptr);
// get old cel index
nOldIndex = m_pPaddle->GetCelIndex();
if (m_nPaddleCelIndex >= N_PADDLE_CELS) {
m_nPaddleCelIndex = m_nPaddleCelIndex - N_PADDLE_CELS;
} else if (m_nPaddleCelIndex < 0) {
m_nPaddleCelIndex = m_nPaddleCelIndex + N_PADDLE_CELS;
}
// verify our calculations
assert((m_nPaddleCelIndex >= 0) && (m_nPaddleCelIndex < N_PADDLE_CELS));
// don't re-paint the paddle if we would paint the same cel
//
if (bPaint || (nOldIndex != m_nPaddleCelIndex)) {
m_pPaddle->SetCel(m_nPaddleCelIndex - 1);
// paint paddle to new location
//
if ((pDC = GetDC()) != nullptr) {
bSuccess = m_pPaddle->PaintSprite(pDC, PADDLE_START_X, PADDLE_START_Y);
assert(bSuccess);
// if the ball is resting on the paddle
//
if (m_bBallOnPaddle) {
assert(m_pBall != nullptr);
// ball is rotating with paddle
//
m_ptBallLocation = BallOnPaddle();
// paint the ball to it's new location
//
m_pBall->PaintSprite(pDC, (int)m_ptBallLocation.x, (int)m_ptBallLocation.y);
} else {
#if 0
// if the paddle's rectangle hit the ball's rectangle
//
if (m_pPaddle->InterceptOccurred()) {
// did the paddle actually hit the ball?
//
if (m_pPaddle->TestInterception(pDC, m_pBall)) {
double angle;
TRACE("Paddle Hit Ball\n");
//
// handle collision with ball
//
vPaddle.SetVector(-13, 31);
// cel index determines paddle angle
//
angle = (2 * PI / N_PADDLE_CELS) * m_nPaddleCelIndex;
// if paddle is moving clockwise then rotate -90 instead of 90 degrees
//
angle += ((nOldIndex < m_nPaddleCelIndex) ? (PI / 2) : (-PI / 2));
vPaddle.Rotate(angle);
vPaddle.Unitize();
m_vBallVector.Unitize();
// handle spin
//
m_vBallVector += vPaddle * 2;
}
}
#endif
}
ReleaseDC(pDC);
}
}
}
void CFugeWindow::StartBricks() {
CDC *pDC;
int i, nBricks;
nBricks = m_nNumRows * BRICKS_PER_ROW;
for (i = 0; i < nBricks; i++) {
if (!m_bBrickVisible[i]) {
m_bBrickVisible[i] = true;
}
}
if ((pDC = GetDC()) != nullptr) {
PaintBricks(pDC);
ReleaseDC(pDC);
}
}
void CFugeWindow::PaintBricks(CDC *pDC) {
CSize size;
CDC *pMemDC;
CBitmap *pBmp, *pMemBmp;
CPalette *pPalOld, *pScreenPalOld;
HBITMAP hOldBitmap;
int i;
assert(pDC != nullptr);
assert((m_nNumRows >= 0) && (m_nNumRows <= N_ROWS));
if (FileExists(pszFugeArt[m_nNumRows])) {
pBmp = FetchBitmap(pDC, nullptr, pszFugeArt[m_nNumRows]);
pMemDC = nullptr;
pMemBmp = nullptr;
if ((pMemDC = new CDC) != nullptr) {
if ((pMemBmp = new CBitmap) != nullptr) {
size = GetBitmapSize(pBmp);
if ((m_nNumRows != 0) && (pMemBmp->CreateCompatibleBitmap(pDC, size.cx, size.cy) != false)) {
pMemDC->CreateCompatibleDC(nullptr);
pPalOld = pMemDC->SelectPalette(m_pGamePalette, false);
pMemDC->RealizePalette();
hOldBitmap = SelectBitmap(pMemDC->m_hDC, pMemBmp->m_hObject);
PaintBitmap(pMemDC, m_pGamePalette, pBmp);
// repaint all the visible bricks
//
for (i = 0; i < N_BRICKS; i++) {
if (!m_bBrickVisible[i]) {
EraseBrick(pMemDC, i);
}
}
pScreenPalOld = pDC->SelectPalette(m_pGamePalette, false);
pDC->BitBlt(0, 0, size.cx, size.cy, pMemDC, 0, 0, SRCCOPY);
pDC->SelectPalette(pScreenPalOld, false);
SelectBitmap(pMemDC->m_hDC, hOldBitmap);
pMemDC->SelectPalette(pPalOld, false);
// fall back to yucky method
//
} else {
PaintBitmap(pDC, m_pGamePalette, pBmp);
if (m_nNumRows != 0) {
// repaint all the visible bricks
//
for (i = 0; i < N_BRICKS; i++) {
if (!m_bBrickVisible[i]) {
EraseBrick(pDC, i);
}
}
}
}
}
}
if (pMemBmp != nullptr) {
delete pMemBmp;
pMemBmp = nullptr;
}
if (pMemDC != nullptr) {
delete pMemDC;
pMemDC = nullptr;
}
if (pBmp != nullptr) {
delete pBmp;
pBmp = nullptr;
}
}
if (m_pScrollButton != nullptr) {
m_pScrollButton->Invalidate(false);
m_pScrollButton->UpdateWindow();
}
}
void CFugeWindow::EndBricks() {
memset(m_bBrickVisible, 0, sizeof(bool) * N_BRICKS);
}
void CFugeWindow::LoadIniSettings() {
if (pGameParams->bPlayingMetagame) {
m_bOutterWall = false;
m_nInitNumBalls = 1;
m_nInitStartLevel = 3;
m_nGForceFactor = GFORCE_DEF;
m_nInitPaddleSize = PSIZE_MAX;
switch (pGameParams->nSkillLevel) {
case SKILLLEVEL_LOW:
m_nInitBallSpeed = 4;
break;
case SKILLLEVEL_MEDIUM:
m_nInitBallSpeed = 6;
break;
default:
assert(pGameParams->nSkillLevel == SKILLLEVEL_HIGH);
m_nInitBallSpeed = 8;
break;
}
} else {
m_nInitNumBalls = GetPrivateProfileInt(INI_SECTION, "NumberOfBalls", BALLS_DEF, INI_FILENAME);
if ((m_nInitNumBalls < BALLS_MIN) || (m_nInitNumBalls > BALLS_MAX))
m_nInitNumBalls = BALLS_DEF;
m_nInitStartLevel = GetPrivateProfileInt(INI_SECTION, "StartingLevel", LEVEL_DEF, INI_FILENAME);
if ((m_nInitStartLevel < LEVEL_MIN) || (m_nInitStartLevel > LEVEL_MAX))
m_nInitStartLevel = LEVEL_DEF;
m_nInitBallSpeed = GetPrivateProfileInt(INI_SECTION, "BallSpeed", SPEED_DEF, INI_FILENAME);
if ((m_nInitBallSpeed < SPEED_MIN) || (m_nInitBallSpeed > SPEED_MAX))
m_nInitBallSpeed = SPEED_DEF;
m_nInitPaddleSize = GetPrivateProfileInt(INI_SECTION, "PaddleSize", PSIZE_DEF, INI_FILENAME);
if ((m_nInitPaddleSize < PSIZE_MIN) || (m_nInitPaddleSize > PSIZE_MAX))
m_nInitPaddleSize = PSIZE_DEF;
int outerWall = GetPrivateProfileInt(INI_SECTION, "OutterWall", 0, INI_FILENAME);
m_bOutterWall = outerWall != 0;
m_nGForceFactor = GetPrivateProfileInt(INI_SECTION, "Gravity", GFORCE_DEF, INI_FILENAME);
if ((m_nGForceFactor < GFORCE_MIN) || (m_nGForceFactor > GFORCE_MAX))
m_nGForceFactor = GFORCE_DEF;
}
}
void CFugeWindow::GameReset() {
KillTimer(TIMER_ID); // stop the timer
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
#else
sndPlaySound(nullptr, SND_ASYNC); // stop all sounds
#endif
}
EndBricks(); // remove all bricks from sprite chain
EndBall(); // remove ball from sprite chain
EndPaddle(); // remove paddle from sprite chain
m_fTurboBoost = 0.0; // no turbo
m_lExtraLifeScore = EXTRA_LIFE_SCORE; // user needs this many points for an extra life
m_bGameActive = false; // there is no currently active game
m_bPause = false; // the game is not paused
m_bBallOnPaddle = false; // Ball is not yet on paddle
m_bMovingPaddle = false; // user is not moving the paddle
m_nBalls = m_nInitNumBalls; // reset # of balls
m_nBallSpeed = m_nInitBallSpeed; // reset ball speed
m_nNumRows = m_nInitStartLevel; // reset number of brick rows
m_nBricks = m_nNumRows * BRICKS_PER_ROW; // get new brick count
m_lScore = 0; // reset the score
m_bPaddleHit = false; // paddle starts fresh
}
void CFugeWindow::OnTimer(uintptr nEvent) {
// there should be only one timer
assert(nEvent == TIMER_ID);
//
// continue as long as there is a currently active non-paused game
//
if (m_bGameActive) {
// check for joystick movement
//
if (m_bJoyActive) {
JOYINFO joyInfo;
joyGetPos(JOYSTICKID1, &joyInfo);
OnJoyStick(joyInfo.wButtons, ((long)joyInfo.wYpos << 16) | joyInfo.wXpos);
}
if (!m_bPause) {
PaintBall();
PaintBall();
}
}
}
long CFugeWindow::OnJoyStick(unsigned int wParam, long lParam) {
long nThresholdX, nThresholdY;
if (m_bGameActive) {
if (wParam & JOY_BUTTON1) {
if (m_bBallOnPaddle) {
LaunchBall();
}
}
nThresholdX = m_nJoyOrgX;
nThresholdX -= (unsigned int)LOWORD(lParam);
nThresholdY = m_nJoyOrgY;
nThresholdY -= (unsigned int)HIWORD(lParam);
if (nThresholdY > 5000) {
// up and left
if (nThresholdX > 5000) {
m_nPaddleCelIndex = 25;
// up and right
} else if (nThresholdX < -5000) {
m_nPaddleCelIndex = 2;
// just up
} else {
m_nPaddleCelIndex = 29;
}
} else if (nThresholdY < -5000) {
// down and left
if (nThresholdX > 5000) {
m_nPaddleCelIndex = 17;
// down and right
} else if (nThresholdX < -5000) {
m_nPaddleCelIndex = 9;
// just down
} else {
m_nPaddleCelIndex = 13;
}
} else {
// left
if (nThresholdX > 5000) {
m_nPaddleCelIndex = 21;
// right
} else if (nThresholdX < -5000) {
m_nPaddleCelIndex = 5;
}
}
PaintPaddle(false);
}
return 0;
}
void CFugeWindow::OnMouseMove(unsigned int, CPoint point) {
int nMove;
if (m_bGameActive && m_bMovingPaddle) {
// Need to implement the mouse better for paddle movement
//
GetCursorPos(&point);
if (point.x > m_ptOrigin.x) {
nMove = (point.x - m_ptOrigin.x) / MOUSE_SENS + 1;
m_nPaddleCelIndex += nMove;
PaintPaddle(false);
SetCursorPos(m_ptOrigin.x, m_ptOrigin.y);
} else if (point.x < m_ptOrigin.x) {
nMove = -((m_ptOrigin.x - point.x) / MOUSE_SENS + 1);
m_nPaddleCelIndex += nMove;
PaintPaddle(false);
SetCursorPos(m_ptOrigin.x, m_ptOrigin.y);
}
// hide the cursor
SetCursor(nullptr);
} else {
SetCursor(LoadCursor(nullptr, IDC_ARROW));
}
}
void CFugeWindow::OnRButtonUp(unsigned int nFlags, CPoint point) {
if (m_bGameActive) {
// toggle move paddle mode
m_bMovingPaddle = !m_bMovingPaddle;
if (m_bMovingPaddle) {
SetCursor(nullptr);
SetCursorPos(m_ptOrigin.x, m_ptOrigin.y);
GameResume();
} else {
SetCursor(LoadCursor(nullptr, IDC_ARROW));
GamePause();
}
} else {
CFrameWnd::OnRButtonDown(nFlags, point);
}
}
void CFugeWindow::OnLButtonDown(unsigned int nFlags, CPoint point) {
CRect boothRect,
tentRect,
peopRect,
car1Rect,
car2Rect,
car3Rect,
car4Rect,
car5Rect,
car6Rect,
car7Rect,
car8Rect,
car9Rect,
car10Rect;
int nPick = 0;
boothRect.SetRect(BOOTH_X, BOOTH_Y, BOOTH_X + BOOTH_DX, BOOTH_Y + BOOTH_DY);
tentRect.SetRect(TENT_X, TENT_Y, TENT_X + TENT_DX, TENT_Y + TENT_DY);
peopRect.SetRect(PEOPLE_X, PEOPLE_Y, PEOPLE_X + PEOPLE_DX, PEOPLE_Y + PEOPLE_DY);
car1Rect.SetRect(CAR1_X, CAR1_Y, CAR1_X + CAR_DX, CAR1_Y + CAR_DY);
car2Rect.SetRect(CAR2_X, CAR2_Y, CAR2_X + CAR_DX, CAR2_Y + CAR_DY);
car3Rect.SetRect(CAR3_X, CAR3_Y, CAR3_X + CAR_DX, CAR3_Y + CAR_DY);
car4Rect.SetRect(CAR4_X, CAR4_Y, CAR4_X + CAR_DX, CAR4_Y + CAR_DY);
car5Rect.SetRect(CAR5_X, CAR5_Y, CAR5_X + CAR_DX, CAR5_Y + CAR_DY);
car6Rect.SetRect(CAR6_X, CAR6_Y, CAR6_X + CAR_DX, CAR6_Y + CAR_DY);
car7Rect.SetRect(CAR7_X, CAR7_Y, CAR7_X + CAR_DX, CAR7_Y + CAR_DY);
car8Rect.SetRect(CAR8_X, CAR8_Y, CAR8_X + CAR_DX, CAR8_Y + CAR_DY);
car9Rect.SetRect(CAR9_X, CAR9_Y, CAR9_X + CAR9_DX, CAR9_Y + CAR9_DY);
car10Rect.SetRect(CAR10_X, CAR10_Y, CAR10_X + CAR10_DX, CAR10_Y + CAR10_DY);
// User clicked on the Title - NewGame button
//
if (m_rNewGameButton.PtInRect(point)) {
// if we are not playing from the metagame
//
if (!pGameParams->bPlayingMetagame) {
// start a new game
PlayGame();
}
} else if (boothRect.PtInRect(point)) {
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_BOOTH,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE | SOUND_QUEUE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_BOOTH, SND_ASYNC);
#endif
}
} else if (tentRect.PtInRect(point)) {
if (pGameParams->bSoundEffectsEnabled) {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_TENT,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE | SOUND_QUEUE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_TENT, SND_ASYNC);
#endif
}
} else if (peopRect.PtInRect(point)) {
if (pGameParams->bSoundEffectsEnabled) {
nPick = brand() % NUM_WAVS;
if (nPick == 0) {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_PEOPLE1,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE | SOUND_QUEUE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_PEOPLE1, SND_ASYNC);
#endif
} else {
#if CSOUND
pEffect = new CSound((CWnd *)this, WAV_PEOPLE2,
SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE | SOUND_QUEUE); //...Wave file, to delete itself
(*pEffect).play(); //...play the narration
#else
sndPlaySound(WAV_PEOPLE2, SND_ASYNC);
#endif
}
}
} else if (((((((((car1Rect.PtInRect(point) || car2Rect.PtInRect(point)) || car3Rect.PtInRect(point)) ||
car4Rect.PtInRect(point)) || car5Rect.PtInRect(point)) || car6Rect.PtInRect(point)) ||
car7Rect.PtInRect(point)) || car8Rect.PtInRect(point)) || car9Rect.PtInRect(point)) ||
car10Rect.PtInRect(point)) {
if (pGameParams->bSoundEffectsEnabled) {
nPick = brand() % NUM_WAVS;
#if CSOUND
// Wave file, to delete itself play the narration
//
pEffect = new CSound((CWnd *)this, ((nPick == 0) ? WAV_CAR1 : WAV_CAR2), SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE | SOUND_QUEUE);
pEffect->play();
#else
sndPlaySound(((nPick == 0) ? WAV_CAR1 : WAV_CAR2), SND_ASYNC);
#endif
}
} else {
if (m_bGameActive) {
if (m_bBallOnPaddle) {
LaunchBall();
}
} else {
// is this needed ?
CFrameWnd::OnLButtonDown(nFlags, point);
}
}
}
void CFugeWindow::OnSysChar(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags) {
// terminate app on ALT_Q
//
if ((nChar == 'q') && (nFlags & 0x2000)) {
PostMessage(WM_CLOSE, 0, 0);
} else {
// default action
CFrameWnd ::OnSysChar(nChar, nRepCnt, nFlags);
}
}
void CFugeWindow::OnSysKeyDown(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags) {
switch (nChar) {
// User has hit ALT_F4 so close down this App
//
case VK_F4:
PostMessage(WM_CLOSE, 0, 0);
break;
default:
CFrameWnd::OnSysKeyDown(nChar, nRepCnt, nFlags);
break;
}
}
void CFugeWindow::OnKeyDown(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags) {
// Handle keyboard input
//
switch (nChar) {
// Move paddle clockwise
//
case VK_UP:
case VK_RIGHT:
if (m_bGameActive) {
m_nPaddleCelIndex += PADDLE_CEL_JUMP;
PaintPaddle(false);
}
break;
// Move paddle counter-clockwise
//
case VK_DOWN:
case VK_LEFT:
if (m_bGameActive) {
m_nPaddleCelIndex -= PADDLE_CEL_JUMP;
PaintPaddle(false);
}
break;
case VK_RETURN:
case VK_SPACE:
if (m_bGameActive) {
if (m_bBallOnPaddle) {
LaunchBall();
}
}
break;
//
// Bring up the Rules
//
case VK_F1: {
GamePause();
CSound::waitWaveSounds();
CRules RulesDlg(this, "fuge.txt", m_pGamePalette, (pGameParams->bSoundEffectsEnabled ? WAV_NARRATION : nullptr));
RulesDlg.DoModal();
GameResume();
}
break;
//
// Bring up the options menu
//
case VK_F2:
SendMessage(WM_COMMAND, IDC_MENU, BN_CLICKED);
break;
case VK_SCROLL:
if (m_bGameActive) {
if (!m_bPause) {
GamePause();
} else {
GameResume();
}
}
break;
default:
CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);
break;
}
}
void CFugeWindow::OnActivate(unsigned int nState, CWnd *, bool bMinimized) {
if (!bMinimized) {
switch (nState) {
case WA_ACTIVE:
case WA_CLICKACTIVE:
InvalidateRect(nullptr, false);
break;
default:
break;
}
}
}
LRESULT CFugeWindow::OnMCINotify(WPARAM wParam, LPARAM lParam) {
CSound *pSound;
pSound = CSound::OnMCIStopped(wParam, lParam);
if (pSound != nullptr)
OnSoundNotify(pSound);
return 0;
}
LRESULT CFugeWindow::OnMMIONotify(WPARAM wParam, LPARAM lParam) {
CSound *pSound;
pSound = CSound::OnMMIOStopped(wParam, lParam);
if (pSound != nullptr)
OnSoundNotify(pSound);
return 0;
}
void CFugeWindow::OnSoundNotify(CSound *) {
//
// Add your code to process explicit notification of a sound "done" event here.
// pSound is a pointer to a CSound object for which you requested SOUND_NOTIFY.
//
}
void CFugeWindow::OnClose() {
CBrush myBrush;
CRect myRect;
CDC *pDC;
ClipCursor(nullptr);
// perform cleanup
//
GameReset();
ReleaseMasterSounds();
ReleaseMasterSprites(); // release all master sprites
// Stop the sountrack
//
if (m_pSoundTrack != nullptr) {
// we should have been playing music
assert(pGameParams->bMusicEnabled);
delete m_pSoundTrack;
m_pSoundTrack = nullptr;
}
#if CSOUND
CSound::clearSounds();
#endif
//
// de-allocate the main menu scroll button
//
assert(m_pScrollButton != nullptr);
if (m_pScrollButton != nullptr) {
delete m_pScrollButton;
m_pScrollButton = nullptr;
}
//
// need to de-allocate the game palette
//
assert(m_pGamePalette != nullptr);
if (m_pGamePalette != nullptr) {
m_pGamePalette->DeleteObject();
delete m_pGamePalette;
m_pGamePalette = nullptr;
}
if ((pDC = GetDC()) != nullptr) { // paint black
myRect.SetRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
myBrush.CreateStockObject(BLACK_BRUSH);
pDC->FillRect(&myRect, &myBrush);
ReleaseDC(pDC);
}
CFrameWnd::OnClose();
MFC::PostMessage(ghParentWnd, WM_PARENTNOTIFY, WM_DESTROY, 0L);
}
//
// CFugeWindow message map:
// Associate messages with member functions.
//
BEGIN_MESSAGE_MAP(CFugeWindow, CFrameWnd)
ON_WM_PAINT()
ON_WM_CLOSE()
ON_WM_TIMER()
ON_WM_MOUSEMOVE()
ON_WM_SYSCHAR()
ON_WM_KEYDOWN()
ON_WM_SYSKEYDOWN()
ON_WM_RBUTTONUP()
ON_WM_LBUTTONDOWN()
ON_WM_ACTIVATE()
ON_MESSAGE(MM_MCINOTIFY, CFugeWindow::OnMCINotify)
ON_MESSAGE(MM_WOM_DONE, CFugeWindow::OnMMIONotify)
END_MESSAGE_MAP()
void CALLBACK GetGameParams(CWnd *pParentWnd) {
//
// Our user preference dialog box is self contained in this object
//
CUserCfgDlg dlgUserCfg(pParentWnd, pGamePalette, IDD_USERCFG);
}
} // namespace Fuge
} // namespace HodjNPodj
} // namespace Bagel