Commit f9b63108 authored by Unicorn369's avatar Unicorn369 Committed by fallenstardust

add sound_openal (by kevinlul)

parent b538dc43
#include "sound_manager.h"
#include "config.h"
#ifdef IRRKLANG_STATIC
#include "../ikpmp3/ikpMP3.h"
#endif
namespace ygo {
bool SoundManager::Init(double sounds_volume, double music_volume, bool sounds_enabled, bool music_enabled, void* payload) {
soundsEnabled = sounds_enabled;
musicEnabled = music_enabled;
rnd.seed(time(0));
bgm_scene = -1;
RefreshBGMList();
RefreshChantsList();
#ifdef YGOPRO_USE_IRRKLANG
soundEngine = irrklang::createIrrKlangDevice();
if(!soundEngine) {
return soundsEnabled = musicEnabled = false;
} else {
#ifdef IRRKLANG_STATIC
irrklang::ikpMP3Init(soundEngine);
#endif
sfxVolume = sounds_volume;
bgmVolume = music_volume;
return true;
}
#else
try {
openal = std::make_unique<YGOpen::OpenALSingleton>();
sfx = std::make_unique<YGOpen::OpenALSoundLayer>(openal);
bgm = std::make_unique<YGOpen::OpenALSoundLayer>(openal);
sfx->setVolume(sounds_volume);
bgm->setVolume(music_volume);
return true;
}
catch (std::runtime_error& e) {
return soundsEnabled = musicEnabled = false;
}
#endif // YGOPRO_USE_IRRKLANG
}
SoundManager::~SoundManager() {
#ifdef YGOPRO_USE_IRRKLANG
if (soundBGM)
soundBGM->drop();
if (soundEngine)
soundEngine->drop();
#endif
}
void SoundManager::RefreshBGMList() {
Utils::Makedirectory(TEXT("./sound/BGM/"));
Utils::Makedirectory(TEXT("./sound/BGM/duel"));
Utils::Makedirectory(TEXT("./sound/BGM/menu"));
Utils::Makedirectory(TEXT("./sound/BGM/deck"));
Utils::Makedirectory(TEXT("./sound/BGM/advantage"));
Utils::Makedirectory(TEXT("./sound/BGM/disadvantage"));
Utils::Makedirectory(TEXT("./sound/BGM/win"));
Utils::Makedirectory(TEXT("./sound/BGM/lose"));
Utils::Makedirectory(TEXT("./sound/chants"));
RefreshBGMDir(TEXT(""), BGM::DUEL);
RefreshBGMDir(TEXT("duel"), BGM::DUEL);
RefreshBGMDir(TEXT("menu"), BGM::MENU);
RefreshBGMDir(TEXT("deck"), BGM::DECK);
RefreshBGMDir(TEXT("advantage"), BGM::ADVANTAGE);
RefreshBGMDir(TEXT("disadvantage"), BGM::DISADVANTAGE);
RefreshBGMDir(TEXT("win"), BGM::WIN);
RefreshBGMDir(TEXT("lose"), BGM::LOSE);
}
void SoundManager::RefreshBGMDir(path_string path, BGM scene) {
for(auto& file : Utils::FindfolderFiles(TEXT("./sound/BGM/") + path, { TEXT("mp3"), TEXT("ogg"), TEXT("wav") })) {
auto conv = Utils::ToUTF8IfNeeded(path + TEXT("/") + file);
BGMList[BGM::ALL].push_back(conv);
BGMList[scene].push_back(conv);
}
}
void SoundManager::RefreshChantsList() {
for(auto& file : Utils::FindfolderFiles(TEXT("./sound/chants"), { TEXT("mp3"), TEXT("ogg"), TEXT("wav") })) {
auto scode = Utils::GetFileName(TEXT("./sound/chants/") + file);
unsigned int code = std::stoi(scode);
if(code && !ChantsList.count(code))
ChantsList[code] = Utils::ToUTF8IfNeeded(file);
}
}
void SoundManager::PlaySoundEffect(SFX sound) {
static const std::map<SFX, const char*> fx = {
{SUMMON, "./sound/summon.wav"},
{SPECIAL_SUMMON, "./sound/specialsummon.wav"},
{ACTIVATE, "./sound/activate.wav"},
{SET, "./sound/set.wav"},
{FLIP, "./sound/flip.wav"},
{REVEAL, "./sound/reveal.wav"},
{EQUIP, "./sound/equip.wav"},
{DESTROYED, "./sound/destroyed.wav"},
{BANISHED, "./sound/banished.wav"},
{TOKEN, "./sound/token.wav"},
{ATTACK, "./sound/attack.wav"},
{DIRECT_ATTACK, "./sound/directattack.wav"},
{DRAW, "./sound/draw.wav"},
{SHUFFLE, "./sound/shuffle.wav"},
{DAMAGE, "./sound/damage.wav"},
{RECOVER, "./sound/gainlp.wav"},
{COUNTER_ADD, "./sound/addcounter.wav"},
{COUNTER_REMOVE, "./sound/removecounter.wav"},
{COIN, "./sound/coinflip.wav"},
{DICE, "./sound/diceroll.wav"},
{NEXT_TURN, "./sound/nextturn.wav"},
{PHASE, "./sound/phase.wav"},
{PLAYER_ENTER, "./sound/playerenter.wav"},
{CHAT, "./sound/chatmessage.wav"}
};
if (!soundsEnabled) return;
#ifdef YGOPRO_USE_IRRKLANG
if (soundEngine) {
auto sfx = soundEngine->play2D(fx.at(sound), false, true);
sfx->setVolume(sfxVolume);
sfx->setIsPaused(false);
}
#else
if (sfx) sfx->play(fx.at(sound), false);
#endif
}
void SoundManager::PlayMusic(const std::string& song, bool loop) {
if(!musicEnabled) return;
#ifdef YGOPRO_USE_IRRKLANG
if(!soundBGM || soundBGM->getSoundSource()->getName() != song) {
StopBGM();
if (soundEngine) {
soundBGM = soundEngine->play2D(song.c_str(), loop, false, true);
soundBGM->setVolume(bgmVolume);
}
}
#else
StopBGM();
if (bgm) bgmCurrent = bgm->play(song, loop);
#endif
}
void SoundManager::PlayBGM(BGM scene) {
auto& list = BGMList[scene];
int count = list.size();
#ifdef YGOPRO_USE_IRRKLANG
if(musicEnabled && (scene != bgm_scene || (soundBGM && soundBGM->isFinished()) || !soundBGM) && count > 0) {
#else
if (musicEnabled && (scene != bgm_scene || !bgm->exists(bgmCurrent)) && count > 0) {
#endif
bgm_scene = scene;
int bgm = (std::uniform_int_distribution<>(0, count - 1))(rnd);
std::string BGMName = "./sound/BGM/" + list[bgm];
PlayMusic(BGMName, true);
}
}
void SoundManager::StopBGM() {
#ifdef YGOPRO_USE_IRRKLANG
if(soundBGM) {
soundBGM->stop();
soundBGM->drop();
soundBGM = nullptr;
}
#else
bgm->stopAll();
#endif
}
bool SoundManager::PlayChant(unsigned int code) {
if(ChantsList.count(code)) {
#ifdef YGOPRO_USE_IRRKLANG
if (soundEngine && !soundEngine->isCurrentlyPlaying(("./sound/chants/" + ChantsList[code]).c_str())) {
auto chant = soundEngine->play2D(("./sound/chants/" + ChantsList[code]).c_str());
chant->setVolume(sfxVolume);
chant->setIsPaused(false);
}
#else
if (bgm) bgm->play("./sound/chants/" + ChantsList[code], false);
#endif
return true;
}
return false;
}
void SoundManager::SetSoundVolume(double volume) {
#ifdef YGOPRO_USE_IRRKLANG
sfxVolume = volume;
#else
if (sfx) sfx->setVolume(volume);
#endif
}
void SoundManager::SetMusicVolume(double volume) {
#ifdef YGOPRO_USE_IRRKLANG
if (soundBGM) soundBGM->setVolume(volume);
bgmVolume = volume;
#else
if (bgm) bgm->setVolume(volume);
#endif
}
void SoundManager::EnableSounds(bool enable) {
soundsEnabled = enable;
}
void SoundManager::EnableMusic(bool enable) {
musicEnabled = enable;
if(!musicEnabled) {
#ifdef YGOPRO_USE_IRRKLANG
if(soundBGM){
if(!soundBGM->isFinished())
soundBGM->stop();
soundBGM->drop();
soundBGM = nullptr;
}
#else
StopBGM();
#endif
}
}
} // namespace ygo
#ifndef SOUNDMANAGER_H
#define SOUNDMANAGER_H
#include <random>
#ifdef YGOPRO_USE_IRRKLANG
#include <irrKlang.h>
#else
#include "sound_openal.h"
#endif
#include "utils.h"
namespace ygo {
class SoundManager {
public:
enum SFX {
SUMMON,
SPECIAL_SUMMON,
ACTIVATE,
SET,
FLIP,
REVEAL,
EQUIP,
DESTROYED,
BANISHED,
TOKEN,
ATTACK,
DIRECT_ATTACK,
DRAW,
SHUFFLE,
DAMAGE,
RECOVER,
COUNTER_ADD,
COUNTER_REMOVE,
COIN,
DICE,
NEXT_TURN,
PHASE,
PLAYER_ENTER,
CHAT
};
enum BGM {
ALL,
DUEL,
MENU,
DECK,
ADVANTAGE,
DISADVANTAGE,
WIN,
LOSE
};
#ifndef YGOPRO_USE_IRRKLANG
SoundManager() : openal(nullptr), sfx(nullptr) {}
#endif
~SoundManager();
bool Init(double sounds_volume, double music_volume, bool sounds_enabled, bool music_enabled, void* payload = nullptr);
void RefreshBGMList();
void PlaySoundEffect(SFX sound);
void PlayMusic(const std::string& song, bool loop);
void PlayBGM(BGM scene);
void StopBGM();
bool PlayChant(unsigned int code);
void SetSoundVolume(double volume);
void SetMusicVolume(double volume);
void EnableSounds(bool enable);
void EnableMusic(bool enable);
private:
std::vector<std::string> BGMList[8];
std::map<unsigned int, std::string> ChantsList;
int bgm_scene = -1;
std::mt19937 rnd;
#ifdef YGOPRO_USE_IRRKLANG
irrklang::ISoundEngine* soundEngine;
irrklang::ISound* soundBGM;
double sfxVolume = 1.0;
double bgmVolume = 1.0;
#else
std::unique_ptr<YGOpen::OpenALSingleton> openal;
std::unique_ptr<YGOpen::OpenALSoundLayer> sfx;
std::unique_ptr<YGOpen::OpenALSoundLayer> bgm;
int bgmCurrent = -1;
#endif
void RefreshBGMDir(path_string path, BGM scene);
void RefreshChantsList();
bool soundsEnabled = false;
bool musicEnabled = false;
};
}
#endif //SOUNDMANAGER_H
\ No newline at end of file
#include "sound_openal.h"
#include <array>
#include <iterator>
#include <mpg123.h>
#include <sndfile.h>
#include "utils.h"
namespace YGOpen {
static void delete_ALCdevice(ALCdevice* ptr)
{
if (ptr) {
alcCloseDevice(ptr);
}
}
static void delete_ALCcontext(ALCcontext* ptr)
{
if (ptr) {
alcMakeContextCurrent(nullptr);
alcDestroyContext(ptr);
}
}
OpenALSingleton::OpenALSingleton() : device(nullptr, delete_ALCdevice), context(nullptr, delete_ALCcontext) {
device.reset(alcOpenDevice(nullptr));
if (!device) {
throw std::runtime_error("Failed to create OpenAL audio device!");
}
context.reset(alcCreateContext(device.get(), nullptr));
if (!alcMakeContextCurrent(context.get())) {
throw std::runtime_error("Failed to set OpenAL audio context!");
}
mpg123_init();
}
OpenALSingleton::~OpenALSingleton() {
mpg123_exit();
}
OpenALSoundLayer::OpenALSoundLayer(const std::unique_ptr<OpenALSingleton>& openal) : openal(openal), buffers(), playing() {}
OpenALSoundLayer::~OpenALSoundLayer() {
stopAll();
for (const auto& iter : buffers) {
alDeleteBuffers(1, &iter.second->id);
}
}
static inline bool alUtilInitBuffer(std::shared_ptr<OpenALSoundBuffer> data) {
alGetError();
alGenBuffers(1, &data->id);
ALenum error = alGetError();
if (error != AL_NO_ERROR) return false;
alBufferData(data->id, data->format, data->buffer.data(), data->buffer.size(), data->frequency);
error = alGetError();
if (error != AL_NO_ERROR) return false;
return true;
}
static inline ALenum alUtilFormatFromMp3(const int channels, const int encoding) {
if (channels & MPG123_STEREO) {
if (encoding & MPG123_ENC_SIGNED_16) {
return AL_FORMAT_STEREO16;
}
else {
return AL_FORMAT_STEREO8;
}
}
else {
if (encoding & MPG123_ENC_SIGNED_16) {
return AL_FORMAT_MONO16;
}
else {
return AL_FORMAT_MONO8;
}
}
}
static std::shared_ptr<OpenALSoundBuffer> loadMp3(const std::string& filename) {
auto data = std::make_shared<OpenALSoundBuffer>();
std::vector<unsigned char> buffer;
size_t bufferSize;
int mpgError, channels, encoding;
long rate;
auto mpgHandle = mpg123_new(nullptr, &mpgError);
if (!mpgHandle) return nullptr;
if (mpg123_open(mpgHandle, filename.c_str()) != MPG123_OK ||
mpg123_getformat(mpgHandle, &rate, &channels, &encoding) != MPG123_OK) {
mpg123_delete(mpgHandle);
return nullptr;
}
mpg123_format_none(mpgHandle);
mpg123_format(mpgHandle, rate, channels, encoding);
bufferSize = mpg123_outblock(mpgHandle);
buffer.resize(bufferSize);
for(size_t read = 1; read != 0 && mpgError == MPG123_OK; ) {
mpgError = mpg123_read(mpgHandle, buffer.data(), bufferSize, &read);
data->buffer.insert(data->buffer.end(), buffer.begin(), buffer.end());
}
mpg123_close(mpgHandle);
mpg123_delete(mpgHandle);
if (mpgError != MPG123_DONE) return nullptr;
data->format = alUtilFormatFromMp3(channels, encoding);
data->frequency = rate;
return alUtilInitBuffer(data) ? data : nullptr;
}
static std::shared_ptr<OpenALSoundBuffer> loadSnd(const std::string& filename) {
auto data = std::make_shared<OpenALSoundBuffer>();
SF_INFO info;
std::unique_ptr<SNDFILE, std::function<void(SNDFILE*)>> file(
sf_open(filename.c_str(), SFM_READ, &info),
[](SNDFILE* ptr) { sf_close(ptr); });
if (!file) return nullptr;
data->frequency = info.samplerate;
data->format = info.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
data->buffer.resize(info.frames * info.channels * sizeof(short));
if (sf_readf_short(file.get(), reinterpret_cast<short*>(data->buffer.data()), info.frames) != info.frames) return nullptr;
return alUtilInitBuffer(data) ? data : nullptr;
}
bool OpenALSoundLayer::load(const std::string& filename)
{
std::shared_ptr<OpenALSoundBuffer> data(nullptr);
auto ext = ygo::Utils::GetFileExtension(filename);
if (ext == "mp3") {
data = loadMp3(filename);
}
else {
data = loadSnd(filename);
}
if (!data) return false;
buffers[filename] = data;
return true;
}
int OpenALSoundLayer::play(const std::string& filename, bool loop)
{
maintain();
if (buffers.find(filename) == buffers.end()) {
if (!load(filename)) return -1;
}
ALuint buffer = buffers.at(filename)->id;
ALuint source;
alGetError();
alGenSources(1, &source);
ALenum error = alGetError();
if (error != AL_NO_ERROR) return -1;
alSourcei(source, AL_BUFFER, buffer);
alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
alSourcef(source, AL_GAIN, volume);
alSourcePlay(source);
error = alGetError();
if (error != AL_NO_ERROR) {
alDeleteSources(1, &source);
return -1;
}
playing.insert(source);
return source;
}
bool OpenALSoundLayer::exists(int sound)
{
maintain();
return playing.find(sound) != playing.end();
}
void OpenALSoundLayer::stop(int sound)
{
maintain();
const auto iter = playing.find(sound);
if (playing.find(sound) != playing.end()) {
alSourceStop(sound);
alDeleteSources(1, &*iter);
playing.erase(iter);
}
}
void OpenALSoundLayer::stopAll()
{
for (const auto& iter : playing) {
alSourceStop(iter);
alDeleteSources(1, &iter);
}
playing.clear();
}
void OpenALSoundLayer::setVolume(float gain)
{
volume = gain;
for (const auto& iter : playing) {
alSourcef(iter, AL_GAIN, volume);
}
}
void OpenALSoundLayer::maintain() {
std::unordered_set<ALuint> toDelete;
for (const auto& iter : playing) {
ALint state;
alGetSourcei(iter, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING) {
toDelete.insert(iter);
}
}
for (const auto& iter : toDelete) {
alSourceStop(iter);
alDeleteSources(1, &iter);
playing.erase(iter);
}
}
} // namespace YGOpen
#ifndef YGOPEN_SOUND_OPENAL_H
#define YGOPEN_SOUND_OPENAL_H
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <AL/al.h>
#include <AL/alc.h>
namespace YGOpen {
/* Modified from minetest: src/client/sound.h, src/client/sound_openal.cpp
* https://github.com/minetest/minetest
* Licensed under GNU LGPLv2.1
*/
struct OpenALSoundBuffer
{
ALenum format;
ALsizei frequency;
ALuint id;
std::vector<char> buffer;
};
class OpenALSingleton {
public:
OpenALSingleton();
~OpenALSingleton();
std::unique_ptr<ALCdevice, void (*)(ALCdevice* ptr)> device;
std::unique_ptr<ALCcontext, void(*)(ALCcontext* ptr)> context;
};
class OpenALSoundLayer {
public:
OpenALSoundLayer(const std::unique_ptr<OpenALSingleton>& openal);
~OpenALSoundLayer();
bool load(const std::string& filename);
int play(const std::string& filename, bool loop);
bool exists(int sound);
void stop(int sound);
void stopAll();
void setVolume(float gain);
private:
void maintain();
const std::unique_ptr<OpenALSingleton>& openal;
std::unordered_map<std::string, std::shared_ptr<OpenALSoundBuffer>> buffers;
std::unordered_set<ALuint> playing;
float volume = 1.0f;
};
}
#endif //YGOPEN_SOUND_OPENAL_H
#include "utils.h"
#include "game.h"
#include <fstream>
#include "bufferio.h"
#ifdef _WIN32
#include "../irrlicht/src/CIrrDeviceWin32.h"
#elif defined(__linux__)
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#elif defined(__APPLE__)
#include "osx_menu.h"
#endif
namespace ygo {
bool Utils::Makedirectory(const path_string& path) {
#ifdef _WIN32
return CreateDirectory(path.c_str(), NULL) || ERROR_ALREADY_EXISTS == GetLastError();
#else
return !mkdir(&path[0], 0777) || errno == EEXIST;
#endif
}
bool Utils::Movefile(const path_string& _source, const path_string& _destination) {
path_string source = ParseFilename(_source);
path_string destination = ParseFilename(_destination);
if(source == destination)
return false;
std::ifstream src(source, std::ios::binary);
if(!src.is_open())
return false;
std::ofstream dst(destination, std::ios::binary);
if(!dst.is_open())
return false;
dst << src.rdbuf();
src.close();
Deletefile(source);
return true;
}
bool Utils::Deletefile(const path_string& source) {
#ifdef _WIN32
return DeleteFile(source.c_str());
#else
return remove(source.c_str()) == 0;
#endif
}
bool Utils::ClearDirectory(const path_string& path) {
#ifdef _WIN32
WIN32_FIND_DATA fdata;
HANDLE fh = FindFirstFile((path + TEXT("*.*")).c_str(), &fdata);
if(fh != INVALID_HANDLE_VALUE) {
do {
path_string name = fdata.cFileName;
if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if(name == TEXT("..") || name == TEXT(".")) {
continue;
}
Deletedirectory(path + name + TEXT("/"));
continue;
} else {
Deletefile(path + name);
}
} while(FindNextFile(fh, &fdata));
FindClose(fh);
}
return true;
#else
DIR * dir;
struct dirent * dirp = nullptr;
if((dir = opendir(path.c_str())) != nullptr) {
struct stat fileStat;
while((dirp = readdir(dir)) != nullptr) {
stat((path + dirp->d_name).c_str(), &fileStat);
std::string name = dirp->d_name;
if(S_ISDIR(fileStat.st_mode)) {
if(name == ".." || name == ".") {
continue;
}
Deletedirectory(path + name + "/");
continue;
} else {
Deletefile(path + name);
}
}
closedir(dir);
}
return true;
#endif
}
bool Utils::Deletedirectory(const path_string& source) {
ClearDirectory(source);
#ifdef _WIN32
return RemoveDirectory(source.c_str());
#else
return rmdir(source.c_str()) == 0;
#endif
}
void Utils::CreateResourceFolders() {
//create directories if missing
Makedirectory(TEXT("deck"));
Makedirectory(TEXT("puzzles"));
Makedirectory(TEXT("pics"));
Makedirectory(TEXT("pics/field"));
Makedirectory(TEXT("pics/cover"));
Makedirectory(TEXT("pics/temp/"));
ClearDirectory(TEXT("pics/temp/"));
Makedirectory(TEXT("replay"));
Makedirectory(TEXT("screenshots"));
}
void Utils::takeScreenshot(irr::IrrlichtDevice* device)
{
irr::video::IVideoDriver* const driver = device->getVideoDriver();
//get image from the last rendered frame
irr::video::IImage* const image = driver->createScreenShot();
if (image) //should always be true, but you never know. ;)
{
//construct a filename, consisting of local time and file extension
irr::c8 filename[64];
snprintf(filename, 64, "screenshots/ygopro_%u.png", device->getTimer()->getRealTime());
//write screenshot to file
if (!driver->writeImageToFile(image, filename))
device->getLogger()->log(L"Failed to take screenshot.", irr::ELL_WARNING);
//Don't forget to drop image since we don't need it anymore.
image->drop();
}
}
void Utils::ToggleFullscreen() {
#ifdef _WIN32
static RECT nonFullscreenSize;
static std::vector<RECT> monitors;
static bool maximized = false;
if(monitors.empty()) {
EnumDisplayMonitors(0, 0, [](HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData) -> BOOL {
auto monitors = reinterpret_cast<std::vector<RECT>*>(pData);
monitors->push_back(*lprcMonitor);
return TRUE;
}, (LPARAM)&monitors);
}
mainGame->is_fullscreen = !mainGame->is_fullscreen;
HWND hWnd;
irr::video::SExposedVideoData exposedData = mainGame->driver->getExposedVideoData();
if(mainGame->driver->getDriverType() == irr::video::EDT_DIRECT3D9)
hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd);
else
hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd);
LONG_PTR style = WS_POPUP;
RECT clientSize = {};
if(mainGame->is_fullscreen) {
if(GetWindowLong(hWnd, GWL_STYLE) & WS_MAXIMIZE) {
maximized = true;
}
GetWindowRect(hWnd, &nonFullscreenSize);
style = WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
for(auto& rect : monitors) {
POINT windowCenter = { (nonFullscreenSize.left + (nonFullscreenSize.right - nonFullscreenSize.left) / 2), (nonFullscreenSize.top + (nonFullscreenSize.bottom - nonFullscreenSize.top) / 2) };
if(PtInRect(&rect, windowCenter)) {
clientSize = rect;
break;
}
}
} else {
style = WS_THICKFRAME | WS_SYSMENU | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
clientSize = nonFullscreenSize;
if(maximized) {
style |= WS_MAXIMIZE;
maximized = false;
}
}
if(!SetWindowLongPtr(hWnd, GWL_STYLE, style))
mainGame->ErrorLog("Could not change window style.");
const s32 width = clientSize.right - clientSize.left;
const s32 height = clientSize.bottom - clientSize.top;
SetWindowPos(hWnd, HWND_TOP, clientSize.left, clientSize.top, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
static_cast<irr::CIrrDeviceWin32::CCursorControl*>(mainGame->device->getCursorControl())->updateBorderSize(mainGame->is_fullscreen, true);
#elif defined(__linux__)
struct {
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long inputMode;
unsigned long status;
} hints = {};
Display* display = XOpenDisplay(NULL);;
Window window;
static bool wasHorizontalMaximized = false, wasVerticalMaximized = false;
Window child;
int revert;
mainGame->is_fullscreen = !mainGame->is_fullscreen;
XGetInputFocus(display, &window, &revert);
Atom wm_state = XInternAtom(display, "_NET_WM_STATE", false);
Atom max_horz = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", false);
Atom max_vert = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", false);
auto checkMaximized = [&]() {
long maxLength = 1024;
Atom actualType;
int actualFormat;
unsigned long i, numItems, bytesAfter;
unsigned char *propertyValue = NULL;
if(XGetWindowProperty(display, window, wm_state,
0l, maxLength, false, XA_ATOM, &actualType,
&actualFormat, &numItems, &bytesAfter,
&propertyValue) == Success) {
Atom* atoms = (Atom *)propertyValue;
for(i = 0; i < numItems; ++i) {
if(atoms[i] == max_vert) {
wasVerticalMaximized = true;
} else if(atoms[i] == max_horz) {
wasHorizontalMaximized = true;
}
}
XFree(propertyValue);
}
};
if(mainGame->is_fullscreen)
checkMaximized();
if(!wasHorizontalMaximized && !wasVerticalMaximized) {
XEvent xev = {};
xev.type = ClientMessage;
xev.xclient.window = window;
xev.xclient.message_type = wm_state;
xev.xclient.format = 32;
xev.xclient.data.l[0] = mainGame->is_fullscreen ? 1 : 0;
int i = 1;
if(!wasHorizontalMaximized)
xev.xclient.data.l[i++] = max_horz;
if(!wasVerticalMaximized)
xev.xclient.data.l[i++] = max_vert;
if(i == 2)
xev.xclient.data.l[i] = 0;
XSendEvent(display, DefaultRootWindow(display), False, SubstructureNotifyMask, &xev);
}
Atom property = XInternAtom(display, "_MOTIF_WM_HINTS", true);
hints.flags = 2;
hints.decorations = mainGame->is_fullscreen ? 0 : 1;
XChangeProperty(display, window, property, property, 32, PropModeReplace, (unsigned char*)&hints, 5);
XMapWindow(display, window);
XFlush(display);
#elif defined(__APPLE__)
EDOPRO_ToggleFullScreen();
#endif
}
void Utils::changeCursor(irr::gui::ECURSOR_ICON icon) {
irr::gui::ICursorControl* cursor = mainGame->device->getCursorControl();
if (cursor->getActiveIcon() != icon) {
cursor->setActiveIcon(icon);
}
}
void Utils::FindfolderFiles(const path_string& path, const std::function<void(path_string, bool, void*)>& cb, void* payload) {
#ifdef _WIN32
WIN32_FIND_DATA fdataw;
HANDLE fh = FindFirstFile((NormalizePath(path) + TEXT("*.*")).c_str(), &fdataw);
if(fh != INVALID_HANDLE_VALUE) {
do {
cb(fdataw.cFileName, !!(fdataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY), payload);
} while(FindNextFile(fh, &fdataw));
FindClose(fh);
}
#else
DIR * dir;
struct dirent * dirp = nullptr;
auto _path = NormalizePath(path);
if((dir = opendir(_path.c_str())) != nullptr) {
struct stat fileStat;
while((dirp = readdir(dir)) != nullptr) {
stat((_path + dirp->d_name).c_str(), &fileStat);
cb(dirp->d_name, !!S_ISDIR(fileStat.st_mode), payload);
}
closedir(dir);
}
#endif
}
std::vector<path_string> Utils::FindfolderFiles(const path_string& path, std::vector<path_string> extensions, int subdirectorylayers) {
std::vector<path_string> res;
FindfolderFiles(path, [&res, extensions, path, subdirectorylayers](path_string name, bool isdir, void* payload) {
if(isdir) {
if(subdirectorylayers) {
if(name == TEXT("..") || name == TEXT(".")) {
return;
}
std::vector<path_string> res2 = FindfolderFiles(path + name + TEXT("/"), extensions, subdirectorylayers - 1);
for(auto&file : res2) {
file = name + TEXT("/") + file;
}
res.insert(res.end(), res2.begin(), res2.end());
}
return;
} else {
if(extensions.size() && std::find(extensions.begin(), extensions.end(), Utils::GetFileExtension(name)) == extensions.end())
return;
res.push_back(name.c_str());
}
});
return res;
}
void Utils::FindfolderFiles(IrrArchiveHelper& archive, const path_string& path, const std::function<bool(int, path_string, bool, void*)>& cb, void* payload) {
auto _path = ParseFilename(NormalizePath(path, false));
auto& indexfolders = archive.folderindexes[_path].first;
auto& indexfiles = archive.folderindexes[_path].second;
for(int i = indexfolders.first; i < indexfolders.second && cb(i, archive.archive->getFileList()->getFileName(i).c_str(), true, payload); i++) {}
for(int i = indexfiles.first; i < indexfiles.second && cb(i, archive.archive->getFileList()->getFileName(i).c_str(), false, payload); i++) {}
}
std::vector<int> Utils::FindfolderFiles(IrrArchiveHelper& archive, const path_string& path, std::vector<path_string> extensions, int subdirectorylayers) {
std::vector<int> res;
FindfolderFiles(archive, path, [&res, arc = archive.archive, extensions, path, subdirectorylayers, &archive](int index, path_string name, bool isdir, void* payload)->bool {
if(isdir) {
if(subdirectorylayers) {
if(name == TEXT("..") || name == TEXT(".")) {
return true;
}
std::vector<int> res2 = FindfolderFiles(archive, path + name + TEXT("/"), extensions, subdirectorylayers - 1);
res.insert(res.end(), res2.begin(), res2.end());
}
return true;
} else {
if(extensions.size() && std::find(extensions.begin(), extensions.end(), Utils::GetFileExtension(name)) == extensions.end())
return true;
res.push_back(index);
}
return true;
});
return res;
}
irr::io::IReadFile* Utils::FindandOpenFileFromArchives(const path_string & path, const path_string & name) {
for(auto& archive : mainGame->archives) {
int res = -1;
Utils::FindfolderFiles(archive, path, [match = &name, &res](int index, path_string name, bool isdir, void* payload)->bool {
if(isdir)
return false;
if(name == (*match)) {
res = index;
return false;
}
return true;
});
if(res != -1) {
auto reader = archive.archive->createAndOpenFile(res);
if(reader)
return reader;
}
}
return nullptr;
}
std::wstring Utils::NormalizePath(std::wstring path, bool trailing_slash) {
std::replace(path.begin(), path.end(), L'\\', L'/');
std::vector<std::wstring> paths = ygo::Game::TokenizeString<std::wstring>(path, L"/");
if(paths.empty())
return path;
std::wstring normalpath;
if(paths.front() == L".") {
paths.erase(paths.begin());
normalpath += L".";
}
for(auto it = paths.begin(); it != paths.end();) {
if((*it).empty()) {
it = paths.erase(it);
continue;
}
if((*it) == L".") {
it = paths.erase(it);
continue;
}
if((*it) == L".." && it != paths.begin() && (*(it - 1)) != L"..") {
it = paths.erase(paths.erase(it - 1, it));
continue;
}
it++;
}
if(!paths.empty()) {
if(!normalpath.empty())
normalpath += L"/";
for(auto it = paths.begin(); it != (paths.end() - 1); it++) {
normalpath += *it + L"/";
}
normalpath += paths.back();
}
if(trailing_slash && normalpath.back() != L'/')
normalpath += L"/";
return normalpath;
}
std::wstring Utils::GetFileExtension(std::wstring file) {
size_t dotpos = file.find_last_of(L".");
if(dotpos == std::wstring::npos)
return L"";
std::wstring extension = file.substr(dotpos + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
return extension;
}
std::wstring Utils::GetFilePath(std::wstring file) {
std::replace(file.begin(), file.end(), L'\\', L'/');
size_t slashpos = file.find_last_of(L'/');
if(slashpos == std::wstring::npos)
return file;
std::wstring extension = file.substr(0, slashpos);
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
return extension;
}
std::wstring Utils::GetFileName(std::wstring file) {
std::replace(file.begin(), file.end(), L'\\', L'/');
size_t dashpos = file.find_last_of(L"/");
if(dashpos == std::wstring::npos)
dashpos = 0;
else
dashpos++;
size_t dotpos = file.find_last_of(L".");
if(dotpos == std::wstring::npos)
dotpos = file.size();
std::wstring name = file.substr(dashpos, dotpos - dashpos);
return name;
}
std::string Utils::NormalizePath(std::string path, bool trailing_slash) {
std::replace(path.begin(), path.end(), '\\', '/');
std::vector<std::string> paths = ygo::Game::TokenizeString<std::string>(path, "/");
if(paths.empty())
return path;
std::string normalpath;
for(auto it = paths.begin(); it != paths.end();) {
if((*it).empty()) {
it = paths.erase(it);
continue;
}
if((*it) == "." && it != paths.begin()) {
it = paths.erase(it);
continue;
}
if((*it) != ".." && it != paths.begin() && (it + 1) != paths.end() && (*(it + 1)) == "..") {
it = paths.erase(paths.erase(it));
continue;
}
it++;
}
for(auto it = paths.begin(); it != (paths.end() - 1); it++) {
normalpath += *it + "/";
}
normalpath += paths.back();
if(trailing_slash)
normalpath += "/";
return normalpath;
}
std::string Utils::GetFileExtension(std::string file) {
size_t dotpos = file.find_last_of(".");
if(dotpos == std::string::npos)
return "";
std::string extension = file.substr(dotpos + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
std::string Utils::GetFilePath(std::string file) {
std::replace(file.begin(), file.end(), '\\', '/');
size_t slashpos = file.find_last_of(".");
if(slashpos == std::string::npos)
return file;
std::string extension = file.substr(0, slashpos);
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
return extension;
}
std::string Utils::GetFileName(std::string file) {
std::replace(file.begin(), file.end(), '\\', '/');
size_t dashpos = file.find_last_of("/");
if(dashpos == std::wstring::npos)
dashpos = 0;
else
dashpos++;
size_t dotpos = file.find_last_of(".");
if(dotpos == std::string::npos)
dotpos = file.size();
std::string name = file.substr(dashpos, dotpos - dashpos);
return name;
}
path_string Utils::ParseFilename(const std::wstring& input) {
#ifdef UNICODE
return input;
#else
return BufferIO::EncodeUTF8s(input);
#endif
}
path_string Utils::ParseFilename(const std::string& input) {
#ifdef UNICODE
return BufferIO::DecodeUTF8s(input);
#else
return input;
#endif
}
std::string Utils::ToUTF8IfNeeded(const path_string& input) {
#ifdef UNICODE
return BufferIO::EncodeUTF8s(input);
#else
return input;
#endif
}
std::wstring Utils::ToUnicodeIfNeeded(const path_string & input) {
#ifdef UNICODE
return input;
#else
return BufferIO::DecodeUTF8s(input);
#endif
}
void Utils::IrrArchiveHelper::ParseList(irr::io::IFileArchive* _archive) {
archive = _archive;
auto list = archive->getFileList();
std::vector<path_string> list_full;
folderindexes[TEXT(".")] = { { -1, -1 }, { -1, -1 } };
for(u32 i = 0; i < list->getFileCount(); ++i) {
list_full.push_back(list->getFullFileName(i).c_str());
if(list->isDirectory(i)) {
folderindexes[list->getFullFileName(i).c_str()] = { { -1, -1 }, { -1, -1 } };
auto& name_path = list->getFullFileName(i);
auto& name = list->getFileName(i);
if(name_path.size() == name.size()) {
/*special case, root folder*/
folderindexes[TEXT("")] = { { std::min((unsigned)folderindexes[TEXT("")].first.first, i), i + 1 }, folderindexes[TEXT("")].second };
} else {
path_string path = NormalizePath(name_path.subString(0, name_path.size() - name.size() - 1).c_str(), false);
folderindexes[path] = { { std::min((unsigned)folderindexes[path].first.first, i), i + 1 }, folderindexes[path].second };
}
} else {
auto& name_path = list->getFullFileName(i);
auto& name = list->getFileName(i);
if(name_path.size() == name.size()) {
/*special case, root folder*/
folderindexes[TEXT("")] = { folderindexes[TEXT("")].first, { std::min((unsigned)folderindexes[TEXT("")].second.first, i), i + 1 } };
} else {
path_string path = NormalizePath(name_path.subString(0, name_path.size() - name.size() - 1).c_str(), false);
folderindexes[path] = { folderindexes[path].first, { std::min((unsigned)folderindexes[path].second.first, i), i + 1 } };
}
}
}
}
}
#ifndef UTILS_H
#define UTILS_H
#include <irrlicht.h>
#include <string>
#include <vector>
#include <functional>
#include <fstream>
#include <map>
#ifndef _WIN32
#include <dirent.h>
#include <sys/stat.h>
#endif
using path_string = std::basic_string<irr::fschar_t>;
namespace ygo {
class Utils {
public:
class IrrArchiveHelper {
public:
irr::io::IFileArchive* archive;
std::map<path_string/*folder name*/, std::pair<std::pair<int/*begin folder offset*/, int/*end folder offset*/>, std::pair<int/*begin file offset*/, int/*end file offset*/>>> folderindexes;
IrrArchiveHelper(irr::io::IFileArchive* archive) { ParseList(archive); };
void ParseList(irr::io::IFileArchive* archive);
};
static bool Makedirectory(const path_string& path);
static bool Movefile(const path_string& source, const path_string& destination);
static path_string ParseFilename(const std::wstring& input);
static path_string ParseFilename(const std::string& input);
static std::string ToUTF8IfNeeded(const path_string& input);
static std::wstring ToUnicodeIfNeeded(const path_string& input);
static bool Deletefile(const path_string& source);
static bool ClearDirectory(const path_string& path);
static bool Deletedirectory(const path_string& source);
static void CreateResourceFolders();
static void takeScreenshot(irr::IrrlichtDevice* device);
static void ToggleFullscreen();
static void changeCursor(irr::gui::ECURSOR_ICON icon);
static void FindfolderFiles(const path_string& path, const std::function<void(path_string, bool, void*)>& cb, void* payload = nullptr);
static std::vector<path_string> FindfolderFiles(const path_string& path, std::vector<path_string> extensions, int subdirectorylayers = 0);
static void FindfolderFiles(IrrArchiveHelper& archive, const path_string& path, const std::function<bool(int, path_string, bool, void*)>& cb, void* payload = nullptr);
static std::vector<int> FindfolderFiles(IrrArchiveHelper& archive, const path_string& path, std::vector<path_string> extensions, int subdirectorylayers = 0);
static irr::io::IReadFile* FindandOpenFileFromArchives(const path_string& path, const path_string& name);
static std::wstring NormalizePath(std::wstring path, bool trailing_slash = true);
static std::wstring GetFileExtension(std::wstring file);
static std::wstring GetFilePath(std::wstring file);
static std::wstring GetFileName(std::wstring file);
static std::string NormalizePath(std::string path, bool trailing_slash = true);
static std::string GetFileExtension(std::string file);
static std::string GetFilePath(std::string file);
static std::string GetFileName(std::string file);
};
}
#endif //UTILS_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment