tibia-rme/source/about_window.cpp
Eduardo Dantas 2057c5203b
improve: improve: replacing boosts with std functions (#415)
Removed boosts (replaced with equivalent on std 14/17/20)

With this modification, there are only 16 libs for the project to add, simplifying a lot.

So two additional libraries will be needed to compile: asio,
nlohmann-json

I also removed OTGZ support by default and disabled it, making it optional (needs to be enabled). Anyone who wants to use it will need to activate this, I think it's ideal since few should use it, so a majority don't use it, which is fairer that those who need to activate it to use it than those who don't need to have this in their program.
2023-03-25 21:42:38 -03:00

878 lines
19 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor 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.
//
// Remere's Map Editor 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 "main.h"
#include "gui.h"
#include "about_window.h"
#include <fstream>
#include <typeinfo>
#include <memory>
class GamePanel : public wxPanel
{
public:
GamePanel(wxWindow* parent, int width, int height);
virtual ~GamePanel();
void OnPaint(wxPaintEvent&);
void OnKeyDown(wxKeyEvent&);
void OnKeyUp(wxKeyEvent&);
void OnIdle(wxIdleEvent&);
void pause() { paused_val = true; }
void unpause() { paused_val = false; }
bool paused() const { return paused_val || dead; }
protected:
virtual void Render(wxDC& pdc) = 0;
virtual void GameLoop(int time) = 0;
virtual void OnKey(wxKeyEvent& event, bool down) = 0;
virtual int getFPS() const = 0;
protected:
wxStopWatch game_timer;
private:
bool paused_val;
DECLARE_EVENT_TABLE()
protected:
bool dead;
};
const int TETRIS_MAPHEIGHT = 20;
const int TETRIS_MAPWIDTH = 10;
class TetrisPanel : public GamePanel
{
public:
TetrisPanel(wxWindow* parent);
~TetrisPanel();
protected:
virtual void Render(wxDC& pdc);
virtual void GameLoop(int time);
virtual void OnKey(wxKeyEvent& event, bool down);
virtual int getFPS() const { return lines / 10 + 3; }
enum Color {
NO_COLOR,
RED,
BLUE,
GREEN,
STEEL,
YELLOW,
PURPLE,
WHITE,
};
enum BlockType {
FIRST_BLOCK,
BLOCK_TOWER = FIRST_BLOCK,
BLOCK_SQUARE,
BLOCK_TRIANGLE,
BLOCK_L,
BLOCK_J,
BLOCK_Z,
BLOCK_S,
LAST_BLOCK = BLOCK_S
};
struct Block {
Color structure[4][4];
int x, y;
} block;
const wxBrush& GetBrush(Color color) const;
bool BlockCollisionTest(int mx, int my) const;
void RemoveRow(int row);
void NewBlock();
void MoveBlock(int x, int y);
void RotateBlock();
void NewGame();
void EndGame();
void AddScore(int lines);
int score;
int lines;
Color map[TETRIS_MAPWIDTH][TETRIS_MAPHEIGHT];
};
const int SNAKE_MAPHEIGHT = 20;
const int SNAKE_MAPWIDTH = 20;
class SnakePanel : public GamePanel
{
public:
SnakePanel(wxWindow* parent);
~SnakePanel();
protected:
virtual void Render(wxDC& pdc);
virtual void GameLoop(int time);
virtual void OnKey(wxKeyEvent& event, bool down);
virtual int getFPS() const { return 7; }
enum {
NORTH,
SOUTH,
WEST,
EAST,
};
void NewApple();
void Move(int dir);
void NewGame();
void EndGame();
void UpdateTitle();
// -1 is apple, 0 is nothing, >0 is snake (will decay in n rounds)
int length;
int last_dir;
int map[SNAKE_MAPWIDTH][SNAKE_MAPHEIGHT];
};
//=============================================================================
// About Window - Information window about the application
BEGIN_EVENT_TABLE(AboutWindow, wxDialog)
EVT_BUTTON(wxID_OK, AboutWindow::OnClickOK)
EVT_BUTTON(ABOUT_VIEW_LICENSE, AboutWindow::OnClickLicense)
EVT_MENU(ABOUT_RUN_TETRIS, AboutWindow::OnTetris)
EVT_MENU(ABOUT_RUN_SNAKE, AboutWindow::OnSnake)
EVT_MENU(wxID_CANCEL, AboutWindow::OnClickOK)
END_EVENT_TABLE()
AboutWindow::AboutWindow(wxWindow* parent) :
wxDialog(parent, wxID_ANY, "About", wxDefaultPosition, wxSize(300, 320), wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX),
game_panel(nullptr)
{
wxString about;
std::string compiler;
#if defined(__clang__)
compiler = fmt::format("Clang++ {}.{}.{}", __clang_major__, __clang_minor__, __clang_patchlevel__);
#elif defined(_MSC_VER)
compiler = fmt::format("Microsoft Visual Studio {}", _MSC_VER);
#elif defined(__GNUC__)
compiler = fmt::format("G++ {}.{}.{}", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#else
compiler = "unknown";
#endif
about << "This is an OpenTibia Map Editor created by Remere.\n";
about << "Version " << __W_RME_VERSION__ << " for ";
about <<
#ifdef __WINDOWS__
"Windows";
#elif __LINUX__
"Linux";
#elif __APPLE__
"macOS";
#else
"Unsupported OS";
#endif
about << "\n\n";
about << "Using " << wxVERSION_STRING << " interface\n";
about << "OpenGL version " << wxString((char*)glGetString(GL_VERSION), wxConvUTF8) << "\n";
about << "\n";
about << "This program comes with ABSOLUTELY NO WARRANTY;\n";
about << "for details see the LICENSE file.\n";
about << "This is free software, and you are welcome to redistribute it\n";
about << "under certain conditions.\n";
about << "\n";
about << "Compiled on: " << __TDATE__ << " : " << __TTIME__ << "\n";
about << "Compiled with: " << compiler << "\n";
topsizer = newd wxBoxSizer(wxVERTICAL);
topsizer->Add(newd wxStaticText(this, wxID_ANY, about), 1, wxALL, 20);
wxSizer* choicesizer = newd wxBoxSizer(wxHORIZONTAL);
choicesizer->Add(newd wxButton(this, wxID_OK, "OK"), wxSizerFlags(1).Center());
topsizer->Add(choicesizer, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT | wxBOTTOM, 20);
wxAcceleratorEntry entries[3];
entries[0].Set(wxACCEL_NORMAL, WXK_ESCAPE, wxID_CANCEL);
entries[1].Set(wxACCEL_NORMAL, 't', ABOUT_RUN_TETRIS);
entries[2].Set(wxACCEL_NORMAL, 's', ABOUT_RUN_SNAKE);
wxAcceleratorTable accel(3, entries);
SetAcceleratorTable(accel);
SetSizerAndFit(topsizer);
Centre(wxBOTH);
}
AboutWindow::~AboutWindow()
{
////
}
void AboutWindow::OnClickOK(wxCommandEvent& WXUNUSED(event))
{
EndModal(0);
}
void AboutWindow::OnClickLicense(wxCommandEvent& WXUNUSED(event))
{
FileName path;
try {
path = wxStandardPaths::Get().GetExecutablePath();
} catch(std::bad_cast&) {
return;
}
path.SetFullName("COPYING.txt");
std::ifstream gpl(path.GetFullPath().mb_str());
std::string gpl_str;
char ch;
while(gpl.get(ch)) {
gpl_str += ch;
}
g_gui.ShowTextBox(this, "License", wxstr(gpl_str.size()? gpl_str : "The COPYING.txt file is not available."));
}
void AboutWindow::OnTetris(wxCommandEvent&)
{
if(!game_panel) {
DestroyChildren();
game_panel = newd TetrisPanel(this);
topsizer->Add(game_panel, 1, wxALIGN_CENTER | wxALL, 7);
Fit();
game_panel->SetFocus();
SetWindowStyleFlag(wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX);
Refresh();
}
}
void AboutWindow::OnSnake(wxCommandEvent&)
{
if(!game_panel) {
DestroyChildren();
game_panel = newd SnakePanel(this);
topsizer->Add(game_panel, 1, wxALIGN_CENTER | wxALL, 7);
Fit();
game_panel->SetFocus();
SetWindowStyleFlag(wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX);
Refresh();
}
}
//=============================================================================
// GamePanel - Abstract class for games
BEGIN_EVENT_TABLE(GamePanel, wxPanel)
EVT_KEY_DOWN(GamePanel::OnKeyDown)
EVT_KEY_UP(GamePanel::OnKeyUp)
EVT_PAINT(GamePanel::OnPaint)
EVT_IDLE(GamePanel::OnIdle)
END_EVENT_TABLE()
GamePanel::GamePanel(wxWindow *parent, int width, int height) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(width, height), wxWANTS_CHARS),
paused_val(false),
dead(false)
{
// Receive idle events
SetExtraStyle(wxWS_EX_PROCESS_IDLE);
// Complete redraw
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
}
GamePanel::~GamePanel()
{
////
}
void GamePanel::OnPaint(wxPaintEvent&)
{
wxBufferedPaintDC pdc(this);
Render(pdc);
}
void GamePanel::OnKeyDown(wxKeyEvent& event)
{
switch(event.GetKeyCode()) {
case WXK_ESCAPE: {
if(dead) return;
wxDialog* dlg = (wxDialog*)GetParent();
dlg->EndModal(0);
break;
}
default: {
OnKey(event, true);
break;
}
}
}
void GamePanel::OnKeyUp(wxKeyEvent& event)
{
OnKey(event, false);
}
void GamePanel::OnIdle(wxIdleEvent& event)
{
int time = game_timer.Time();
if(time > 1000 / getFPS()) {
game_timer.Start();
if(!paused()) GameLoop(time);
}
if(!paused()) {
event.RequestMore(true);
}
}
//=============================================================================
// TetrisPanel - A window with a Tetris game!
TetrisPanel::TetrisPanel(wxWindow* parent) : GamePanel(parent, 16*TETRIS_MAPWIDTH, 16*TETRIS_MAPHEIGHT)
{
NewGame();
}
TetrisPanel::~TetrisPanel()
{
////
}
const wxBrush& TetrisPanel::GetBrush(Color color) const
{
static std::unique_ptr<wxBrush> yellow_brush;
static std::unique_ptr<wxBrush> purple_brush;
if(yellow_brush.get() == nullptr) yellow_brush.reset(newd wxBrush(wxColor(255, 255, 0)));
if(purple_brush.get() == nullptr) purple_brush.reset(newd wxBrush(wxColor(128, 0, 255)));
const wxBrush* brush = nullptr;
switch(color) {
case RED: brush = wxRED_BRUSH; break;
case BLUE: brush = wxCYAN_BRUSH; break;
case GREEN: brush = wxGREEN_BRUSH; break;
case PURPLE: brush = purple_brush.get(); break;
case YELLOW: brush = yellow_brush.get(); break;
case WHITE: brush = wxWHITE_BRUSH; break;
case STEEL: brush = wxGREY_BRUSH; break;
default: brush = wxBLACK_BRUSH; break;
}
return *brush;
}
void TetrisPanel::Render(wxDC& pdc)
{
pdc.SetBackground(*wxBLACK_BRUSH);
pdc.Clear();
for(int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
for(int x = 0; x < TETRIS_MAPWIDTH; ++x) {
pdc.SetBrush(GetBrush(map[x][y]));
pdc.DrawRectangle(x * 16, y * 16, 16, 16);
}
}
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
if(block.structure[x][y] != NO_COLOR) {
pdc.SetBrush(GetBrush(block.structure[x][y]));
pdc.DrawRectangle((block.x + x) * 16, (block.y + y) * 16, 16, 16);
}
}
}
}
void TetrisPanel::OnKey(wxKeyEvent& event, bool down)
{
if(!down || dead) return;
switch(event.GetKeyCode()) {
case WXK_SPACE: {
if(paused())
unpause();
else pause();
break;
}
case WXK_NUMPAD_UP:
case WXK_UP: {
if(dead) return;
unpause();
RotateBlock();
break;
}
case WXK_NUMPAD_DOWN:
case WXK_DOWN: {
if(dead) return;
unpause();
MoveBlock(0, 1);
break;
}
case WXK_NUMPAD_LEFT:
case WXK_LEFT: {
if(dead) return;
unpause();
MoveBlock(-1, 0);
break;
}
case WXK_NUMPAD_RIGHT:
case WXK_RIGHT: {
if(dead) return;
unpause();
MoveBlock(1, 0);
break;
}
}
}
void TetrisPanel::NewGame()
{
NewBlock();
score = 0;
lines = 0;
game_timer.Start();
unpause();
dead = false;
// Clear map
for(int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
for(int x = 0; x < TETRIS_MAPWIDTH; ++x) {
map[x][y] = NO_COLOR;
}
}
AddScore(0); // Update title
}
void TetrisPanel::AddScore(int lines_added)
{
lines += lines_added;
score += lines_added*lines_added * 10;
wxString title = "Remere's Tetris : ";
title << score << " points ";
title << lines << " lines";
((wxTopLevelWindow*)GetParent())->SetTitle(title);
}
void TetrisPanel::GameLoop(int time)
{
MoveBlock(0, 1);
}
bool TetrisPanel::BlockCollisionTest(int mx, int my) const
{
int nx = block.x + mx;
int ny = block.y + my;
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
if(block.structure[x][y] != NO_COLOR) {
if(nx + x < 0 || nx + x > TETRIS_MAPWIDTH - 1 || ny + y < 0 || ny + y > TETRIS_MAPHEIGHT - 1) {
return true;
}
}
}
}
for(int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
for(int x = 0; x < TETRIS_MAPWIDTH; ++x) {
if(x >= nx && x < nx + 4 && y >= ny && y < ny + 4) {
if(map[x][y] != NO_COLOR && block.structure[x - nx][y - ny] != NO_COLOR) {
return true;
}
}
}
}
return false;
}
void TetrisPanel::RemoveRow(int row)
{
for(int x = 0; x < TETRIS_MAPWIDTH; ++x) {
for(int y = row; y > 0; --y) { //Move all above one step down
map[x][y] = map[x][y-1];
}
}
}
void TetrisPanel::NewBlock()
{
block.x = TETRIS_MAPWIDTH / 2;
block.y = -1;
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
block.structure[x][y] = NO_COLOR;
}
}
switch(random(FIRST_BLOCK, LAST_BLOCK)) {
case BLOCK_TOWER: {
block.structure[1][0] = RED;
block.structure[1][1] = RED;
block.structure[1][2] = RED;
block.structure[1][3] = RED;
block.y = 0;
break;
}
default: case BLOCK_SQUARE: {
block.structure[1][1] = BLUE;
block.structure[1][2] = BLUE;
block.structure[2][1] = BLUE;
block.structure[2][2] = BLUE;
break;
}
case BLOCK_TRIANGLE: {
block.structure[1][1] = STEEL;
block.structure[0][2] = STEEL;
block.structure[1][2] = STEEL;
block.structure[2][2] = STEEL;
break;
}
case BLOCK_Z: {
block.structure[0][1] = YELLOW;
block.structure[1][1] = YELLOW;
block.structure[1][2] = YELLOW;
block.structure[2][2] = YELLOW;
break;
}
case BLOCK_S: {
block.structure[2][1] = GREEN;
block.structure[1][1] = GREEN;
block.structure[1][2] = GREEN;
block.structure[0][2] = GREEN;
break;
}
case BLOCK_J: {
block.structure[1][1] = WHITE;
block.structure[2][1] = WHITE;
block.structure[2][2] = WHITE;
block.structure[2][3] = WHITE;
break;
}
case BLOCK_L: {
block.structure[2][1] = PURPLE;
block.structure[1][1] = PURPLE;
block.structure[1][2] = PURPLE;
block.structure[1][3] = PURPLE;
// break; missing add it?
}
}
}
void TetrisPanel::MoveBlock(int x, int y)
{
if(BlockCollisionTest(x, y)) {
if(y == 1) { // moving down...
if(block.y < 1) { // Out of bounds!
dead = true;
g_gui.PopupDialog("Game Over", "You reached a score of " + i2ws(score) + "!", wxOK);
NewGame();
SetFocus();
} else {
// Freeze the old block onto the map
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
if(block.structure[x][y] != NO_COLOR) {
map[block.x+x][block.y+y] = block.structure[x][y];
}
}
}
// Any cleared rows?
int cleared = 0;
for(int y = 0; y < TETRIS_MAPHEIGHT; ++y) {
bool full = true;
for(int x = 0; x < TETRIS_MAPWIDTH; ++x) {
if(map[x][y] == NO_COLOR) {
full = false;
}
}
if(full) {
RemoveRow(y);
++cleared;
}
}
AddScore(cleared);
NewBlock();
}
} // If we're not moving down, we're not moving the block either
} else {
// No collision so move the block!
block.x += x;
block.y += y;
}
Refresh();
}
void TetrisPanel::RotateBlock()
{
Block temp;
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
temp.structure[3-x][y] = block.structure[y][x];
}
}
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
if(temp.structure[x][y] != NO_COLOR) {
if(block.x + x < 0 || block.x + x > TETRIS_MAPWIDTH - 1 || block.y + y < 0 || block.y + y > TETRIS_MAPHEIGHT - 1) {
return;
}
}
}
}
for(int y = 0; y < TETRIS_MAPWIDTH; ++y) {
for(int x = 0; x < TETRIS_MAPHEIGHT; ++x) {
if(x >= block.x && x < block.x + 4 && y >= block.y && y < block.y +4) {
if(map[x][y] != NO_COLOR && temp.structure[x - block.x][y - block.y] != NO_COLOR) {
return;
}
}
}
}
for(int y = 0; y < 4; ++y) {
for(int x = 0; x < 4; ++x) {
block.structure[x][y] = temp.structure[x][y];
}
}
Refresh();
}
//=============================================================================
// SnakePanel - A window with a Snake game!
SnakePanel::SnakePanel(wxWindow* parent) : GamePanel(parent, 16*SNAKE_MAPWIDTH, 16*SNAKE_MAPHEIGHT)
{
NewGame();
}
SnakePanel::~SnakePanel()
{
////
}
void SnakePanel::Render(wxDC& pdc)
{
pdc.SetBackground(*wxBLACK_BRUSH);
pdc.Clear();
wxBrush snakebrush(wxColor(0, 0, 255));
wxBrush applebrush(wxColor(255, 0, 0));
double lblue = 1.0;
double lred = 0.5;
double lgreen = 0.0;
for(int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
for(int x = 0; x < SNAKE_MAPWIDTH; ++x) {
if(map[x][y] == -1) { // Apple
pdc.SetBrush(applebrush);
pdc.DrawRectangle(x*16, y*16, 16, 16);
} else if(map[x][y] > 0) { // Snake
double snook = double(map[x][y]) / length;
snakebrush.SetColour(wxColor(
int(255.0 * (1.0 - abs(lred - snook))),
int(255.0 * (1.0 - abs(lgreen - snook))),
int(255.0 * (1.0 - abs(lblue - snook)))
)
);
pdc.SetBrush(snakebrush);
pdc.DrawRectangle(x*16, y*16, 16, 16);
}
}
}
}
void SnakePanel::OnKey(wxKeyEvent& event, bool down)
{
if(!down) return;
int keyCode = event.GetKeyCode();
if(keyCode == WXK_SPACE) {
if(paused())
unpause();
else
pause();
} else if(!dead) {
switch (keyCode) {
case WXK_NUMPAD_UP:
case WXK_UP: {
unpause();
Move(NORTH);
break;
}
case WXK_NUMPAD_DOWN:
case WXK_DOWN: {
unpause();
Move(SOUTH);
break;
}
case WXK_NUMPAD_LEFT:
case WXK_LEFT: {
unpause();
Move(WEST);
break;
}
case WXK_NUMPAD_RIGHT:
case WXK_RIGHT: {
unpause();
Move(EAST);
break;
}
}
}
}
void SnakePanel::NewGame()
{
length = 3;
game_timer.Start();
last_dir = NORTH;
unpause();
dead = false;
// Clear map
for(int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
for(int x = 0; x < SNAKE_MAPWIDTH; ++x) {
map[x][y] = 0;
}
}
map[SNAKE_MAPWIDTH / 2][SNAKE_MAPHEIGHT / 2] = length;
NewApple();
UpdateTitle(); // Update title
}
void SnakePanel::UpdateTitle()
{
wxString title = "Remere's Snake : ";
title << length << " segments";
((wxTopLevelWindow*)GetParent())->SetTitle(title);
}
void SnakePanel::GameLoop(int time)
{
Move(last_dir);
}
void SnakePanel::NewApple()
{
bool possible = false;
for(int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
for(int x = 0; x < SNAKE_MAPWIDTH; ++x) {
if(map[x][y] == 0) {
possible = true;
}
if(possible) break;
}
if(possible) break;
}
if(possible) {
while(true) {
int x = random(0, SNAKE_MAPWIDTH - 1);
int y = random(0, SNAKE_MAPHEIGHT - 1);
if(map[x][y] == 0) {
map[x][y] = -1;
break;
}
}
}
}
void SnakePanel::Move(int dir)
{
if((last_dir == NORTH && dir == SOUTH) ||
(last_dir == WEST && dir == EAST) ||
(last_dir == EAST && dir == WEST) ||
(last_dir == SOUTH && dir == NORTH)) {
return;
}
int nx = 0, ny = 0;
int head_x = 0, head_y = 0;
for(int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
for(int x = 0; x < SNAKE_MAPWIDTH; ++x) {
if(map[x][y] == length) {
head_x = x;
head_y = y;
}
}
}
switch(dir) {
case NORTH: {
nx = head_x;
ny = head_y - 1;
break;
}
case SOUTH: {
nx = head_x;
ny = head_y + 1;
break;
}
case WEST: {
nx = head_x - 1;
ny = head_y;
break;
}
case EAST: {
nx = head_x + 1;
ny = head_y;
break;
}
default:
return;
}
if(map[nx][ny] > 0 || nx < 0 || ny < 0 || nx >= SNAKE_MAPWIDTH || ny >= SNAKE_MAPHEIGHT) {
// Crash
dead = true;
g_gui.PopupDialog("Game Over", "You reached a length of " + i2ws(length) + "!", wxOK);
NewGame();
SetFocus();
} else {
// Walk!
if(map[nx][ny] == -1) {
// Took apple!
length += 1;
UpdateTitle();
NewApple();
} else {
for(int y = 0; y < SNAKE_MAPHEIGHT; ++y) {
for(int x = 0; x < SNAKE_MAPWIDTH; ++x) {
if(map[x][y] > 0) {
map[x][y] -= 1;
}
}
}
}
map[nx][ny] = length;
}
last_dir = dir;
Refresh();
}