parent
375e33b5e0
commit
b3045ee397
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,619 @@ |
||||
/*
|
||||
OneLoneCoder.com - Command Line Game Engine |
||||
"Who needs a frame buffer?" - @Javidx9 |
||||
|
||||
Mega Big Thanks to KrossX from the discord server for taking |
||||
the time to develop an OpenGL wrapper for the olcConsoleGameEngine. |
||||
|
||||
If you have had difficulty getting consoles to look correct it |
||||
could be due to your version of Windows. This version of the |
||||
console game engine is for you! |
||||
|
||||
License |
||||
~~~~~~~ |
||||
One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 |
||||
This program comes with ABSOLUTELY NO WARRANTY. |
||||
This is free software, and you are welcome to redistribute it |
||||
under certain conditions; See license for details. |
||||
|
||||
Original works located at: |
||||
https://www.github.com/onelonecoder
|
||||
https://www.onelonecoder.com
|
||||
https://www.youtube.com/javidx9
|
||||
GNU GPLv3 |
||||
https://github.com/OneLoneCoder/videos/blob/master/LICENSE
|
||||
|
||||
From Javidx9 :) |
||||
~~~~~~~~~~~~~~~ |
||||
Hello! Ultimately I don't care what you use this for. It's intended to be |
||||
educational, and perhaps to the oddly minded - a little bit of fun. |
||||
Please hack this, change it and use it in any way you see fit. You acknowledge |
||||
that I am not responsible for anything bad that happens as a result of |
||||
your actions. However this code is protected by GNU GPLv3, see the license in the |
||||
github repo. This means you must attribute me if you use it. You can view this |
||||
license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE
|
||||
Cheers! |
||||
|
||||
Background |
||||
~~~~~~~~~~ |
||||
If you've seen any of my videos - I like to do things using the windows console. It's quick |
||||
and easy, and allows you to focus on just the code that matters - ideal when you're |
||||
experimenting. Thing is, I have to keep doing the same initialisation and display code |
||||
each time, so this class wraps that up. |
||||
|
||||
Author |
||||
~~~~~~ |
||||
Twitter: @javidx9 http://twitter.com/javidx9
|
||||
Blog: http://www.onelonecoder.com
|
||||
YouTube: http://www.youtube.com/javidx9
|
||||
|
||||
Videos: |
||||
~~~~~~ |
||||
Original: https://youtu.be/cWc0hgYwZyc
|
||||
Added mouse support: https://youtu.be/tdqc9hZhHxM
|
||||
Beginners Guide: https://youtu.be/u5BhrA8ED0o
|
||||
|
||||
Shout Outs! |
||||
~~~~~~~~~~~ |
||||
Thanks to cool people who helped with testing, bug-finding and fixing! |
||||
wowLinh, JavaJack59, idkwid, kingtatgi, Return Null, CPP Guy |
||||
|
||||
Last Updated: 02/07/2018 |
||||
|
||||
Usage: |
||||
~~~~~~ |
||||
This class is abstract, so you must inherit from it. Override the OnUserCreate() function |
||||
with all the stuff you need for your application (for thready reasons it's best to do |
||||
this in this function and not your class constructor). Override the OnUserUpdate(float fElapsedTime) |
||||
function with the good stuff, it gives you the elapsed time since the last call so you |
||||
can modify your stuff dynamically. Both functions should return true, unless you need |
||||
the application to close. |
||||
|
||||
int main() |
||||
{ |
||||
// Use olcConsoleGameEngine derived app
|
||||
OneLoneCoder_Example game; |
||||
// Create a console with resolution 160x100 characters
|
||||
// Each character occupies 8x8 pixels
|
||||
game.ConstructConsole(160, 100, 8, 8); |
||||
// Start the engine!
|
||||
game.Start(); |
||||
return 0; |
||||
} |
||||
|
||||
Input is also handled for you - interrogate the m_keys[] array with the virtual |
||||
keycode you want to know about. bPressed is set for the frame the key is pressed down |
||||
in, bHeld is set if the key is held down, bReleased is set for the frame the key |
||||
is released in. The same applies to mouse! m_mousePosX and Y can be used to get |
||||
the current cursor position, and m_mouse[1..5] returns the mouse buttons. |
||||
The draw routines treat characters like pixels. By default they are set to white solid |
||||
blocks - but you can draw any unicode character, using any of the colours listed below. |
||||
|
||||
There may be bugs! |
||||
|
||||
See my other videos for examples! |
||||
http://www.youtube.com/javidx9
|
||||
|
||||
Lots of programs to try: |
||||
http://www.github.com/OneLoneCoder/videos
|
||||
|
||||
Chat on the Discord server: |
||||
https://discord.gg/WhwHUMV
|
||||
|
||||
Be bored by Twitch: |
||||
http://www.twitch.tv/javidx9
|
||||
*/ |
||||
#pragma once |
||||
|
||||
#ifndef UNICODE |
||||
#pragma message("Please enable UNICODE for your compiler! VS: Project Properties -> General -> \ |
||||
Character Set -> Use Unicode. Thanks! For now, I'll try enabling it for you - Javidx9") |
||||
#define UNICODE |
||||
#define _UNICODE |
||||
#endif |
||||
|
||||
#pragma comment(lib, "user32.lib") |
||||
#pragma comment(lib, "gdi32.lib") |
||||
#pragma comment(lib, "opengl32.lib") |
||||
#pragma comment(lib, "winmm.lib") |
||||
|
||||
#include <windows.h> |
||||
#include <gl/gl.h> |
||||
#include <iostream> |
||||
#include <chrono> |
||||
#include <vector> |
||||
#include <list> |
||||
#include <thread> |
||||
#include <atomic> |
||||
#include <condition_variable> |
||||
using namespace std; |
||||
#define GL_GENERATE_MIPMAP 0x8191 |
||||
#define GL_GENERATE_MIPMAP_HINT 0x8192 |
||||
typedef BOOL(WINAPI wglSwapInterval_t) (int interval); |
||||
|
||||
|
||||
enum COLOUR |
||||
{ |
||||
FG_BLACK = 0x0000, |
||||
FG_DARK_BLUE = 0x0001, |
||||
FG_DARK_GREEN = 0x0002, |
||||
FG_DARK_CYAN = 0x0003, |
||||
FG_DARK_RED = 0x0004, |
||||
FG_DARK_MAGENTA = 0x0005, |
||||
FG_DARK_YELLOW = 0x0006, |
||||
FG_GREY = 0x0007, // Thanks MS :-/
|
||||
FG_DARK_GREY = 0x0008, |
||||
FG_BLUE = 0x0009, |
||||
FG_GREEN = 0x000A, |
||||
FG_CYAN = 0x000B, |
||||
FG_RED = 0x000C, |
||||
FG_MAGENTA = 0x000D, |
||||
FG_YELLOW = 0x000E, |
||||
FG_WHITE = 0x000F, |
||||
BG_BLACK = 0x0000, |
||||
BG_DARK_BLUE = 0x0010, |
||||
BG_DARK_GREEN = 0x0020, |
||||
BG_DARK_CYAN = 0x0030, |
||||
BG_DARK_RED = 0x0040, |
||||
BG_DARK_MAGENTA = 0x0050, |
||||
BG_DARK_YELLOW = 0x0060, |
||||
BG_GREY = 0x0070, |
||||
BG_DARK_GREY = 0x0080, |
||||
BG_BLUE = 0x0090, |
||||
BG_GREEN = 0x00A0, |
||||
BG_CYAN = 0x00B0, |
||||
BG_RED = 0x00C0, |
||||
BG_MAGENTA = 0x00D0, |
||||
BG_YELLOW = 0x00E0, |
||||
BG_WHITE = 0x00F0, |
||||
}; |
||||
enum PIXEL_TYPE |
||||
{ |
||||
PIXEL_SOLID = 0x2588, |
||||
PIXEL_THREEQUARTERS = 0x2593, |
||||
PIXEL_HALF = 0x2592, |
||||
PIXEL_QUARTER = 0x2591, |
||||
}; |
||||
|
||||
|
||||
class olcSprite |
||||
{ |
||||
public: |
||||
olcSprite() |
||||
{ |
||||
|
||||
} |
||||
|
||||
olcSprite(int w, int h) |
||||
{ |
||||
Create(w, h); |
||||
} |
||||
|
||||
olcSprite(wstring sFile) |
||||
{ |
||||
if (!Load(sFile)) |
||||
Create(8, 8); |
||||
} |
||||
|
||||
int nWidth = 0; |
||||
int nHeight = 0; |
||||
|
||||
//Yer touching private things king!
|
||||
//private:
|
||||
wchar_t *m_Glyphs = nullptr; |
||||
short *m_Colours = nullptr; |
||||
|
||||
private: |
||||
void Create(int w, int h) |
||||
{ |
||||
nWidth = w; |
||||
nHeight = h; |
||||
m_Glyphs = new wchar_t[w*h]; |
||||
m_Colours = new short[w*h]; |
||||
for (int i = 0; i < w*h; i++) |
||||
{ |
||||
m_Glyphs[i] = L' '; |
||||
m_Colours[i] = FG_BLACK; |
||||
} |
||||
} |
||||
|
||||
public: |
||||
void SetGlyph(int x, int y, wchar_t c) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return; |
||||
else |
||||
m_Glyphs[y * nWidth + x] = c; |
||||
} |
||||
|
||||
void SetColour(int x, int y, short c) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return; |
||||
else |
||||
m_Colours[y * nWidth + x] = c; |
||||
} |
||||
|
||||
wchar_t GetGlyph(int x, int y) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return L' '; |
||||
else |
||||
return m_Glyphs[y * nWidth + x]; |
||||
} |
||||
|
||||
short GetColour(int x, int y) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return FG_BLACK; |
||||
else |
||||
return m_Colours[y * nWidth + x]; |
||||
} |
||||
|
||||
wchar_t SampleGlyph(float x, float y) |
||||
{ |
||||
int sx = (int)(x * (float)nWidth); |
||||
int sy = (int)(y * (float)nHeight - 1.0f); |
||||
if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) |
||||
return L' '; |
||||
else |
||||
return m_Glyphs[sy * nWidth + sx]; |
||||
} |
||||
|
||||
short SampleColour(float x, float y) |
||||
{ |
||||
int sx = (int)(x * (float)nWidth); |
||||
int sy = (int)(y * (float)nHeight - 1.0f); |
||||
if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) |
||||
return FG_BLACK; |
||||
else |
||||
return m_Colours[sy * nWidth + sx]; |
||||
} |
||||
|
||||
bool Save(wstring sFile) |
||||
{ |
||||
FILE *f = nullptr; |
||||
_wfopen_s(&f, sFile.c_str(), L"wb"); |
||||
if (f == nullptr) |
||||
return false; |
||||
|
||||
fwrite(&nWidth, sizeof(int), 1, f); |
||||
fwrite(&nHeight, sizeof(int), 1, f); |
||||
fwrite(m_Colours, sizeof(short), nWidth * nHeight, f); |
||||
fwrite(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); |
||||
|
||||
fclose(f); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool Load(wstring sFile) |
||||
{ |
||||
delete[] m_Glyphs; |
||||
delete[] m_Colours; |
||||
nWidth = 0; |
||||
nHeight = 0; |
||||
|
||||
FILE *f = nullptr; |
||||
_wfopen_s(&f, sFile.c_str(), L"rb"); |
||||
if (f == nullptr) |
||||
return false; |
||||
|
||||
fread(&nWidth, sizeof(int), 1, f); |
||||
fread(&nHeight, sizeof(int), 1, f); |
||||
|
||||
Create(nWidth, nHeight); |
||||
|
||||
fread(m_Colours, sizeof(short), nWidth * nHeight, f); |
||||
fread(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); |
||||
|
||||
fclose(f); |
||||
return true; |
||||
} |
||||
|
||||
}; |
||||
|
||||
class olcConsoleGameEngineGLOOP |
||||
{ |
||||
// Mega Thanks to KrossX for completely converting the olcConsoleGameEngine to OpenGL!
|
||||
uint32_t m_ColourPalette[16] = // 0xAABBGGRR
|
||||
{ |
||||
0xFF000000, // BLACK
|
||||
0xFF800000, // DARK_BLUE
|
||||
0xFF008000, // DARK_GREEN
|
||||
0xFF808000, // DARK_CYAN
|
||||
0xFF000080, // DARK_RED
|
||||
0xFF800080, // DARK_MAGENTA
|
||||
0xFF008080, // DARK_YELLOW
|
||||
0xFFC0C0C0, // GREY
|
||||
0xFF808080, // DARK_GREY
|
||||
0xFFFF0000, // BLUE
|
||||
0xFF00FF00, // GREEN
|
||||
0xFFFFFF00, // CYAN
|
||||
0xFF0000FF, // RED
|
||||
0xFFFF00FF, // MAGENTA
|
||||
0xFF00FFFF, // YELLOW
|
||||
0xFFFFFFFF // WHITE
|
||||
}; |
||||
|
||||
void UpdateMousePosition(int x, int y); |
||||
void ToggleFullscreen(HWND hWnd); |
||||
void WindowResize(void); |
||||
void WindowUpdateScale(void); |
||||
int SetPixelFormatGL(void); |
||||
static LRESULT CALLBACK olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); |
||||
HWND ConstructWindow(int width, int height); |
||||
|
||||
public: |
||||
olcConsoleGameEngineGLOOP(); |
||||
int ConstructConsole(int width, int height, int fontw, int fonth); |
||||
virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F); |
||||
void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawString(int x, int y, wstring c, short col = 0x000F); |
||||
void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F); |
||||
void Clip(int &x, int &y); |
||||
void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F); |
||||
void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F); |
||||
void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawSprite(int x, int y, olcSprite *sprite); |
||||
void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h); |
||||
void DrawWireFrameModel(const std::vector<std::pair<float, float>> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, short c = PIXEL_SOLID); |
||||
~olcConsoleGameEngineGLOOP(); |
||||
|
||||
|
||||
void GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size); |
||||
|
||||
public: |
||||
void Start(); |
||||
int ScreenWidth(){ return m_nScreenWidth; } |
||||
int ScreenHeight(){ return m_nScreenHeight; } |
||||
|
||||
private: |
||||
void GameThread(); |
||||
|
||||
|
||||
public: |
||||
// User MUST OVERRIDE THESE!!
|
||||
virtual bool OnUserCreate() = 0; |
||||
virtual bool OnUserUpdate(float fElapsedTime) = 0; |
||||
|
||||
// Optional for clean up
|
||||
virtual bool OnUserDestroy() |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
|
||||
protected: |
||||
struct sKeyState |
||||
{ |
||||
bool bPressed; |
||||
bool bReleased; |
||||
bool bHeld; |
||||
} m_keys[256], m_mouse[5]; |
||||
|
||||
int m_mousePosX; |
||||
int m_mousePosY; |
||||
|
||||
public: |
||||
sKeyState GetKey(int nKeyID) { return m_keys[nKeyID]; } |
||||
int GetMouseX() { return m_mousePosX; } |
||||
int GetMouseY() { return m_mousePosY; } |
||||
sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; } |
||||
bool IsFocused() { return m_bConsoleInFocus; } |
||||
|
||||
|
||||
protected: |
||||
int Error(const wchar_t *msg); |
||||
|
||||
|
||||
protected: // Audio Engine =====================================================================
|
||||
|
||||
class olcAudioSample |
||||
{ |
||||
public: |
||||
olcAudioSample() |
||||
{ |
||||
|
||||
} |
||||
|
||||
olcAudioSample(std::wstring sWavFile) |
||||
{ |
||||
// Load Wav file and convert to float format
|
||||
FILE *f = nullptr; |
||||
_wfopen_s(&f, sWavFile.c_str(), L"rb"); |
||||
if (f == nullptr) |
||||
return; |
||||
|
||||
char dump[4]; |
||||
std::fread(&dump, sizeof(char), 4, f); // Read "RIFF"
|
||||
if (strncmp(dump, "RIFF", 4) != 0) return; |
||||
std::fread(&dump, sizeof(char), 4, f); // Not Interested
|
||||
std::fread(&dump, sizeof(char), 4, f); // Read "WAVE"
|
||||
if (strncmp(dump, "WAVE", 4) != 0) return; |
||||
|
||||
// Read Wave description chunk
|
||||
std::fread(&dump, sizeof(char), 4, f); // Read "fmt "
|
||||
std::fread(&dump, sizeof(char), 4, f); // Not Interested
|
||||
std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk
|
||||
// Note the -2, because the structure has 2 bytes to indicate its own size
|
||||
// which are not in the wav file
|
||||
|
||||
// Just check if wave format is compatible with olcCGE
|
||||
if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) |
||||
{ |
||||
std::fclose(f); |
||||
return; |
||||
} |
||||
|
||||
// Search for audio data chunk
|
||||
long nChunksize = 0; |
||||
std::fread(&dump, sizeof(char), 4, f); // Read chunk header
|
||||
std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size
|
||||
while (strncmp(dump, "data", 4) != 0) |
||||
{ |
||||
// Not audio data, so just skip it
|
||||
std::fseek(f, nChunksize, SEEK_CUR); |
||||
std::fread(&dump, sizeof(char), 4, f); |
||||
std::fread(&nChunksize, sizeof(long), 1, f); |
||||
} |
||||
|
||||
// Finally got to data, so read it all in and convert to float samples
|
||||
nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); |
||||
nChannels = wavHeader.nChannels; |
||||
|
||||
// Create floating point buffer to hold audio sample
|
||||
fSample = new float[nSamples * nChannels]; |
||||
float *pSample = fSample; |
||||
|
||||
// Read in audio data and normalise
|
||||
for (long i = 0; i < nSamples; i++) |
||||
{ |
||||
for (int c = 0; c < nChannels; c++) |
||||
{ |
||||
short s = 0; |
||||
std::fread(&s, sizeof(short), 1, f); |
||||
*pSample = (float)s / (float)(MAXSHORT); |
||||
pSample++; |
||||
} |
||||
} |
||||
|
||||
// All done, flag sound as valid
|
||||
std::fclose(f); |
||||
bSampleValid = true; |
||||
} |
||||
|
||||
WAVEFORMATEX wavHeader; |
||||
float *fSample = nullptr; |
||||
long nSamples = 0; |
||||
int nChannels = 0; |
||||
bool bSampleValid = false; |
||||
}; |
||||
|
||||
void EnableSound(); |
||||
|
||||
// This vector holds all loaded sound samples in memory
|
||||
std::vector<olcAudioSample> vecAudioSamples; |
||||
|
||||
// This structure represents a sound that is currently playing. It only
|
||||
// holds the sound ID and where this instance of it is up to for its
|
||||
// current playback
|
||||
struct sCurrentlyPlayingSample |
||||
{ |
||||
int nAudioSampleID = 0; |
||||
long nSamplePosition = 0; |
||||
bool bFinished = false; |
||||
bool bLoop = false; |
||||
}; |
||||
std::list<sCurrentlyPlayingSample> listActiveSamples; |
||||
|
||||
// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID
|
||||
// number is returned if successful, otherwise -1
|
||||
unsigned int LoadAudioSample(std::wstring sWavFile); |
||||
|
||||
// Add sample 'id' to the mixers sounds to play list
|
||||
void PlaySample(int id, bool bLoop = false); |
||||
void StopSample(int id); |
||||
|
||||
// The audio system uses by default a specific wave format
|
||||
bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, |
||||
unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); |
||||
|
||||
// Stop and clean up audio system
|
||||
bool DestroyAudio(); |
||||
|
||||
// Handler for soundcard request for more data
|
||||
void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); |
||||
|
||||
// Static wrapper for sound card handler
|
||||
static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); |
||||
|
||||
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
|
||||
// with audio data. If no requests are available it goes dormant until the sound
|
||||
// card is ready for more data. The block is fille by the "user" in some manner
|
||||
// and then issued to the soundcard.
|
||||
void AudioThread(); |
||||
|
||||
// Overridden by user if they want to generate sound in real-time
|
||||
virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep); |
||||
|
||||
|
||||
// Overriden by user if they want to manipulate the sound before it is played
|
||||
virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample); |
||||
|
||||
|
||||
// The Sound Mixer - If the user wants to play many sounds simultaneously, and
|
||||
// perhaps the same sound overlapping itself, then you need a mixer, which
|
||||
// takes input from all sound sources for that audio frame. This mixer maintains
|
||||
// a list of sound locations for all concurrently playing audio samples. Instead
|
||||
// of duplicating audio data, we simply store the fact that a sound sample is in
|
||||
// use and an offset into its sample data. As time progresses we update this offset
|
||||
// until it is beyound the length of the sound sample it is attached to. At this
|
||||
// point we remove the playing souind from the list.
|
||||
//
|
||||
// Additionally, the users application may want to generate sound instead of just
|
||||
// playing audio clips (think a synthesizer for example) in whcih case we also
|
||||
// provide an "onUser..." event to allow the user to return a sound for that point
|
||||
// in time.
|
||||
//
|
||||
// Finally, before the sound is issued to the operating system for performing, the
|
||||
// user gets one final chance to "filter" the sound, perhaps changing the volume
|
||||
// or adding funky effects
|
||||
float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); |
||||
|
||||
|
||||
unsigned int m_nSampleRate; |
||||
unsigned int m_nChannels; |
||||
unsigned int m_nBlockCount; |
||||
unsigned int m_nBlockSamples; |
||||
unsigned int m_nBlockCurrent; |
||||
|
||||
short* m_pBlockMemory = nullptr; |
||||
WAVEHDR *m_pWaveHeaders = nullptr; |
||||
HWAVEOUT m_hwDevice = nullptr; |
||||
|
||||
std::thread m_AudioThread; |
||||
std::atomic<bool> m_bAudioThreadActive = false; |
||||
std::atomic<unsigned int> m_nBlockFree = 0; |
||||
std::condition_variable m_cvBlockNotZero; |
||||
std::mutex m_muxBlockNotZero; |
||||
std::atomic<float> m_fGlobalTime = 0.0f; |
||||
bool m_bEnableSound = false; |
||||
|
||||
protected: |
||||
int m_nScreenWidth; |
||||
int m_nScreenHeight; |
||||
int m_nWindowWidth; |
||||
int m_nWindowHeight; |
||||
int m_nFontWidth; |
||||
int m_nFontHeight; |
||||
float m_fDrawScale; |
||||
float m_fDrawOffsetX; |
||||
float m_fDrawOffsetY; |
||||
float *m_fVertexArray; |
||||
uint32_t *m_uIndicesArray; |
||||
uint32_t *m_uForegroundColorArray; |
||||
uint32_t *m_uBackgroundColorArray; |
||||
float *m_fTexCoordArray; |
||||
CHAR_INFO *m_bufScreen; |
||||
CHAR_INFO *m_bufScreen_old; |
||||
uint8_t *m_bufMemory; |
||||
wstring m_sAppName; |
||||
SMALL_RECT m_rectWindow; |
||||
short m_keyOldState[256] = { 0 }; |
||||
short m_keyNewState[256] = { 0 }; |
||||
bool m_mouseOldState[5] = { 0 }; |
||||
bool m_mouseNewState[5] = { 0 }; |
||||
bool m_bConsoleInFocus = true; |
||||
bool m_bDoWindowUpdate = false; |
||||
HWND m_hConsole = nullptr; |
||||
HWND m_hWnd = nullptr; |
||||
HDC m_hDevCtx = nullptr; |
||||
HGLRC m_hRenCtx = nullptr; |
||||
GLuint m_uFontTexture; |
||||
static atomic<bool> m_bAtomActive; |
||||
static condition_variable m_cvGameFinished; |
||||
static mutex m_muxGame; |
||||
}; |
@ -0,0 +1,807 @@ |
||||
/*
|
||||
OneLoneCoder.com - Battle Royale Engine - Console User Interface |
||||
|
||||
License |
||||
~~~~~~~ |
||||
One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 |
||||
This program comes with ABSOLUTELY NO WARRANTY. |
||||
This is free software, and you are welcome to redistribute it |
||||
under certain conditions; See license for details. |
||||
|
||||
GNU GPLv3 |
||||
~~~~~~~~~ |
||||
https://github.com/OneLoneCoder/videos/blob/master/LICENSE
|
||||
|
||||
Author of Original Works |
||||
~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
Twitter: http://twitter.com/javidx9
|
||||
Blog: http://www.onelonecoder.com
|
||||
YouTube: http://www.youtube.com/javidx9
|
||||
Github: http://www.github.com/OneLoneCoder/videos
|
||||
Discord: https://discord.gg/WhwHUMV
|
||||
Twitch: http://www.twitch.tv/javidx9
|
||||
SoundCloud: https://www.soundcloud.com/onelonecoder
|
||||
*/ |
||||
|
||||
#include "olcConsoleGameEngineOOP.h" |
||||
|
||||
olcConsoleGameEngineOOP::olcConsoleGameEngineOOP() |
||||
{ |
||||
m_nScreenWidth = 80; |
||||
m_nScreenHeight = 30; |
||||
|
||||
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
||||
m_hConsoleIn = GetStdHandle(STD_INPUT_HANDLE); |
||||
|
||||
memset(m_keyNewState, 0, 256 * sizeof(short)); |
||||
memset(m_keyOldState, 0, 256 * sizeof(short)); |
||||
memset(m_keys, 0, 256 * sizeof(sKeyState)); |
||||
m_mousePosX = 0; |
||||
m_mousePosY = 0; |
||||
|
||||
m_bEnableSound = false; |
||||
m_sAppName = L"Default"; |
||||
} |
||||
|
||||
olcConsoleGameEngineOOP::~olcConsoleGameEngineOOP() |
||||
{ |
||||
SetConsoleActiveScreenBuffer(m_hOriginalConsole); |
||||
delete[] m_bufScreen; |
||||
} |
||||
|
||||
int olcConsoleGameEngineOOP::ConstructConsole(int width, int height, int fontw, int fonth) |
||||
{ |
||||
if (m_hConsole == INVALID_HANDLE_VALUE) |
||||
return Error(L"Bad Handle"); |
||||
|
||||
m_nScreenWidth = width; |
||||
m_nScreenHeight = height; |
||||
|
||||
// Update 13/09/2017 - It seems that the console behaves differently on some systems
|
||||
// and I'm unsure why this is. It could be to do with windows default settings, or
|
||||
// screen resolutions, or system languages. Unfortunately, MSDN does not offer much
|
||||
// by way of useful information, and so the resulting sequence is the reult of experiment
|
||||
// that seems to work in multiple cases.
|
||||
//
|
||||
// The problem seems to be that the SetConsoleXXX functions are somewhat circular and
|
||||
// fail depending on the state of the current console properties, i.e. you can't set
|
||||
// the buffer size until you set the screen size, but you can't change the screen size
|
||||
// until the buffer size is correct. This coupled with a precise ordering of calls
|
||||
// makes this procedure seem a little mystical :-P. Thanks to wowLinh for helping - Jx9
|
||||
|
||||
// Change console visual size to a minimum so ScreenBuffer can shrink
|
||||
// below the actual visual size
|
||||
m_rectWindow = { 0, 0, 1, 1 }; |
||||
SetConsoleWindowInfo(m_hConsole, TRUE, &m_rectWindow); |
||||
|
||||
// Set the size of the screen buffer
|
||||
COORD coord = { (short)m_nScreenWidth, (short)m_nScreenHeight }; |
||||
if (!SetConsoleScreenBufferSize(m_hConsole, coord)) |
||||
Error(L"SetConsoleScreenBufferSize"); |
||||
|
||||
// Assign screen buffer to the console
|
||||
if (!SetConsoleActiveScreenBuffer(m_hConsole)) |
||||
return Error(L"SetConsoleActiveScreenBuffer"); |
||||
|
||||
// Set the font size now that the screen buffer has been assigned to the console
|
||||
CONSOLE_FONT_INFOEX cfi; |
||||
cfi.cbSize = sizeof(cfi); |
||||
cfi.nFont = 0; |
||||
cfi.dwFontSize.X = fontw; |
||||
cfi.dwFontSize.Y = fonth; |
||||
cfi.FontFamily = FF_DONTCARE; |
||||
cfi.FontWeight = FW_NORMAL; |
||||
//wcscpy_s(cfi.FaceName, L"Lucida Console");
|
||||
//wcscpy_s(cfi.FaceName, L"Liberation Mono");
|
||||
wcscpy_s(cfi.FaceName, L"Consolas"); |
||||
//wcscpy_s(cfi.FaceName, L"Raster");
|
||||
if (!SetCurrentConsoleFontEx(m_hConsole, false, &cfi)) |
||||
return Error(L"SetCurrentConsoleFontEx"); |
||||
|
||||
// Get screen buffer info and check the maximum allowed window size. Return
|
||||
// error if exceeded, so user knows their dimensions/fontsize are too large
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi; |
||||
if (!GetConsoleScreenBufferInfo(m_hConsole, &csbi)) |
||||
return Error(L"GetConsoleScreenBufferInfo"); |
||||
if (m_nScreenHeight > csbi.dwMaximumWindowSize.Y) |
||||
return Error(L"Screen Height / Font Height Too Big"); |
||||
if (m_nScreenWidth > csbi.dwMaximumWindowSize.X) |
||||
return Error(L"Screen Width / Font Width Too Big"); |
||||
|
||||
// Set Physical Console Window Size
|
||||
m_rectWindow = { 0, 0, (short)m_nScreenWidth - 1, (short)m_nScreenHeight - 1 }; |
||||
if (!SetConsoleWindowInfo(m_hConsole, TRUE, &m_rectWindow)) |
||||
return Error(L"SetConsoleWindowInfo"); |
||||
|
||||
// Set flags to allow mouse input
|
||||
if (!SetConsoleMode(m_hConsoleIn, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT)) |
||||
return Error(L"SetConsoleMode"); |
||||
|
||||
// Allocate memory for screen buffer
|
||||
m_bufScreen = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight]; |
||||
memset(m_bufScreen, 0, sizeof(CHAR_INFO) * m_nScreenWidth * m_nScreenHeight); |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::Draw(int x, int y, wchar_t c, short col) |
||||
{ |
||||
if (x >= 0 && x < m_nScreenWidth && y >= 0 && y < m_nScreenHeight) |
||||
{ |
||||
m_bufScreen[y * m_nScreenWidth + x].Char.UnicodeChar = c; |
||||
m_bufScreen[y * m_nScreenWidth + x].Attributes = col; |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::Fill(int x1, int y1, int x2, int y2, wchar_t c, short col) |
||||
{ |
||||
Clip(x1, y1); |
||||
Clip(x2, y2); |
||||
for (int x = x1; x < x2; x++) |
||||
for (int y = y1; y < y2; y++) |
||||
Draw(x, y, c, col); |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::DrawString(int x, int y, wstring c, short col) |
||||
{ |
||||
for (size_t i = 0; i < c.size(); i++) |
||||
{ |
||||
m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; |
||||
m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::DrawStringAlpha(int x, int y, wstring c, short col) |
||||
{ |
||||
for (size_t i = 0; i < c.size(); i++) |
||||
{ |
||||
if (c[i] != L' ') |
||||
{ |
||||
m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; |
||||
m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::Clip(int &x, int &y) |
||||
{ |
||||
if (x < 0) x = 0; |
||||
if (x >= m_nScreenWidth) x = m_nScreenWidth; |
||||
if (y < 0) y = 0; |
||||
if (y >= m_nScreenHeight) y = m_nScreenHeight; |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::DrawLine(int x1, int y1, int x2, int y2, wchar_t c, short col) |
||||
{ |
||||
int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; |
||||
dx = x2 - x1; |
||||
dy = y2 - y1; |
||||
dx1 = abs(dx); |
||||
dy1 = abs(dy); |
||||
px = 2 * dy1 - dx1; |
||||
py = 2 * dx1 - dy1; |
||||
if (dy1 <= dx1) |
||||
{ |
||||
if (dx >= 0) |
||||
{ |
||||
x = x1; |
||||
y = y1; |
||||
xe = x2; |
||||
} |
||||
else |
||||
{ |
||||
x = x2; |
||||
y = y2; |
||||
xe = x1; |
||||
} |
||||
Draw(x, y, c, col); |
||||
for (i = 0; x<xe; i++) |
||||
{ |
||||
x = x + 1; |
||||
if (px<0) |
||||
px = px + 2 * dy1; |
||||
else |
||||
{ |
||||
if ((dx<0 && dy<0) || (dx>0 && dy>0)) |
||||
y = y + 1; |
||||
else |
||||
y = y - 1; |
||||
px = px + 2 * (dy1 - dx1); |
||||
} |
||||
Draw(x, y, c, col); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
if (dy >= 0) |
||||
{ |
||||
x = x1; |
||||
y = y1; |
||||
ye = y2; |
||||
} |
||||
else |
||||
{ |
||||
x = x2; |
||||
y = y2; |
||||
ye = y1; |
||||
} |
||||
Draw(x, y, c, col); |
||||
for (i = 0; y<ye; i++) |
||||
{ |
||||
y = y + 1; |
||||
if (py <= 0) |
||||
py = py + 2 * dx1; |
||||
else |
||||
{ |
||||
if ((dx<0 && dy<0) || (dx>0 && dy>0)) |
||||
x = x + 1; |
||||
else |
||||
x = x - 1; |
||||
py = py + 2 * (dx1 - dy1); |
||||
} |
||||
Draw(x, y, c, col); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::DrawCircle(int xc, int yc, int r, wchar_t c, short col) |
||||
{ |
||||
int x = 0; |
||||
int y = r; |
||||
int p = 3 - 2 * r; |
||||
if (!r) return; |
||||
|
||||
while (y >= x) // only formulate 1/8 of circle
|
||||
{ |
||||
Draw(xc - x, yc - y, c, col);//upper left left
|
||||
Draw(xc - y, yc - x, c, col);//upper upper left
|
||||
Draw(xc + y, yc - x, c, col);//upper upper right
|
||||
Draw(xc + x, yc - y, c, col);//upper right right
|
||||
Draw(xc - x, yc + y, c, col);//lower left left
|
||||
Draw(xc - y, yc + x, c, col);//lower lower left
|
||||
Draw(xc + y, yc + x, c, col);//lower lower right
|
||||
Draw(xc + x, yc + y, c, col);//lower right right
|
||||
if (p < 0) p += 4 * x++ + 6; |
||||
else p += 4 * (x++ - y--) + 10; |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::FillCircle(int xc, int yc, int r, wchar_t c, short col) |
||||
{ |
||||
// Taken from wikipedia
|
||||
int x = 0; |
||||
int y = r; |
||||
int p = 3 - 2 * r; |
||||
if (!r) return; |
||||
|
||||
auto drawline = [&](int sx, int ex, int ny) |
||||
{ |
||||
for (int i = sx; i < ex; i++) |
||||
Draw(i, ny, c, col); |
||||
}; |
||||
|
||||
while (y >= x) |
||||
{ |
||||
// Modified to draw scan-lines instead of edges
|
||||
drawline(xc - x, xc + x, yc - y); |
||||
drawline(xc - y, xc + y, yc - x); |
||||
drawline(xc - x, xc + x, yc + y); |
||||
drawline(xc - y, xc + y, yc + x); |
||||
if (p < 0) p += 4 * x++ + 6; |
||||
else p += 4 * (x++ - y--) + 10; |
||||
} |
||||
}; |
||||
|
||||
void olcConsoleGameEngineOOP::DrawSprite(int x, int y, olcSprite *sprite) |
||||
{ |
||||
if (sprite == nullptr) |
||||
return; |
||||
|
||||
for (int i = 0; i < sprite->nWidth; i++) |
||||
{ |
||||
for (int j = 0; j < sprite->nHeight; j++) |
||||
{ |
||||
if (sprite->GetGlyph(i, j) != L' ') |
||||
Draw(x + i, y + j, sprite->GetGlyph(i, j), sprite->GetColour(i, j)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h) |
||||
{ |
||||
if (sprite == nullptr) |
||||
return; |
||||
|
||||
for (int i = 0; i < w; i++) |
||||
{ |
||||
for (int j = 0; j < h; j++) |
||||
{ |
||||
if (sprite->GetGlyph(i + ox, j + oy) != L' ') |
||||
Draw(x + i, y + j, sprite->GetGlyph(i + ox, j + oy), sprite->GetColour(i + ox, j + oy)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::DrawWireFrameModel(const vector<pair<float, float>> &vecModelCoordinates, float x, float y, float r, float s, short col, wchar_t c) |
||||
{ |
||||
// pair.first = x coordinate
|
||||
// pair.second = y coordinate
|
||||
|
||||
// Create translated model vector of coordinate pairs
|
||||
vector<pair<float, float>> vecTransformedCoordinates; |
||||
int verts = vecModelCoordinates.size(); |
||||
vecTransformedCoordinates.resize(verts); |
||||
|
||||
// Rotate
|
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
vecTransformedCoordinates[i].first = vecModelCoordinates[i].first * cosf(r) - vecModelCoordinates[i].second * sinf(r); |
||||
vecTransformedCoordinates[i].second = vecModelCoordinates[i].first * sinf(r) + vecModelCoordinates[i].second * cosf(r); |
||||
} |
||||
|
||||
// Scale
|
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first * s; |
||||
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second * s; |
||||
} |
||||
|
||||
// Translate
|
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first + x; |
||||
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second + y; |
||||
} |
||||
|
||||
// Draw Closed Polygon
|
||||
for (int i = 0; i < verts + 1; i++) |
||||
{ |
||||
int j = (i + 1); |
||||
DrawLine((int)vecTransformedCoordinates[i % verts].first, (int)vecTransformedCoordinates[i % verts].second, |
||||
(int)vecTransformedCoordinates[j % verts].first, (int)vecTransformedCoordinates[j % verts].second, c, col); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
void olcConsoleGameEngineOOP::Start() |
||||
{ |
||||
m_bAtomActive = true; |
||||
|
||||
// Star the thread
|
||||
thread t = thread(&olcConsoleGameEngineOOP::GameThread, this); |
||||
|
||||
// Wait for thread to be exited
|
||||
t.join(); |
||||
} |
||||
|
||||
int olcConsoleGameEngineOOP::ScreenWidth() |
||||
{ |
||||
return m_nScreenWidth; |
||||
} |
||||
|
||||
int olcConsoleGameEngineOOP::ScreenHeight() |
||||
{ |
||||
return m_nScreenHeight; |
||||
} |
||||
|
||||
void olcConsoleGameEngineOOP::GameThread() |
||||
{ |
||||
// Create user resources as part of this thread
|
||||
if (!OnUserCreate()) |
||||
m_bAtomActive = false; |
||||
|
||||
// Check if sound system should be enabled
|
||||
if (m_bEnableSound) |
||||
{ |
||||
if (!CreateAudio()) |
||||
{ |
||||
m_bAtomActive = false; // Failed to create audio system
|
||||
m_bEnableSound = false; |
||||
} |
||||
} |
||||
|
||||
auto tp1 = chrono::system_clock::now(); |
||||
auto tp2 = chrono::system_clock::now(); |
||||
|
||||
while (m_bAtomActive) |
||||
{ |
||||
// Run as fast as possible
|
||||
while (m_bAtomActive) |
||||
{ |
||||
// Handle Timing
|
||||
tp2 = chrono::system_clock::now(); |
||||
chrono::duration<float> elapsedTime = tp2 - tp1; |
||||
tp1 = tp2; |
||||
float fElapsedTime = elapsedTime.count(); |
||||
|
||||
// Handle Keyboard Input
|
||||
for (int i = 0; i < 256; i++) |
||||
{ |
||||
m_keyNewState[i] = GetAsyncKeyState(i); |
||||
|
||||
m_keys[i].bPressed = false; |
||||
m_keys[i].bReleased = false; |
||||
|
||||
if (m_keyNewState[i] != m_keyOldState[i]) |
||||
{ |
||||
if (m_keyNewState[i] & 0x8000) |
||||
{ |
||||
m_keys[i].bPressed = !m_keys[i].bHeld; |
||||
m_keys[i].bHeld = true; |
||||
} |
||||
else |
||||
{ |
||||
m_keys[i].bReleased = true; |
||||
m_keys[i].bHeld = false; |
||||
} |
||||
} |
||||
|
||||
m_keyOldState[i] = m_keyNewState[i]; |
||||
} |
||||
|
||||
// Handle Mouse Input - Check for window events
|
||||
INPUT_RECORD inBuf[32]; |
||||
DWORD events = 0; |
||||
GetNumberOfConsoleInputEvents(m_hConsoleIn, &events); |
||||
if (events > 0) |
||||
ReadConsoleInput(m_hConsoleIn, inBuf, events, &events); |
||||
|
||||
// Handle events - we only care about mouse clicks and movement
|
||||
// for now
|
||||
for (DWORD i = 0; i < events; i++) |
||||
{ |
||||
switch (inBuf[i].EventType) |
||||
{ |
||||
case FOCUS_EVENT: |
||||
{ |
||||
m_bConsoleInFocus = inBuf[i].Event.FocusEvent.bSetFocus; |
||||
} |
||||
break; |
||||
|
||||
case MOUSE_EVENT: |
||||
{ |
||||
switch (inBuf[i].Event.MouseEvent.dwEventFlags) |
||||
{ |
||||
case MOUSE_MOVED: |
||||
{ |
||||
m_mousePosX = inBuf[i].Event.MouseEvent.dwMousePosition.X; |
||||
m_mousePosY = inBuf[i].Event.MouseEvent.dwMousePosition.Y; |
||||
} |
||||
break; |
||||
|
||||
case 0: |
||||
{ |
||||
for (int m = 0; m < 5; m++) |
||||
m_mouseNewState[m] = (inBuf[i].Event.MouseEvent.dwButtonState & (1 << m)) > 0; |
||||
|
||||
} |
||||
break; |
||||
|
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
break; |
||||
// We don't care just at the moment
|
||||
} |
||||
} |
||||
|
||||
for (int m = 0; m < 5; m++) |
||||
{ |
||||
m_mouse[m].bPressed = false; |
||||
m_mouse[m].bReleased = false; |
||||
|
||||
if (m_mouseNewState[m] != m_mouseOldState[m]) |
||||
{ |
||||
if (m_mouseNewState[m]) |
||||
{ |
||||
m_mouse[m].bPressed = true; |
||||
m_mouse[m].bHeld = true; |
||||
} |
||||
else |
||||
{ |
||||
m_mouse[m].bReleased = true; |
||||
m_mouse[m].bHeld = false; |
||||
} |
||||
} |
||||
|
||||
m_mouseOldState[m] = m_mouseNewState[m]; |
||||
} |
||||
|
||||
|
||||
// Handle Frame Update
|
||||
if (!OnUserUpdate(fElapsedTime)) |
||||
m_bAtomActive = false; |
||||
|
||||
// Update Title & Present Screen Buffer
|
||||
wchar_t s[256]; |
||||
swprintf_s(s, 256, L"OneLoneCoder.com - Console Game Engine - %s - FPS: %3.2f - %d ", m_sAppName.c_str(), 1.0f / fElapsedTime, events); |
||||
SetConsoleTitle(s); |
||||
WriteConsoleOutput(m_hConsole, m_bufScreen, { (short)m_nScreenWidth, (short)m_nScreenHeight }, { 0,0 }, &m_rectWindow); |
||||
} |
||||
|
||||
if (m_bEnableSound) |
||||
{ |
||||
// Close and Clean up audio system
|
||||
} |
||||
|
||||
if (OnUserDestroy()) |
||||
{ |
||||
// User has permitted destroy, so exit and clean up
|
||||
|
||||
delete[] m_bufScreen; |
||||
SetConsoleActiveScreenBuffer(m_hOriginalConsole); |
||||
m_cvGameFinished.notify_one(); |
||||
} |
||||
else |
||||
{ |
||||
// User denied destroy for some reason, so continue running
|
||||
m_bAtomActive = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// Optional for clean up
|
||||
bool olcConsoleGameEngineOOP::OnUserDestroy() |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
int olcConsoleGameEngineOOP::Error(const wchar_t *msg) |
||||
{ |
||||
wchar_t buf[256]; |
||||
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, NULL); |
||||
SetConsoleActiveScreenBuffer(m_hOriginalConsole); |
||||
wprintf(L"ERROR: %s\n\t%s\n", msg, buf); |
||||
return 0; |
||||
} |
||||
|
||||
BOOL olcConsoleGameEngineOOP::CloseHandler(DWORD evt) |
||||
{ |
||||
// Note this gets called in a seperate OS thread, so it must
|
||||
// only exit when the game has finished cleaning up, or else
|
||||
// the process will be killed before OnUserDestroy() has finished
|
||||
if (evt == CTRL_CLOSE_EVENT) |
||||
{ |
||||
m_bAtomActive = false; |
||||
|
||||
// Wait for thread to be exited
|
||||
unique_lock<mutex> ul(m_muxGame); |
||||
m_cvGameFinished.wait(ul); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
unsigned int olcConsoleGameEngineOOP::LoadAudioSample(std::wstring sWavFile) |
||||
{ |
||||
if (!m_bEnableSound) |
||||
return -1; |
||||
|
||||
olcAudioSample a(sWavFile); |
||||
if (a.bSampleValid) |
||||
{ |
||||
vecAudioSamples.push_back(a); |
||||
return vecAudioSamples.size(); |
||||
} |
||||
else |
||||
return -1; |
||||
} |
||||
|
||||
// Add sample 'id' to the mixers sounds to play list
|
||||
void olcConsoleGameEngineOOP::PlaySample(int id, bool bLoop) |
||||
{ |
||||
listActiveSamples.push_back({ id, 0, false, bLoop }); |
||||
} |
||||
|
||||
void StopSample(int id) |
||||
{ |
||||
|
||||
} |
||||
|
||||
// The audio system uses by default a specific wave format
|
||||
bool olcConsoleGameEngineOOP::CreateAudio(unsigned int nSampleRate, unsigned int nChannels, |
||||
unsigned int nBlocks, unsigned int nBlockSamples) |
||||
{ |
||||
// Initialise Sound Engine
|
||||
m_bAudioThreadActive = false; |
||||
m_nSampleRate = nSampleRate; |
||||
m_nChannels = nChannels; |
||||
m_nBlockCount = nBlocks; |
||||
m_nBlockSamples = nBlockSamples; |
||||
m_nBlockFree = m_nBlockCount; |
||||
m_nBlockCurrent = 0; |
||||
m_pBlockMemory = nullptr; |
||||
m_pWaveHeaders = nullptr; |
||||
|
||||
// Device is available
|
||||
WAVEFORMATEX waveFormat; |
||||
waveFormat.wFormatTag = WAVE_FORMAT_PCM; |
||||
waveFormat.nSamplesPerSec = m_nSampleRate; |
||||
waveFormat.wBitsPerSample = sizeof(short) * 8; |
||||
waveFormat.nChannels = m_nChannels; |
||||
waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; |
||||
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; |
||||
waveFormat.cbSize = 0; |
||||
|
||||
// Open Device if valid
|
||||
if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) |
||||
return DestroyAudio(); |
||||
|
||||
// Allocate Wave|Block Memory
|
||||
m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; |
||||
if (m_pBlockMemory == nullptr) |
||||
return DestroyAudio(); |
||||
ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); |
||||
|
||||
m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; |
||||
if (m_pWaveHeaders == nullptr) |
||||
return DestroyAudio(); |
||||
ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); |
||||
|
||||
// Link headers to block memory
|
||||
for (unsigned int n = 0; n < m_nBlockCount; n++) |
||||
{ |
||||
m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); |
||||
m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); |
||||
} |
||||
|
||||
m_bAudioThreadActive = true; |
||||
m_AudioThread = std::thread(&olcConsoleGameEngineOOP::AudioThread, this); |
||||
|
||||
// Start the ball rolling with the sound delivery thread
|
||||
std::unique_lock<std::mutex> lm(m_muxBlockNotZero); |
||||
m_cvBlockNotZero.notify_one(); |
||||
return true; |
||||
} |
||||
|
||||
// Stop and clean up audio system
|
||||
bool olcConsoleGameEngineOOP::DestroyAudio() |
||||
{ |
||||
m_bAudioThreadActive = false; |
||||
return false; |
||||
} |
||||
|
||||
// Handler for soundcard request for more data
|
||||
void olcConsoleGameEngineOOP::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) |
||||
{ |
||||
if (uMsg != WOM_DONE) return; |
||||
m_nBlockFree++; |
||||
std::unique_lock<std::mutex> lm(m_muxBlockNotZero); |
||||
m_cvBlockNotZero.notify_one(); |
||||
} |
||||
|
||||
// Static wrapper for sound card handler
|
||||
void CALLBACK olcConsoleGameEngineOOP::waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) |
||||
{ |
||||
((olcConsoleGameEngineOOP*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); |
||||
} |
||||
|
||||
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
|
||||
// with audio data. If no requests are available it goes dormant until the sound
|
||||
// card is ready for more data. The block is fille by the "user" in some manner
|
||||
// and then issued to the soundcard.
|
||||
void olcConsoleGameEngineOOP::AudioThread() |
||||
{ |
||||
m_fGlobalTime = 0.0f; |
||||
float fTimeStep = 1.0f / (float)m_nSampleRate; |
||||
|
||||
// Goofy hack to get maximum integer for a type at run-time
|
||||
short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; |
||||
float fMaxSample = (float)nMaxSample; |
||||
short nPreviousSample = 0; |
||||
|
||||
while (m_bAudioThreadActive) |
||||
{ |
||||
// Wait for block to become available
|
||||
if (m_nBlockFree == 0) |
||||
{ |
||||
std::unique_lock<std::mutex> lm(m_muxBlockNotZero); |
||||
while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly
|
||||
m_cvBlockNotZero.wait(lm); |
||||
} |
||||
|
||||
// Block is here, so use it
|
||||
m_nBlockFree--; |
||||
|
||||
// Prepare block for processing
|
||||
if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) |
||||
waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); |
||||
|
||||
short nNewSample = 0; |
||||
int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; |
||||
|
||||
auto clip = [](float fSample, float fMax) |
||||
{ |
||||
if (fSample >= 0.0) |
||||
return fmin(fSample, fMax); |
||||
else |
||||
return fmax(fSample, -fMax); |
||||
}; |
||||
|
||||
for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) |
||||
{ |
||||
// User Process
|
||||
for (unsigned int c = 0; c < m_nChannels; c++) |
||||
{ |
||||
nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); |
||||
m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; |
||||
nPreviousSample = nNewSample; |
||||
} |
||||
|
||||
m_fGlobalTime = m_fGlobalTime + fTimeStep; |
||||
} |
||||
|
||||
// Send block to sound device
|
||||
waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); |
||||
waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); |
||||
m_nBlockCurrent++; |
||||
m_nBlockCurrent %= m_nBlockCount; |
||||
} |
||||
} |
||||
|
||||
// Overridden by user if they want to generate sound in real-time
|
||||
float olcConsoleGameEngineOOP::onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) |
||||
{ |
||||
return 0.0f; |
||||
} |
||||
|
||||
// Overriden by user if they want to manipulate the sound before it is played
|
||||
float olcConsoleGameEngineOOP::onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) |
||||
{ |
||||
return fSample; |
||||
} |
||||
|
||||
// The Sound Mixer - If the user wants to play many sounds simultaneously, and
|
||||
// perhaps the same sound overlapping itself, then you need a mixer, which
|
||||
// takes input from all sound sources for that audio frame. This mixer maintains
|
||||
// a list of sound locations for all concurrently playing audio samples. Instead
|
||||
// of duplicating audio data, we simply store the fact that a sound sample is in
|
||||
// use and an offset into its sample data. As time progresses we update this offset
|
||||
// until it is beyound the length of the sound sample it is attached to. At this
|
||||
// point we remove the playing souind from the list.
|
||||
//
|
||||
// Additionally, the users application may want to generate sound instead of just
|
||||
// playing audio clips (think a synthesizer for example) in whcih case we also
|
||||
// provide an "onUser..." event to allow the user to return a sound for that point
|
||||
// in time.
|
||||
//
|
||||
// Finally, before the sound is issued to the operating system for performing, the
|
||||
// user gets one final chance to "filter" the sound, perhaps changing the volume
|
||||
// or adding funky effects
|
||||
float olcConsoleGameEngineOOP::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) |
||||
{ |
||||
// Accumulate sample for this channel
|
||||
float fMixerSample = 0.0f; |
||||
|
||||
for (auto &s : listActiveSamples) |
||||
{ |
||||
// Calculate sample position
|
||||
s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); |
||||
|
||||
// If sample position is valid add to the mix
|
||||
if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) |
||||
fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; |
||||
else |
||||
s.bFinished = true; // Else sound has completed
|
||||
} |
||||
|
||||
// If sounds have completed then remove them
|
||||
listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); |
||||
|
||||
// The users application might be generating sound, so grab that if it exists
|
||||
fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); |
||||
|
||||
// Return the sample via an optional user override to filter the sound
|
||||
return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); |
||||
} |
||||
|
||||
|
||||
atomic<bool> olcConsoleGameEngineOOP::m_bAtomActive = false; |
||||
condition_variable olcConsoleGameEngineOOP::m_cvGameFinished; |
||||
mutex olcConsoleGameEngineOOP::m_muxGame; |
@ -0,0 +1,601 @@ |
||||
/*
|
||||
OneLoneCoder.com - Command Line Game Engine |
||||
"Who needs a frame buffer?" - @Javidx9 |
||||
|
||||
The Original & Best :P (in OOP form) |
||||
|
||||
License |
||||
~~~~~~~ |
||||
One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 |
||||
This program comes with ABSOLUTELY NO WARRANTY. |
||||
This is free software, and you are welcome to redistribute it |
||||
under certain conditions; See license for details. |
||||
|
||||
Original works located at: |
||||
https://www.github.com/onelonecoder
|
||||
https://www.onelonecoder.com
|
||||
https://www.youtube.com/javidx9
|
||||
|
||||
GNU GPLv3 |
||||
https://github.com/OneLoneCoder/videos/blob/master/LICENSE
|
||||
|
||||
Disclaimer |
||||
~~~~~~~~~~ |
||||
I don't care what you use this for. It's intended to be educational, and perhaps |
||||
to the oddly minded - a little bit of fun. Please hack this, change it and use it |
||||
in any way you see fit. BUT, you acknowledge that I am not responsible for anything |
||||
bad that happens as a result of your actions. However, if good stuff happens, I |
||||
would appreciate a shout out, or at least give the blog some publicity for me. |
||||
Cheers! |
||||
|
||||
Background |
||||
~~~~~~~~~~ |
||||
If you've seen any of my videos - I like to do things using the windows console. It's quick |
||||
and easy, and allows you to focus on just the code that matters - ideal when you're |
||||
experimenting. Thing is, I have to keep doing the same initialisation and display code |
||||
each time, so this class wraps that up. |
||||
|
||||
Author |
||||
~~~~~~ |
||||
Twitter: @javidx9 http://twitter.com/javidx9
|
||||
Blog: http://www.onelonecoder.com
|
||||
YouTube: http://www.youtube.com/javidx9
|
||||
|
||||
Videos: |
||||
~~~~~~ |
||||
Original: https://youtu.be/cWc0hgYwZyc
|
||||
Added mouse support: https://youtu.be/tdqc9hZhHxM
|
||||
Beginners Guide: https://youtu.be/u5BhrA8ED0o
|
||||
|
||||
Shout Outs! |
||||
~~~~~~~~~~~ |
||||
Thanks to cool people who helped with testing, bug-finding and fixing! |
||||
YouTube: wowLinh, JavaJack59, idkwid, kingtatgi |
||||
|
||||
Last Updated: 02/07/2018 |
||||
|
||||
Usage: |
||||
~~~~~~ |
||||
This class is abstract, so you must inherit from it. Override the OnUserCreate() function |
||||
with all the stuff you need for your application (for thready reasons it's best to do |
||||
this in this function and not your class constructor). Override the OnUserUpdate(float fElapsedTime) |
||||
function with the good stuff, it gives you the elapsed time since the last call so you |
||||
can modify your stuff dynamically. Both functions should return true, unless you need |
||||
the application to close. |
||||
|
||||
int main() |
||||
{ |
||||
// Use olcConsoleGameEngine derived app
|
||||
OneLoneCoder_Example game; |
||||
|
||||
// Create a console with resolution 160x100 characters
|
||||
// Each character occupies 8x8 pixels
|
||||
game.ConstructConsole(160, 100, 8, 8); |
||||
|
||||
// Start the engine!
|
||||
game.Start(); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
Input is also handled for you - interrogate the m_keys[] array with the virtual |
||||
keycode you want to know about. bPressed is set for the frame the key is pressed down |
||||
in, bHeld is set if the key is held down, bReleased is set for the frame the key |
||||
is released in. The same applies to mouse! m_mousePosX and Y can be used to get |
||||
the current cursor position, and m_mouse[1..5] returns the mouse buttons. |
||||
|
||||
The draw routines treat characters like pixels. By default they are set to white solid |
||||
blocks - but you can draw any unicode character, using any of the colours listed below. |
||||
|
||||
There may be bugs! |
||||
|
||||
See my other videos for examples! |
||||
http://www.youtube.com/javidx9
|
||||
|
||||
Lots of programs to try: |
||||
http://www.github.com/OneLoneCoder/videos
|
||||
|
||||
Chat on the Discord server: |
||||
https://discord.gg/WhwHUMV
|
||||
|
||||
Be bored by Twitch: |
||||
http://www.twitch.tv/javidx9
|
||||
|
||||
|
||||
|
||||
bool IsWin8OrLater() |
||||
{ |
||||
DWORD version = GetVersion(); |
||||
DWORD major = (DWORD) (LOBYTE(LOWORD(version))); |
||||
DWORD minor = (DWORD) (HIBYTE(LOWORD(version))); |
||||
|
||||
return (major > 6) || ((major == 6) && (minor >= 2) && (minor < 4)); |
||||
} |
||||
|
||||
*/ |
||||
|
||||
#pragma once |
||||
#pragma comment(lib, "winmm.lib") |
||||
|
||||
#ifndef UNICODE |
||||
#error Please enable UNICODE for your compiler! VS: Project Properties -> General -> \ |
||||
Character Set -> Use Unicode. Thanks! - Javidx9 |
||||
#endif |
||||
|
||||
#include <iostream> |
||||
#include <chrono> |
||||
#include <vector> |
||||
#include <list> |
||||
#include <thread> |
||||
#include <atomic> |
||||
#include <condition_variable> |
||||
#include <sstream> |
||||
#include <streambuf> |
||||
using namespace std; |
||||
|
||||
#include <windows.h> |
||||
|
||||
|
||||
enum COLOUR |
||||
{ |
||||
FG_BLACK = 0x0000, |
||||
FG_DARK_BLUE = 0x0001, |
||||
FG_DARK_GREEN = 0x0002, |
||||
FG_DARK_CYAN = 0x0003, |
||||
FG_DARK_RED = 0x0004, |
||||
FG_DARK_MAGENTA = 0x0005, |
||||
FG_DARK_YELLOW = 0x0006, |
||||
FG_GREY = 0x0007, // Thanks MS :-/
|
||||
FG_DARK_GREY = 0x0008, |
||||
FG_BLUE = 0x0009, |
||||
FG_GREEN = 0x000A, |
||||
FG_CYAN = 0x000B, |
||||
FG_RED = 0x000C, |
||||
FG_MAGENTA = 0x000D, |
||||
FG_YELLOW = 0x000E, |
||||
FG_WHITE = 0x000F, |
||||
BG_BLACK = 0x0000, |
||||
BG_DARK_BLUE = 0x0010, |
||||
BG_DARK_GREEN = 0x0020, |
||||
BG_DARK_CYAN = 0x0030, |
||||
BG_DARK_RED = 0x0040, |
||||
BG_DARK_MAGENTA = 0x0050, |
||||
BG_DARK_YELLOW = 0x0060, |
||||
BG_GREY = 0x0070, |
||||
BG_DARK_GREY = 0x0080, |
||||
BG_BLUE = 0x0090, |
||||
BG_GREEN = 0x00A0, |
||||
BG_CYAN = 0x00B0, |
||||
BG_RED = 0x00C0, |
||||
BG_MAGENTA = 0x00D0, |
||||
BG_YELLOW = 0x00E0, |
||||
BG_WHITE = 0x00F0, |
||||
}; |
||||
|
||||
enum PIXEL_TYPE |
||||
{ |
||||
PIXEL_SOLID = 0x2588, |
||||
PIXEL_THREEQUARTERS = 0x2593, |
||||
PIXEL_HALF = 0x2592, |
||||
PIXEL_QUARTER = 0x2591, |
||||
}; |
||||
|
||||
class olcSprite |
||||
{ |
||||
public: |
||||
olcSprite() |
||||
{ |
||||
|
||||
} |
||||
|
||||
olcSprite(int w, int h) |
||||
{ |
||||
Create(w, h); |
||||
} |
||||
|
||||
olcSprite(wstring sFile) |
||||
{ |
||||
if (!Load(sFile)) |
||||
Create(8, 8); |
||||
} |
||||
|
||||
int nWidth = 0; |
||||
int nHeight = 0; |
||||
|
||||
private: |
||||
wchar_t *m_Glyphs = nullptr; |
||||
short *m_Colours = nullptr; |
||||
|
||||
void Create(int w, int h) |
||||
{ |
||||
nWidth = w; |
||||
nHeight = h; |
||||
m_Glyphs = new wchar_t[w*h]; |
||||
m_Colours = new short[w*h]; |
||||
for (int i = 0; i < w*h; i++) |
||||
{ |
||||
m_Glyphs[i] = L' '; |
||||
m_Colours[i] = FG_BLACK; |
||||
} |
||||
} |
||||
|
||||
public: |
||||
void SetGlyph(int x, int y, wchar_t c) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return; |
||||
else |
||||
m_Glyphs[y * nWidth + x] = c; |
||||
} |
||||
|
||||
void SetColour(int x, int y, short c) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return; |
||||
else |
||||
m_Colours[y * nWidth + x] = c; |
||||
} |
||||
|
||||
wchar_t GetGlyph(int x, int y) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return L' '; |
||||
else |
||||
return m_Glyphs[y * nWidth + x]; |
||||
} |
||||
|
||||
short GetColour(int x, int y) |
||||
{ |
||||
if (x <0 || x >= nWidth || y < 0 || y >= nHeight) |
||||
return FG_BLACK; |
||||
else |
||||
return m_Colours[y * nWidth + x]; |
||||
} |
||||
|
||||
wchar_t SampleGlyph(float x, float y) |
||||
{ |
||||
int sx = (int)(x * (float)nWidth); |
||||
int sy = (int)(y * (float)nHeight - 1.0f); |
||||
if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) |
||||
return L' '; |
||||
else |
||||
return m_Glyphs[sy * nWidth + sx]; |
||||
} |
||||
|
||||
short SampleColour(float x, float y) |
||||
{ |
||||
int sx = (int)(x * (float)nWidth); |
||||
int sy = (int)(y * (float)nHeight - 1.0f); |
||||
if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) |
||||
return FG_BLACK; |
||||
else |
||||
return m_Colours[sy * nWidth + sx]; |
||||
} |
||||
|
||||
bool Save(wstring sFile) |
||||
{ |
||||
FILE *f = nullptr; |
||||
_wfopen_s(&f, sFile.c_str(), L"wb"); |
||||
if (f == nullptr) |
||||
return false; |
||||
|
||||
fwrite(&nWidth, sizeof(int), 1, f); |
||||
fwrite(&nHeight, sizeof(int), 1, f); |
||||
fwrite(m_Colours, sizeof(short), nWidth * nHeight, f); |
||||
fwrite(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); |
||||
|
||||
fclose(f); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool Load(wstring sFile) |
||||
{ |
||||
delete[] m_Glyphs; |
||||
delete[] m_Colours; |
||||
nWidth = 0; |
||||
nHeight = 0; |
||||
|
||||
FILE *f = nullptr; |
||||
_wfopen_s(&f, sFile.c_str(), L"rb"); |
||||
if (f == nullptr) |
||||
return false; |
||||
|
||||
fread(&nWidth, sizeof(int), 1, f); |
||||
fread(&nHeight, sizeof(int), 1, f); |
||||
|
||||
Create(nWidth, nHeight); |
||||
|
||||
fread(m_Colours, sizeof(short), nWidth * nHeight, f); |
||||
fread(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); |
||||
|
||||
fclose(f); |
||||
return true; |
||||
} |
||||
|
||||
bool LoadFromResource(unsigned int id) |
||||
{ |
||||
HRSRC res = FindResource(NULL, MAKEINTRESOURCE(id), RT_RCDATA); |
||||
if (!res) return false; |
||||
|
||||
HGLOBAL res_handle = LoadResource(NULL, res); |
||||
if (!res_handle) return false; |
||||
|
||||
char* res_data = (char*)LockResource(res_handle); |
||||
DWORD res_size = SizeofResource(NULL, res); |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
istringstream iss(res_data); |
||||
istream *is = &iss; |
||||
|
||||
delete[] m_Glyphs; |
||||
delete[] m_Colours; |
||||
nWidth = 0; |
||||
nHeight = 0; |
||||
|
||||
is->read((char*)&nWidth, sizeof(int) * 1); |
||||
is->read((char*)&nHeight, sizeof(int) * 1); |
||||
|
||||
Create(nWidth, nHeight); |
||||
|
||||
iss.read((char*)m_Colours, sizeof(short) * nWidth * nHeight);
|
||||
iss.read((char*)m_Glyphs, sizeof(wchar_t) * nWidth * nHeight); |
||||
|
||||
|
||||
return true; |
||||
} |
||||
|
||||
}; |
||||
|
||||
|
||||
class olcConsoleGameEngineOOP |
||||
{ |
||||
public: |
||||
olcConsoleGameEngineOOP(); |
||||
~olcConsoleGameEngineOOP(); |
||||
|
||||
public: |
||||
int ConstructConsole(int width, int height, int fontw, int fonth); |
||||
void Start(); |
||||
|
||||
public: |
||||
virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F); |
||||
void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawString(int x, int y, wstring c, short col = 0x000F); |
||||
void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F); |
||||
void Clip(int &x, int &y); |
||||
void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); |
||||
void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); |
||||
void DrawSprite(int x, int y, olcSprite *sprite); |
||||
void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h); |
||||
void DrawWireFrameModel(const vector<pair<float, float>> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, wchar_t c = PIXEL_SOLID); |
||||
int ScreenWidth(); |
||||
int ScreenHeight(); |
||||
|
||||
private: |
||||
void GameThread(); |
||||
|
||||
protected: |
||||
// User MUST OVERRIDE THESE!!
|
||||
virtual bool OnUserCreate() = 0; |
||||
virtual bool OnUserUpdate(float fElapsedTime) = 0; |
||||
|
||||
// Optional for clean up
|
||||
virtual bool OnUserDestroy(); |
||||
|
||||
|
||||
int Error(const wchar_t *msg); |
||||
static BOOL CloseHandler(DWORD evt); |
||||
|
||||
protected: |
||||
|
||||
|
||||
struct sKeyState |
||||
{ |
||||
bool bPressed; |
||||
bool bReleased; |
||||
bool bHeld; |
||||
} m_keys[256], m_mouse[5]; |
||||
|
||||
int m_mousePosX; |
||||
int m_mousePosY; |
||||
|
||||
public: |
||||
sKeyState GetKey(int nKeyID) { return m_keys[nKeyID]; } |
||||
int GetMouseX() { return m_mousePosX; } |
||||
int GetMouseY() { return m_mousePosY; } |
||||
sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; } |
||||
bool IsFocused() { return m_bConsoleInFocus; } |
||||
|
||||
|
||||
protected: |
||||
class olcAudioSample |
||||
{ |
||||
public: |
||||
olcAudioSample() |
||||
{ |
||||
|
||||
} |
||||
|
||||
olcAudioSample(std::wstring sWavFile) |
||||
{ |
||||
// Load Wav file and convert to float format
|
||||
FILE *f = nullptr; |
||||
_wfopen_s(&f, sWavFile.c_str(), L"rb"); |
||||
if (f == nullptr) |
||||
return; |
||||
|
||||
char dump[4]; |
||||
std::fread(&dump, sizeof(char), 4, f); // Read "RIFF"
|
||||
if (strncmp(dump, "RIFF", 4) != 0) return; |
||||
std::fread(&dump, sizeof(char), 4, f); // Not Interested
|
||||
std::fread(&dump, sizeof(char), 4, f); // Read "WAVE"
|
||||
if (strncmp(dump, "WAVE", 4) != 0) return; |
||||
|
||||
// Read Wave description chunk
|
||||
std::fread(&dump, sizeof(char), 4, f); // Read "fmt "
|
||||
std::fread(&dump, sizeof(char), 4, f); // Not Interested
|
||||
std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk
|
||||
// Note the -2, because the structure has 2 bytes to indicate its own size
|
||||
// which are not in the wav file
|
||||
|
||||
// Just check if wave format is compatible with olcCGE
|
||||
if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) |
||||
{ |
||||
std::fclose(f); |
||||
return; |
||||
} |
||||
|
||||
// Search for audio data chunk
|
||||
long nChunksize = 0; |
||||
std::fread(&dump, sizeof(char), 4, f); // Read chunk header
|
||||
std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size
|
||||
while (strncmp(dump, "data", 4) != 0) |
||||
{ |
||||
// Not audio data, so just skip it
|
||||
std::fseek(f, nChunksize, SEEK_CUR); |
||||
std::fread(&dump, sizeof(char), 4, f); |
||||
std::fread(&nChunksize, sizeof(long), 1, f); |
||||
} |
||||
|
||||
// Finally got to data, so read it all in and convert to float samples
|
||||
nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); |
||||
nChannels = wavHeader.nChannels; |
||||
|
||||
// Create floating point buffer to hold audio sample
|
||||
fSample = new float[nSamples * nChannels]; |
||||
float *pSample = fSample; |
||||
|
||||
// Read in audio data and normalise
|
||||
for (long i = 0; i < nSamples; i++) |
||||
{ |
||||
for (int c = 0; c < nChannels; c++) |
||||
{ |
||||
short s = 0; |
||||
std::fread(&s, sizeof(short), 1, f); |
||||
*pSample = (float)s / (float)(MAXSHORT); |
||||
pSample++; |
||||
} |
||||
} |
||||
|
||||
// All done, flag sound as valid
|
||||
std::fclose(f); |
||||
bSampleValid = true; |
||||
} |
||||
|
||||
WAVEFORMATEX wavHeader; |
||||
float *fSample = nullptr; |
||||
long nSamples = 0; |
||||
int nChannels = 0; |
||||
bool bSampleValid = false; |
||||
}; |
||||
|
||||
// This vector holds all loaded sound samples in memory
|
||||
std::vector<olcAudioSample> vecAudioSamples; |
||||
|
||||
// This structure represents a sound that is currently playing. It only
|
||||
// holds the sound ID and where this instance of it is up to for its
|
||||
// current playback
|
||||
struct sCurrentlyPlayingSample |
||||
{ |
||||
int nAudioSampleID = 0; |
||||
long nSamplePosition = 0; |
||||
bool bFinished = false; |
||||
bool bLoop = false; |
||||
}; |
||||
std::list<sCurrentlyPlayingSample> listActiveSamples; |
||||
|
||||
// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID
|
||||
// number is returned if successful, otherwise -1
|
||||
unsigned int LoadAudioSample(std::wstring sWavFile); |
||||
|
||||
// Add sample 'id' to the mixers sounds to play list
|
||||
void PlaySample(int id, bool bLoop = false); |
||||
|
||||
void StopSample(int id); |
||||
|
||||
// The audio system uses by default a specific wave format
|
||||
bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, |
||||
unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); |
||||
|
||||
// Stop and clean up audio system
|
||||
bool DestroyAudio(); |
||||
|
||||
// Handler for soundcard request for more data
|
||||
void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); |
||||
|
||||
// Static wrapper for sound card handler
|
||||
static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); |
||||
|
||||
// Audio thread. This loop responds to requests from the soundcard to fill 'blocks'
|
||||
// with audio data. If no requests are available it goes dormant until the sound
|
||||
// card is ready for more data. The block is fille by the "user" in some manner
|
||||
// and then issued to the soundcard.
|
||||
void AudioThread(); |
||||
|
||||
// Overridden by user if they want to generate sound in real-time
|
||||
virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep); |
||||
|
||||
// Overriden by user if they want to manipulate the sound before it is played
|
||||
virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample); |
||||
|
||||
// The Sound Mixer - If the user wants to play many sounds simultaneously, and
|
||||
// perhaps the same sound overlapping itself, then you need a mixer, which
|
||||
// takes input from all sound sources for that audio frame. This mixer maintains
|
||||
// a list of sound locations for all concurrently playing audio samples. Instead
|
||||
// of duplicating audio data, we simply store the fact that a sound sample is in
|
||||
// use and an offset into its sample data. As time progresses we update this offset
|
||||
// until it is beyound the length of the sound sample it is attached to. At this
|
||||
// point we remove the playing souind from the list.
|
||||
//
|
||||
// Additionally, the users application may want to generate sound instead of just
|
||||
// playing audio clips (think a synthesizer for example) in whcih case we also
|
||||
// provide an "onUser..." event to allow the user to return a sound for that point
|
||||
// in time.
|
||||
//
|
||||
// Finally, before the sound is issued to the operating system for performing, the
|
||||
// user gets one final chance to "filter" the sound, perhaps changing the volume
|
||||
// or adding funky effects
|
||||
float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); |
||||
|
||||
protected: |
||||
int m_nScreenWidth; |
||||
int m_nScreenHeight; |
||||
CHAR_INFO *m_bufScreen; |
||||
wstring m_sAppName; |
||||
HANDLE m_hOriginalConsole; |
||||
CONSOLE_SCREEN_BUFFER_INFO m_OriginalConsoleInfo; |
||||
HANDLE m_hConsole; |
||||
HANDLE m_hConsoleIn; |
||||
SMALL_RECT m_rectWindow; |
||||
short m_keyOldState[256] = { 0 }; |
||||
short m_keyNewState[256] = { 0 }; |
||||
bool m_mouseOldState[5] = { 0 }; |
||||
bool m_mouseNewState[5] = { 0 }; |
||||
bool m_bConsoleInFocus = true; |
||||
|
||||
bool m_bEnableSound = false; |
||||
unsigned int m_nSampleRate; |
||||
unsigned int m_nChannels; |
||||
unsigned int m_nBlockCount; |
||||
unsigned int m_nBlockSamples; |
||||
unsigned int m_nBlockCurrent; |
||||
short* m_pBlockMemory = nullptr; |
||||
WAVEHDR *m_pWaveHeaders = nullptr; |
||||
HWAVEOUT m_hwDevice = nullptr; |
||||
std::thread m_AudioThread; |
||||
std::atomic<bool> m_bAudioThreadActive = false; |
||||
std::atomic<unsigned int> m_nBlockFree = 0; |
||||
std::condition_variable m_cvBlockNotZero; |
||||
std::mutex m_muxBlockNotZero; |
||||
std::atomic<float> m_fGlobalTime = 0.0f; |
||||
|
||||
static atomic<bool> m_bAtomActive; |
||||
static condition_variable m_cvGameFinished; |
||||
static mutex m_muxGame; |
||||
}; |
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue