Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F130260
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
133 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Functions.cpp b/src/Functions.cpp
index 27c8502..a3c3f40 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1642 +1,1645 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <math.h>
#include <locale.h>
#include <algorithm>
#include <SDL.h>
#include <SDL_mixer.h>
#include <SDL_syswm.h>
#include <SDL_ttf.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "LevelPack.h"
#include "TitleMenu.h"
#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
#include "LevelPlaySelect.h"
#include "Addons.h"
#include "InputManager.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include "ScriptExecutor.h"
#include "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Cursors.h"
#include "ScriptAPI.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
#include "libs/findlocale/findlocale.h"
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Initialise the soundManager.
//The SoundManager is used to keep track of the sfx in the game.
SoundManager soundManager;
//Initialise the levelPackManager.
//The LevelPackManager is used to prevent loading levelpacks multiple times and for the game to know which levelpacks there are.
LevelPackManager levelPackManager;
//The scriptExecutor used for executing scripts.
ScriptExecutor scriptExecutor;
//Map containing changed settings using command line arguments.
map<string,string> tmpSettings;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=nullptr;
SDL_Renderer* sdlRenderer=nullptr;
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
//NOTE: We let SDL_gfx render it.
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//rectangleRGBA(&renderer,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
const SDL_Rect r{x,y,w,h};
SDL_RenderDrawRect(&renderer,&r);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
SDL_Renderer* rd = &renderer;
//FIXME, this may get the wrong color on system with different endianness.
//Fill content's background color from function parameter
SDL_SetRenderDrawColor(rd,color >> 24,color >> 16,color >> 8,color >> 0);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderFillRect(rd, &r);
}
SDL_SetRenderDrawColor(rd,0,0,0,255);
//Draw first black borders around content and leave 1 pixel in every corner
SDL_RenderDrawLine(rd,x+1,y,x+w-2,y);
SDL_RenderDrawLine(rd,x+1,y+h-1,x+w-2,y+h-1);
SDL_RenderDrawLine(rd,x,y+1,x,y+h-2);
SDL_RenderDrawLine(rd,x+w-1,y+1,x+w-1,y+h-2);
//Fill the corners with transperent color to create anti-aliased borders
SDL_SetRenderDrawColor(rd,0,0,0,160);
SDL_RenderDrawPoint(rd,x,y);
SDL_RenderDrawPoint(rd,x,y+h-1);
SDL_RenderDrawPoint(rd,x+w-1,y);
SDL_RenderDrawPoint(rd,x+w-1,y+h-1);
//Draw second lighter border around content
SDL_SetRenderDrawColor(rd,0,0,0,64);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderDrawRect(rd,&r);
}
SDL_SetRenderDrawColor(rd,0,0,0,50);
//Create anti-aliasing in corners of second border
SDL_RenderDrawPoint(rd,x+1,y+1);
SDL_RenderDrawPoint(rd,x+1,y+h-2);
SDL_RenderDrawPoint(rd,x+w-2,y+1);
SDL_RenderDrawPoint(rd,x+w-2,y+h-2);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color){
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//NOTE: We let SDL_gfx render it.
//lineRGBA(&renderer,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
SDL_RenderDrawLine(&renderer,x1,y1,x2,y2);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,renderer,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),renderer,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),renderer,color);
}
}
ScreenData creationFailed() {
return ScreenData{ nullptr };
}
ScreenData createScreen(){
//Check if we are going fullscreen.
if(settings->getBoolValue("fullscreen"))
pickFullscreenResolution();
//Set the screen_width and height.
SCREEN_WIDTH=atoi(settings->getValue("width").c_str());
SCREEN_HEIGHT=atoi(settings->getValue("height").c_str());
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Set the flags.
Uint32 flags = 0;
Uint32 currentFlags = SDL_GetWindowFlags(sdlWindow);
//#if !defined(ANDROID)
// flags |= SDL_DOUBLEBUF;
//#endif
if(settings->getBoolValue("fullscreen")) {
flags|=SDL_WINDOW_FULLSCREEN; //TODO with SDL2 we can also do SDL_WINDOW_FULLSCREEN_DESKTOP
}
else if(settings->getBoolValue("resizable"))
flags|=SDL_WINDOW_RESIZABLE;
//Create the window and renderer if they don't exist and check if there weren't any errors.
if (!sdlWindow && !sdlRenderer) {
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, flags, &sdlWindow, &sdlRenderer);
if(!sdlWindow || !sdlRenderer){
std::cerr << "FATAL ERROR: SDL_CreateWindowAndRenderer failed.\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BlendMode::SDL_BLENDMODE_BLEND);
// White background so we see the menu on failure.
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, 255);
} else if (sdlWindow) {
// Try changing to/from fullscreen
if(SDL_SetWindowFullscreen(sdlWindow, flags & SDL_WINDOW_FULLSCREEN) != 0) {
std::cerr << "WARNING: Failed to switch to fullscreen: " << SDL_GetError() << std::endl;
};
currentFlags = SDL_GetWindowFlags(sdlWindow);
// Change fullscreen resolution
if((currentFlags & SDL_WINDOW_FULLSCREEN ) || (currentFlags & SDL_WINDOW_FULLSCREEN_DESKTOP)) {
SDL_DisplayMode m{0,0,0,0,nullptr};
SDL_GetWindowDisplayMode(sdlWindow,&m);
m.w = SCREEN_WIDTH;
m.h = SCREEN_HEIGHT;
if(SDL_SetWindowDisplayMode(sdlWindow, &m) != 0) {
std::cerr << "WARNING: Failed to set display mode: " << SDL_GetError() << std::endl;
}
} else {
SDL_SetWindowSize(sdlWindow, SCREEN_WIDTH, SCREEN_HEIGHT);
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow();
//Set the the window caption.
SDL_SetWindowTitle(sdlWindow, ("Me and My Shadow "+version).c_str());
//FIXME Seems to be obsolete
// SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return ScreenData{sdlRenderer};
}
vector<_res> getResolutionList(){
//Vector that will hold the resolutions to choose from.
vector<_res> resolutionList;
//Enumerate available resolutions using SDL_ListModes()
//NOTE: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
// SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SCREEN_FLAGS|SDL_ANYFORMAT);
//NOTe - currently only using the first display (0)
int numDisplayModes = SDL_GetNumDisplayModes(0);
if(numDisplayModes < 1){
cerr<<"ERROR: Can't enumerate available screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const _res predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
//Fill the resolutionList.
for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
//Fill the resolutionList.
for(int i=0;i < numDisplayModes; ++i){
SDL_DisplayMode mode;
int error = SDL_GetDisplayMode(0, i, &mode);
if(error < 0) {
//We failed to get a display mode. Should we crash here?
std::cerr << "ERROR: Failed to get display mode " << i << " " << std::endl;
}
//Check if the resolution is higher than the minimum (800x600).
if(mode.w >= 800 && mode.h >= 600){
_res res={mode.w, mode.h};
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Return the resolution list.
return resolutionList;
}
void pickFullscreenResolution(){
//Get the resolution list.
vector<_res> resolutionList=getResolutionList();
//The resolution that will hold the final result, we start with the minimum (800x600).
_res closestMatch={800,600};
int width=atoi(getSettings()->getValue("width").c_str());
//int height=atoi(getSettings()->getValue("height").c_str());
//Now loop through the resolutionList.
for(int i=0;i<(int)resolutionList.size();i++){
//The delta between the closestMatch and the resolution from the list.
int dM=(closestMatch.w-resolutionList[i].w);
//The delta between the target width and the resolution from the list.
int dT=(width-resolutionList[i].w);
//Since the resolutions are getting higher the lower (more negative) the further away it is.
//That's why we check if the deltaMatch is lower than the the deltaTarget.
if((dM)<(dT)){
closestMatch.w=resolutionList[i].w;
closestMatch.h=resolutionList[i].h;
}
}
//Now set the resolution to the closest match.
char s[64];
sprintf(s,"%d",closestMatch.w);
getSettings()->setValue("width",s);
sprintf(s,"%d",closestMatch.h);
getSettings()->setValue("height",s);
}
void configureWindow(){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//We use a new function in SDL2 to restrict minimum window size
SDL_SetWindowMinimumSize(sdlWindow, 800, 600);
}
void onVideoResize(ImageManager& imageManager, SDL_Renderer &renderer){
//Check if the resize event isn't malformed.
if(event.window.data1<=0 || event.window.data2<=0)
return;
//Check the size limit.
//TODO: SDL2 porting note: This may break on systems non-X11 or Windows systems as the window size won't be limited
//there.
if(event.window.data1<800)
event.window.data1=800;
if(event.window.data2<600)
event.window.data2=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.window.data1 && SCREEN_HEIGHT==event.window.data2)
return;
char s[32];
//Set the new width and height.
SDL_snprintf(s,32,"%d",event.window.data1);
getSettings()->setValue("width",s);
SDL_snprintf(s,32,"%d",event.window.data2);
getSettings()->setValue("height",s);
//FIXME: THIS doesn't work properly.
//Do resizing.
SCREEN_WIDTH = event.window.data1;
SCREEN_HEIGHT = event.window.data2;
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Tell the theme to resize.
if(!loadTheme(imageManager,renderer,""))
return;
//And let the currentState update it's GUI to the new resolution.
currentState->resize(imageManager, renderer);
}
ScreenData init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
std::cerr << "FATAL ERROR: SDL_Init failed\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
//Initialze SDL_mixer (audio).
//Note for SDL2 port: Changed frequency from 22050 to 44100.
//22050 caused some sound artifacts on my system, and I'm not sure
//why one would use it in this day and age anyhow.
//unless it's for compatability with some legacy system.
- if(Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,512)==-1){
+ if(Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,1024)==-1){
std::cerr << "FATAL ERROR: Mix_OpenAudio failed\nError: " << Mix_GetError() << std::endl;
return creationFailed();
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
+ //Increase the number of channels.
+ soundManager.setNumberOfChannels(16);
+
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
std::cerr << "FATAL ERROR: TTF_Init failed\nError: " << TTF_GetError() << std::endl;
return creationFailed();
}
//Create the screen.
ScreenData screenData(createScreen());
if(!screenData) {
return creationFailed();
}
//Load key config. Then initialize joystick support.
inputMgr.loadConfig();
inputMgr.openAllJoysitcks();
//Init tinygettext for translations for the right language
dictionaryManager = new tinygettext::DictionaryManager();
dictionaryManager->add_directory(getDataPath()+"locale");
dictionaryManager->set_charset("UTF-8");
//Check if user have defined own language. If not, find it out for the player using findlocale
string lang=getSettings()->getValue("lang");
if(lang.length()>0){
printf("Locale set by user to %s\n",lang.c_str());
language=lang;
}else{
FL_Locale *locale;
FL_FindLocale(&locale,FL_MESSAGES);
printf("Locale isn't set by user: %s\n",locale->lang);
language=locale->lang;
if(locale->country!=NULL){
language+=string("_")+string(locale->country);
}
if(locale->variant!=NULL){
language+=string("@")+string(locale->variant);
}
FL_FreeLocale(&locale);
}
//Now set the language in the dictionaryManager.
dictionaryManager->set_language(tinygettext::Language::from_name(language));
//Disable annoying 'Couldn't translate: blah blah blah'
tinygettext::Log::set_log_info_callback(NULL);
//Set time format to the user-preference of the system.
setlocale(LC_TIME,"");
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Structure that holds the event type/name pair.
struct EventTypeName{
int type;
const char* name;
};
//Create the types of game object event types.
{
const EventTypeName types[]={
{GameObjectEvent_PlayerWalkOn,"playerWalkOn"},
{GameObjectEvent_PlayerIsOn,"playerIsOn"},
{GameObjectEvent_PlayerLeave,"playerLeave"},
{GameObjectEvent_OnCreate,"onCreate"},
{GameObjectEvent_OnEnterFrame,"onEnterFrame"},
{ GameObjectEvent_OnPlayerInteraction, "onPlayerInteraction" },
{GameObjectEvent_OnToggle,"onToggle"},
{GameObjectEvent_OnSwitchOn,"onSwitchOn"},
{GameObjectEvent_OnSwitchOff,"onSwitchOff"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::gameObjectEventNameMap[types[i].name]=types[i].type;
Game::gameObjectEventTypeMap[types[i].type]=types[i].name;
}
}
//Create the types of level event types.
{
const EventTypeName types[]={
{LevelEvent_OnCreate,"onCreate"},
{LevelEvent_OnSave,"onSave"},
{LevelEvent_OnLoad,"onLoad"},
{LevelEvent_OnReset,"onReset"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::levelEventNameMap[types[i].name]=types[i].type;
Game::levelEventTypeMap[types[i].type]=types[i].name;
}
}
//Nothing went wrong so we return true.
return screenData;
}
static TTF_Font* loadFont(const char* name,int size){
TTF_Font* tmpFont=TTF_OpenFont((getDataPath()+"font/"+name+".ttf").c_str(),size);
if(tmpFont){
return tmpFont;
}else{
#if defined(ANDROID)
//Android has built-in DroidSansFallback.ttf. (?)
return TTF_OpenFont("/system/fonts/DroidSansFallback.ttf",size);
#else
return TTF_OpenFont((getDataPath()+"font/DroidSansFallback.ttf").c_str(),size);
#endif
}
}
bool loadFonts(){
//Load the fonts.
//NOTE: This is a separate method because it will be called separately when re-initing in case of language change.
//First close the fonts if needed.
if(fontTitle)
TTF_CloseFont(fontTitle);
if(fontGUI)
TTF_CloseFont(fontGUI);
if(fontGUISmall)
TTF_CloseFont(fontGUISmall);
if(fontText)
TTF_CloseFont(fontText);
if(fontMono)
TTF_CloseFont(fontMono);
/// TRANSLATORS: Font used in GUI:
/// - Use "knewave" for languages using Latin and Latin-derived alphabets
/// - "DroidSansFallback" can be used for non-Latin writing systems
fontTitle=loadFont(_("knewave"),55);
fontGUI=loadFont(_("knewave"),32);
fontGUISmall=loadFont(_("knewave"),24);
/// TRANSLATORS: Font used for normal text:
/// - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived alphabets
/// - "DroidSansFallback" can be used for non-Latin writing systems
fontText=loadFont(_("Blokletters-Viltstift"),16);
fontMono=loadFont("VeraMono",12);
if(fontTitle==NULL || fontGUI==NULL || fontGUISmall==NULL || fontText==NULL || fontMono==NULL){
printf("ERROR: Unable to load fonts! \n");
return false;
}
//Nothing went wrong so return true.
return true;
}
//Generate small arrows used for some GUI widgets.
static void generateArrows(SDL_Renderer& renderer){
TTF_Font* fontArrow=loadFont(_("knewave"),18);
arrowLeft1=textureFromText(renderer,*fontArrow,"<",themeTextColor);
arrowRight1=textureFromText(renderer,*fontArrow,">",themeTextColor);
arrowLeft2=textureFromText(renderer,*fontArrow,"<",themeTextColorDialog);
arrowRight2=textureFromText(renderer,*fontArrow,">",themeTextColorDialog);
TTF_CloseFont(fontArrow);
}
bool loadTheme(ImageManager& imageManager,SDL_Renderer& renderer,std::string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
}
//Resize background or load specific theme
bool success=true;
if(name==""||name.empty()){
objThemes.scaleToScreen();
}else{
string theme=processFileName(name);
if(objThemes.appendThemeFromFile(theme+"/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
success=false;
}
}
generateArrows(renderer);
//Everything went fine so return true.
return success;
}
static SDL_Cursor* loadCursor(const char* image[]){
int i,row,col;
//The array that holds the data (0=white 1=black)
Uint8 data[4*32];
//The array that holds the alpha mask (0=transparent 1=visible)
Uint8 mask[4*32];
//The coordinates of the hotspot of the cursor.
int hotspotX, hotspotY;
i=-1;
//Loop through the rows and columns.
//NOTE: We assume a cursor size of 32x32.
for(row=0;row<32;++row){
for(col=0; col<32;++col){
if(col % 8) {
data[i]<<=1;
mask[i]<<=1;
}else{
++i;
data[i]=mask[i]=0;
}
switch(image[4+row][col]){
case '+':
data[i] |= 0x01;
mask[i] |= 0x01;
break;
case '.':
mask[i] |= 0x01;
break;
default:
break;
}
}
}
//Get the hotspot x and y locations from the last line of the cursor.
sscanf(image[4+row],"%d,%d",&hotspotX,&hotspotY);
return SDL_CreateCursor(data,mask,32,32,hotspotX,hotspotY);
}
bool loadFiles(ImageManager& imageManager, SDL_Renderer& renderer){
//Load the fonts.
if(!loadFonts())
return false;
//Show a loading screen
{
int w = 0,h = 0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
SDL_Color fg={255,255,255,0};
TexturePtr loadingTexture = textureFromText(renderer, *fontTitle, _("Loading..."),fg);
SDL_Rect loadingRect = rectFromTexture(*loadingTexture);
loadingRect.x = (w-loadingRect.w)/2;
loadingRect.y = (h-loadingRect.h)/2;
SDL_RenderCopy(sdlRenderer, loadingTexture.get(), NULL, &loadingRect);
SDL_RenderPresent(sdlRenderer);
SDL_RenderClear(sdlRenderer);
}
musicManager.destroy();
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Load all the music lists from the data and user data path.
{
vector<string> musicLists=enumAllFiles((getDataPath()+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
musicLists=enumAllFiles((getUserPath(USER_DATA)+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
}
//Set the list to the configured one.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
soundManager.loadSound((getDataPath()+"sfx/jump.wav").c_str(),"jump");
soundManager.loadSound((getDataPath()+"sfx/hit.wav").c_str(),"hit");
soundManager.loadSound((getDataPath()+"sfx/checkpoint.wav").c_str(),"checkpoint");
soundManager.loadSound((getDataPath()+"sfx/swap.wav").c_str(),"swap");
soundManager.loadSound((getDataPath()+"sfx/toggle.ogg").c_str(),"toggle");
soundManager.loadSound((getDataPath()+"sfx/error.wav").c_str(),"error");
soundManager.loadSound((getDataPath()+"sfx/collect.wav").c_str(),"collect");
soundManager.loadSound((getDataPath()+"sfx/achievement.ogg").c_str(),"achievement");
//Load the cursor images from the Cursor.h file.
cursors[CURSOR_POINTER]=loadCursor(pointer);
cursors[CURSOR_CARROT]=loadCursor(ibeam);
cursors[CURSOR_DRAG]=loadCursor(closedhand);
cursors[CURSOR_SIZE_HOR]=loadCursor(size_hor);
cursors[CURSOR_SIZE_VER]=loadCursor(size_ver);
cursors[CURSOR_SIZE_FDIAG]=loadCursor(size_fdiag);
cursors[CURSOR_SIZE_BDIAG]=loadCursor(size_bdiag);
cursors[CURSOR_REMOVE]=loadCursor(remove_cursor);
//Set the default cursor right now.
SDL_SetCursor(cursors[CURSOR_POINTER]);
levelPackManager.destroy();
//Now sum up all the levelpacks.
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getDataPath()+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"custom/levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"custom/levelpacks/"+*i);
}
//Now we add a special levelpack that will contain the levels not in a levelpack.
LevelPack* levelsPack=new LevelPack;
levelsPack->levelpackName="Levels";
levelsPack->levelpackPath="Levels/";
//NOTE: Set the type of 'levels' to main so it won't be added to the custom packs, even though it contains non-main levels.
levelsPack->type=COLLECTION;
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
customLevelsPack->levelpackPath="Custom Levels/";
customLevelsPack->type=COLLECTION;
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}
//List the custom levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
customLevelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
customLevelsPack->setLocked(customLevelsPack->getLevelCount()-1);
}
//Add them to the manager.
levelPackManager.addLevelPack(levelsPack);
levelPackManager.addLevelPack(customLevelsPack);
//Load statistics
statsMgr.loadPicture(renderer, imageManager);
statsMgr.registerAchievements(imageManager);
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
statsMgr.reloadCompletedLevelsAndAchievements();
statsMgr.reloadOtherAchievements();
//Load the theme, both menu and default.
//NOTE: Loading theme may fail and returning false would stop everything, default theme will be used instead.
if (!loadTheme(imageManager,renderer,getSettings()->getValue("theme"))){
getSettings()->setValue("theme","%DATA%/themes/Cloudscape");
saveSettings();
}
//Nothing failed so return true.
return true;
}
bool loadSettings(){
settings=new Settings(getUserPath(USER_CONFIG)+"meandmyshadow.cfg");
settings->parseFile();
//Now apply settings changed through command line arguments, if any.
map<string,string>::iterator it;
for(it=tmpSettings.begin();it!=tmpSettings.end();++it){
settings->setValue(it->first,it->second);
}
tmpSettings.clear();
//Always return true?
return true;
}
bool saveSettings(){
return settings->save();
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
SoundManager* getSoundManager(){
return &soundManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
ScriptExecutor* getScriptExecutor(){
return &scriptExecutor;
}
void flipScreen(SDL_Renderer& renderer){
// Render the data from the back buffer.
SDL_RenderPresent(&renderer);
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Delete dictionaryManager.
delete dictionaryManager;
//Get rid of the currentstate.
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//These calls to destroy makes sure stuff is
//deleted before SDL is uninitialised (as these managers are stack allocated
//globals.)
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
soundManager.destroy();
//Destroy the cursors.
for(int i=0;i<CURSOR_MAX;i++){
SDL_FreeCursor(cursors[i]);
cursors[i]=NULL;
}
//Destroy the levelPackManager.
levelPackManager.destroy();
levels=NULL;
//Close all joysticks.
inputMgr.closeAllJoysticks();
//Close the fonts and quit SDL_ttf.
TTF_CloseFont(fontTitle);
TTF_CloseFont(fontGUI);
TTF_CloseFont(fontGUISmall);
TTF_CloseFont(fontText);
TTF_CloseFont(fontMono);
TTF_Quit();
//Remove the temp surface.
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(sdlWindow);
arrowLeft1.reset(nullptr);
arrowLeft2.reset(nullptr);
arrowRight1.reset(nullptr);
arrowRight2.reset(nullptr);
//Stop audio.and quit
Mix_CloseAudio();
//SDL2 porting note. Not sure why this was only done on apple.
//#ifndef __APPLE__
Mix_Quit();
//#endif
//And finally quit SDL.
SDL_Quit();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(ImageManager& imageManager, SDL_Renderer& renderer, int fade){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Fade out, if fading is enabled.
if (currentState && settings->getBoolValue("fading")) {
for (; fade >= 0; fade -= 17) {
currentState->render(imageManager, renderer);
//TODO: Shouldn't the gamestate take care of rendering the GUI?
if (GUIObjectRoot) GUIObjectRoot->render(renderer);
dimScreen(renderer, static_cast<Uint8>(255 - fade));
//draw new achievements (if any) as overlay
statsMgr.render(imageManager, renderer);
flipScreen(renderer);
SDL_Delay(25);
}
}
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
{
currentState=NULL;
Game* game=new Game(renderer, imageManager);
currentState=game;
//Check if we should load record file or a level.
if(!Game::recordFile.empty()){
game->loadRecord(imageManager,renderer,Game::recordFile.c_str());
Game::recordFile.clear();
}else{
game->loadLevel(imageManager,renderer,levels->getLevelFile());
levels->saveLevelProgress();
}
}
break;
case STATE_MENU:
currentState=new Menu(imageManager, renderer);
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect(imageManager, renderer);
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect(imageManager, renderer);
break;
case STATE_LEVEL_EDITOR:
{
currentState=NULL;
LevelEditor* levelEditor=new LevelEditor(renderer, imageManager);
currentState=levelEditor;
//Load the selected level.
levelEditor->loadLevel(imageManager,renderer,levels->getLevelFile());
}
break;
case STATE_OPTIONS:
currentState=new Options(imageManager, renderer);
break;
case STATE_ADDONS:
currentState=new Addons(renderer, imageManager);
break;
case STATE_CREDITS:
currentState=new Credits(imageManager,renderer);
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen(imageManager,renderer);
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
void channelFinishedHook(int channel){
soundManager.channelFinished(channel);
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
int parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-f" || argument=="-fullscreen" || argument=="--fullscreen"){
tmpSettings["fullscreen"]="1";
}else if(argument=="-w" || argument=="-windowed" || argument=="--windowed"){
tmpSettings["fullscreen"]="0";
}else if(argument=="-mv" || argument=="-music" || argument=="--music"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set the music volume.
tmpSettings["music"]=argv[i];
}else if(argument=="-sv" || argument=="-sound" || argument=="--sound"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set sound volume.
tmpSettings["sound"]=argv[i];
}else if(argument=="-set" || argument=="--set"){
//We need a second and a third argument so we increase i.
i+=2;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//And set the setting.
tmpSettings[argv[i-1]]=argv[i];
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("%s\n",version.c_str());
return 0;
}else if(argument=="-h" || argument=="-help" || argument=="--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return -1;
}else{
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return -1;
}
}
//If everything went well we can return true.
return 1;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, string prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//The GUI objects.
GUIObject* obj;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,200,600,200,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//Pointer to the string.
char* lps=(char*)prompt.c_str();
//Pointer to a character.
char* lp=NULL;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Add a GUIObjectLabel with the sentence.
root->addChild(new GUILabel(imageManager,renderer,0,y,root->width,25,lps,0,true,true,GUIGravityCenter));
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
root->top=(SCREEN_HEIGHT-y)/2;
root->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]=_("OK");value[0]=MsgBoxOK;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]=_("Abort");value[0]=MsgBoxAbort;
button[1]=_("Retry");value[1]=MsgBoxRetry;
button[2]=_("Ignore");value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
button[2]=_("Cancel");value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]=_("Retry");value[0]=MsgBoxRetry;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]=_("OK");value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Reduce y so that the buttons fit inside the frame.
y-=40;
double places[3]={0.0};
if(count==1){
places[0]=0.5;
}else if(count==2){
places[0]=0.4;
places[1]=0.6;
}else if(count==3){
places[0]=0.3;
places[1]=0.5;
places[2]=0.7;
}
//Loop to add the buttons.
for(int i=0;i<count;i++){
obj=new GUIButton(imageManager,renderer,root->width*places[i],y,-1,36,button[i].c_str(),value[i],true,true,GUIGravityCenter);
obj->eventCallback=&objHandler;
root->addChild(obj);
}
}
//Now we dim the screen and keep the GUI rendering/updating.
GUIOverlay* overlay=new GUIOverlay(renderer,root);
overlay->enterLoop(imageManager,renderer, true);
//And return the result.
return (msgBoxResult)objHandler.ret;
}
struct fileDialogHandler:public GUIEventCallback{
public:
//The ret(urn) value, true=ok and false=cancel
bool ret;
//Boolean if it's a save dialog.
bool isSave;
//Boolean if the file should be verified.
bool verifyFile;
//Boolean if files should be listed instead of directories.
bool files;
//Pointer to the textfield containing the filename.
GUIObject* txtName;
//Pointer to the listbox containing the different files.
GUIListBox* lstFile;
//The extension the files listed should have.
const char* extension;
//The current filename.
string fileName;
//The current search path.
string path;
//Vector containing the search paths.
vector<string> searchPath;
public:
//Constructor.
fileDialogHandler(bool isSave=false,bool verifyFile=false, bool files=true):ret(false),
isSave(isSave),verifyFile(verifyFile),
files(files),txtName(NULL),lstFile(NULL),extension(NULL){}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer,std::string name,GUIObject* obj,int /*eventType*/) override {
//Check for the ok event.
if(name=="cmdOK"){
//Get the entered fileName from the text field.
std::string s=txtName->caption;
//If it doesn't contain a slash we need to add the path to the fileName.
if(s.find_first_of("/")==string::npos)
s=path+s;
//If the string empty we return.
if(s.empty() || s.find_first_of("*?")!=string::npos)
return;
//We only need to check for extensions if it isn't a folder dialog.
if(files){
//If there isn't right extension add it.
size_t found=s.find_first_of(".");
if(found!=string::npos)
s.replace(s.begin()+found+1,s.end(),extension);
else if (s.substr(found+1)!=extension)
s.append(string(".")+extension);
}
//Check if we should save or load the file.
//
if(isSave){
//Open the file with read permission to check if it already exists.
FILE* f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it exists.
if(f){
//Close the file.
fclose(f);
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render(imageManager,renderer);
//Prompt the user with a Yes or No question.
/// TRANSLATORS: Filename is coming before this text
if(msgBox(imageManager,renderer, tfm::format(_("%s already exists.\nDo you want to overwrite it?"),s),MsgBoxYesNo,_("Overwrite Prompt"))!=MsgBoxYes){
//He answered no, so we return.
return;
}
}
//Check if we should verify the file.
//Verifying only applies to files not to directories.
if(verifyFile && files){
//Open the file with write permission.
f=fopen(processFileName(s).c_str(),"wb");
//Check if their aren't problems.
if(f){
//Close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render(imageManager,renderer);
//The file can't be opened so tell the user.
msgBox(imageManager,renderer, tfm::format(_("Can't open file %s."),s),MsgBoxOKOnly,_("Error"));
return;
}
}
}else if(verifyFile && files){
//We need to verify a file for opening.
FILE *f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it didn't fail.
if(f){
//Succes, so close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render(imageManager,renderer);
//Unable to open file so tell the user.
msgBox(imageManager,renderer, tfm::format(_("Can't open file %s."),s),MsgBoxOKOnly,_("Error"));
return;
}
}
//If we haven't returned then it's fine.
//Set the fileName to the chosen file.
fileName=s;
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Set return to true.
ret=true;
}else if(name=="cmdCancel"){
//Cancel means we can kill the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="lstFile"){
//Get a pointer to the listbox.
GUIListBox* obj1=lstFile;
//Make sure the option exist and change textfield to it.
if(obj1!=NULL && txtName!=NULL && obj1->value>=0 && obj1->value<(int)obj1->item.size()){
txtName->caption=obj1->item[obj1->value];
}
}else if(name=="lstSearchIn"){
//Get the searchpath listbox.
GUISingleLineListBox *obj1=dynamic_cast<GUISingleLineListBox*>(obj);
//Check if the entry exists.
if(obj1!=NULL && lstFile!=NULL && obj1->value>=0 && obj1->value<(int)searchPath.size()){
//Temp string.
string s;
//Get the new search path.
path=searchPath[obj1->value];
//Make sure it isn't empty.
if(!path.empty()){
//Process the filename.
s=processFileName(path);
}else{
//It's empty so we give the userpath.
s=getUserPath();
}
//Fill the list with files or directories.
if(files) {
lstFile->item=enumAllFiles(s,extension);
}else
lstFile->item=enumAllDirs(s);
//Remove any selection from the list.
lstFile->value=-1;
}
}
}
};
//SDL2 port note: Commented this out since it was unused.
/*
bool fileDialog(ImageManager& imageManager,SDL_Renderer& renderer, string& fileName,const char* title,const char* extension,const char* path,bool isSave,bool verifyFile,bool files){
//Pointer to GUIObject to make the GUI with.
GUIObject* obj;
//Create the fileDialogHandler, used for event handling.
fileDialogHandler objHandler(isSave,verifyFile,files);
//Vector containing the pathNames.
vector<string> pathNames;
//Set the extension of the objHandler.
objHandler.extension=extension;
//We now need to splits the given path into multiple path names.
if(path && path[0]){
//The string isn't empty.
//Pointer to the paths string.
char* lp=(char*)path;
//Pointer to the first newline.
char* lps=strchr(lp,'\n');
//Pointer used for checking if their's another newline.
//It will indicate if it's the last set or not.
char* lpe;
//Check for a newline.
if(lps){
//We have newline(s) so loop forever.
//We can only break out of the loop when the string ends.
for(;;){
//Add the first searchpath.
//This is the beginning of the string (lp) to the first newline. (lps)
objHandler.searchPath.push_back(string(lp,lps-lp));
//We should have another newline so search for it.
lpe=strchr(lps+1,'\n');
if(lpe){
//We found it so we add that to the pathname.
pathNames.push_back(string(lps+1,lpe-lps-1));
//And start over again by setting lp to the start of a new set of searchPath/pathName.
lp=lpe+1;
}else{
//There is no newline anymore, meaning the last entry, the rest of the string must be the pathName.
pathNames.push_back(string(lps+1));
//And break out of the loop.
break;
}
//We haven't broken out so search for a newline.
lps=strchr(lp,'\n');
//If there isn't a newline break.
if(!lps)
break;
}
}else{
//There is no newline thus the whole string is the searchPath.
objHandler.searchPath.push_back(path);
}
}else{
//Empty so put an empty string as searchPath.
objHandler.searchPath.push_back(string());
}
//It's time to create the GUI.
//If there are more than one pathNames we need to add a GUISingleLineListBox.
int base_y=pathNames.empty()?20:60;
//Create the frame.
GUIObject* root=new GUIFrame(100,100-base_y/2,600,400+base_y,title?title:(isSave?_("Save File"):_("Load File")));
//Create the search path list box if needed.
if(!pathNames.empty()){
root->addChild(new GUILabel(8,40,184,36,_("Search In")));
GUISingleLineListBox* obj1=new GUISingleLineListBox(160,40,432,36);
obj1->addItems(pathNames);
obj1->value=0;
obj1->name="lstSearchIn";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
}
//Add the FileName label and textfield.
root->addChild(new GUILabel(8,20+base_y,184,36,_("File Name")));
{
//Fill the textbox with the given fileName.
string s=fileName;
if(!isSave){
//But only if it isn't empty.
if(s.empty() && extension && extension[0])
s=string("*.")+string(extension);
}
//Create the textbox and add it to the GUI.
objHandler.txtName=new GUITextBox(160,20+base_y,432,36,s.c_str());
root->addChild(objHandler.txtName);
}
//Now we add the ListBox containing the files or directories.
{
GUIListBox* obj1=new GUIListBox(8,60+base_y,584,292);
//Get the searchPath.
string s=objHandler.searchPath[0];
//Make sure it isn't empty.
if(!s.empty()){
objHandler.path=s;
s=processFileName(s);
}else{
s=getUserPath();
}
//Check if we should list files or directories.
if(files){
//Fill the list with files.
obj1->item=enumAllFiles(s,extension);
}else{
//Fill the list with directories.
obj1->item=enumAllDirs(s);
}
obj1->name="lstFile";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
objHandler.lstFile=obj1;
}
//Now create the OK and Cancel buttons.
obj=new GUIButton(200,360+base_y,192,36,_("OK"));
obj->name="cmdOK";
obj->eventCallback=&objHandler;
root->addChild(obj);
obj=new GUIButton(400,360+base_y,192,36,_("Cancel"));
obj->name="cmdCancel";
obj->eventCallback=&objHandler;
root->addChild(obj);
//Create the gui overlay.
GUIOverlay* overlay=new GUIOverlay(renderer,root);
overlay->enterLoop(imageManager,renderer);
//Now determine what the return value is (and if there is one).
if(objHandler.ret)
fileName=objHandler.fileName;
return objHandler.ret;
}
*/
// A helper function to read a character from utf8 string
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadForward(const char* s, int& p) {
int ch = (unsigned char)s[p];
if (ch < 0x80){
if (ch) p++;
return ch;
} else if (ch < 0xC0){
// skip invalid characters
while (((unsigned char)s[p] & 0xC0) == 0x80) p++;
return -1;
} else if (ch < 0xE0){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
ch = ((ch & 0x1F) << 6) | (c2 & 0x3F);
p++;
return ch;
} else if (ch < 0xF0){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
int c3 = (unsigned char)s[++p];
if ((c3 & 0xC0) != 0x80) return -1;
ch = ((ch & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
p++;
return ch;
} else if (ch < 0xF8){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
int c3 = (unsigned char)s[++p];
if ((c3 & 0xC0) != 0x80) return -1;
int c4 = (unsigned char)s[++p];
if ((c4 & 0xC0) != 0x80) return -1;
ch = ((ch & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
if (ch >= 0x110000) ch = -1;
p++;
return ch;
} else {
p++;
return -1;
}
}
// A helper function to read a character backward from utf8 string (experimental)
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadBackward(const char* s, int& p) {
if (p <= 0) return 0;
do {
p--;
} while (p > 0 && ((unsigned char)s[p] & 0xC0) == 0x80);
int tmp = p;
return utf8ReadForward(s, tmp);
}
diff --git a/src/MusicManager.cpp b/src/MusicManager.cpp
index 4a8d5d4..a4f06a4 100644
--- a/src/MusicManager.cpp
+++ b/src/MusicManager.cpp
@@ -1,392 +1,396 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MusicManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "FileManager.h"
#include "Functions.h"
#include <fstream>
#include <iostream>
#include <algorithm>
#include <SDL_mixer.h>
using namespace std;
MusicManager::MusicManager(){
Mix_HookMusicFinished(musicStoppedHook);
Mix_VolumeMusic(MIX_MAX_VOLUME);
enabled=false;
currentList="default";
lastTime=0;
playing=NULL;
}
MusicManager::~MusicManager(){
//We call destroy().
destroy();
}
void MusicManager::destroy(){
//Loop through the imageCollection and free them.
std::map<std::string,Music*>::iterator i;
for(i=musicCollection.begin();i!=musicCollection.end();++i){
if(i->second!=NULL){
Mix_FreeMusic(i->second->music);
if(i->second->loop)
Mix_FreeMusic(i->second->loop);
delete i->second;
}
}
playing=NULL;
//And clear the collection and the lists.
musicCollection.clear();
musicLists.clear();
}
void MusicManager::setEnabled(bool enable){
//Set the new status.
if(enabled!=enable)
enabled=enable;
else
return;
if(enable){
//It got turned on, so start the menu music.
playMusic("menu",false);
}else{
//Stop the current music.
Mix_HaltMusic();
Mix_VolumeMusic(atoi(getSettings()->getValue("music").c_str()));
}
}
void MusicManager::setVolume(int volume){
- Mix_VolumeMusic(volume);
+ if (playing) {
+ Mix_VolumeMusic(playing->volume * float(volume / float(MIX_MAX_VOLUME)));
+ } else {
+ Mix_VolumeMusic(volume);
+ }
}
string MusicManager::loadMusic(const std::string &file,const std::string &list){
//Open the .music file.
ifstream musicFile;
musicFile.open(file.c_str());
string returnString="";
string listPrefix=list;
//Add the trailing slash to the list.
//NOTE: It's added here to prevent a slash in the global list.
if(!listPrefix.empty())
listPrefix+="/";
//The path from where the actual audio files should be read.
string musicPath=pathFromFileName(file);
//Check if the file exists.
if(musicFile){
//Now parse the file.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(musicFile,&obj,true)){
cerr<<"ERROR: Invalid file format of music description file."<<endl;
}
}
//Loop through the entries.
for(unsigned int i=0;i<obj.subNodes.size();i++){
TreeStorageNode* obj1=obj.subNodes[i];
if(obj1==NULL)
continue;
if(!obj1->value.empty() && obj1->name=="music"){
//Make sure that this music isn't already loaded.
map<string,Music*>::iterator it=musicCollection.find(listPrefix+obj1->value[0]);
if(it==musicCollection.end()){
//We've found an entry for a music file.
Music* music=new Music;
music->music=NULL;
music->loop=NULL;
//Load some data.
for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
if(i->first=="file"){
//Load the music file.
music->music=Mix_LoadMUS((musicPath+i->second[0]).c_str());
}
if(i->first=="loopfile"){
//Load the loop file.
music->loop=Mix_LoadMUS((musicPath+i->second[0]).c_str());
}
if(i->first=="trackname"){
music->trackName=i->second[0];
}
if(i->first=="author"){
music->author=i->second[0];
}
if(i->first=="license"){
music->license=i->second[0];
}
if(i->first=="start"){
music->start=(atoi(i->second[0].c_str()));
}
if(i->first=="volume"){
music->volume=(atoi(i->second[0].c_str()));
}
if(i->first=="loopstart"){
music->loopStart=(atoi(i->second[0].c_str()));
}
if(i->first=="loopend"){
music->loopEnd=(atoi(i->second[0].c_str()));
}
}
//Set the default value for lastTime.
music->lastTime=-1;
music->name=listPrefix+obj1->value[0];
//Now add it to the collection.
musicCollection[listPrefix+obj1->value[0]]=music;
}
//Add the name of the music to the return string even if it's already loaded.
//This is to allow music to be in multiple music lists.
if(!returnString.empty())
returnString+=',';
returnString+=obj1->value[0];
}
}
}
//Return the return string.
return returnString;
}
bool MusicManager::loadMusicList(const std::string &file){
//Open the .list file.
ifstream musicFile;
musicFile.open(file.c_str());
//Check if the file exists.
if(musicFile){
//Now parse the file.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(musicFile,&obj,true)){
cerr<<"ERROR: Invalid file format of music list file."<<endl;
return false;
}
}
//Get the name of the list.
std::string name;
{
map<string,vector<string> >::iterator it=obj.attributes.find("name");
if(it!=obj.attributes.end()){
name=obj.attributes["name"][0];
}else{
cerr<<"ERROR: No name for music list "<<file<<endl;
return false;
}
}
//Check if the list isn't already loaded.
std::map<std::string,std::vector<std::string> >::iterator it=musicLists.find(name);
if(it!=musicLists.end())
return true;
//The path where the .music files should be.
string musicPath=pathFromFileName(file);
//Loop through the entries.
for(unsigned int i=0;i<obj.subNodes.size();i++){
TreeStorageNode* obj1=obj.subNodes[i];
if(obj1==NULL)
continue;
if(!obj1->value.empty() && obj1->name=="musicfile"){
//Load the music file.
string result=loadMusic(musicPath+obj1->value[0],name);
if(!result.empty()){
if(result.find(',')!=string::npos){
size_t pos=result.find(',');
while(pos!=string::npos){
musicLists[name].push_back(result.substr(pos,result.find(',',pos+1)));
}
}else{
musicLists[name].push_back(result);
}
}
}
}
}else{
cerr<<"ERROR: Unable to open music list file "<<file<<endl;
return false;
}
//Nothing went wrong so return true.
return true;
}
void MusicManager::playMusic(const std::string &name,bool fade){
//Make sure music is enabled.
if(!enabled)
return;
//Check if the music is in the collection.
Music* music=musicCollection[name];
if(music==NULL){
cerr<<"ERROR: Unable to play music "<<name<<endl;
return;
}
//Now check if we should fade the previous one out.
if(fade){
Mix_FadeOutMusic(375);
//Set the next music.
nextMusic=name;
}else{
if(music->loopStart<=0 && music->loop==NULL){
Mix_FadeInMusicPos(music->music,-1,0,music->start);
}else{
Mix_FadeInMusicPos(music->music,0,0,music->start);
}
Mix_VolumeMusic(music->volume*float(atoi(getSettings()->getValue("music").c_str())/float(MIX_MAX_VOLUME)));
//Set the playing pointer.
playing=music;
}
}
string MusicManager::getCurrentMusic(){
return playing->name;
}
void MusicManager::pickMusic(){
//Make sure the currentList exists.
vector<std::string> list=musicLists[currentList];
if(list.empty()){
cerr<<"ERROR: Unknown music list "<<currentList<<endl;
return;
}
//Shuffle the list.
random_shuffle(list.begin(),list.end());
//Now loop through the music and search the oldest.
Music* oldest=NULL;
for(unsigned int i=0;i<list.size();i++){
//As key we use "list/name" to prevent collision.
string listEntry=currentList+"/"+list[i];
//Check if oldest is set.
if(oldest==NULL){
//It isn't so pick the first music.
oldest=musicCollection[listEntry];
continue;
}
//Check if this song is null.
if(musicCollection[listEntry]==NULL)
continue;
//Check if this music is never played.
if(musicCollection[listEntry]->lastTime==-1){
oldest=musicCollection[listEntry];
//And break out.
break;
}
//Check if this music is older.
if(musicCollection[listEntry]->lastTime<oldest->lastTime){
oldest=musicCollection[listEntry];
}
}
//Check if oldest ins't null.
if(oldest!=NULL){
playMusic(oldest->name);
//Set the lastTime and increase it.
oldest->lastTime=lastTime;
lastTime++;
}
}
void MusicManager::musicStopped(){
//Check if there's a music to play.
if(!nextMusic.empty()){
//Check if the music is in the collection.
Music* music=musicCollection[nextMusic];
if(music==NULL){
cerr<<"ERROR: Unable to play music "<<nextMusic<<endl;
return;
}
if(music->loopStart<=0){
Mix_FadeInMusicPos(music->music,-1,375,music->start);
}else{
Mix_FadeInMusicPos(music->music,0,375,music->start);
}
Mix_VolumeMusic(music->volume*float(atoi(getSettings()->getValue("music").c_str())/float(MIX_MAX_VOLUME)));
//Set playing.
playing=music;
//Now reset nextMusic.
nextMusic.clear();
}else{
//Check what kind of loop.
if(playing->loop!=NULL){
Mix_FadeInMusicPos(playing->loop,-1,0,playing->loopStart);
}else{
//This is for looping the end of music.
Mix_FadeInMusicPos(playing->music,0,0,playing->loopStart);
}
}
}
void MusicManager::setMusicList(const string &listName){
//Check if the list exists.
vector<std::string> list=musicLists[listName];
if(list.empty()){
cerr<<"ERROR: Unknown music list "<<listName<<endl;
return;
}
//The list exist and contains music so set it as current.
currentList=listName;
}
std::string MusicManager::getCurrentMusicList(){
return currentList;
}
vector<string> MusicManager::createCredits(){
//Vector that will be returned.
vector<string> result;
//Loop through the music tracks.
std::map<std::string,Music*>::iterator it;
for(it=musicCollection.begin();it!=musicCollection.end();++it){
result.push_back(" - "+it->second->trackName);
result.push_back(" License: "+it->second->license);
result.push_back(" Attribution: "+it->second->author);
}
//And return the result.
return result;
}
diff --git a/src/ScriptAPI.cpp b/src/ScriptAPI.cpp
index 878775b..97096ae 100644
--- a/src/ScriptAPI.cpp
+++ b/src/ScriptAPI.cpp
@@ -1,1400 +1,1407 @@
/*
* Copyright (C) 2012-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ScriptAPI.h"
#include "ScriptExecutor.h"
#include "SoundManager.h"
#include "Functions.h"
#include "Game.h"
#include "MusicManager.h"
#include <iostream>
using namespace std;
/////////////////////////// HELPER MACRO ///////////////////////////
#define HELPER_GET_AND_CHECK_ARGS(ARGS) \
int args = lua_gettop(state); \
if(args != ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d.", __FUNCTION__, ARGS); \
}
#define HELPER_GET_AND_CHECK_ARGS_RANGE(ARGS1, ARGS2) \
int args = lua_gettop(state); \
if(args < ARGS1 || args > ARGS2) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d-%d.", __FUNCTION__, ARGS1, ARGS2); \
}
#define HELPER_GET_AND_CHECK_ARGS_2(ARGS1, ARGS2) \
int args = lua_gettop(state); \
if(args != ARGS1 && args != ARGS2) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected %d or %d.", __FUNCTION__, ARGS1, ARGS2); \
}
#define HELPER_GET_AND_CHECK_ARGS_AT_LEAST(ARGS) \
int args = lua_gettop(state); \
if(args < ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected at least %d.", __FUNCTION__, ARGS); \
}
#define HELPER_GET_AND_CHECK_ARGS_AT_MOST(ARGS) \
int args = lua_gettop(state); \
if(args > ARGS) { \
return luaL_error(state, "Incorrect number of arguments for %s, expected at most %d.", __FUNCTION__, ARGS); \
}
//================================================================
#define HELPER_CHECK_ARGS_TYPE(INDEX, TYPE) \
if(!lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s.", INDEX, __FUNCTION__, #TYPE); \
}
#define HELPER_CHECK_ARGS_TYPE_NO_HINT(INDEX, TYPE) \
if(!lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_ARGS_TYPE_2(INDEX, TYPE1, TYPE2) \
if(!lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s or %s.", INDEX, __FUNCTION__, #TYPE1, #TYPE2); \
}
#define HELPER_CHECK_ARGS_TYPE_2_NO_HINT(INDEX, TYPE1, TYPE2) \
if(!lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_ARGS_TYPE_OR_NIL(INDEX, TYPE) \
HELPER_CHECK_ARGS_TYPE_2(INDEX, TYPE, nil)
#define HELPER_CHECK_ARGS_TYPE_OR_NIL_NO_HINT(INDEX, TYPE) \
HELPER_CHECK_ARGS_TYPE_2_NO_HINT(INDEX, TYPE, nil)
//================================================================
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE(INDEX, TYPE) \
if(args>=INDEX && !lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s.", INDEX, __FUNCTION__, #TYPE); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_NO_HINT(INDEX, TYPE) \
if(args>=INDEX && !lua_is##TYPE(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_2(INDEX, TYPE1, TYPE2) \
if(args>=INDEX && !lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s, should be %s or %s.", INDEX, __FUNCTION__, #TYPE1, #TYPE2); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_2_NO_HINT(INDEX, TYPE1, TYPE2) \
if(args>=INDEX && !lua_is##TYPE1(state,INDEX) && !lua_is##TYPE2(state,INDEX)) { \
return luaL_error(state, "Invalid type for argument %d of %s.", INDEX, __FUNCTION__); \
}
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(INDEX, TYPE) \
HELPER_CHECK_OPTIONAL_ARGS_TYPE_2(INDEX, TYPE, nil)
#define HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL_NO_HINT(INDEX, TYPE) \
HELPER_CHECK_OPTIONAL_ARGS_TYPE_2_NO_HINT(INDEX, TYPE, nil)
//================================================================
#define _F(FUNC) \
{ #FUNC, _L::FUNC }
///////////////////////////BLOCK SPECIFIC///////////////////////////
class BlockScriptAPI {
public:
static int getFlags(const Block* block) {
return block->flags;
}
static void setFlags(Block* block, int flags) {
block->flags = flags;
}
static void fragileUpdateState(Block* block, int state) {
block->flags = state;
const char* s = (state == 0) ? "default" : ((state == 1) ? "fragile1" : ((state == 2) ? "fragile2" : "fragile3"));
block->appearance.changeState(s);
}
static int getTemp(const Block* block) {
return block->temp;
}
static void setTemp(Block* block, int value) {
block->temp = value;
}
static int getPathMaxTime(const Block* block) {
int result = 0;
for (const SDL_Rect& p : block->movingPos) {
result += p.w;
}
return result;
}
};
namespace block {
int getBlockById(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is an id (string).
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Get the actual game object.
string id = lua_tostring(state, 1);
std::vector<Block*>& levelObjects = game->levelObjects;
Block* object = NULL;
for (unsigned int i = 0; i < levelObjects.size(); i++){
if (levelObjects[i]->getEditorProperty("id") == id){
object = levelObjects[i];
break;
}
}
if (object == NULL){
//Unable to find the requested object.
//Return nothing, will result in a nil in the script.
return 0;
}
//Create the userdatum.
object->createUserData(state, "block");
//We return one object, the userdatum.
return 1;
}
int getBlocksById(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is an id (string).
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Get the actual game object.
string id = lua_tostring(state, 1);
std::vector<Block*>& levelObjects = game->levelObjects;
std::vector<Block*> result;
for (unsigned int i = 0; i < levelObjects.size(); i++){
if (levelObjects[i]->getEditorProperty("id") == id){
result.push_back(levelObjects[i]);
}
}
//Create the table that will hold the result.
lua_createtable(state, result.size(), 0);
//Loop through the results.
for (unsigned int i = 0; i < result.size(); i++){
//Create the userdatum.
result[i]->createUserData(state, "block");
//And set the table.
lua_rawseti(state, -2, i + 1);
}
//We return one object, the userdatum.
return 1;
}
int moveTo(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->moveTo(x, y);
return 0;
}
int getLocation(lua_State* state){
//Make sure there's only one argument and that argument is an userdatum.
HELPER_GET_AND_CHECK_ARGS(1);
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
lua_pushnumber(state, object->getBox().x);
lua_pushnumber(state, object->getBox().y);
return 2;
}
int setLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
object->setLocation(x, y);
return 0;
}
int growTo(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->growTo(w, h);
return 0;
}
int getSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Get the object.
lua_pushnumber(state, object->getBox().w);
lua_pushnumber(state, object->getBox().h);
return 2;
}
int setSize(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Now get the pointer to the object.
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
int w = lua_tonumber(state, 2);
int h = lua_tonumber(state, 3);
object->setSize(w, h);
return 0;
}
int getType(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL || object->type < 0 || object->type >= TYPE_MAX) return 0;
lua_pushstring(state, Game::blockName[object->type]);
return 1;
}
int changeThemeState(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
object->appearance.changeState(lua_tostring(state, 2));
return 0;
}
int setVisible(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL)
return 0;
bool visible = lua_toboolean(state, 2);
object->visible = visible;
return 0;
}
int isVisible(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL)
return 0;
lua_pushboolean(state, object->visible);
return 1;
}
int getEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()) return 0;
//Check compiled script
map<int, int>::iterator script = object->compiledScripts.find(it->second);
if (script == object->compiledScripts.end()) return 0;
//Get event handler
lua_rawgeti(state, LUA_REGISTRYINDEX, script->second);
return 1;
}
//It will return old event handler.
int setEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
HELPER_CHECK_ARGS_TYPE_OR_NIL(3, function);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::const_iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", eventType.c_str());
return lua_error(state);
}
//Check compiled script
int scriptIndex = LUA_REFNIL;
{
map<int, int>::iterator script = object->compiledScripts.find(it->second);
if (script != object->compiledScripts.end()) scriptIndex = script->second;
}
//Set new event handler
object->compiledScripts[it->second] = luaL_ref(state, LUA_REGISTRYINDEX);
//Get old event handler and unreference it
lua_rawgeti(state, LUA_REGISTRYINDEX, scriptIndex);
luaL_unref(state, LUA_REGISTRYINDEX, scriptIndex);
return 1;
}
int onEvent(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 2);
map<string, int>::const_iterator it = Game::gameObjectEventNameMap.find(eventType);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", eventType.c_str());
return lua_error(state);
}
object->onEvent(it->second);
return 0;
}
int isActivated(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x1) ? 0 : 1);
return 1;
default:
return 0;
}
}
int setActivated(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~1) | (lua_toboolean(state, 2) ? 0 : 1)
);
break;
}
return 0;
}
int isAutomatic(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x1) ? 1 : 0);
return 1;
default:
return 0;
}
}
int setAutomatic(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, boolean);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_PORTAL:
BlockScriptAPI::setFlags(object,
(BlockScriptAPI::getFlags(object) & ~1) | (lua_toboolean(state, 2) ? 1 : 0)
);
break;
}
return 0;
}
int getBehavior(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
case TYPE_SWITCH:
switch (BlockScriptAPI::getFlags(object) & 0x3) {
case 0:
lua_pushstring(state, "on");
break;
case 1:
lua_pushstring(state, "off");
break;
default:
lua_pushstring(state, "toggle");
break;
}
return 1;
default:
return 0;
}
}
int setBehavior(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, string);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
case TYPE_SWITCH:
{
int newFlags = BlockScriptAPI::getFlags(object) & ~3;
std::string s = lua_tostring(state, 2);
if (s == "on") newFlags |= 0;
else if (s == "off") newFlags |= 1;
else newFlags |= 2;
BlockScriptAPI::setFlags(object, newFlags);
}
break;
}
return 0;
}
int getState(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_FRAGILE:
lua_pushnumber(state, BlockScriptAPI::getFlags(object));
return 1;
default:
return 0;
}
}
int setState(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_FRAGILE:
{
int oldState = BlockScriptAPI::getFlags(object);
int newState = (int)lua_tonumber(state, 2);
if (newState < 0) newState = 0;
else if (newState > 3) newState = 3;
if (newState != oldState) {
BlockScriptAPI::fragileUpdateState(object, newState);
}
}
break;
}
return 0;
}
int isPlayerOn(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_BUTTON:
lua_pushboolean(state, (BlockScriptAPI::getFlags(object) & 0x4) ? 1 : 0);
return 1;
default:
return 0;
}
}
int getPathMaxTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushnumber(state, BlockScriptAPI::getPathMaxTime(object));
return 1;
default:
return 0;
}
}
int getPathTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
lua_pushnumber(state, BlockScriptAPI::getTemp(object));
return 1;
default:
return 0;
}
}
int setPathTime(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
Block* object = Block::getObjectFromUserData(state, 1);
if (object == NULL) return 0;
switch (object->type) {
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
BlockScriptAPI::setTemp(object, (int)lua_tonumber(state, 2));
break;
}
return 0;
}
}
#define _L block
//Array with the methods for the block library.
static const struct luaL_Reg blocklib_m[]={
_F(getBlockById),
_F(getBlocksById),
_F(moveTo),
_F(getLocation),
_F(setLocation),
_F(growTo),
_F(getSize),
_F(setSize),
_F(getType),
_F(changeThemeState),
_F(setVisible),
_F(isVisible),
_F(getEventHandler),
_F(setEventHandler),
_F(onEvent),
_F(isActivated),
_F(setActivated),
_F(isAutomatic),
_F(setAutomatic),
_F(getBehavior),
_F(setBehavior),
_F(getState),
_F(setState),
_F(isPlayerOn),
_F(getPathMaxTime),
_F(getPathTime),
_F(setPathTime),
{ NULL, NULL }
};
#undef _L
int luaopen_block(lua_State* state){
luaL_newlib(state,blocklib_m);
//Create the metatable for the block userdata.
luaL_newmetatable(state,"block");
lua_pushstring(state,"__index");
lua_pushvalue(state,-2);
lua_settable(state,-3);
Block::registerMetatableFunctions(state,-3);
//Register the functions and methods.
luaL_setfuncs(state,blocklib_m,0);
return 1;
}
//////////////////////////PLAYER SPECIFIC///////////////////////////
struct PlayerUserDatum{
char sig1,sig2,sig3,sig4;
};
Player* getPlayerFromUserData(lua_State* state,int idx){
PlayerUserDatum* ud=(PlayerUserDatum*)lua_touserdata(state,1);
//Make sure the user datum isn't null.
if(!ud) return NULL;
//Get the game state.
Game* game=dynamic_cast<Game*>(currentState);
if(game==NULL) return NULL;
Player* player=NULL;
//Check the signature to see if it's the player or the shadow.
if(ud->sig1=='P' && ud->sig2=='L' && ud->sig3=='Y' && ud->sig4=='R')
player=&game->player;
else if(ud->sig1=='S' && ud->sig2=='H' && ud->sig3=='D' && ud->sig4=='W')
player=&game->shadow;
return player;
}
namespace playershadow {
int getLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the object.
lua_pushnumber(state, player->getBox().x);
lua_pushnumber(state, player->getBox().y);
return 2;
}
int setLocation(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(3);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_ARGS_TYPE(2, number); // integer
HELPER_CHECK_ARGS_TYPE(3, number); // integer
//Get the player.
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the new location.
int x = lua_tonumber(state, 2);
int y = lua_tonumber(state, 3);
player->setLocation(x, y);
return 0;
}
int jump(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_2(1, 2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number); // integer
//Get the player.
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the new location.
if (args == 2){
int yVel = lua_tonumber(state, 2);
player->jump(yVel);
} else{
//Use default jump strength.
player->jump();
}
return 0;
}
int isShadow(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
lua_pushboolean(state, player->isShadow());
return 1;
}
int getCurrentStand(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE_NO_HINT(1, userdata);
Player* player = getPlayerFromUserData(state, 1);
if (player == NULL) return 0;
//Get the actual game object.
Block* object = player->getObjCurrentStand();
if (object == NULL){
return 0;
}
//Create the userdatum.
object->createUserData(state, "block");
//We return one object, the userdatum.
return 1;
}
}
#define _L playershadow
//Array with the methods for the player and shadow library.
static const struct luaL_Reg playerlib_m[]={
_F(getLocation),
_F(setLocation),
_F(jump),
_F(isShadow),
_F(getCurrentStand),
{NULL,NULL}
};
#undef _L
int luaopen_player(lua_State* state){
luaL_newlib(state,playerlib_m);
//Create the metatable for the player userdata.
luaL_newmetatable(state,"player");
lua_pushstring(state,"__index");
lua_pushvalue(state,-2);
lua_settable(state,-3);
//Now create two default player user data, one for the player and one for the shadow.
PlayerUserDatum* ud=(PlayerUserDatum*)lua_newuserdata(state,sizeof(PlayerUserDatum));
ud->sig1='P';ud->sig2='L';ud->sig3='Y';ud->sig4='R';
luaL_getmetatable(state,"player");
lua_setmetatable(state,-2);
lua_setglobal(state,"player");
ud=(PlayerUserDatum*)lua_newuserdata(state,sizeof(PlayerUserDatum));
ud->sig1='S';ud->sig2='H';ud->sig3='D';ud->sig4='W';
luaL_getmetatable(state,"player");
lua_setmetatable(state,-2);
lua_setglobal(state,"shadow");
//Register the functions and methods.
luaL_setfuncs(state,playerlib_m,0);
return 1;
}
//////////////////////////LEVEL SPECIFIC///////////////////////////
namespace level {
int getSize(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Returns level size.
lua_pushinteger(state, LEVEL_WIDTH);
lua_pushinteger(state, LEVEL_HEIGHT);
return 2;
}
int getWidth(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Returns level size.
lua_pushinteger(state, LEVEL_WIDTH);
return 1;
}
int getHeight(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Returns level size.
lua_pushinteger(state, LEVEL_HEIGHT);
return 1;
}
int getName(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level name.
lua_pushstring(state, game->getLevelName().c_str());
return 1;
}
int getEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 1);
map<string, int>::iterator it = Game::levelEventNameMap.find(eventType);
if (it == Game::levelEventNameMap.end()) return 0;
//Check compiled script
map<int, int>::iterator script = game->compiledScripts.find(it->second);
if (script == game->compiledScripts.end()) return 0;
//Get event handler
lua_rawgeti(state, LUA_REGISTRYINDEX, script->second);
return 1;
}
//It will return old event handler.
int setEventHandler(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_ARGS_TYPE_OR_NIL(2, function);
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check event type
string eventType = lua_tostring(state, 1);
map<string, int>::const_iterator it = Game::levelEventNameMap.find(eventType);
if (it == Game::levelEventNameMap.end()){
lua_pushfstring(state, "Unknown level event type: '%s'.", eventType.c_str());
return lua_error(state);
}
//Check compiled script
int scriptIndex = LUA_REFNIL;
{
map<int, int>::iterator script = game->compiledScripts.find(it->second);
if (script != game->compiledScripts.end()) scriptIndex = script->second;
}
//Set new event handler
game->compiledScripts[it->second] = luaL_ref(state, LUA_REGISTRYINDEX);
//Get old event handler and unreference it
lua_rawgeti(state, LUA_REGISTRYINDEX, scriptIndex);
luaL_unref(state, LUA_REGISTRYINDEX, scriptIndex);
return 1;
}
int win(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
if (stateID == STATE_LEVEL_EDITOR)
return 0;
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->won = true;
return 0;
}
int getTime(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->time);
return 1;
}
int getRecordings(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Returns level size.
lua_pushinteger(state, game->recordings);
return 1;
}
int broadcastObjectEvent(lua_State* state) {
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS_RANGE(1, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(2, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL(3, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE_OR_NIL_NO_HINT(4, userdata);
//Check event type
int eventType = 0;
{
string s = lua_tostring(state, 1);
auto it = Game::gameObjectEventNameMap.find(s);
if (it == Game::gameObjectEventNameMap.end()){
lua_pushfstring(state, "Unknown block event type: '%s'.", s.c_str());
return lua_error(state);
} else {
eventType = it->second;
}
}
//Check object type
int objType = -1;
if (args >= 2 && lua_isstring(state, 2)) {
string s = lua_tostring(state, 2);
auto it = Game::blockNameMap.find(s);
if (it == Game::blockNameMap.end()){
lua_pushfstring(state, "Unknown object type: '%s'.", s.c_str());
return lua_error(state);
} else {
objType = it->second;
}
}
//Check id
const char* id = NULL;
if (args >= 3 && lua_isstring(state, 3)) {
id = lua_tostring(state, 3);
}
//Check target
Block *target = NULL;
if (args >= 4) {
target = Block::getObjectFromUserData(state, 4);
}
//Check if the currentState is the game state.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->broadcastObjectEvent(eventType, objType, id, target);
return 0;
}
}
#define _L level
//Array with the methods for the level library.
static const struct luaL_Reg levellib_m[]={
_F(getSize),
_F(getWidth),
_F(getHeight),
_F(getName),
_F(getEventHandler),
_F(setEventHandler),
_F(win),
_F(getTime),
_F(getRecordings),
_F(broadcastObjectEvent),
{NULL,NULL}
};
#undef _L
int luaopen_level(lua_State* state){
luaL_newlib(state,levellib_m);
//Register the functions and methods.
luaL_setfuncs(state,levellib_m,0);
return 1;
}
/////////////////////////CAMERA SPECIFIC///////////////////////////
//FIXME: I can't define namespace camera since there is already a global variable named camera.
//Therefore I use struct camera for a workaround.
struct camera {
static int setMode(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(1);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
string mode = lua_tostring(state, 1);
//Get the game for setting the camera.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
//Check which mode.
if (mode == "player"){
game->cameraMode = Game::CAMERA_PLAYER;
} else if (mode == "shadow"){
game->cameraMode = Game::CAMERA_SHADOW;
} else{
//Unknown OR invalid camera mode.
return luaL_error(state, "Unknown or invalid camera mode for %s: '%s'.", __FUNCTION__, mode.c_str());
}
//Returns nothing.
return 0;
}
static int lookAt(lua_State* state){
//Check the number of arguments.
HELPER_GET_AND_CHECK_ARGS(2);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, number); // integer
HELPER_CHECK_ARGS_TYPE(2, number); // integer
//Get the point.
int x = lua_tonumber(state, 1);
int y = lua_tonumber(state, 2);
//Get the game for setting the camera.
Game* game = dynamic_cast<Game*>(currentState);
if (game == NULL) return 0;
game->cameraMode = Game::CAMERA_CUSTOM;
game->cameraTarget.x = x;
game->cameraTarget.y = y;
return 0;
}
};
#define _L camera
//Array with the methods for the camera library.
static const struct luaL_Reg cameralib_m[]={
_F(setMode),
_F(lookAt),
{NULL,NULL}
};
#undef _L
int luaopen_camera(lua_State* state){
luaL_newlib(state,cameralib_m);
//Register the functions and methods.
luaL_setfuncs(state,cameralib_m,0);
return 1;
}
/////////////////////////AUDIO SPECIFIC///////////////////////////
namespace audio {
int playSound(lua_State* state){
//Get the number of args, this can be anything from one to three.
- HELPER_GET_AND_CHECK_ARGS_RANGE(1, 3);
+ HELPER_GET_AND_CHECK_ARGS_RANGE(1, 4);
//Check if the arguments are of the right type.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, number); // integer
HELPER_CHECK_OPTIONAL_ARGS_TYPE(3, boolean);
+ HELPER_CHECK_OPTIONAL_ARGS_TYPE(4, number); // integer
//Default values for concurrent and force.
//See SoundManager.h
- int concurrent = 1;
+ int concurrent = -1;
bool force = false;
+ int fadeMusic = -1;
//If there's a second one it should be an integer.
if (args > 1){
concurrent = lua_tonumber(state, 2);
}
//If there's a third one it should be a boolean.
if (args > 2){
force = lua_toboolean(state, 3);
}
+ if (args > 3){
+ fadeMusic = lua_tonumber(state, 4);
+ }
+
//Get the name of the sound.
string sound = lua_tostring(state, 1);
//Try to play the sound.
- getSoundManager()->playSound(sound, concurrent, force);
+ int channel = getSoundManager()->playSound(sound, concurrent, force, fadeMusic);
- //Returns nothing.
- return 0;
+ //Returns whether the operation is successful.
+ lua_pushboolean(state, channel >= 0 ? 1 : 0);
+ return 1;
}
int playMusic(lua_State* state){
//Get the number of args, this can be either one or two.
HELPER_GET_AND_CHECK_ARGS_2(1, 2);
//Make sure the first argument is a string.
HELPER_CHECK_ARGS_TYPE(1, string);
HELPER_CHECK_OPTIONAL_ARGS_TYPE(2, boolean);
//Default value of fade for playMusic.
//See MusicManager.h.
bool fade = true;
//If there's a second one it should be a boolean.
if (args > 1){
fade = lua_toboolean(state, 2);
}
//Get the name of the music.
string music = lua_tostring(state, 1);
//Try to switch to the new music.
getMusicManager()->playMusic(music, fade);
//Returns nothing.
return 0;
}
int pickMusic(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Let the music manager pick a song from the current music list.
getMusicManager()->pickMusic();
return 0;
}
int setMusicList(lua_State* state){
//Get the number of args, this MUST be one.
HELPER_GET_AND_CHECK_ARGS(1);
//Make sure the given argument is a string.
HELPER_CHECK_ARGS_TYPE(1, string);
//And set the music list in the music manager.
string list = lua_tostring(state, 1);
getMusicManager()->setMusicList(list);
return 0;
}
int getMusicList(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Return the name of the song (contains list prefix).
lua_pushstring(state, getMusicManager()->getCurrentMusicList().c_str());
return 1;
}
int currentMusic(lua_State* state){
//NOTE: this function accepts 0 arguments, but we ignore the argument count.
//Return the name of the song (contains list prefix).
lua_pushstring(state, getMusicManager()->getCurrentMusic().c_str());
return 1;
}
}
#define _L audio
//Array with the methods for the audio library.
static const struct luaL_Reg audiolib_m[]={
_F(playSound),
_F(playMusic),
_F(pickMusic),
_F(setMusicList),
_F(getMusicList),
_F(currentMusic),
{NULL,NULL}
};
#undef _L
int luaopen_audio(lua_State* state){
luaL_newlib(state,audiolib_m);
//Register the functions and methods.
luaL_setfuncs(state,audiolib_m,0);
return 1;
}
diff --git a/src/SoundManager.cpp b/src/SoundManager.cpp
index 8be2fb1..da4ce5c 100644
--- a/src/SoundManager.cpp
+++ b/src/SoundManager.cpp
@@ -1,116 +1,148 @@
/*
* Copyright (C) 2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SoundManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "FileManager.h"
#include "Functions.h"
+#include "MusicManager.h"
#include <stdio.h>
#include <fstream>
#include <iostream>
#include <algorithm>
using namespace std;
SoundManager::SoundManager(){
Mix_ChannelFinished(channelFinishedHook);
+
+ //Assume the channel count is default number
+ playing.resize(MIX_CHANNELS);
+ playingFlags.resize(MIX_CHANNELS, 0);
}
SoundManager::~SoundManager(){
//We call destroy().
destroy();
}
+void SoundManager::setNumberOfChannels(int channels) {
+ //in case of the allocate channels is different from what we requested
+ channels = Mix_AllocateChannels(channels);
+
+ playing.resize(channels);
+ playingFlags.resize(channels, 0);
+}
+
void SoundManager::destroy(){
//Loop through the imageCollection and free them.
std::map<std::string,Mix_Chunk*>::iterator i;
for(i=sounds.begin();i!=sounds.end();++i){
if(i->second!=NULL){
Mix_FreeChunk(i->second);
}
}
//And clear the collection.
sounds.clear();
}
void SoundManager::loadSound(const std::string &file,const std::string &name){
//Make sure the name isn't in use already.
Mix_Chunk* sfx=sounds[name];
if(sfx!=NULL){
cerr<<"WARNING: There's already a sound registered for: "<<name<<endl;
return;
}
//Now load the sound file.
sfx=Mix_LoadWAV(file.c_str());
if(sfx==NULL)
cerr<<"ERROR: Can't load sound file "<<file<<", "<<SDL_GetError()<<endl;
sounds[name]=sfx;
}
-void SoundManager::playSound(const std::string &name,const int concurrent,const bool force){
+int SoundManager::playSound(const std::string &name, int concurrent, bool force, int fadeMusic){
//Make sure sound is enabled.
if(!getSettings()->getBoolValue("sound"))
- return;
+ return -1;
//Check if the music is in the collection.
Mix_Chunk* sfx=sounds[name];
if(sfx==NULL){
cerr<<"WARNING: No such sound registered: "<<name<<endl;
- return;
+ return -1;
}
//Check if there's a limit to the number of times the sfx should be played at the same time.
if(concurrent!=-1){
//There is so check the number of currently playing.
int currentPlaying=0;
- for(int i=0;i<MIX_CHANNELS;i++){
+ for(int i=0;i<(int)playing.size();i++){
if(playing[i]==name)
currentPlaying++;
}
//Check if there are too many of the same effect playing.
if(currentPlaying>=concurrent)
- return;
+ return -1;
}
//Try to play the sfx on a free channel.
int channel=Mix_PlayChannel(-1,sfx,0);
//Check if there was an error.
if(channel==-1){
if(force){
//We need to clear a channel for the sfx since it has to be played.
//FIXME: Always clear the first channel?
Mix_HaltChannel(0);
channel=Mix_PlayChannel(0,sfx,0);
if(channel==-1)
cerr<<"WARNING: Unable to forcefully play sound '"<<name<<"'!"<<endl;
}else{
cerr<<"WARNING: Unable to play sound '"<<name<<"', out of channels."<<endl;
}
- }else{
- playing[channel]=name;
}
+
+ if (channel >= 0) {
+ int flags = 0;
+
+ if (fadeMusic >= 0 && getSettings()->getBoolValue("music")) {
+ flags |= 0x1;
+ getMusicManager()->setVolume(fadeMusic*float(atoi(getSettings()->getValue("music").c_str()) / float(MIX_MAX_VOLUME)));
+ }
+
+ playing[channel] = name;
+ playingFlags[channel] = flags;
+ }
+
+ return channel;
}
void SoundManager::channelFinished(int channel){
+ int flags = playingFlags[channel];
+
+ if (flags & 0x1) {
+ getMusicManager()->setVolume(atoi(getSettings()->getValue("music").c_str()));
+ }
+
playing[channel].clear();
+ playingFlags[channel] = 0;
}
diff --git a/src/SoundManager.h b/src/SoundManager.h
index ad01c38..efda9a1 100644
--- a/src/SoundManager.h
+++ b/src/SoundManager.h
@@ -1,62 +1,71 @@
/*
* Copyright (C) 2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOUNDMANAGER_H
#define SOUNDMANAGER_H
#include <SDL_mixer.h>
#include <string>
#include <map>
#include <vector>
//Class for loading and playing sound effects.
class SoundManager{
private:
//Map containing the sfx chunk by name.
std::map<std::string,Mix_Chunk*> sounds;
//Array that holds the name of the sfx that is playing on each channel.
- std::string playing[MIX_CHANNELS];
+ std::vector<std::string> playing;
+
+ //Flags of the channel.
+ //0x1: Temporarily turn the music volume down.
+ std::vector<int> playingFlags;
public:
//Constructor.
SoundManager();
//Destructor.
~SoundManager();
+ //Set the number of channels. Please call this function when no sound effects are playing.
+ void setNumberOfChannels(int channels);
+
//Destroys the sound chunks.
void destroy();
//This method will load one sfx file and add it to the collection.
//file: The filename of the sfx file.
//name: The name the sfx should have.
void loadSound(const std::string &file,const std::string &name);
//This method will start playing a sfx.
//name: The name of the sfx to play.
//concurrent: Integer containing the number of times the same sfx can be played at once, -1 is unlimited.
- //force: Boolean if the sound must get
- void playSound(const std::string &name,const int concurrent=1,const bool force=false);
+ //force: Boolean if the sound must get
+ //fadeMusic: A factor to temporarily turn the music volume down (0-128 or -1).
+ //Return value: the channel of the sfx. -1 means failed (channed is full, invalid sfx name, sfx volume is 0, etc.)
+ int playSound(const std::string &name, int concurrent = -1, bool force = false, int fadeMusic = -1);
//Method that is called when a sfx is done playing.
//channel: The channel number.
void channelFinished(int channel);
};
#endif
diff --git a/src/StatisticsManager.cpp b/src/StatisticsManager.cpp
index 99d671d..0936226 100644
--- a/src/StatisticsManager.cpp
+++ b/src/StatisticsManager.cpp
@@ -1,801 +1,801 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StatisticsManager.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "Functions.h"
#include "LevelPackManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include "libs/tinyformat/tinyformat.h"
#include <SDL_ttf.h>
#if defined(WIN32)
#define PRINTF_LONGLONG "%I64d"
#else
#define PRINTF_LONGLONG "%lld"
#endif
using namespace std;
StatisticsManager statsMgr;
static const int achievementDisplayTime=(FPS*4500)/1000;
static const int achievementIntervalTime=achievementDisplayTime+(FPS*500)/1000;
static map<string,AchievementInfo*> avaliableAchievements;
//================================================================
StatisticsManager::StatisticsManager(){
bmDropShadow=NULL;
bmQuestionMark=NULL;
bmAchievement=NULL;
startTime=time(NULL);
tutorialLevels=0;
clear();
}
void StatisticsManager::clear(){
playerTravelingDistance=shadowTravelingDistance=0.0f;
playerJumps=shadowJumps
=playerDies=shadowDies
=playerSquashed=shadowSquashed
=completedLevels=silverLevels=goldLevels
=recordTimes=switchTimes=swapTimes=saveTimes=loadTimes
=playTime=levelEditTime
=createdLevels=tutorialCompleted=tutorialGold=0;
achievements.clear();
queuedAchievements.clear();
achievementTime=0;
currentAchievement=0;
if(bmAchievement){
bmAchievement.reset();
}
}
#define LOAD_STATS(var,func) { \
vector<string> &v=node.attributes[ #var ]; \
if(!v.empty() && !v[0].empty()) \
var=func(v[0].c_str()); \
}
void StatisticsManager::loadFile(const std::string& fileName){
clear();
ifstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
POASerializer serializer;
if(!serializer.readNode(file,&node,true)) return;
//load statistics
LOAD_STATS(playerTravelingDistance,atof);
LOAD_STATS(shadowTravelingDistance,atof);
LOAD_STATS(playerJumps,atoi);
LOAD_STATS(shadowJumps,atoi);
LOAD_STATS(playerDies,atoi);
LOAD_STATS(shadowDies,atoi);
LOAD_STATS(playerSquashed,atoi);
LOAD_STATS(shadowSquashed,atoi);
LOAD_STATS(recordTimes,atoi);
LOAD_STATS(switchTimes,atoi);
LOAD_STATS(swapTimes,atoi);
LOAD_STATS(saveTimes,atoi);
LOAD_STATS(loadTimes,atoi);
LOAD_STATS(playTime,atoi);
LOAD_STATS(levelEditTime,atoi);
LOAD_STATS(createdLevels,atoi);
//load achievements.
//format is: name;time,name;time,...
{
vector<string> &v=node.attributes["achievements"];
for(unsigned int i=0;i<v.size();i++){
string s=v[i];
time_t t=0;
string::size_type lps=s.find(';');
if(lps!=string::npos){
string s1=s.substr(lps+1);
s=s.substr(0,lps);
long long n;
sscanf(s1.c_str(),PRINTF_LONGLONG,&n);
t=(time_t)n;
}
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(s);
if(it!=avaliableAchievements.end()){
OwnedAchievement ach={t,it->second};
achievements[it->first]=ach;
}
}
}
}
//Call when level edit is start
void StatisticsManager::startLevelEdit(){
levelEditStartTime=time(NULL);
}
//Call when level edit is end
void StatisticsManager::endLevelEdit(){
levelEditTime+=time(NULL)-levelEditStartTime;
}
//update in-game time
void StatisticsManager::updatePlayTime(){
time_t endTime=time(NULL);
playTime+=endTime-startTime;
startTime=endTime;
}
#define SAVE_STATS(var,pattern) { \
sprintf(s,pattern,var); \
node.attributes[ #var ].push_back(s); \
}
void StatisticsManager::saveFile(const std::string& fileName){
char s[64];
//update in-game time
updatePlayTime();
ofstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
//save statistics
SAVE_STATS(playerTravelingDistance,"%.2f");
SAVE_STATS(shadowTravelingDistance,"%.2f");
SAVE_STATS(playerJumps,"%d");
SAVE_STATS(shadowJumps,"%d");
SAVE_STATS(playerDies,"%d");
SAVE_STATS(shadowDies,"%d");
SAVE_STATS(playerSquashed,"%d");
SAVE_STATS(shadowSquashed,"%d");
SAVE_STATS(recordTimes,"%d");
SAVE_STATS(switchTimes,"%d");
SAVE_STATS(swapTimes,"%d");
SAVE_STATS(saveTimes,"%d");
SAVE_STATS(loadTimes,"%d");
SAVE_STATS(playTime,"%d");
SAVE_STATS(levelEditTime,"%d");
SAVE_STATS(createdLevels,"%d");
//save achievements.
//format is: name;time,name;time,...
{
vector<string>& v=node.attributes["achievements"];
for(map<string,OwnedAchievement>::iterator it=achievements.begin();it!=achievements.end();++it){
stringstream strm;
char s[32];
long long n=it->second.achievedTime;
sprintf(s,
#ifdef WIN32
"%I64d",
#else
"%Ld",
#endif
n);
strm<<it->first<<";"<<s;
v.push_back(strm.str());
}
}
POASerializer serializer;
serializer.writeNode(&node,file,true,true);
}
void StatisticsManager::loadPicture(SDL_Renderer& renderer, ImageManager& imageManager){
//Load drop shadow picture
bmDropShadow=imageManager.loadTexture(getDataPath()+"gfx/dropshadow.png", renderer);
bmQuestionMark=imageManager.loadImage(getDataPath()+"gfx/menu/questionmark.png");
}
void StatisticsManager::registerAchievements(ImageManager& imageManager){
if(!avaliableAchievements.empty()) return;
for(int i=0;achievementList[i].id!=NULL;i++){
avaliableAchievements[achievementList[i].id]=&achievementList[i];
if(achievementList[i].imageFile!=NULL){
achievementList[i].imageSurface = imageManager.loadImage(getDataPath()+achievementList[i].imageFile);
}
}
}
void StatisticsManager::render(ImageManager&,SDL_Renderer &renderer){
if(achievementTime==0 && !bmAchievement && currentAchievement<(int)queuedAchievements.size()){
//create surface
bmAchievement=createAchievementSurface(renderer, queuedAchievements[currentAchievement++]);
//FIXME: Draw the box.
//drawGUIBox(0,0,bmAchievement->w,bmAchievement->h,bmAchievement,0xFFFFFF00);
//check if queue is empty
if(currentAchievement>=(int)queuedAchievements.size()){
queuedAchievements.clear();
currentAchievement=0;
}
//play a sound
- getSoundManager()->playSound("achievement");
+ getSoundManager()->playSound("achievement", 1, false, 32);
}
//check if we need to display achievements
if(bmAchievement){
achievementTime++;
if(achievementTime<=0){
return;
}else if(achievementTime<=5){
drawAchievement(renderer,achievementTime);
}else if(achievementTime<=achievementDisplayTime-5){
drawAchievement(renderer,5);
}else if(achievementTime<achievementDisplayTime){
drawAchievement(renderer,achievementDisplayTime-achievementTime);
}else if(achievementTime>=achievementIntervalTime){
if(bmAchievement){
bmAchievement.reset();
}
achievementTime=0;
}
}
}
void StatisticsManager::newAchievement(const std::string& id,bool save){
//check avaliable achievements
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(id);
if(it==avaliableAchievements.end()) return;
//check if already have this achievement
if(save){
map<string,OwnedAchievement>::iterator it2=achievements.find(id);
if(it2!=achievements.end()) return;
OwnedAchievement ach={time(NULL),it->second};
achievements[id]=ach;
}
//add it to queue
queuedAchievements.push_back(it->second);
}
float StatisticsManager::getAchievementProgress(AchievementInfo* info){
if(!strcmp(info->id,"experienced")){
return float(completedLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"expert")){
return float(goldLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"tutorial")){
if(tutorialLevels>0)
return float(tutorialCompleted)/float(tutorialLevels)*100.0f;
else
return 0.0f;
}
if(!strcmp(info->id,"tutorialGold")){
if(tutorialLevels>0)
return float(tutorialGold)/float(tutorialLevels)*100.0f;
else
return 0.0f;
}
if(!strcmp(info->id,"create50")){
return float(createdLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"frog")){
return float(playerJumps+shadowJumps)/1000.0f*100.0f;
}
if(!strcmp(info->id,"die50")){
return float(playerDies+shadowDies)/50.0f*100.0f;
}
if(!strcmp(info->id,"die1000")){
return float(playerDies+shadowDies)/1000.0f*100.0f;
}
if(!strcmp(info->id,"suqash50")){
return float(playerSquashed+shadowSquashed)/50.0f*100.0f;
}
if(!strcmp(info->id,"travel100")){
return (playerTravelingDistance+shadowTravelingDistance)/100.0f*100.0f;
}
if(!strcmp(info->id,"travel1k")){
return (playerTravelingDistance+shadowTravelingDistance)/1000.0f*100.0f;
}
if(!strcmp(info->id,"travel10k")){
return (playerTravelingDistance+shadowTravelingDistance)/10000.0f*100.0f;
}
if(!strcmp(info->id,"travel42k")){
return (playerTravelingDistance+shadowTravelingDistance)/42195.0f*100.0f;
}
if(!strcmp(info->id,"record100")){
return float(recordTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"record1k")){
return float(recordTimes)/1000.0f*100.0f;
}
if(!strcmp(info->id,"switch100")){
return float(switchTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"switch1k")){
return float(switchTimes)/1000.0f*100.0f;
}
if(!strcmp(info->id,"swap100")){
return float(swapTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"swap1k")){
return float(swapTimes)/1000.0f*100.0f;
}
//not found
return 0.0f;
}
SharedTexture StatisticsManager::createAchievementSurface(SDL_Renderer& renderer, AchievementInfo* info,SDL_Rect* rect,bool showTip,const time_t *achievedTime){
if(info==NULL || info->id==NULL) return NULL;
//prepare text
SurfacePtr title0(nullptr);
SurfacePtr title1(nullptr);
vector<SDL_Surface*> descSurfaces;
SDL_Color fg={0,0,0};
int fontHeight=TTF_FontLineSkip(fontText);
bool showDescription=false;
bool showImage=false;
float achievementProgress=0.0f;
if(showTip){
title0.reset(TTF_RenderUTF8_Blended(fontText,_("New achievement:"),fg));
title1.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
showDescription=showImage=true;
}else if(achievedTime){
char s[128];
strftime(s,sizeof(s),"%c",localtime(achievedTime));
stringstream strm;
tinyformat::format(strm,_("Achieved on %s"),s);
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
title1.reset(TTF_RenderUTF8_Blended(fontText,strm.str().c_str(),fg));
showDescription=showImage=true;
}else if(info->displayStyle==ACHIEVEMENT_HIDDEN){
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_("Unknown achievement"),fg));
}else{
if(info->displayStyle==ACHIEVEMENT_PROGRESS){
achievementProgress=getAchievementProgress(info);
stringstream strm;
tinyformat::format(strm,_("Achieved %1.0f%%"),achievementProgress);
title1.reset(TTF_RenderUTF8_Blended(fontText,strm.str().c_str(),fg));
}else{
title1.reset(TTF_RenderUTF8_Blended(fontText,_("Not achieved"),fg));
}
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
showDescription= info->displayStyle==ACHIEVEMENT_ALL || info->displayStyle==ACHIEVEMENT_PROGRESS;
showImage=true;
}
if(info->description!=NULL && showDescription){
string description=_(info->description);
string::size_type lps=0,lpe;
for(;;){
lpe=description.find('\n',lps);
if(lpe==string::npos){
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps)+' ').c_str(),fg));
break;
}else{
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps,lpe-lps)+' ').c_str(),fg));
lps=lpe+1;
}
}
}
//calculate the size
int w=0,h=0,w1=8,h1=0;
if(title0!=NULL){
if(title0->w>w) w=title0->w;
h1+=title0->h;
}
if(title1!=NULL){
if(title1->w>w) w=title1->w;
h1+=title1->h;
/*//calc progress bar size
if(!showTip && !achievedTime && info->displayStyle==ACHIEVEMENT_PROGRESS){
h1+=4;
}*/
}
const int preferredImageWidth = 50;
const int preferredImageHeight = 50;
if(showImage){
if(info->imageSurface!=NULL){
// NEW: we have the preferred image size
const int width = std::max(info->r.w, preferredImageWidth);
const int height = std::max(info->r.h, preferredImageHeight);
w1+=width+8;
w+=width+8;
if(height>h1) h1=height;
}
}else{
w1+=bmQuestionMark->w+8;
w+=bmQuestionMark->w+8;
if(bmQuestionMark->h>h1) h1=bmQuestionMark->h;
}
h=h1+8;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
if(descSurfaces[i]->w>w) w=descSurfaces[i]->w;
}
}
h+=descSurfaces.size()*fontHeight;
w+=16;
h+=16;
//check if size is specified
int left=0,top=0;
if(rect!=NULL){
//NOTE: SDL2 port. This was never used.
/* if(surface!=NULL){
left=rect->x;
top=rect->y;
}*/
if(rect->w>0) w=rect->w;
else rect->w=w;
rect->h=h;
}
//create surface if necessary
SurfacePtr surface = createSurface(w, h);
std::unique_ptr<SDL_Renderer,decltype(&SDL_DestroyRenderer)> surfaceRenderer(
SDL_CreateSoftwareRenderer(surface.get()), &SDL_DestroyRenderer);
//draw background
const SDL_Rect r={left,top,w,h};
if(showTip || achievedTime){
SDL_FillRect(surface.get(),&r,SDL_MapRGB(surface->format,255,255,255));
}else{
SDL_FillRect(surface.get(),&r,SDL_MapRGB(surface->format,192,192,192));
}
//draw horizontal separator
//FIXME: this is moved from StatisticsScreen::createGUI
if (!showTip) {
const SDL_Rect r0 = { left, top, w, 1 };
const SDL_Rect r1 = { left, top + h - 2, w, 1 };
const SDL_Rect r2 = { left, top + h - 1, w, 1 };
Uint32 c0 = achievedTime ? SDL_MapRGB(surface->format, 224, 224, 224) : SDL_MapRGB(surface->format, 168, 168, 168);
Uint32 c2 = achievedTime ? SDL_MapRGB(surface->format, 128, 128, 128) : SDL_MapRGB(surface->format, 96, 96, 96);
SDL_FillRect(surface.get(), &r0, c0);
SDL_FillRect(surface.get(), &r1, c0);
SDL_FillRect(surface.get(), &r2, c2);
}
//draw picture
if(showImage){
if(info->imageSurface){
// NEW: we have the preferred image size
SDL_Rect r={left+8,top+8+(h1-info->r.h)/2,0,0};
if (info->r.w < preferredImageWidth) r.x += (preferredImageWidth - info->r.w) / 2;
SDL_BlitSurface(info->imageSurface,&info->r,surface.get(),&r);
}
}else{
SDL_Rect r={left+8,top+8+(h1-bmQuestionMark->h)/2,0,0};
SDL_BlitSurface(bmQuestionMark,NULL,surface.get(),&r);
}
//draw text
h=8;
if(title0){
SDL_Rect r={left+w1,top+h,0,0};
SDL_BlitSurface(title0.get(),NULL,surface.get(),&r);
h+=title0->h;
}
if(title1){
SDL_Rect r={left+w1,top+h,0,0};
//Draw progress bar.
if(!showTip && !achievedTime && info->displayStyle==ACHIEVEMENT_PROGRESS){
//Draw borders.
SDL_Rect r1={r.x,r.y,w-8-r.x,title1->h};
drawGUIBox(r1.x,r1.y,r1.w,r1.h,*surfaceRenderer,0x1D);
//Draw progress.
r1.x++;
r1.y++;
r1.w=int(achievementProgress/100.0f*float(r1.w-2)+0.5f);
r1.h-=2;
SDL_SetRenderDrawColor(surfaceRenderer.get(),0,0,0,100);
SDL_RenderFillRect(surfaceRenderer.get(),&r1);
//shift the text a little bit (???)
r.x+=2;
r.y+=2;
}
//Draw text.
SDL_BlitSurface(title1.get(),NULL,surface.get(),&r);
}
h=h1+16;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_Rect r={left+8,top+h+static_cast<int>(i)*fontHeight,0,0};
SDL_BlitSurface(descSurfaces[i],NULL,surface.get(),&r);
}
}
//clean up
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_FreeSurface(descSurfaces[i]);
}
}
//FIXME: Should we clear the vector here?
//over
return textureFromSurface(renderer, std::move(surface));
}
void StatisticsManager::drawAchievement(SDL_Renderer& renderer,int alpha){
if(!bmAchievement || alpha<=0) {
return;
}
if(alpha>5) alpha=5;
SDL_Rect r = rectFromTexture(*bmAchievement);
int w=0,h=0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
r.x = w-32-r.w;
r.y = 32;
SDL_SetTextureAlphaMod(bmAchievement.get(), alpha*40);
applyTexture(r.x, r.y,bmAchievement, renderer);
if(!bmDropShadow) {
return;
}
//draw drop shadow - corner
{
int w1=r.w/2,w2=r.w-w1,h1=r.h/2,h2=r.h-h1;
if(w1>16) w1=16;
if(w2>16) w2=16;
if(h1>16) h1=16;
if(h2>16) h2=16;
const int x=(5-alpha)*64;
//top-left
SDL_Rect r1={x,0,w1+16,h1+16};//),r2={r.x-16,r.y-16,0,0};
SDL_Rect r2 ={r.x-16, r.y-16, r1.w, r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//top-right
r1.x=x+48-w2;r2.w=r1.w =w2+16;r2.x=r.x+r.w-w2;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom-right
r1.y=48-h2;r2.h=r1.h=h2+16;r2.y=r.y+r.h-h2;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom-left
r1.x=x;r2.w=r1.w=w1+16;r2.x=r.x-16;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
}
//draw drop shadow - border
int i=r.w-32;
while(i>0){
const int ii=i>128?128:i;
//top
SDL_Rect r1={0,256-alpha*16,ii,16};
SDL_Rect r2={r.x+r.w-16-i,r.y-16,r1.w,r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom
r1.x=128;r2.y=r.y+r.h;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
i-=ii;
}
i=r.h-32;
while(i>0){
const int ii=i>128?128:i;
//top
SDL_Rect r1={512-alpha*16,0,16,ii};
SDL_Rect r2={r.x-16,r.y+r.h-16-i, r1.w, r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom
r1.y=128;r2.x=r.x+r.w;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
i-=ii;
}
}
void StatisticsManager::reloadCompletedLevelsAndAchievements(){
completedLevels=silverLevels=goldLevels=0;
LevelPackManager *lpm=getLevelPackManager();
vector<pair<string,string> > v=lpm->enumLevelPacks();
bool tutorial=false,tutorialIsGold=false;
for(unsigned int i=0;i<v.size();i++){
string& s=v[i].first;
LevelPack *levels=lpm->getLevelPack(s);
levels->loadProgress();
bool b=false;
if(s==lpm->tutorialLevelPackPath){
tutorialLevels=levels->getLevelCount();
tutorialCompleted=tutorialGold=0;
b=tutorial=tutorialIsGold=true;
}
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
completedLevels++;
if(b) tutorialCompleted++;
if(medal==2) silverLevels++;
if(medal==3){
goldLevels++;
if(b) tutorialGold++;
}
if(medal!=3 && b) tutorialIsGold=false;
}else if(b){
tutorial=tutorialIsGold=false;
}
}
}
//upadte achievements
updateLevelAchievements();
updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialIsGold?2:0));
}
void StatisticsManager::reloadOtherAchievements(){
int i;
if(playTime>=7200) newAchievement("addicted");
if(playTime>=86400) newAchievement("loyalFan");
if(levelEditTime>=7200) newAchievement("constructor");
if(levelEditTime>=86400) newAchievement("constructor2");
if(createdLevels>=1) newAchievement("create1");
if(createdLevels>=50) newAchievement("create50");
i=playerJumps+shadowJumps;
if(i>=1000) newAchievement("frog");
i=playerDies+shadowDies;
if(i>=1) newAchievement("die1");
if(i>=50) newAchievement("die50");
if(i>=1000) newAchievement("die1000");
i=playerSquashed+shadowSquashed;
if(i>=1) newAchievement("squash1");
if(i>=50) newAchievement("squash50");
float d=playerTravelingDistance+shadowTravelingDistance;
if(d>=100.0f) newAchievement("travel100");
if(d>=1000.0f) newAchievement("travel1k");
if(d>=10000.0f) newAchievement("travel10k");
if(d>=42195.0f) newAchievement("travel42k");
if(recordTimes>=100) newAchievement("record100");
if(recordTimes>=1000) newAchievement("record1k");
if(switchTimes>=100) newAchievement("switch100");
if(switchTimes>=1000) newAchievement("switch1k");
if(swapTimes>=100) newAchievement("swap100");
if(swapTimes>=1000) newAchievement("swap1k");
if(saveTimes>=1000) newAchievement("save1k");
if(loadTimes>=1000) newAchievement("load1k");
if(version.find("Development")!=string::npos) newAchievement("programmer");
}
//Update level specified achievements.
//Make sure the completed level count is correct.
void StatisticsManager::updateLevelAchievements(){
if(completedLevels>=1) newAchievement("newbie");
if(goldLevels>=1) newAchievement("goodjob");
if(completedLevels>=50) newAchievement("experienced");
if(goldLevels>=50) newAchievement("expert");
}
//Update tutorial specified achievements.
//Make sure the level progress of tutorial is correct.
void StatisticsManager::updateTutorialAchievements(){
//find tutorial level pack
LevelPackManager *lpm=getLevelPackManager();
LevelPack *levels=lpm->getTutorialLevelPack();
if(levels==NULL) return;
bool tutorial=true,tutorialIsGold=true;
tutorialLevels=levels->getLevelCount();
tutorialCompleted=tutorialGold=0;
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
tutorialCompleted++;
if(medal!=3) tutorialIsGold=false;
else tutorialGold++;
}else{
tutorial=tutorialIsGold=false;
break;
}
}
//upadte achievements
updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialIsGold?2:0));
}
//internal function
//flags: a bit-field value indicates which achievements we have.
void StatisticsManager::updateTutorialAchievementsInternal(int flags){
if(flags&1) newAchievement("tutorial");
if(flags&2) newAchievement("tutorialGold");
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Jun 15, 11:27 PM (2 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
72025
Default Alt Text
(133 KB)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline