Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F118562
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
262 KB
Referenced Files
None
Subscribers
None
View Options
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/data/font/VeraMono.ttf b/data/font/VeraMono.ttf
new file mode 100644
index 0000000..139f0b4
Binary files /dev/null and b/data/font/VeraMono.ttf differ
diff --git a/src/Block.cpp b/src/Block.cpp
index 1c3a729..19ef568 100644
--- a/src/Block.cpp
+++ b/src/Block.cpp
@@ -1,875 +1,874 @@
/*
* Copyright (C) 2011-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 "GameObjects.h"
#include "Game.h"
#include "Player.h"
#include "Block.h"
#include "Functions.h"
#include "Globals.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
Block::Block(int x,int y,int type,Game* parent):
GameObject(parent),
temp(0),
tempSave(0),
flags(0),
flagsSave(0),
dx(0),
xSave(0),
dy(0),
ySave(0),
loop(true),
editorFlags(0)
{
//First set the location and size of the box.
//The default size is 50x50.
box.x=x;
box.y=y;
box.w=50;
box.h=50;
//Also set the
boxBase.x=x;
boxBase.y=y;
boxBase.w=50;
boxBase.h=50;
//Set the type.
this->type=type;
//Some types need type specific code.
if(type==TYPE_START_PLAYER){
//This is the player start so set the player here.
//We center the player, the player is 23px wide.
parent->player.setPosition(box.x+(box.w-23)/2,box.y);
parent->player.fx=box.x+(box.w-23)/2;
parent->player.fy=box.y;
}else if(type==TYPE_START_SHADOW){
//This is the shadow start so set the shadow here.
//We center the shadow, the shadow is 23px wide.
parent->shadow.setPosition(box.x+(box.w-23)/2,box.y);
parent->shadow.fx=box.x+(box.w-23)/2;
parent->shadow.fy=box.y;
}
objCurrentStand=NULL;
inAir=true;
inAirSave=true;
xVel=yVel=xVelBase=yVelBase=0;
xVelSave=yVelSave=xVelBaseSave=yVelBaseSave=0;
//And load the appearance.
objThemes.getBlock(type)->createInstance(&appearance);
}
Block::~Block(){}
void Block::show(){
//Check if the block is visible.
if(checkCollision(camera,box)==true || (stateID==STATE_LEVEL_EDITOR && checkCollision(camera,boxBase)==true)){
SDL_Rect r={0,0,50,50};
//What we need to draw depends on the type of block.
switch(type){
case TYPE_CHECKPOINT:
//Check if the checkpoint is last used.
if(parent!=NULL && parent->objLastCheckPoint==this){
if(!temp) appearance.changeState("activated");
temp=1;
}else{
if(temp) appearance.changeState("default");
temp=0;
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
if(temp){
r.x=50-temp;
r.w=temp;
appearance.draw(screen,box.x-camera.x-50+temp,box.y-camera.y,&r);
r.x=0;
r.w=50-temp;
appearance.draw(screen,box.x-camera.x+temp,box.y-camera.y,&r);
return;
}
break;
case TYPE_NOTIFICATION_BLOCK:
if(message.empty()==false){
appearance.draw(screen, box.x - camera.x, box.y - camera.y);
return;
}
break;
}
//Always draw the base.
appearance.drawState("base", screen, boxBase.x - camera.x, boxBase.y - camera.y);
//Now draw normal.
appearance.draw(screen, box.x - camera.x, box.y - camera.y);
//Some types need to draw something on top of the base/default.
switch(type){
case TYPE_BUTTON:
if(flags&4){
if(temp<5) temp++;
}else{
if(temp>0) temp--;
}
appearance.drawState("button",screen,box.x-camera.x,box.y-camera.y-5+temp);
break;
}
}
}
SDL_Rect Block::getBox(int boxType){
SDL_Rect r={0,0,0,0};
switch(boxType){
case BoxType_Base:
return boxBase;
case BoxType_Previous:
r.x=box.x-dx;
r.y=box.y-dy;
r.w=box.w;
r.h=box.h;
return r;
case BoxType_Delta:
r.x=dx;
r.y=dy;
return r;
case BoxType_Velocity:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
//NOTE: We can safely assume that the velocity is the same as the delta since speed can't change.
r.x=dx;
r.y=dy;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
r.x=(flags&1)?0:dx;
break;
default:
r.x=xVel;
r.y=yVel;
}
return r;
case BoxType_Current:
return box;
}
return r;
}
void Block::setLocation(int x,int y){
//The block has moved so calculate the delta.
dx=x-box.x;
dy=y-box.y;
//And set the new location.
box.x=x;
box.y=y;
}
void Block::saveState(){
tempSave=temp;
flagsSave=flags;
xSave=box.x-boxBase.x;
ySave=box.y-boxBase.y;
appearance.saveAnimation();
//In case of a pushable block we need to save some more.
if(type==TYPE_PUSHABLE){
xVelSave=xVel;
yVelSave=yVel;
xVelBaseSave=xVelBase;
yVelBaseSave=yVelBase;
}
}
void Block::loadState(){
temp=tempSave;
flags=flagsSave;
switch(type){
case TYPE_PUSHABLE:
xVel=xVelSave;
yVel=yVelSave;
xVelBase=xVelBaseSave;
yVelBase=yVelBaseSave;
//NOTE: Fallthrough is intended.
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK: //FIXME: Shouldn't moving spikes be here as well (???)
box.x=boxBase.x+xSave;
box.y=boxBase.y+ySave;
break;
}
appearance.loadAnimation();
}
void Block::reset(bool save){
//We need to reset so we clear the temp and saves.
if(save){
temp=tempSave=xSave=ySave=0;
flags=flagsSave=editorFlags;
}else{
temp=0;
flags=editorFlags;
}
//Reset the block to it's original location.
box.x=boxBase.x;
box.y=boxBase.y;
//Reset any velocity.
xVel=yVel=xVelBase=yVelBase=0;
//Also reset the appearance.
appearance.resetAnimation(save);
appearance.changeState("default");
//If it's a fragile block we need to update the appearance.
switch(type){
case TYPE_FRAGILE:
{
const char* s=(flags==0)?"default":((flags==1)?"fragile1":((flags==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
break;
}
}
void Block::playAnimation(int flags){
//TODO Why int flags????
switch(type){
case TYPE_SWAP:
appearance.changeState("activated");
break;
case TYPE_SWITCH:
temp^=1;
appearance.changeState(temp?"activated":"default");
break;
}
}
void Block::onEvent(int eventType){
//Iterator used to check if the map contains certain entries.
map<int,string>::iterator it;
//Check if the data contains the id block.
it=scripts.find(eventType);
if(it!=scripts.end()){
//Set the id of the block.
getScriptExecutor()->executeScript(it->second);
return;
}
//Event handling.
switch(eventType){
case GameObjectEvent_PlayerWalkOn:
switch(type){
case TYPE_FRAGILE:
flags++;
{
const char* s=(flags==0)?"default":((flags==1)?"fragile1":((flags==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
break;
}
break;
case GameObjectEvent_PlayerIsOn:
switch(type){
case TYPE_BUTTON:
dx=1;
break;
}
break;
case GameObjectEvent_OnToggle:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags^=1;
break;
case TYPE_PORTAL:
appearance.changeState("activated");
break;
case TYPE_COLLECTABLE:
appearance.changeState("inactive");
flags=1;
break;
}
break;
case GameObjectEvent_OnSwitchOn:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags&=~1;
break;
case TYPE_EXIT:
appearance.changeState("default");
break;
}
break;
case GameObjectEvent_OnSwitchOff:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags|=1;
break;
case TYPE_EXIT:
appearance.changeState("closed");
break;
}
break;
}
}
int Block::queryProperties(int propertyType,Player* obj){
switch(propertyType){
case GameObjectProperty_PlayerCanWalkOn:
switch(type){
case TYPE_BLOCK:
case TYPE_MOVING_BLOCK:
case TYPE_CONVEYOR_BELT:
case TYPE_BUTTON:
case TYPE_PUSHABLE:
return 1;
case TYPE_SHADOW_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_SHADOW_CONVEYOR_BELT:
if(obj!=NULL && obj->isShadow()) return 1;
break;
case TYPE_FRAGILE:
if(flags<3) return 1;
break;
}
break;
case GameObjectProperty_IsSpikes:
switch(type){
case TYPE_SPIKES:
case TYPE_MOVING_SPIKES:
return 1;
}
break;
case GameObjectProperty_Flags:
return flags;
break;
default:
break;
}
return 0;
}
void Block::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){
//Every block has an id.
obj.push_back(pair<string,string>("id",id));
//Block specific properties.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
char s[64],s0[64];
sprintf(s,"%d",(int)movingPos.size());
obj.push_back(pair<string,string>("MovingPosCount",s));
obj.push_back(pair<string,string>("disabled",(editorFlags&0x1)?"1":"0"));
obj.push_back(pair<string,string>("loop",loop?"1":"0"));
for(unsigned int i=0;i<movingPos.size();i++){
- sprintf(s0+1,"%d",i);
+ sprintf(s0+1,"%u",i);
sprintf(s,"%d",movingPos[i].x);
s0[0]='x';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].y);
s0[0]='y';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].w);
s0[0]='t';
obj.push_back(pair<string,string>(s0,s));
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
char s[64];
obj.push_back(pair<string,string>("disabled",(editorFlags&0x1)?"1":"0"));
sprintf(s,"%d",dx);
obj.push_back(pair<string,string>("speed",s));
}
break;
case TYPE_PORTAL:
obj.push_back(pair<string,string>("automatic",(editorFlags&0x1)?"1":"0"));
obj.push_back(pair<string,string>("destination",destination));
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
string s;
switch(editorFlags&0x3){
case 1:
s="on";
break;
case 2:
s="off";
break;
default:
s="toggle";
break;
}
obj.push_back(pair<string,string>("behaviour",s));
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
string value=message;
//Change \n with the characters '\n'.
while(value.find('\n',0)!=string::npos){
size_t pos=value.find('\n',0);
value=value.replace(pos,1,"\\n");
}
obj.push_back(pair<string,string>("message",value));
}
break;
case TYPE_FRAGILE:
{
char s[64];
sprintf(s,"%d",editorFlags);
obj.push_back(pair<string,string>("state",s));
}
break;
}
}
void Block::setEditorData(std::map<std::string,std::string>& obj){
//Iterator used to check if the map contains certain entries.
map<string,string>::iterator it;
//Check if the data contains the id block.
it=obj.find("id");
if(it!=obj.end()){
//Set the id of the block.
id=obj["id"];
}
//Block specific properties.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Make sure that the editor data contains MovingPosCount.
it=obj.find("MovingPosCount");
if(it!=obj.end()){
char s0[64];
- int m=0;
- m=atoi(obj["MovingPosCount"].c_str());
+ int m=atoi(obj["MovingPosCount"].c_str());
movingPos.clear();
for(int i=0;i<m;i++){
SDL_Rect r={0,0,0,0};
sprintf(s0+1,"%d",i);
s0[0]='x';
r.x=atoi(obj[s0].c_str());
s0[0]='y';
r.y=atoi(obj[s0].c_str());
s0[0]='t';
r.w=atoi(obj[s0].c_str());
movingPos.push_back(r);
}
}
//Check if the disabled key is in the data.
it=obj.find("disabled");
if(it!=obj.end()){
string s=obj["disabled"];
editorFlags=0;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
//Check if the loop key is in the data.
it=obj.find("loop");
if(it!=obj.end()){
string s=obj["loop"];
loop=false;
if(s=="true" || atoi(s.c_str()))
loop=true;
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
//Check if there's a speed key in the editor data.
it=obj.find("speed");
if(it!=obj.end()){
dx=atoi(obj["speed"].c_str());
}
//Check if the disabled key is in the data.
it=obj.find("disabled");
if(it!=obj.end()){
string s=obj["disabled"];
editorFlags=0;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
}
break;
case TYPE_PORTAL:
{
//Check if the automatic key is in the data.
it=obj.find("automatic");
if(it!=obj.end()){
string s=obj["automatic"];
editorFlags=0;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
//Check if the destination key is in the data.
it=obj.find("destination");
if(it!=obj.end()){
destination=obj["destination"];
}
}
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Check if the behaviour key is in the data.
it=obj.find("behaviour");
if(it!=obj.end()){
string s=obj["behaviour"];
editorFlags=0;
if(s=="on" || s==_("On")) editorFlags|=1;
else if(s=="off" || s==_("Off")) editorFlags|=2;
flags=flagsSave=editorFlags;
}
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
//Check if the message key is in the data.
it=obj.find("message");
if(it!=obj.end()){
message=obj["message"];
//Change the characters '\n' to a real \n
while(message.find("\\n")!=string::npos){
message=message.replace(message.find("\\n"),2,"\n");
}
}
}
break;
case TYPE_FRAGILE:
{
//Check if the status is in the data.
it=obj.find("state");
if(it!=obj.end()){
editorFlags=atoi(obj["state"].c_str());
flags=editorFlags;
{
const char* s=(flags==0)?"default":((flags==1)?"fragile1":((flags==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
}
}
}
}
std::string Block::getEditorProperty(std::string property){
//First get the complete editor data.
vector<pair<string,string> > objMap;
vector<pair<string,string> >::iterator it;
getEditorData(objMap);
//Loop through the entries.
for(it=objMap.begin();it!=objMap.end();++it){
if(it->first==property)
return it->second;
}
//Nothing found.
return "";
}
void Block::setEditorProperty(std::string property,std::string value){
//Create a map to hold the property.
std::map<std::string,std::string> editorData;
editorData[property]=value;
//And call the setEditorData method.
setEditorData(editorData);
}
/*//debug
int block_test_count=-1;
bool block_test_only=false;*/
void Block::move(){
appearance.updateAnimation();
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
/*//debug
if(block_test_only || parent->time==416){
cout<<"Time:"<<(parent->time)<<" Recorded:"<<block_test_count<<" Coord:"<<box.x<<","<<box.y<<endl;
block_test_only=false;
}*/
if(!(flags&0x1)) temp++;
int t=temp;
SDL_Rect r0={0,0,0,0},r1;
dx=0;
dy=0;
for(unsigned int i=0;i<movingPos.size();i++){
r1.x=movingPos[i].x;
r1.y=movingPos[i].y;
r1.w=movingPos[i].w;
if(t==0&&r1.w==0){
r1.w=1;
flags|=0x1;
}
if(t>=0 && t<(int)r1.w){
int newX=boxBase.x+(int)(float(r0.x)+(float(r1.x)-float(r0.x))*float(t)/float(r1.w));
int newY=boxBase.y+(int)(float(r0.y)+(float(r1.y)-float(r0.y))*float(t)/float(r1.w));
dx=newX-box.x;
dy=newY-box.y;
box.x=newX;
box.y=newY;
return;
}else if(t==(int)r1.w){
//If the time is the time of the movingPosition then set it equal to the location.
//We do this to prevent a slight edge between normal blocks and moving blocks.
int newX=boxBase.x+r1.x;
int newY=boxBase.y+r1.y;
dx=newX-box.x;
dy=newY-box.y;
box.x=newX;
box.y=newY;
return;
}
t-=r1.w;
r0.x=r1.x;
r0.y=r1.y;
}
//Only reset the stuff when we're looping.
if(loop){
temp=0;
if(!movingPos.empty() && movingPos.back().x==0 && movingPos.back().y==0){
dx=boxBase.x-box.x;
dy=boxBase.y-box.y;
}
box.x=boxBase.x;
box.y=boxBase.y;
}
}
break;
case TYPE_BUTTON:
{
int new_flags=dx?4:0;
if((flags^new_flags)&4){
flags=(flags&~4)|new_flags;
if(parent && (new_flags || (flags&3)==0)){
//Make sure that id isn't empty.
if(!id.empty()){
parent->broadcastObjectEvent(0x10000|(flags&3),-1,id.c_str());
}else{
cerr<<"Warning: invalid button id!"<<endl;
}
}
}
dx=0;
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
if((flags&1)==0){
temp=(temp+dx)%50;
if(temp<0) temp+=50;
}
break;
case TYPE_PUSHABLE:
{
objCurrentStand=NULL;
if(inAir==true){
yVel+=1;
//Cap fall speed to 13.
if(yVel>13){
yVel=13;
}
}
//An array that will hold all the GameObjects that are involved in the collision/movement.
vector<GameObject*> objects;
//Determine the collision frame.
SDL_Rect frame={box.x,box.y,box.w,box.h};
//Keep the horizontal movement of the player in mind.
if(xVel+xVelBase>=0) {
frame.w+=(xVel+xVelBase);
}else{
frame.x+=(xVel+xVelBase);
frame.w-=(xVel+xVelBase);
}
//And the vertical movement.
if(yVel+yVelBase>=0) {
frame.h+=(yVel+yVelBase);
}else{
frame.y+=(yVel+yVelBase);
frame.h-=(yVel+yVelBase);
}
//Loop through the game objects.
for(unsigned int o=0; o<parent->levelObjects.size(); o++){
//Check if the player can collide with this game object.
//NOTE: As opposed to the player and the shadow, pushable blocks can collide with spikes.
if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,NULL)
&& !(parent->levelObjects[o]->type==TYPE_SPIKES || parent->levelObjects[o]->type==TYPE_MOVING_SPIKES))
continue;
if(parent->levelObjects[o]==this)
continue;
//Check if the block is inside the frame.
if(checkCollision(frame,parent->levelObjects[o]->getBox())){
objects.push_back(parent->levelObjects[o]);
continue;
}
//Additional checks need to be made for moving blocks.
if(parent->levelObjects[o]->type==TYPE_MOVING_BLOCK || parent->levelObjects[o]->type==TYPE_MOVING_SHADOW_BLOCK || parent->levelObjects[o]->type==TYPE_MOVING_SPIKES) {
//Check the movement of these blocks to see if they will collide.
SDL_Rect v=parent->levelObjects[o]->getBox(BoxType_Velocity);
SDL_Rect r=parent->levelObjects[o]->getBox();
r.x+=v.x;
r.y+=v.y;
if(checkCollision(frame,r)) {
objects.push_back(parent->levelObjects[o]);
}
}
}
//The current location of the player, used to set the player back if he's squashed to prevent displacement.
int lastX=box.x;
int lastY=box.y;
//Move the player.
box.x+=xVel;
//Loop through the objects related to the (horizontal) collision/movement.
for(unsigned int o=0; o<objects.size(); o++){
//Get the collision box of the levelobject.
SDL_Rect r=objects[o]->getBox();
//Check collision with the player.
//NOTE: Although the object is inside the collision frame we need to check if it collides with the player box (xVel applied).
if(checkCollision(box,r)){
//We have collision, get the velocity of the box.
SDL_Rect v=objects[o]->getBox(BoxType_Delta);
//Check on which side of the box the player is.
if(box.x + box.w/2 <= r.x + r.w/2){
//The left side of the block.
if(xVel+xVelBase>v.x){
if(box.x>r.x-box.w){
if(objects[o]->type==TYPE_PUSHABLE){
//box.x=r.x-box.w+(xVel+xVelBase)/2;
(dynamic_cast<Block*>(objects[o]))->xVel=(xVel+xVelBase)/2;
}
box.x=r.x-box.w;
}
}
}else{
//The right side of the block.
if(xVel+xVelBase<v.x){
if(box.x<r.x+r.w){
if(objects[o]->type==TYPE_PUSHABLE){
//box.x=r.x-box.w+(xVel+xVelBase)/2;
(dynamic_cast<Block*>(objects[o]))->xVel=(xVel+xVelBase)/2;
}
box.x=r.x+r.w;
}
}
}
}
}
//Now apply the yVel. (gravity, jumping, etc..)
box.y+=yVel;
//Assume we are in air and are able to move unless proven otherwise (???).
inAir=true;
//Loop through all the objects related to the (vertical) collision/movement.
for(unsigned int o=0; o<objects.size(); o++){
//Get the collision box of the levelobject.
SDL_Rect r=objects[o]->getBox();
//NOTE: Although the object is inside the collision frame we need to check if it collides with the player box (yVel applied).
if(checkCollision(box,r)){
//Get the velocity of the gameobject.
SDL_Rect v=objects[o]->getBox(BoxType_Delta);
//Check which side of the object the player is.
if(box.y+box.h/2<=r.y+r.h/2){
if(yVel>=v.y || yVel>=0){
inAir=false;
box.y=r.y-box.h;
yVel=1;
objCurrentStand=objects[o];
}
}else{
//FIXME: The player can have a yVel of 0 and get squashed if he is standing on the other.
if(yVel<=v.y+1){
yVel=v.y>0?v.y:0;
if(box.y<r.y+r.h){
box.y=r.y+r.h;
}
}
}
}
}
dx=box.x-lastX;
dy=box.y-lastY;
xVel=0;
}
break;
}
}
diff --git a/src/FileManager.cpp b/src/FileManager.cpp
index 4e803b3..57cf19e 100644
--- a/src/FileManager.cpp
+++ b/src/FileManager.cpp
@@ -1,752 +1,752 @@
/*
* Copyright (C) 2011-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 <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include "Globals.h"
#include "FileManager.h"
#include "Functions.h"
#ifdef __APPLE__
#include "archive.h"
#include "archive_entry.h"
#else
#include <archive.h>
#include <archive_entry.h>
#endif
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <direct.h>
#pragma comment(lib,"shlwapi.lib")
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Under Windows there's just one userpath.
#ifdef WIN32
string userPath,dataPath,appPath,exeName;
#else
//But on other platforms we make a difference between the userPath (config files) and the userDataPath (data files).
//Finally there's the path for cache data userCachePath.
string userPath,userDataPath,userCachePath,dataPath,appPath,exeName;
#endif
bool configurePaths() {
//Get the appPath and the exeName.
{
char s[4096];
int i,m;
#ifdef WIN32
m=GetModuleFileNameA(NULL,s,sizeof(s));
#else
m=readlink("/proc/self/exe",s,sizeof(s));
#endif
s[m]=0;
for(i=m-1;i>=0;i--){
if(s[i]=='/'||s[i]=='\\'){
s[i]=0;
break;
}
}
appPath=s;
exeName=s+i+1;
}
//TODO: Check if the userpath is empty before setting userPath???
//Check if the userPath is empty.
if(getUserPath().empty()){
#ifdef WIN32
//Get the userPath.
char s[1024];
SHGetSpecialFolderPathA(NULL,s,CSIDL_PERSONAL,1);
userPath=s;
userPath+="\\My Games\\meandmyshadow\\";
#else
//Temp variable that is used to prevent NULL assignement.
char* env;
//First get the $XDG_CONFIG_HOME env var.
env=getenv("XDG_CONFIG_HOME");
//If it's null set userPath to $HOME/.config/.
if(env!=NULL){
userPath=env;
}else{
userPath=getenv("HOME");
userPath+="/.config";
}
//And add meandmyshadow to it.
userPath+="/meandmyshadow/";
//Now get the $XDG_DATA_HOME env var.
env=getenv("XDG_DATA_HOME");
//If it's null set userDataPath to $HOME/.local/share.
if(env!=NULL){
userDataPath=env;
}else{
userDataPath=getenv("HOME");
userDataPath+="/.local/share";
}
//And add meandmyshadow to it.
userDataPath+="/meandmyshadow/";
//Now get the $XDG_CACHE_HOME env var.
env=getenv("XDG_CACHE_HOME");
//If it's null set userCachePath to $HOME/.cache.
if(env!=NULL){
userCachePath=env;
}else{
userCachePath=getenv("HOME");
userCachePath+="/.cache";
}
//And add meandmyshadow to it.
userCachePath+="/meandmyshadow/";
//Set env null.
env=NULL;
#endif
//Print the userPath.
cout<<"User preferences will be fetched from: "<<userPath<<endl;
#ifndef WIN32
//In case of a non-Windows computer show the user data path.
cout<<"User data will be fetched from: "<<userDataPath<<endl;
#endif
}
#ifdef WIN32
//Create the userPath folder and other subfolders.
createDirectory(userPath.c_str());
createDirectory((userPath+"levels").c_str());
createDirectory((userPath+"levelpacks").c_str());
createDirectory((userPath+"themes").c_str());
createDirectory((userPath+"progress").c_str());
createDirectory((userPath+"tmp").c_str());
//The records folder for recordings.
createDirectory((userPath+"records").c_str());
createDirectory((userPath+"records\\autosave").c_str());
//And the custom folder inside the userpath.
createDirectory((userPath+"custom").c_str());
createDirectory((userPath+"custom\\levels").c_str());
createDirectory((userPath+"custom\\levelpacks").c_str());
#else
//Create the userPath.
createDirectory(userPath.c_str());
createDirectory(userDataPath.c_str());
createDirectory(userCachePath.c_str());
//Also create other folders in the userpath.
createDirectory((userDataPath+"/levels").c_str());
createDirectory((userDataPath+"/levelpacks").c_str());
createDirectory((userDataPath+"/themes").c_str());
createDirectory((userDataPath+"/progress").c_str());
createDirectory((userCachePath+"/tmp").c_str());
//The records folder for recordings.
createDirectory((userDataPath+"/records").c_str());
createDirectory((userDataPath+"/records/autosave").c_str());
//And the custom folder inside the userpath.
createDirectory((userDataPath+"/custom").c_str());
createDirectory((userDataPath+"/custom/levels").c_str());
createDirectory((userDataPath+"/custom/levelpacks").c_str());
#endif
//Get the dataPath by trying multiple relative locations.
{
FILE *f;
string s;
while(true){
//try existing one
if(!dataPath.empty()){
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
}
//try "./"
dataPath="./data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try "../"
dataPath="../data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try App.Path
dataPath=getAppPath()+"/data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try App.Path+"/../"
dataPath=getAppPath()+"/../data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try DATA_PATH
#ifdef DATA_PATH
dataPath=DATA_PATH;
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
#endif
#ifdef __APPLE__
extern std::string get_data_path();
dataPath = get_data_path();
dataPath=get_data_path();
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
#endif
//error: can't find file
return false;
}
//Print the dataPath.
cout<<"Data files will be fetched from: "<<dataPath<<endl;
}
return true;
}
std::vector<std::string> enumAllFiles(std::string path,const char* extension,bool containsPath){
vector<string> v;
#ifdef WIN32
string s1;
WIN32_FIND_DATAA f;
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="\\";
}
s1=path;
if(extension!=NULL && *extension){
s1+="*.";
s1+=extension;
}else{
s1+="*";
}
HANDLE h=FindFirstFileA(s1.c_str(),&f);
if(h==NULL||h==INVALID_HANDLE_VALUE) return v;
do{
if(!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
if(containsPath){
v.push_back(path+f.cFileName);
}else{
v.push_back(f.cFileName);
}
}
}while(FindNextFileA(h,&f));
FindClose(h);
return v;
#else
int len=0;
if(extension!=NULL && *extension) len=strlen(extension);
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="/";
}
DIR *pDir;
struct dirent *pDirent;
pDir=opendir(path.c_str());
if(pDir==NULL) return v;
while((pDirent=readdir(pDir))!=NULL){
if(pDirent->d_name[0]=='.'){
if(pDirent->d_name[1]==0||
(pDirent->d_name[1]=='.'&&pDirent->d_name[2]==0)) continue;
}
string s1=path+pDirent->d_name;
struct stat S_stat;
lstat(s1.c_str(),&S_stat);
if(!S_ISDIR(S_stat.st_mode)){
if(len>0){
if((int)s1.size()<len+1) continue;
if(s1[s1.size()-len-1]!='.') continue;
if(strcasecmp(&s1[s1.size()-len],extension)) continue;
}
if(containsPath){
v.push_back(s1);
}else{
v.push_back(string(pDirent->d_name));
}
}
}
closedir(pDir);
return v;
#endif
}
std::vector<std::string> enumAllDirs(std::string path,bool containsPath){
vector<string> v;
#ifdef WIN32
string s1;
WIN32_FIND_DATAA f;
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="\\";
}
s1=path+"*";
HANDLE h=FindFirstFileA(s1.c_str(),&f);
if(h==NULL||h==INVALID_HANDLE_VALUE) return v;
do{
// skip '.' and '..' and hidden folders
if(f.cFileName[0]=='.'){
/*if(f.cFileName[1]==0||
(f.cFileName[1]=='.'&&f.cFileName[2]==0))*/ continue;
}
if(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
if(containsPath){
v.push_back(path+f.cFileName);
}else{
v.push_back(f.cFileName);
}
}
}while(FindNextFileA(h,&f));
FindClose(h);
return v;
#else
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="/";
}
DIR *pDir;
struct dirent *pDirent;
pDir=opendir(path.c_str());
if(pDir==NULL) return v;
while((pDirent=readdir(pDir))!=NULL){
if(pDirent->d_name[0]=='.'){
if(pDirent->d_name[1]==0||
(pDirent->d_name[1]=='.'&&pDirent->d_name[2]==0)) continue;
}
string s1=path+pDirent->d_name;
struct stat S_stat;
lstat(s1.c_str(),&S_stat);
if(S_ISDIR(S_stat.st_mode)){
//Skip hidden folders.
s1=string(pDirent->d_name);
if(s1.find('.')==0) continue;
//Add result to vector.
if(containsPath){
v.push_back(path+pDirent->d_name);
}else{
v.push_back(s1);
}
}
}
closedir(pDir);
return v;
#endif
}
std::string processFileName(const std::string& s){
string prefix=dataPath;
//FIXME: Do we still need those last three?
//REMARK: maybe 'return prefix+s;' is not needed (?)
// it causes some bugs such as can't save level progress
if(s.compare(0,6,"%DATA%")==0){
if(s.size()>6 && (s[6]=='/' || s[6]=='\\')){
return dataPath+s.substr(7);
}else{
return dataPath+s.substr(6);
}
}else if(s.compare(0,6,"%USER%")==0){
if(s.size()>6 && (s[6]=='/' || s[6]=='\\')){
return getUserPath(USER_DATA)+s.substr(7);
}else{
return getUserPath(USER_DATA)+s.substr(6);
}
}else if(s.compare(0,9,"%LVLPACK%")==0){
if(s.size()>9 && (s[9]=='/' || s[9]=='\\')){
return prefix+"levelpacks/"+s.substr(10);
}else{
return prefix+"levelpacks/"+s.substr(9);
}
}else if(s.compare(0,5,"%LVL%")==0){
if(s.size()>5 && (s[5]=='/' || s[5]=='\\')){
return prefix+"levels/"+s.substr(6);
}else{
return prefix+"levels/"+s.substr(5);
}
}else if(s.compare(0,8,"%THEMES%")==0){
if(s.size()>8 && (s[8]=='/' || s[8]=='\\')){
return prefix+"themes/"+s.substr(9);
}else{
return prefix+"themes/"+s.substr(8);
}
}else if(s.size()>0 && (s[0]=='/' || s[0]=='\\')){
return s;
#ifdef WIN32
// Another fix for Windows :(
}else if(s.size()>1 && (s[1]==':')){
return s;
#endif
}else{
return prefix+s;
}
}
std::string fileNameFromPath(const std::string &path, const bool webURL){
std::string filename;
size_t pos;
#ifdef WIN32
// FIXME: '/' in string should be '/' not '\/',
// we don't need to escape it
if(webURL){
pos = path.find_last_of("/");
}else{
// NOTE: sometimes path separator in Windows can be '/',
// so we must check botn '\' and '/'
pos = path.find_last_of("\\/");
}
#else
// FIXME: '/' in string should be '/' not '\/',
// we don't need to escape it
pos = path.find_last_of("/");
#endif
if(pos != std::string::npos)
filename.assign(path.begin() + pos + 1, path.end());
else
filename=path;
return filename;
}
std::string pathFromFileName(const std::string &filename){
std::string path;
// FIXME: '/' in string should be '/' not '\/',
// we don't need to escape it
#ifdef WIN32
// NOTE: sometimes path separator in Windows can be '/',
// so we must check botn '\' and '/'
size_t pos = filename.find_last_of("\\/");
#else
size_t pos = filename.find_last_of("/");
#endif
if(pos != std::string::npos)
path.assign(filename.begin(), filename.begin() + pos +1);
else
path=filename;
return path;
}
bool downloadFile(const string &path, const string &destination) {
string filename=fileNameFromPath(path,true);
FILE* file = fopen((destination+filename).c_str(), "wb");
bool status=downloadFile(path,file);
fclose(file);
//And return the status.
return status;
}
bool downloadFile(const string &path, FILE* destination) {
CURL* curl=curl_easy_init();
// proxy test (test only)
string internetProxy = getSettings()->getValue("internet-proxy");
size_t pos = internetProxy.find_first_of(":");
if(pos!=string::npos){
curl_easy_setopt(curl,CURLOPT_PROXYPORT,atoi(internetProxy.substr(pos+1).c_str()));
internetProxy = internetProxy.substr(0,pos);
curl_easy_setopt(curl,CURLOPT_PROXY,internetProxy.c_str());
}
curl_easy_setopt(curl,CURLOPT_URL,path.c_str());
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writeData);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,destination);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return (res==0);
}
size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream){
return fwrite(ptr, size, nmemb, (FILE *)stream);
}
bool extractFile(const string &fileName, const string &destination) {
//Create the archive we're going to extract.
- archive* file=NULL;
+ archive* file;
//Create the destination we're going to extract to.
- archive* dest=NULL;
+ archive* dest;
file=archive_read_new();
dest=archive_write_disk_new();
archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
archive_read_support_format_zip(file);
//Now read the archive.
if(archive_read_open_file(file,fileName.c_str(),10240)) {
cerr<<"Error while reading archive "+fileName<<endl;
return false;
}
//Now write every entry to disk.
int status;
archive_entry* entry=NULL;
while(true) {
status=archive_read_next_header(file,&entry);
if(status==ARCHIVE_EOF){
break;
}
if(status!=ARCHIVE_OK){
cerr<<"Error while reading archive "+fileName<<endl;
return false;
}
archive_entry_copy_pathname(entry,(destination+archive_entry_pathname(entry)).c_str());
status=archive_write_header(dest,entry);
if(status!=ARCHIVE_OK){
cerr<<"Error while extracting archive "+fileName<<endl;
return false;
}else{
copyData(file, dest);
status=archive_write_finish_entry(dest);
if(status!=ARCHIVE_OK){
cerr<<"Error while extracting archive "+fileName<<endl;
return false;
}
}
}
//Finally close the archive.
archive_read_close(file);
archive_read_finish(file);
return true;
}
bool createDirectory(const char* path){
#ifdef WIN32
char s0[1024],s[1024];
GetCurrentDirectoryA(sizeof(s0),s0);
PathCombineA(s,s0,path);
for(unsigned int i=0;i<sizeof(s);i++){
if(s[i]=='\0') break;
else if(s[i]=='/') s[i]='\\';
}
//printf("createDirectory:%s\n",s);
return SHCreateDirectoryExA(NULL,s,NULL)!=0;
#else
return mkdir(path,0777)==0;
#endif
}
bool removeDirectory(const char *path){
#ifdef WIN32
WIN32_FIND_DATAA f;
HANDLE h = FindFirstFileA((string(path)+"\\*").c_str(),&f);
#else
//Open the directory that needs to be removed.
DIR* d=opendir(path);
#endif
//Get the path length
size_t path_len = strlen(path);
//Boolean if the directory is emptied.
//True: succes False: failure
//Default is true because if the directory is empty it will never enter the while loop, but we still have success.
bool r = true;
#ifdef WIN32
if(h!=NULL && h!=INVALID_HANDLE_VALUE) {
#else
//Check if the directory exists.
if(d) {
//Pointer to an entry of the directory.
struct dirent* p;
#endif
#ifdef WIN32
do{
#else
//Loop the entries of the directory that needs to be removed as long as there's no error.
while(r && (p=readdir(d))) {
#endif
/* Skip the names "." and ".." as we don't want to recurse on them. */
#ifdef WIN32
if (!strcmp(f.cFileName, ".") || !strcmp(f.cFileName, "..")) {
#else
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) {
#endif
//The filename is . or .. so we continue to the next entry.
continue;
} else {
//r2 tells if the entry is deleted.
//True: succes False: failure
//Default is false.
bool r2 = false;
char* buf;
size_t len;
#ifdef WIN32
//Get the length of the path + the directory entry name.
len = path_len + strlen(f.cFileName) + 2;
#else
//Get the length of the path + the directory entry name.
len = path_len + strlen(p->d_name) + 2;
#endif
buf = (char*) malloc(len);
if(buf) {
#ifdef WIN32
_snprintf(buf, len, "%s\\%s", path, f.cFileName);
if(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
r2 = removeDirectory(buf);
}else{
r2 = unlink(buf)==0;
}
#else
struct stat statbuf;
snprintf(buf, len, "%s/%s", path, p->d_name);
if(!stat(buf, &statbuf)){
//Check if the entry is a directory or a file.
if (S_ISDIR(statbuf.st_mode)){
//We call ourself(removeDirectory) recursively.
//We return true on success.
r2 = removeDirectory(buf);
}else{
//unlink() returns zero on succes so we set r2 to the unlink(buf)==0.
r2 = unlink(buf)==0;
}
}
#endif
//Free the buf.
free(buf);
}
//We set r to r2 since r2 contains the status of the latest deletion.
r = r2;
}
#ifdef WIN32
}while(r && FindNextFileA(h,&f));
FindClose(h);
#else
}
//Close the directory.
closedir(d);
#endif
}
//The while loop has ended, meaning we (tried) cleared the directory.
//If r is true, meaning no errors we can delete the directory.
if(r){
//The return value of rmdir is 0 when it succeeded.
r = rmdir(path)==0;
}
//Return the status.
return r;
}
bool renameDirectory(const char* oldPath,const char* newPath){
return rename(oldPath,newPath)==0;
}
void copyData(archive* file, archive* dest) {
int status;
const void* buff;
size_t size;
#if ARCHIVE_VERSION_NUMBER < 3000000
off_t offset;
#else
int64_t offset;
#endif
while(true) {
status=archive_read_data_block(file, &buff, &size, &offset);
if(status==ARCHIVE_EOF){
return;
}
if(status!=ARCHIVE_OK){
cerr<<"Error while writing data to disk."<<endl;
return;
}
status=archive_write_data_block(dest, buff, size, offset);
if(status!=ARCHIVE_OK) {
cerr<<"Error while writing data to disk."<<endl;
return;
}
}
}
bool copyFile(const char* source,const char* dest){
//Open the source file.
ifstream fin(source,fstream::binary);
if(!fin)
return false;
//Open the dest file.
ofstream fout(dest,fstream::trunc|fstream::binary);
if(!fout)
return false;
//Copy.
fout<<fin.rdbuf();
return true;
}
bool removeFile(const char* file){
return remove(file)==0;
}
bool createFile(const char* file){
//Open the file with write permission.
FILE* f=fopen(file,"wb");
//Check if there are no problems.
if(f){
//Close the file.
fclose(f);
return true;
}else{
return false;
}
}
diff --git a/src/Functions.cpp b/src/Functions.cpp
index f1a1033..dfbff09 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1701 +1,1707 @@
/*
* Copyright (C) 2011-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 <stdio.h>
#include <math.h>
#include <algorithm>
#include <SDL/SDL.h>
#ifdef __APPLE__
#include <SDL_mixer/SDL_mixer.h>
#include <SDL_gfx/SDL_gfxPrimitives.h>
#include <SDL_gfx/SDL_rotozoom.h>
#else
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_gfxPrimitives.h>
#include <SDL/SDL_rotozoom.h>
#endif
#include <SDL/SDL_syswm.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "Objects.h"
#include "Player.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 "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Cursors.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
extern "C" {
#include "libs/findlocale/findlocale.h"
}
#ifdef HARDWARE_ACCELERATION
#include <GL/gl.h>
#include <GL/glu.h>
//fix some Windows header bug
#ifndef GL_BGR
#define GL_BGR GL_BGR_EXT
#endif
#ifndef GL_BGRA
#define GL_BGRA GL_BGRA_EXT
#endif
#endif
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
//Workaround for the resizing below 800x600 for X systems.
#ifdef __linux__
#include<X11/Xlib.h>
#include<X11/Xutil.h>
#endif
//Initialise the imagemanager.
//The ImageManager is used to prevent loading images multiple times.
ImageManager imageManager;
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//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=0;
#ifdef HARDWARE_ACCELERATION
GLuint screenTexture;
#endif
SDL_Surface* loadImage(string file){
//We use the imageManager to load the file.
return imageManager.loadImage(file);
}
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_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
rectangleRGBA(dest,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//Fill content's background color from function parameter
boxRGBA(dest,x+1,y+1,x+w-2,y+h-2,color >> 24,color >> 16,color >> 8,color >> 0);
//Draw first black borders around content and leave 1 pixel in every corner
lineRGBA(dest,x+1,y,x+w-2,y,0,0,0,255);
lineRGBA(dest,x+1,y+h-1,x+w-2,y+h-1,0,0,0,255);
lineRGBA(dest,x,y+1,x,y+h-2,0,0,0,255);
lineRGBA(dest,x+w-1,y+1,x+w-1,y+h-2,0,0,0,255);
//Fill the corners with transperent color to create anti-aliased borders
pixelRGBA(dest,x,y,0,0,0,160);
pixelRGBA(dest,x,y+h-1,0,0,0,160);
pixelRGBA(dest,x+w-1,y,0,0,0,160);
pixelRGBA(dest,x+w-1,y+h-1,0,0,0,160);
//Draw second lighter border around content
rectangleRGBA(dest,x+1,y+1,x+w-2,y+h-2,0,0,0,64);
//Create anti-aliasing in corners of second border
pixelRGBA(dest,x+1,y+1,0,0,0,50);
pixelRGBA(dest,x+1,y+h-2,0,0,0,50);
pixelRGBA(dest,x+w-2,y+1,0,0,0,50);
pixelRGBA(dest,x+w-2,y+h-2,0,0,0,50);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
lineRGBA(dest,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,dest,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),dest,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),dest,color);
}
}
bool 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;
//Boolean if this is the first screen creation.
bool initial=true;
//Check if we should use gl or software rendering.
if(settings->getBoolValue("gl")){
#ifdef HARDWARE_ACCELERATION
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,32);
SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
//Set the video mode.
Uint32 flags=SDL_HWSURFACE | SDL_OPENGL;
if(settings->getBoolValue("fullscreen"))
flags|=SDL_FULLSCREEN;
else if(settings->getBoolValue("resizable"))
flags|=SDL_RESIZABLE;
if(SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,flags)==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
//Delete the old screen.
//Warning: only if previous mode is OpenGL mode.
//NOTE: The previous mode can't switch during runtime.
if(screen){
SDL_FreeSurface(screen);
screen=NULL;
//There was a screen so this isn't the initial screen creation.
initial=false;
}
//Create a screen
screen=SDL_CreateRGBSurface(SDL_HWSURFACE,SCREEN_WIDTH,SCREEN_HEIGHT,32,0x00FF0000,0x0000FF00,0x000000FF,0);
//Create a texture.
glDeleteTextures(1,&screenTexture);
glGenTextures(1,&screenTexture);
//And set up gl correctly.
glClearColor(0, 0, 0, 0);
glClearDepth(1.0f);
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
glLoadIdentity();
#else
//NOTE: Hardware accelerated rendering requested but compiled without.
cerr<<"FATAL ERROR: Unable to use hardware acceleration (compiled without)."<<endl;
return false;
#endif
}else{
//Set the flags.
Uint32 flags=SDL_HWSURFACE | SDL_DOUBLEBUF;
if(settings->getBoolValue("fullscreen"))
flags|=SDL_FULLSCREEN;
else if(settings->getBoolValue("resizable"))
flags|=SDL_RESIZABLE;
//Check if there already was a screen.
if(screen)
initial=false;
//Create the screen and check if there weren't any errors.
screen=SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,flags);
if(screen==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow(initial);
//Create the temp surface, just a replica of the screen surface, free the previous one if any.
if(tempSurface)
SDL_FreeSurface(tempSurface);
tempSurface=SDL_CreateRGBSurface(SDL_HWSURFACE|SDL_SRCALPHA,
screen->w,screen->h,screen->format->BitsPerPixel,
screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);
//Set the the window caption.
SDL_WM_SetCaption(("Me and My Shadow "+version).c_str(),NULL);
SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return true;
}
#ifdef WIN32
static WNDPROC m_OldWindowProc=NULL;
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
if(msg==WM_GETMINMAXINFO){
if(m_OldWindowProc){
CallWindowProc(m_OldWindowProc,hwnd,msg,wParam,lParam);
}else{
DefWindowProc(hwnd,msg,wParam,lParam);
}
RECT r={0,0,800,600};
AdjustWindowRect(&r,GetWindowLong(hwnd,GWL_STYLE),FALSE);
MINMAXINFO *info=(MINMAXINFO*)lParam;
info->ptMinTrackSize.x=r.right-r.left;
info->ptMinTrackSize.y=r.bottom-r.top;
return 0;
}else{
if(m_OldWindowProc){
return CallWindowProc(m_OldWindowProc,hwnd,msg,wParam,lParam);
}else{
return DefWindowProc(hwnd,msg,wParam,lParam);
}
}
}
#endif
void pickFullscreenResolution(){
//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|SDL_HWSURFACE);
if(modes==NULL || ((intptr_t)modes) == -1){
cout<<"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(unsigned int i=0;modes[i]!=NULL;i++){
//Check if the resolution is higher than the minimum (800x600).
if(modes[i]->w>=800 && modes[i]->h>=600){
_res res={modes[i]->w, modes[i]->h};
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//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(bool initial){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//Retrieve the WM info from SDL containing the window handle.
struct SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
SDL_GetWMInfo(&wmInfo);
#ifdef __linux__
//We assume that a linux system running meandmyshadow is also running an Xorg server.
if(wmInfo.subsystem==SDL_SYSWM_X11){
//Create the size hints to give to the window.
XSizeHints* sizeHints;
if(!(sizeHints=XAllocSizeHints())){
cerr<<"ERROR: Unable to allocate memory for XSizeHings."<<endl;
return;
}
//Configure the size hint.
sizeHints->flags=PMinSize;
sizeHints->min_width=800;
sizeHints->min_height=600;
//Set the normal hints of the window.
(void)wmInfo.info.x11.lock_func;
XSetNormalHints(wmInfo.info.x11.display,wmInfo.info.x11.wmwindow,sizeHints);
(void)wmInfo.info.x11.unlock_func;
//Free size hint structure
XFree(sizeHints);
}else{
//No X11 so an unsupported window manager.
cerr<<"WARNING: Unsupported window manager."<<endl;
}
#elif defined(WIN32)
//We overwrite the window proc of SDL
WNDPROC wndproc=(WNDPROC)GetWindowLong(wmInfo.window,GWL_WNDPROC);
if(wndproc!=NULL && wndproc!=(WNDPROC)WindowProc){
m_OldWindowProc=wndproc;
SetWindowLong(wmInfo.window,GWL_WNDPROC,(LONG)(WNDPROC)WindowProc);
}
#endif
}
void onVideoResize(){
//Check if the resize event isn't malformed.
if(event.resize.w<=0 || event.resize.h<=0)
return;
//Check the size limit.
if(event.resize.w<800)
event.resize.w=800;
if(event.resize.h<600)
event.resize.h=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.resize.w && SCREEN_HEIGHT==event.resize.h)
return;
char s[32];
//Set the new width and height.
sprintf(s,"%d",event.resize.w);
getSettings()->setValue("width",s);
sprintf(s,"%d",event.resize.h);
getSettings()->setValue("height",s);
//Do resizing.
if(!createScreen())
return;
//Tell the theme to resize.
if(!loadTheme(""))
return;
//The new resolution is valid.
//Now we can save the settings. (TODO: should we save?)
//saveSettings();
//And let the currentState update it's GUI to the new resolution.
currentState->resize();
}
//Include the scripting API.
#include "ScriptAPI.h"
bool init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
fprintf(stderr,"FATAL ERROR: SDL_Init failed\n");
return false;
}
//Initialze SDL_mixer (audio).
if(Mix_OpenAudio(22050,MIX_DEFAULT_FORMAT,2,512)==-1){
fprintf(stderr,"FATAL ERROR: Mix_OpenAudio failed\n");
return false;
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
fprintf(stderr,"FATAL ERROR: TTF_Init failed\n");
return false;
}
//Create the screen.
if(!createScreen())
return false;
//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);
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Create the types of game object event types.
struct GameObjectEventTypeName{
int type;
const char* name;
};
const GameObjectEventTypeName types[]={
{GameObjectEvent_PlayerWalkOn,"playerWalkOn"},
{GameObjectEvent_PlayerIsOn,"playerIsOn"},
{GameObjectEvent_PlayerLeave,"playerLeave"},
{GameObjectEvent_OnCreate,"onCreate"},
{GameObjectEvent_OnEnterFrame,"onEnterFrame"},
{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;
}
//Register the ScriptAPI's functions in the scriptExecutor.
registerFunctions(getScriptExecutor());
//Nothing went wrong so we return true.
return true;
}
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{
return TTF_OpenFont((getDataPath()+"font/DroidSansFallback.ttf").c_str(),size);
}
}
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);
/// 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);
if(fontTitle==NULL || fontGUI==NULL || fontGUISmall==NULL || fontText==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(){
TTF_Font* fontArrow=loadFont(_("knewave"),18);
if(arrowLeft1){
SDL_FreeSurface(arrowLeft1);
SDL_FreeSurface(arrowRight1);
SDL_FreeSurface(arrowLeft2);
SDL_FreeSurface(arrowRight2);
}
arrowLeft1=TTF_RenderUTF8_Blended(fontArrow,"<",themeTextColor);
arrowRight1=TTF_RenderUTF8_Blended(fontArrow,">",themeTextColor);
arrowLeft2=TTF_RenderUTF8_Blended(fontArrow,"<",themeTextColorDialog);
arrowRight2=TTF_RenderUTF8_Blended(fontArrow,">",themeTextColorDialog);
TTF_CloseFont(fontArrow);
}
bool loadTheme(string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme")==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")==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
- return false;
+ success=false;
}
}
generateArrows();
//Everything went fine so return true.
- return true;
+ return success;
}
static Mix_Chunk* loadWAV(const char* s){
Mix_Chunk* c=Mix_LoadWAV(s);
if(c!=NULL) return c;
printf("ERROR: Can't load sound file %s: %s\n",s,SDL_GetError());
return NULL;
}
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(){
//Load the fonts.
if(!loadFonts())
return false;
+ fontMono=loadFont("VeraMono",12);
//Show a loading screen
{
SDL_Rect r={0,0,screen->w,screen->h};
SDL_FillRect(screen,&r,0);
SDL_Color fg={255,255,255};
SDL_Surface *surface=TTF_RenderUTF8_Blended(fontTitle,_("Loading..."),fg);
if(surface!=NULL){
r.x=(screen->w-surface->w)/2;
r.y=(screen->h-surface->h)/2;
SDL_BlitSurface(surface,NULL,screen,&r);
SDL_FreeSurface(surface);
}
SDL_Flip(screen);
}
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);
//Always load the default music list for fallback.
musicManager.loadMusicList((getDataPath()+"music/default.list"));
//Load the configured music list.
getMusicManager()->loadMusicList((getDataPath()+"music/"+getSettings()->getValue("musiclist")+".list"));
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
jumpSound=loadWAV((getDataPath()+"sfx/jump.wav").c_str());
hitSound=loadWAV((getDataPath()+"sfx/hit.wav").c_str());
saveSound=loadWAV((getDataPath()+"sfx/checkpoint.wav").c_str());
swapSound=loadWAV((getDataPath()+"sfx/swap.wav").c_str());
toggleSound=loadWAV((getDataPath()+"sfx/toggle.wav").c_str());
errorSound=loadWAV((getDataPath()+"sfx/error.wav").c_str());
collectSound=loadWAV((getDataPath()+"sfx/collect.wav").c_str());
achievementSound=loadWAV((getDataPath()+"sfx/achievement.ogg").c_str());
//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";
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
//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();
statsMgr.registerAchievements();
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
statsMgr.reloadCompletedLevelsAndAchievements();
statsMgr.reloadOtherAchievements();
//Load the theme, both menu and default.
- if(!loadTheme(getSettings()->getValue("theme")))
- return false;
+ //NOTE: Loading theme may fail and returning false would stop everything, default theme will be used instead.
+ if (!loadTheme(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(){
settings->save();
//Always return true?
return true;
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
ScriptExecutor* getScriptExecutor(){
return &scriptExecutor;
}
void flipScreen(){
if(settings->getBoolValue("gl")){
#ifdef HARDWARE_ACCELERATION
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//Create a texture from the screen surface.
glBindTexture(GL_TEXTURE_2D,screenTexture);
//Set the texture's stretching properties
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,0,screen->format->BytesPerPixel,screen->w,screen->h,0,GL_BGRA,GL_UNSIGNED_BYTE,screen->pixels);
glBegin(GL_QUADS);
glTexCoord2i(0,0); glVertex3f(0,0,0);
glTexCoord2i(1,0); glVertex3f(SCREEN_WIDTH,0,0);
glTexCoord2i(1,1); glVertex3f(SCREEN_WIDTH,SCREEN_HEIGHT,0);
glTexCoord2i(0,1); glVertex3f(0,SCREEN_HEIGHT,0);
glEnd();
SDL_GL_SwapBuffers();
#else
//NOTE: Trying to flip the screen using gl while compiled without.
cerr<<"FATAL ERROR: Unable to draw to screen using OpenGL (compiled without)."<<endl;
#endif
}else{
SDL_Flip(screen);
}
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//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;
}
//Destroy the imageManager.
imageManager.destroy();
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
Mix_FreeChunk(jumpSound);
Mix_FreeChunk(hitSound);
Mix_FreeChunk(saveSound);
Mix_FreeChunk(swapSound);
Mix_FreeChunk(toggleSound);
Mix_FreeChunk(errorSound);
Mix_FreeChunk(collectSound);
Mix_FreeChunk(achievementSound);
//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_FreeSurface(tempSurface);
//Stop audio.and quit
Mix_CloseAudio();
#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(){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//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();
currentState=game;
//Call this after changing currentState to make sure OnCreate script event runs correctly.
game->reset(true);
}
break;
case STATE_MENU:
currentState=new Menu();
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect();
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect();
break;
case STATE_LEVEL_EDITOR:
{
currentState=NULL;
LevelEditor *levelEditor=new LevelEditor();
currentState=levelEditor;
//Call this after changing currentState to make sure OnCreate script event runs correctly.
levelEditor->Game::reset(true);
}
break;
case STATE_OPTIONS:
currentState=new Options();
break;
case STATE_ADDONS:
currentState=new Addons();
break;
case STATE_CREDITS:
currentState=new Credits();
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen();
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
//Fade out.
int fade=255;
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
while(fade>0){
fade-=17;
if(fade<0)
fade=0;
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA, fade);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
flipScreen();
SDL_Delay(25);
}
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
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;
}
void setCamera(const SDL_Rect* r,int count){
//SetCamera only works in the Level editor and when mouse is inside window.
if(stateID==STATE_LEVEL_EDITOR&&(SDL_GetAppState()&SDL_APPMOUSEFOCUS)){
//Get the mouse coordinates.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//Don't continue here if mouse is inside one of the boxes given as parameter.
for(int i=0;i<count;i++){
if(checkCollision(mouse,r[i]))
return;
}
//Check if the mouse is near the left edge of the screen.
//Else check if the mouse is near the right edge.
if(x<50){
//We're near the left edge so move the camera.
camera.x-=5;
}else if(x>SCREEN_WIDTH-50){
//We're near the right edge so move the camera.
camera.x+=5;
}
//Check if the mouse is near the top edge of the screen.
//Else check if the mouse is near the bottom edge.
if(y<50){
//We're near the top edge so move the camera.
camera.y-=5;
}else if(y>SCREEN_HEIGHT-50){
//We're near the bottom edge so move the camera.
camera.y+=5;
}
}
}
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(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(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 GUIObject((SCREEN_WIDTH-600)/2,200,600,200,GUIObjectFrame,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 GUIObject(0,y,root->width,25,GUIObjectLabel,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 GUIObject(root->width*places[i],y,-1,36,GUIObjectButton,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(root);
overlay->enterLoop(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(std::string name,GUIObject* obj,int eventType){
//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();
//Prompt the user with a Yes or No question.
/// TRANSLATORS: Filename is coming before this text
if(msgBox(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();
//The file can't be opened so tell the user.
msgBox(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();
//Unable to open file so tell the user.
msgBox(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;
}
}
}
};
bool fileDialog(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 GUIObject(100,100-base_y/2,600,400+base_y,GUIObjectFrame,title?title:(isSave?_("Save File"):_("Load File")));
//Create the search path list box if needed.
if(!pathNames.empty()){
root->addChild(new GUIObject(8,40,184,36,GUIObjectLabel,_("Search In")));
GUISingleLineListBox* obj1=new GUISingleLineListBox(160,40,432,36);
obj1->item=pathNames;
obj1->value=0;
obj1->name="lstSearchIn";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
}
//Add the FileName label and textfield.
root->addChild(new GUIObject(8,20+base_y,184,36,GUIObjectLabel,_("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 GUIObject(160,20+base_y,432,36,GUIObjectTextBox,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 GUIObject(200,360+base_y,192,36,GUIObjectButton,_("OK"));
obj->name="cmdOK";
obj->eventCallback=&objHandler;
root->addChild(obj);
obj=new GUIObject(400,360+base_y,192,36,GUIObjectButton,_("Cancel"));
obj->name="cmdCancel";
obj->eventCallback=&objHandler;
root->addChild(obj);
//Create the gui overlay.
GUIOverlay* overlay=new GUIOverlay(root);
overlay->enterLoop();
//Now determine what the return value is (and if there is one).
if(objHandler.ret)
fileName=objHandler.fileName;
return objHandler.ret;
}
diff --git a/src/GUIListBox.cpp b/src/GUIListBox.cpp
index 25dc71a..0f2cb9e 100644
--- a/src/GUIListBox.cpp
+++ b/src/GUIListBox.cpp
@@ -1,448 +1,433 @@
/*
* Copyright (C) 2011-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 "GUIListBox.h"
using namespace std;
GUIListBox::GUIListBox(int left,int top,int width,int height,bool enabled,bool visible,int gravity):
GUIObject(left,top,width,height,0,NULL,-1,enabled,visible,gravity),itemHeight(24){
//Set the state -1.
state=-1;
//Create the scrollbar and add it to the children.
scrollBar=new GUIScrollBar(0,0,16,0,1,0,0,0,0,0,true,false);
childControls.push_back(scrollBar);
}
GUIListBox::~GUIListBox(){
- //Remove items
+ //Remove all items and cache.
clearItems();
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
bool GUIListBox::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Calculate the scrollbar position.
scrollBar->left=width-16;
scrollBar->height=height;
int m=item.size(),n=(height-4)/itemHeight;
if(m>n){
scrollBar->maxValue=m-n;
scrollBar->smallChange=1;
scrollBar->largeChange=n;
scrollBar->visible=true;
b=b||scrollBar->handleEvents(x,y,enabled,visible,b);
}else{
scrollBar->value=0;
scrollBar->maxValue=0;
scrollBar->visible=false;
}
//Set state negative.
state=-1;
//Check if the GUIListBox is visible, enabled and no event has been processed before.
if(enabled&&visible&&!b){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j;
SDL_GetMouseState(&i,&j);
//Convert the mouse location to a relative location.
i-=x+2;
j-=y+2;
//Check if the mouse is inside the GUIListBox.
if(i>=0&&i<width-4&&j>=0&&j<height-4){
//Calculate the y location with the scrollbar position.
int idx=j/itemHeight+scrollBar->value;
//If the entry isn't above the max we have an event.
if(idx>=0&&idx<(int)item.size()){
state=idx;
//Check if the left mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && value!=idx){
value=idx;
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}
//Check for mouse wheel scrolling.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_WHEELDOWN && scrollBar->enabled){
scrollBar->value+=4;
if(scrollBar->value > scrollBar->maxValue)
scrollBar->value = scrollBar->maxValue;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_WHEELUP && scrollBar->enabled){
scrollBar->value-=4;
if(scrollBar->value < 0)
scrollBar->value = 0;
}
}
}
//Process child controls event except for the scrollbar.
//That's why i starts at one.
for(unsigned int i=1;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIListBox::render(int x,int y,bool draw){
//Rectangle the size of the GUIObject, used to draw borders.
SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible||!draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Default background opacity
int clr=128;
//TODO: Add hover check?
//Draw the box.
Uint32 color=0xFFFFFFFF|clr;
drawGUIBox(x,y,width,height,screen,color);
//We need to draw the items.
//The number of items.
int m=item.size();
//The number of items that are visible.
- int n=(height-4)/itemHeight;
+ int n=floor(height/itemHeight)+1;
//Integer containing the current entry that is being drawn.
int i;
//The y coordinate the current entries reaches.
int j;
-
- //If the number of items is higer than fits on the screen set the begin value (m) to scrollBar->value+n.
+
+ //If the number of items is higher than fits on the screen set the begin value (m) to scrollBar->value+n.
if(m>scrollBar->value+n)
m=scrollBar->value+n;
//Loop through the (visible) entries and draw them.
- for(i=scrollBar->value,j=y+1;i<m;i++,j+=itemHeight){
- //The background color for the entry.
- int clr=-1;
- //If i is the selected entry then give it a light gray background.
- if(value==i){
- clr=0xDDDDDDFF;
- }
+ for(i=scrollBar->value,j=y+1;j>height,i<m;i++,j+=itemHeight){
+ //Check if the current item is out side of the widget.
+ int yOver=itemHeight;
+ if(j+images[i]->h>y+height)
+ yOver=y+height-j;
- //Check if the current entry is selected. If so draw borders around it.
+ //Check if the mouse is hovering on current entry. If so draw borders around it.
if(state==i)
- drawGUIBox(x,j-1,width,itemHeight+1,screen,0x00000000);
+ drawGUIBox(x,j-1,width,yOver+1,screen,0x00000000);
- //Only draw when clr isn't -1.
- if(clr!=-1)
- drawGUIBox(x,j-1,width,itemHeight+1,screen,clr);
+ //Check if the current entry is selected. If so draw a gray background.
+ if(value==i)
+ drawGUIBox(x,j-1,width,yOver+1,screen,0xDDDDDDFF);
- r.x=x+4;
- r.y=j;
- SDL_BlitSurface(images[i],NULL,screen,&r);
-
- //Now draw the text.
- /*const char* s=item[i].c_str();
- //Make sure the text isn't empty.
- if(s && s[0]){
- //Render black text.
- SDL_Color black={0,0,0,0};
- SDL_Surface *bm=TTF_RenderUTF8_Blended(fontText,s,black);
-
- //Calculate the text location, center it vertically.
- r.x=x+4;
- r.y=j+12-bm->h/2;
-
- //Draw the text and free the rendered surface.
- SDL_BlitSurface(bm,NULL,screen,&r);
- SDL_FreeSurface(bm);
- }*/
+ //Draw the image.
+ SDL_Rect clip;
+ clip.x=0;
+ clip.y=0;
+ clip.w=images[i]->w;
+ clip.h=yOver;
+ applySurface(x+4,j,images[i],screen,&clip);
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
void GUIListBox::clearItems(){
item.clear();
for(int i=0;i<images.size();i++){
SDL_FreeSurface(images[i]);
}
images.clear();
}
void GUIListBox::addItem(std::string name, SDL_Surface* image){
item.push_back(name);
if(image){
itemHeight=image->h;
images.push_back(image);
}else if(!image&&!name.empty()){
SDL_Color black={0,0,0,0};
SDL_Surface* tmp=TTF_RenderUTF8_Blended(fontText,name.c_str(),black);
images.push_back(tmp);
}
}
void GUIListBox::updateItem(int index, std::string newText, SDL_Surface* newImage){
item.at(index)=newText;
if(newImage){
itemHeight=newImage->h;
SDL_FreeSurface(images.at(index));
images.at(index)=newImage;
}else if(!newImage&&!newText.empty()){
SDL_FreeSurface(images.at(index));
SDL_Color black={0,0,0,0};
SDL_Surface* tmp=TTF_RenderUTF8_Blended(fontText,newText.c_str(),black);
images.at(index)=tmp;
}
}
std::string GUIListBox::getItem(int index){
return item.at(index);
}
GUISingleLineListBox::GUISingleLineListBox(int left,int top,int width,int height,bool enabled,bool visible,int gravity):
GUIObject(left,top,width,height,0,NULL,-1,enabled,visible,gravity),animation(0){}
bool GUISingleLineListBox::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
state&=~0xF;
if(enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Convert the mouse location to a relative location.
i-=x;
j-=y;
//The selected button.
//0=nothing 1=left 2=right.
int idx=0;
//Check which button the mouse is above.
if(i>=0&&i<width&&j>=0&&j<height){
if(i<26 && i<width/2){
//The left arrow.
idx=1;
}else if(i>=width-26){
//The right arrow.
idx=2;
}
}
//If idx is 0 it means the mous doesn't hover any arrow so reset animation.
if(idx==0)
animation=0;
//Check if there's a mouse button press or not.
if(k&SDL_BUTTON(1)){
if(((state>>4)&0xF)==idx)
state|=idx;
}else{
state|=idx;
}
//Check if there's a mouse press.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && idx){
state=idx|(idx<<4);
}else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && idx && ((state>>4)&0xF)==idx){
int m=(int)item.size();
if(m>0){
if(idx==2){
idx=value+1;
if(idx<0||idx>=m) idx=0;
if(idx!=value){
value=idx;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}else if(idx==1){
idx=value-1;
if(idx<0||idx>=m) idx=m-1;
if(idx!=value){
value=idx;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
}
}
}
}
if(event.type==SDL_MOUSEBUTTONUP) state&=0xF;
}else{
//Set state zero.
state=0;
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUISingleLineListBox::render(int x,int y,bool draw){
//Rectangle the size of the GUIObject, used to draw borders.
SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//NOTE: logic in the render method since it's the only part that gets called every frame.
if((state&0xF)==0x1 || (state&0xF)==0x2){
animation++;
if(animation>20)
animation=-20;
}
//Get the absolute x and y location.
x+=left;
y+=top;
if(gravity==GUIGravityCenter)
gravityX=int(width/2);
else if(gravity==GUIGravityRight)
gravityX=width;
x-=gravityX;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || item[value].compare(cachedCaption)!=0){
//Free the cache.
SDL_FreeSurface(cache);
cache=NULL;
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=item[value];
}
//Draw the text.
if(value>=0 && value<(int)item.size()){
//Get the text.
const char* lp=item[value].c_str();
//Check if the text is empty or not.
if(lp!=NULL && lp[0]){
if(!cache){
SDL_Color color;
if(inDialog)
color=themeTextColorDialog;
else
color=themeTextColor;
cache=TTF_RenderUTF8_Blended(fontGUI,lp,color);
//If the text is too wide then we change to smaller font (?)
//NOTE: The width is 32px smaller (2x16px for the arrows).
if(cache->w>width-32){
SDL_FreeSurface(cache);
cache=TTF_RenderUTF8_Blended(fontGUISmall,lp,color);
}
}
if(draw){
//Center the text both vertically as horizontally.
r.x=x+(width-cache->w)/2;
r.y=y+(height-cache->h)/2-GUI_FONT_RAISE;
//Draw the text and free the surface afterwards.
SDL_BlitSurface(cache,NULL,screen,&r);
}
}
}
if(draw){
//Draw the arrows.
SDL_Rect r2={48,0,16,16};
r.x=x;
if((state&0xF)==0x1)
r.x+=abs(animation/2);
r.y=y+4;
if(inDialog)
applySurface(r.x,r.y,arrowLeft2,screen,NULL);
else
applySurface(r.x,r.y,arrowLeft1,screen,NULL);
r.x=x+width-16;
if((state&0xF)==0x2)
r.x-=abs(animation/2);
if(inDialog)
applySurface(r.x,r.y,arrowRight2,screen,NULL);
else
applySurface(r.x,r.y,arrowRight1,screen,NULL);
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
diff --git a/src/GUIListBox.h b/src/GUIListBox.h
index 49a52ef..e789947 100644
--- a/src/GUIListBox.h
+++ b/src/GUIListBox.h
@@ -1,105 +1,117 @@
/*
* Copyright (C) 2011-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/>.
*/
#ifndef GUILISTBOX_H
#define GUILISTBOX_H
#include "GUIObject.h"
#include "GUIScrollBar.h"
//GUIObject that displays a list.
//It extends GUIObject because it's a special GUIObject.
class GUIListBox:public GUIObject{
public:
//Vector containing the entries of the list.
std::vector<std::string> item;
std::vector<SDL_Surface*> images;
private:
//Scrollbar used when there are more entries than fit on the screen.
GUIScrollBar* scrollBar;
int itemHeight;
public:
//Constructor.
//left: The relative x location of the GUIListBox.
//top: The relative y location of the GUIListBox.
//witdh: The width of the GUIListBox.
//height: The height of the GUIListBox.
//enabled: Boolean if the GUIListBox is enabled or not.
//visible: Boolean if the GUIListBox is visisble or not.
GUIListBox(int left=0,int top=0,int width=0,int height=0,bool enabled=true,bool visible=true,int gravity=GUIGravityLeft);
//Destructor
~GUIListBox();
- //
+ //Method to remove all items and clear cache.
void clearItems();
+
+ //Method to add an item to the widget.
+ //name: Text of the item.
+ //image: Custom image for the widget, if NULL the image will be generated from name string.
void addItem(std::string name, SDL_Surface* image=NULL);
+
+ //Method to update an item in the widget.
+ //index: index of the item.
+ //newName: New text for the item.
+ //newImage: New custom image for the widget, if NULL the image will be generated from newName string.
void updateItem(int index, std::string newText, SDL_Surface* newImage=NULL);
+
+ //Method used the get item names from the widget.
+ //index: index of the item.
std::string getItem(int index);
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUIListBox.
//x: The x location to draw the GUIListBox. (x+left)
//y: The y location to draw the GUIListBox. (y+top)
virtual void render(int x=0,int y=0,bool draw=true);
};
//GUIObject that displays a list on only one line.
//Instead of clicking the entries of the list you iterate through them.
//It extends GUIObject because it's a special GUIObject.
class GUISingleLineListBox:public GUIObject{
public:
//Vector containing the entries of the list.
std::vector<std::string> item;
//Integer used for the animation of the arrow.
int animation;
public:
//Constructor.
//left: The relative x location of the GUIListBox.
//top: The relative y location of the GUIListBox.
//witdh: The width of the GUIListBox.
//height: The height of the GUIListBox.
//enabled: Boolean if the GUIListBox is enabled or not.
//visible: Boolean if the GUIListBox is visisble or not.
GUISingleLineListBox(int left=0,int top=0,int width=0,int height=0,bool enabled=true,bool visible=true,int gravity=GUIGravityLeft);
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUIListBox.
//x: The x location to draw the GUIListBox. (x+left)
//y: The y location to draw the GUIListBox. (y+top)
virtual void render(int x=0,int y=0,bool draw=true);
};
#endif
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index 555a8e3..470dae4 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,671 +1,671 @@
/*
* Copyright (C) 2011-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 "GUIObject.h"
#include <iostream>
#include <list>
using namespace std;
//Set the GUIObjectRoot to NULL.
GUIObject* GUIObjectRoot=NULL;
//Initialise the event queue.
list<GUIEvent> GUIEventQueue;
void GUIObjectHandleEvents(bool kill){
//Check if user resizes the window.
if(event.type==SDL_VIDEORESIZE){
//onVideoResize();
//Don't let other objects process this event (?)
return;
}
//Make sure that GUIObjectRoot isn't null.
if(GUIObjectRoot)
GUIObjectRoot->handleEvents();
//Check for SDL_QUIT.
if(event.type==SDL_QUIT && kill){
//We get a quit event so enter the exit state.
setNextState(STATE_EXIT);
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
//Keep calling events until there are none left.
while(!GUIEventQueue.empty()){
//Get one event and remove it from the queue.
GUIEvent e=GUIEventQueue.front();
GUIEventQueue.pop_front();
//If an eventCallback exist call it.
if(e.eventCallback){
e.eventCallback->GUIEventCallback_OnEvent(e.name,e.obj,e.eventType);
}
}
//We empty the event queue just to be sure.
GUIEventQueue.clear();
}
GUIObject::~GUIObject(){
if(cache){
SDL_FreeSurface(cache);
cache=NULL;
}
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
bool GUIObject::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//Type specific event handling.
switch(type){
case GUIObjectButton:
//Set state to 0.
state=0;
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We have hover so set state to one.
state=1;
//Check for a mouse button press.
if(k&SDL_BUTTON(1))
state=2;
//Check if there's a mouse press and the event hasn't been already processed.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && !b){
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
//Event has been processed.
b=true;
}
}
}
break;
case GUIObjectCheckBox:
//Set state to 0.
state=0;
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We have hover so set state to one.
state=1;
//Check for a mouse button press.
if(k&SDL_BUTTON(1))
state=2;
//Check if there's a mouse press and the event hasn't been already processed.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && !b){
//It's a checkbox so toggle the value.
value=value?0:1;
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
//Event has been processed.
b=true;
}
}
}
break;
case GUIObjectTextBox:
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b){
//Get the keycode.
int key=(int)event.key.keysym.unicode;
//Check if the key is supported.
if(key>=32&&key<=126){
//Add the key to the text after the carrot.
caption.insert((size_t)value,1,char(key));
value=clamp(value+1,0,caption.length());
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}else if(event.key.keysym.sym==SDLK_BACKSPACE){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0&&value>0){
//Remove the character before the carrot.
value=clamp(value-1,0,caption.length());
caption.erase((size_t)value,1);
this->key=SDLK_BACKSPACE;
keyHoldTime=0;
keyTime=5;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}else if(event.key.keysym.sym==SDLK_DELETE){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
//Remove the character after the carrot.
value=clamp(value,0,caption.length());
caption.erase((size_t)value,1);
this->key=SDLK_DELETE;
keyHoldTime=0;
keyTime=5;
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}else if(event.key.keysym.sym==SDLK_RIGHT){
value=clamp(value+1,0,caption.length());
this->key=SDLK_RIGHT;
keyHoldTime=0;
keyTime=5;
}else if(event.key.keysym.sym==SDLK_LEFT){
value=clamp(value-1,0,caption.length());
this->key=SDLK_LEFT;
keyHoldTime=0;
keyTime=5;
}
//The event has been processed.
b=true;
}else if(state==2 && event.type==SDL_KEYUP && !b){
//Check if released key is the same as the holded key.
if(event.key.keysym.sym==key){
//It is so stop the key.
key=-1;
}
}
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We can only increase our state. (nothing->hover->focus).
if(state!=2){
state=1;
}
//Also update the cursor type.
currentCursor=CURSOR_CARROT;
//Check for a mouse button press.
if(k&SDL_BUTTON(1)){
//We have focus.
state=2;
//Move carrot to the place clicked
int click=i-x;
- int wid=0;
if(!cache){
value=0;
}else if(click>cache->w){
value=caption.length();
}else{
+ int wid=0;
for(int i=0;i<caption.length();i++){
int advance;
TTF_GlyphMetrics(fontText,caption[i],NULL,NULL,NULL,NULL,&advance);
wid+=advance;
if(click<wid-advance/2){
value=i;
break;
}
}
}
}
}else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if(state==1){
state=0;
}
//If it's a click event outside the textbox then we blur.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//Set state to 0.
state=0;
}
}
}
break;
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
//Found on gmane.comp.lib.sdl mailing list, see: http://comments.gmane.org/gmane.comp.lib.sdl/33664
//Original code by "Patricia Curtis" and later modified by "Jason"
static void SetSurfaceTrans(SDL_Surface* Src,double PercentTrans){
Uint8 Sbpp = Src->format->BytesPerPixel;
Uint8 *Sbits;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
int amask = 0x000000ff;
int cmask = 0xffffff00;
#else
int amask = 0xff000000;
int cmask = 0x00ffffff;
int Shift = 24;
#endif
int x,y;
Uint32 Pixels;
Uint32 Alpha;
for(y=0;y<Src->h;y++)
{
for(x=0;x<Src->w;x++)
{
Sbits = ((Uint8 *)Src->pixels+(y*Src->pitch)+(x*Sbpp));
Pixels = *((Uint32 *)(Sbits));
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
Alpha = Pixels & mask;
#else
Alpha = (Pixels&amask)>>Shift;
#endif
Alpha*=PercentTrans;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
*((Uint32 *)(Sbits)) = (Pixels & cmask)|Alpha;
#else
*((Uint32 *)(Sbits)) = (Pixels & cmask)|(Alpha<<Shift);
#endif
}
}
}
void GUIObject::render(int x,int y,bool draw){
//Rectangle the size of the GUIObject, used to draw borders.
SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//Free the cache.
SDL_FreeSurface(cache);
cache=NULL;
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget
if(autoWidth)
width=-1;
}
//Now do the type specific rendering.
switch(type){
case GUIObjectLabel:
{
//The rectangle is simple.
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//We don't draw a background and/or border since that label is transparent.
//Get the caption and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Render the text using the small font.
if(cache==NULL){
SDL_Color color;
if(inDialog)
color=themeTextColorDialog;
else
color=themeTextColor;
cache=TTF_RenderUTF8_Blended(fontText,lp,color);
if(width<=0)
width=cache->w;
}
if(draw){
if(gravity==GUIGravityCenter)
gravityX=(width-cache->w)/2;
else if(gravity==GUIGravityRight)
gravityX=width+cache->w;
else
gravityX=0;
//Center the text vertically and draw it to the screen.
r.y=y+(height - cache->h)/2;
r.x+=gravityX;
SDL_BlitSurface(cache,NULL,screen,&r);
}
}
}
break;
case GUIObjectCheckBox:
{
//The rectangle is simple.
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//We render black text.
if(!cache){
SDL_Color color;
if(inDialog)
color=themeTextColorDialog;
else
color=themeTextColor;
cache=TTF_RenderUTF8_Blended(fontText,lp,color);
}
if(draw){
//Calculate the location, center it vertically.
r.x=x;
r.y=y+(height - cache->h)/2;
//Draw the text and free the surface.
SDL_BlitSurface(cache,NULL,screen,&r);
}
}
if(draw){
//Draw the check (or not).
SDL_Rect r1={0,0,16,16};
if(value==1||value==2)
r1.x=value*16;
r.x=x+width-20;
r.y=y+(height-16)/2;
SDL_BlitSurface(bmGUI,&r1,screen,&r);
}
}
break;
case GUIObjectButton:
{
//Get the text.
const char* lp=caption.c_str();
//Make sure the text isn't empty.
if(lp!=NULL && lp[0]){
if(!cache){
SDL_Color color;
if(inDialog)
color=themeTextColorDialog;
else
color=themeTextColor;
if(!smallFont)
cache=TTF_RenderUTF8_Blended(fontGUI,lp,color);
else
cache=TTF_RenderUTF8_Blended(fontGUISmall,lp,color);
if(!enabled)
SetSurfaceTrans(cache,0.5);
if(width<=0){
width=cache->w+50;
if(gravity==GUIGravityCenter){
gravityX=int(width/2);
}else if(gravity==GUIGravityRight){
gravityX=width;
}else{
gravityX=0;
}
}
}
if(draw){
//Center the text both vertically as horizontally.
r.x=x-gravityX+(width-cache->w)/2;
r.y=y+(height-cache->h)/2-GUI_FONT_RAISE;
//Check if the arrows don't fall of.
if(cache->w+32<=width){
//Create a rectangle that selects the right image from bmGUI,
SDL_Rect r2={64,0,16,16};
if(state==1){
if(inDialog){
applySurface(x-gravityX+(width-cache->w)/2+4+cache->w+5,y+2,arrowLeft2,screen,NULL);
applySurface(x-gravityX+(width-cache->w)/2-25,y+2,arrowRight2,screen,NULL);
}else{
applySurface(x-gravityX+(width-cache->w)/2+4+cache->w+5,y+2,arrowLeft1,screen,NULL);
applySurface(x-gravityX+(width-cache->w)/2-25,y+2,arrowRight1,screen,NULL);
}
}else if(state==2){
if(inDialog){
applySurface(x-gravityX+(width-cache->w)/2+4+cache->w,y+2,arrowLeft2,screen,NULL);
applySurface(x-gravityX+(width-cache->w)/2-20,y+2,arrowRight2,screen,NULL);
}else{
applySurface(x-gravityX+(width-cache->w)/2+4+cache->w,y+2,arrowLeft1,screen,NULL);
applySurface(x-gravityX+(width-cache->w)/2-20,y+2,arrowRight1,screen,NULL);
}
}
}
//Draw the text and free the surface.
SDL_BlitSurface(cache,NULL,screen,&r);
}
}
}
break;
case GUIObjectTextBox:
{
//FIXME: Logic in the render method since that is update constant.
if(key!=-1){
//Increase the key time.
keyHoldTime++;
//Make sure the deletionTime isn't to short.
if(keyHoldTime>=keyTime){
keyHoldTime=0;
keyTime--;
if(keyTime<1)
keyTime=1;
//Now check the which key it was.
switch(key){
case SDLK_BACKSPACE:
{
//Remove the character before the carrot.
value=clamp(value-1,0,caption.length());
caption.erase((size_t)value,1);
break;
}
case SDLK_DELETE:
{
//Remove the character after the carrot.
value=clamp(value,0,caption.length());
caption.erase((size_t)value,1);
break;
}
case SDLK_LEFT:
{
value=clamp(value-1,0,caption.length());
break;
}
case SDLK_RIGHT:
{
value=clamp(value+1,0,caption.length());
break;
}
}
}
}
if(draw){
//Default background opacity
int clr=50;
//If hovering or focused make background more visible.
if(state==1)
clr=128;
else if (state==2)
clr=100;
//Draw the box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,screen,color);
}
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
if(!cache){
//Draw the black text.
SDL_Color black={0,0,0,0};
cache=TTF_RenderUTF8_Blended(fontText,lp,black);
}
if(draw){
//Calculate the location, center it vertically.
r.x=x+2;
r.y=y+(height - cache->h)/2;
//Draw the text.
SDL_Rect tmp={0,0,width-2,25};
SDL_BlitSurface(cache,&tmp,screen,&r);
//Only draw the carrot when focus.
if(state==2){
r.x=x;
r.y=y+4;
r.w=2;
r.h=height-8;
int advance;
for(int n=0;n<value;n++){
TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
r.x+=advance;
}
//Make sure that the carrot is inside the textbox.
if(r.x<x+width)
SDL_FillRect(screen,&r,0);
}
}
}else{
//Only draw the carrot when focus.
if(state==2&&draw){
r.x=x+4;
r.y=y+4;
r.w=2;
r.h=height-8;
SDL_FillRect(screen,&r,0);
}
}
}
break;
case GUIObjectFrame:
{
if(draw){
//Create a rectangle the size of the button and fill it.
Uint32 color=0xDDDDDDFF;
drawGUIBox(x,y,width,height,screen,color);
}
//Get the title text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
if(!cache){
cache=TTF_RenderUTF8_Blended(fontGUI,lp,themeTextColorDialog);
}
if(draw){
//Calculate the location, center horizontally and vertically relative to the top.
r.x=x+(width-cache->w)/2;
r.y=y+6-GUI_FONT_RAISE;
//Draw the text and free the surface.
SDL_BlitSurface(cache,NULL,screen,&r);
}
}
}
break;
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
diff --git a/src/GUITextArea.cpp b/src/GUITextArea.cpp
index 44e585e..52a44f0 100644
--- a/src/GUITextArea.cpp
+++ b/src/GUITextArea.cpp
@@ -1,490 +1,572 @@
/*
* Copyright (C) 2011-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 "GUITextArea.h"
#include <cmath>
using namespace std;
GUITextArea::GUITextArea(int left,int top,int width,int height,bool enabled,bool visible):
GUIObject(left,top,width,height,0,NULL,-1,enabled,visible){
- //Set the state 0.
- state=0;
+ //Set some default values.
+ state=value=currentLine=0;
+ setFont(fontText);
+
+ //Add empty text.
+ lines.push_back("");
+ linesCache.push_back(NULL);
+}
+
+GUITextArea::~GUITextArea(){
+ //Free cached images.
+ for(int i=0;i<linesCache.size();i++){
+ SDL_FreeSurface(linesCache[i]);
+ }
+ linesCache.clear();
+}
+
+void GUITextArea::setFont(TTF_Font* font){
+ //NOTE: This fuction shouldn't be called after adding items, so no need to update the whole cache.
+ widgetFont=font;
+ fontHeight=TTF_FontHeight(font)+1;
}
bool GUITextArea::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b){
//Get the keycode.
int key=(int)event.key.keysym.unicode;
//Check if the key is supported.
if(key>=32&&key<=126){
//Add the key to the string.
- caption.insert((size_t)value,1,char(key));
- value=clamp(value+1,0,caption.length());
+ string* str=&lines.at(currentLine);
+ str->insert((size_t)value,1,char(key));
+ value++;
+
+ //Update cache.
+ SDL_Surface** c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}else if(event.key.keysym.sym==SDLK_BACKSPACE){
//Set the key values correct.
this->key=SDLK_BACKSPACE;
keyHoldTime=0;
keyTime=5;
//Delete one character direct to prevent a lag.
- deleteChar(true);
+ backspaceChar();
}else if(event.key.keysym.sym==SDLK_DELETE){
//Set the key values correct.
this->key=SDLK_DELETE;
keyHoldTime=0;
keyTime=5;
//Delete one character direct to prevent a lag.
- deleteChar(false);
- }else if(event.key.keysym.sym==SDLK_RETURN){
- //Enter, thus place a newline.
- caption.insert((size_t)value,1,'\n');
- value=clamp(value+1,0,caption.length());
+ deleteChar();
+ }else if(event.key.keysym.sym==SDLK_RETURN){
+ //Split the current line and update.
+ string str2=lines.at(currentLine).substr(value);
+ lines.at(currentLine)=lines.at(currentLine).substr(0,value);
+
+ SDL_Surface** c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,lines.at(currentLine).c_str(),black);
+
+ //Add the rest in a new line.
+ currentLine++;
+ value=0;
+ lines.insert(lines.begin()+currentLine,str2);
+
+ SDL_Surface* c2;
+ c2=TTF_RenderUTF8_Blended(widgetFont,str2.c_str(),black);
+ linesCache.insert(linesCache.begin()+currentLine,c2);
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
+ }else if(event.key.keysym.sym==SDLK_TAB){
+ //Add a tabulator or here just 2 spaces to the string.
+ string* str=&lines.at(currentLine);
+ str->insert((size_t)value,2,char(' '));
+ value+=2;
+
+ //Update cache.
+ SDL_Surface** c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
}else if(event.key.keysym.sym==SDLK_RIGHT){
//Set the key values correct.
this->key=SDLK_RIGHT;
keyHoldTime=0;
keyTime=5;
//Move the carrot once to prevent a lag.
- value=clamp(value+1,0,caption.length());
+ moveCarrotRight();
}else if(event.key.keysym.sym==SDLK_LEFT){
//Set the key values correct.
this->key=SDLK_LEFT;
keyHoldTime=0;
keyTime=5;
//Move the carrot once to prevent a lag.
- value=clamp(value-1,0,caption.length());
+ moveCarrotLeft();
}else if(event.key.keysym.sym==SDLK_DOWN){
- //Find out where the carrot currently is.
- int xPos=0;
- int lineStart=0;
-
- for(int i=0;i<value;i++){
- if(caption.at(i)=='\n'){
- lineStart=i;
- }
- }
-
- for(int n=lineStart;n<value;n++){
- if(caption.at(n)!='\n'){
- int advance;
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- xPos+=advance;
- }
- }
-
- //Figure out the next line's start and end positions.
- lineStart++;
- for(int i=lineStart;i<caption.length();i++){
- if(caption.at(i)=='\n'){
- lineStart=i;
- break;
- }
- }
-
- int lineEnd=caption.length();
- for(int i=lineStart+1;i<caption.length();i++){
- if(caption.at(i)=='\n'){
- lineEnd=i;
- break;
- }
- }
+ //Set the key values correct.
+ this->key=SDLK_DOWN;
+ keyHoldTime=0;
+ keyTime=5;
- //Calculate the closest position for the carrot.
- int curX=0;
- value=lineEnd;
- for(int n=lineStart;n<lineEnd;n++){
- if(caption.at(n)!='\n'){
- int advance;
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- curX+=advance;
-
- if(xPos<curX-advance/2){
- value=n;
- break;
- }
- }
- }
+ //Move the carrot once to prevent a lag.
+ moveCarrotDown();
}else if(event.key.keysym.sym==SDLK_UP){
- //Find out where the carrot currently is.
- int xPos=0;
- int lineStart=0;
-
- for(int i=0;i<value;i++){
- if(caption.at(i)=='\n'){
- lineStart=i;
- }
- }
-
- for(int n=lineStart;n<value;n++){
- if(caption.at(n)!='\n'){
- int advance;
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- xPos+=advance;
- }
- }
-
- //Figure out the previous line's start and end positions.
- int lineEnd;
- for(int i=lineStart;i>0;i--){
- if(caption.at(i)=='\n'){
- lineEnd=i;
- break;
- }
- }
-
- lineStart=0;
- for(int i=lineEnd-1;i>0;i--){
- if(caption.at(i)=='\n'){
- lineStart=i;
- break;
- }
- }
+ //Set the key values correct.
+ this->key=SDLK_UP;
+ keyHoldTime=0;
+ keyTime=5;
- //Calculate the closest position for the carrot.
- int curX=0;
- value=lineEnd;
- for(int n=lineStart;n<lineEnd;n++){
- if(caption.at(n)!='\n'){
- int advance;
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- curX+=advance;
-
- if(xPos<curX-advance/2){
- value=n;
- break;
- }
- }
- }
+ //Move the carrot once to prevent a lag.
+ moveCarrotUp();
}
//The event has been processed.
b=true;
}else if(state==2 && event.type==SDL_KEYUP && !b){
//Check if released key is the same as the holded key.
if(event.key.keysym.sym==key){
//It is so stop the key.
key=-1;
}
}
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We can only increase our state. (nothing->hover->focus).
if(state!=2){
state=1;
}
//Also update the cursor type.
currentCursor=CURSOR_CARROT;
//Check for a mouse button press.
if(k&SDL_BUTTON(1)){
//We have focus.
state=2;
//Move carrot to the place clicked.
- value=0;
+ currentLine=clamp(floor((j-y)/fontHeight),0,lines.size()-1);
+ string* str=&lines.at(currentLine);
+ value=str->length();
int clickX=i-x;
- int line=floor((j-y)/25.0f);
-
- int wid=0;
- int cline=0;
-
- //First line starts from 0, so no need for calculations.
- if (line>0){
- for(wid=0;wid<caption.length();wid++){
- if(caption.at(wid)=='\n'){
- cline++;
- if(cline==line){
- wid++;
- break;
- }
- }
- }
- }
+ int xPos=0;
- //Check if line contains only a newline. If so, skip all the calculations.
- if((wid<caption.length()&&caption.at(wid)=='\n')||(wid==caption.length())){
- value=wid;
- }else{
- //Where the current line ends?
- int lineEnd = wid;
- for(int i=wid;i<caption.length();i++){
- if(i!=wid && caption.at(i)=='\n')
- break;
- else
- lineEnd++;
- }
+ for(int i=0;i<str->length();i++){
+ int advance;
+ TTF_GlyphMetrics(widgetFont,str->at(i),NULL,NULL,NULL,NULL,&advance);
+ xPos+=advance;
- //Finally move the carrot to correct place.
- int xPos=0;
- for(int i=wid;i<lineEnd;i++){
- int advance;
- TTF_GlyphMetrics(fontText,caption.at(i),NULL,NULL,NULL,NULL,&advance);
- xPos+=advance;
-
- //The carrot will be in the end...
- value=lineEnd;
-
- //...if a proper place isn't found.
- if(clickX<xPos-advance/2){
- value=i;
- break;
- }
+ if(clickX<xPos-advance/2){
+ value=i;
+ break;
}
}
}
}else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if(state==1){
state=0;
}
//If it's a click event outside the textbox then we blur.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//Set state to 0.
state=0;
}
}
}
//Process child controls event except for the scrollbar.
//That's why i starts at one.
for(unsigned int i=1;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
-void GUITextArea::deleteChar(bool back){
- //Boolean if an event should be called.
- bool event=false;
+void GUITextArea::deleteChar(){
+ //Remove a character after the carrot.
+ if(value<lines.at(currentLine).length()){
+ //Normal delete inside of a line.
+ //Update string.
+ string* str=&lines.at(currentLine);
+ str->erase((size_t)value,1);
+
+ //Update cache.
+ SDL_Surface** c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
+ }else{
+ //Make sure there's a line after currentLine.
+ if(currentLine<lines.size()-1){
+ //Append next line.
+ string* str=&lines.at(currentLine);
+ str->append(lines.at(currentLine+1));
+
+ //Remove the unused line.
+ SDL_Surface** c=&linesCache.at(currentLine+1);
+ if(*c) SDL_FreeSurface(*c);
+ lines.erase(lines.begin()+currentLine+1);
+ linesCache.erase(linesCache.begin()+currentLine+1);
+
+ //Update cache.
+ c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
+ }
+ }
- //Check if it's backspace or delete.
- if(back){
- //We need to remove a character so first make sure that there is text.
- if(caption.length()>0&&value>0){
- //Remove the character before the carrot.
- value=clamp(value-1,0,caption.length());
- caption.erase((size_t)value,1);
+ //If there is an event callback.
+ if(eventCallback){
+ GUIEvent e={eventCallback,name,this,GUIEventChange};
+ GUIEventQueue.push_back(e);
+ }
+}
+
+void GUITextArea::backspaceChar(){
+ //Remove a character before the carrot.
+ value--;
+ if(value<0){
+ //Remove a line but append it's content to the previous.
+ //However we can't do this to the first line.
+ if(currentLine>0){
+ //Remove line from display but store the string.
+ string str=lines.at(currentLine);
+ lines.erase(lines.begin()+currentLine);
+ SDL_Surface** c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ linesCache.erase(linesCache.begin()+currentLine);
+
+ //Append that string to the previous line.
+ currentLine--;
+ string* str2=&lines.at(currentLine);
+ value=str2->length();
+ str2->append(str);
- //Set event true.
- event=true;
+ //Update cache.
+ c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,str2->c_str(),black);
+ }else{
+ //Don't let the value become negative.
+ value=0;
+ }
+ }else{
+ //Normal delete inside of a line.
+ //Update string.
+ string* str=&lines.at(currentLine);
+ str->erase((size_t)value,1);
+
+ //Update cache.
+ SDL_Surface** c=&linesCache.at(currentLine);
+ if(*c) SDL_FreeSurface(*c);
+ SDL_Color black={0,0,0,0};
+ *c=TTF_RenderUTF8_Blended(widgetFont,str->c_str(),black);
+ }
+
+ //If there is an event callback.
+ if(eventCallback){
+ GUIEvent e={eventCallback,name,this,GUIEventChange};
+ GUIEventQueue.push_back(e);
+ }
+}
+
+void GUITextArea::moveCarrotRight(){
+ value++;
+ if(value>lines.at(currentLine).length()){
+ if(currentLine==lines.size()-1){
+ value=lines.at(currentLine).length();
+ }else{
+ currentLine++;
+ value=0;
+ }
+ }
+}
+
+void GUITextArea::moveCarrotLeft(){
+ value--;
+ if(value<0){
+ if(currentLine==0){
+ value=0;
+ }else{
+ currentLine--;
+ value=lines.at(currentLine).length();
}
+ }
+}
+
+void GUITextArea::moveCarrotUp(){
+ if(currentLine==0){
+ value=0;
}else{
- if(caption.length()>0){
- //Remove the character after the carrot.
- value=clamp(value,0,caption.length());
- caption.erase((size_t)value,1);
+ //Calculate carrot x position.
+ int carrotX=0;
+ for(int n=0;n<value;n++){
+ int advance;
+ TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
+ carrotX+=advance;
+ }
+
+ //Find out closest match.
+ currentLine--;
+ string* str=&lines.at(currentLine);
+ value=str->length();
+
+ int xPos=0;
+ for(int i=0;i<str->length();i++){
+ int advance;
+ TTF_GlyphMetrics(widgetFont,str->at(i),NULL,NULL,NULL,NULL,&advance);
+ xPos+=advance;
- //Set event true.
- event=true;
+ if(carrotX<xPos-advance/2){
+ value=i;
+ break;
+ }
}
}
+}
- //If there is an event callback and a character is removed then call it.
- if(event && eventCallback){
- GUIEvent e={eventCallback,name,this,GUIEventChange};
- GUIEventQueue.push_back(e);
+void GUITextArea::moveCarrotDown(){
+ if(currentLine==lines.size()-1){
+ value=lines.at(currentLine).length();
+ }else{
+ //Calculate carrot x position.
+ int carrotX=0;
+ for(int n=0;n<value;n++){
+ int advance;
+ TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
+ carrotX+=advance;
+ }
+
+ //Find out closest match.
+ currentLine++;
+ string* str=&lines.at(currentLine);
+ value=str->length();
+
+ int xPos=0;
+ for(int i=0;i<str->length();i++){
+ int advance;
+ TTF_GlyphMetrics(widgetFont,str->at(i),NULL,NULL,NULL,NULL,&advance);
+ xPos+=advance;
+
+ if(carrotX<xPos-advance/2){
+ value=i;
+ break;
+ }
+ }
}
}
void GUITextArea::render(int x,int y,bool draw){
//FIXME: Logic in the render method since that is update constant.
if(key!=-1){
//Increase the key time.
keyHoldTime++;
//Make sure the deletionTime isn't to short.
if(keyHoldTime>=keyTime){
keyHoldTime=0;
keyTime--;
if(keyTime<1)
keyTime=1;
//Now check the which key it was.
switch(key){
case SDLK_BACKSPACE:
- deleteChar(true);
+ backspaceChar();
break;
case SDLK_DELETE:
- deleteChar(false);
+ deleteChar();
break;
case SDLK_LEFT:
- value=clamp(value-1,0,caption.length());
+ moveCarrotLeft();
break;
case SDLK_RIGHT:
- value=clamp(value+1,0,caption.length());
- break;
+ moveCarrotRight();
+ break;
+ case SDLK_UP:
+ moveCarrotUp();
+ break;
+ case SDLK_DOWN:
+ moveCarrotDown();
+ break;
}
}
}
- //Rectangle the size of the GUIObject, used to draw borders.
- SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible||!draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Default background opacity
int clr=128;
//If hovering or focused make background more visible.
if(state==1)
clr=255;
else if (state==2)
clr=230;
//Draw the box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,screen,color);
- r.x=x+1;
- r.y=y+1;
- r.w=width-2;
- r.h=height-2;
-
- //Pointer to the string.
- char* lps=(char*)caption.c_str();
- //Pointer to a character.
- char* lp=NULL;
-
- //The color black.
- SDL_Color black={0,0,0,0};
- //The surface that will hold the text.
- SDL_Surface* bm=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;
-
- //Draw the black text.
- bm=TTF_RenderUTF8_Blended(fontText,lps,black);
-
- //Draw the text.
- SDL_Rect tmp={0,0,width-2,25};
- SDL_BlitSurface(bm,&tmp,screen,&r);
- //NOTE: We free the surface later.
-
- //Increase y with 25, about the height of the text.
- r.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;
- //Restore the character, this way the original string doesn't get destroyed.
- *lp=c;
+ //Draw text.
+ //TODO: support scrolling
+ int lineY=0;
+ for(std::vector<SDL_Surface*>::iterator it=linesCache.begin();it!=linesCache.end();++it){
+ if(*it) applySurface(x+1,y+1+lineY,*it,screen,NULL);
+ lineY+=fontHeight;
}
//Only draw the carrot when focus.
if(state==2){
+ SDL_Rect r;
r.x=x;
- r.y=y+4;
+ r.y=y+4+fontHeight*currentLine;
r.w=2;
- r.h=20;
+ r.h=fontHeight-4;
- //Place the carrot.
- int lineStart=0;
- for(int i=0;i<value;i++){
- if(caption.at(i)=='\n'){
- r.y+=25;
- lineStart=i;
- }
- }
-
- for(int n=lineStart;n<value;n++){
- if(caption.at(n)!='\n'){
- int advance;
- TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
- r.x+=advance;
- }
+ for(int n=0;n<value;n++){
+ int advance;
+ TTF_GlyphMetrics(widgetFont,lines.at(currentLine).at(n),NULL,NULL,NULL,NULL,&advance);
+ r.x+=advance;
}
//Make sure that the carrot is inside the textbox.
if(r.x<x+width)
SDL_FillRect(screen,&r,0);
}
- //Anyway we free the bm surface.
- if(bm!=NULL)
- SDL_FreeSurface(bm);
-
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y,draw);
}
}
+
+void GUITextArea::setString(std::string input){
+ //Clear previous content if any.
+ //Delete every line.
+ lines.clear();
+ //Free cached images.
+ for(int i=0;i<linesCache.size();i++){
+ SDL_FreeSurface(linesCache[i]);
+ }
+ linesCache.clear();
+
+ size_t linePos=0,lineLen=0;
+ SDL_Color black={0,0,0,0};
+ SDL_Surface* bm=NULL;
+
+ //Loop through the input string.
+ for(size_t i=0;i<input.length();++i)
+ {
+ //Check when we come in end of a line.
+ if(input.at(i)=='\n'){
+ //Check if the line is empty.
+ if(lineLen==0){
+ lines.push_back("");
+ linesCache.push_back(NULL);
+ }else{
+ //Read the whole line.
+ string line=input.substr(linePos,lineLen);
+ lines.push_back(line);
+
+ //Render and cache text.
+ bm=TTF_RenderUTF8_Blended(widgetFont,line.c_str(),black);
+ linesCache.push_back(bm);
+ }
+ //Skip '\n' in end of the line.
+ linePos=i+1;
+ lineLen=0;
+ }else{
+ lineLen++;
+ }
+ }
+
+ //The string might not end with a newline.
+ //That's why we're going to add end rest of the string as one line.
+ string line=input.substr(linePos);
+ lines.push_back(line);
+
+ bm=TTF_RenderUTF8_Blended(widgetFont,line.c_str(),black);
+ linesCache.push_back(bm);
+}
+
+string GUITextArea::getString(){
+ string tmp;
+ for(vector<string>::iterator it=lines.begin();it!=lines.end();++it){
+ //Append a newline only if not the first line.
+ if(it!=lines.begin())
+ tmp.append(1,'\n');
+ //Append the line.
+ tmp.append(*it);
+ }
+ return tmp;
+}
diff --git a/src/GUITextArea.h b/src/GUITextArea.h
index fd1c08f..be27bc0 100644
--- a/src/GUITextArea.h
+++ b/src/GUITextArea.h
@@ -1,56 +1,99 @@
/*
* Copyright (C) 2011-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/>.
*/
#ifndef GUITEXTAREA_H
#define GUITEXTAREA_H
+#ifdef __APPLE__
+#include <SDL_ttf/SDL_ttf.h>
+#else
+#include <SDL/SDL_ttf.h>
+#endif
+
#include "GUIObject.h"
-//GUIObject that displays a list.
+//GUIObject based widget for multiline text input.
//It extends GUIObject because it's a special GUIObject.
class GUITextArea:public GUIObject{
private:
//Method that will remove the last character of the text.
- //back: Boolean if the key backspace is used. (delete otherwise)
- void deleteChar(bool back);
+ void backspaceChar();
+ void deleteChar();
+
+ //Methods to move the carrot by one character/line.
+ void moveCarrotLeft();
+ void moveCarrotRight();
+ void moveCarrotUp();
+ void moveCarrotDown();
+
+ //Pointer to the font used in the widget.
+ TTF_Font* widgetFont;
+
+ //Widget's text.
+ //One line per vector element.
+ std::vector<std::string> lines;
+
+ //Cache for rendered lines.
+ //Will be updated alongside with variable text.
+ std::vector<SDL_Surface*> linesCache;
+
+ //Variable for carrot position.
+ //NOTE: We will use variable "value" from GUIObject for position within the current line.
+ int currentLine;
+
+ //Height of the font.
+ int fontHeight;
public:
//Constructor.
//left: The relative x location of the GUIListBox.
//top: The relative y location of the GUIListBox.
//witdh: The width of the GUIListBox.
//height: The height of the GUIListBox.
//enabled: Boolean if the GUIListBox is enabled or not.
//visible: Boolean if the GUIListBox is visisble or not.
GUITextArea(int left=0,int top=0,int width=0,int height=0,bool enabled=true,bool visible=true);
+ //Destructor
+ ~GUITextArea();
+
+ //Method used to change the font.
+ //font: Pointer to the font
+ void setFont(TTF_Font* font);
+
+ //Method used to get widget's text in a single string.
+ std::string getString();
+
+ //Method used to set widget's text.
+ void setString(std::string input);
+
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false);
//Method that will render the GUITextArea.
//x: The x location to draw the GUITextArea. (x+left)
//y: The y location to draw the GUITextArea. (y+top)
virtual void render(int x=0,int y=0,bool draw=true);
};
#endif
diff --git a/src/Globals.cpp b/src/Globals.cpp
index 2ad9fb6..1ef309e 100644
--- a/src/Globals.cpp
+++ b/src/Globals.cpp
@@ -1,106 +1,109 @@
/*
* Copyright (C) 2011-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 "Globals.h"
#ifdef __APPLE__
#include <SDL_mixer/SDL_mixer.h>
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#endif
#include "libs/tinygettext/tinygettext.hpp"
//Set the defautl value for the screen width and height.
int SCREEN_WIDTH=800;
int SCREEN_HEIGHT=600;
//Set the default value for the level width and height.
int LEVEL_HEIGHT=0;
int LEVEL_WIDTH=0;
//The language that in which the game should be translated.
std::string language;
//The DictionaryManager that is used to translate the game itself.
tinygettext::DictionaryManager* dictionaryManager=NULL;
//Set both the screen and the tempSurface to NULL.
SDL_Surface* screen=NULL;
SDL_Surface* tempSurface=NULL;
//Font that is used for titles.
//Knewave large.
TTF_Font* fontTitle=NULL;
//Font that is used for captions of buttons and other GUI elements.
//Knewave small.
TTF_Font* fontGUI=NULL;
//Font that is used for long captions of buttons and other GUI elements.
//Knewave small.
TTF_Font* fontGUISmall=NULL;
//Font that is used for (long) text.
//Blokletter-Viltstift small.
TTF_Font* fontText=NULL;
+//Font used for scripting editor.
+//Monospace.
+TTF_Font* fontMono;
//Small arrows used for GUI widgets.
//2 directions and 2 different/same colors depending on theme.
SDL_Surface* arrowLeft1=NULL;
SDL_Surface* arrowRight1=NULL;
SDL_Surface* arrowLeft2=NULL;
SDL_Surface* arrowRight2=NULL;
//Sound played when the player jumps.
Mix_Chunk* jumpSound=NULL;
//Sound played when the player dies.
Mix_Chunk* hitSound=NULL;
//Sound played when the saves a state.
Mix_Chunk* saveSound=NULL;
//Sound played when the player swaps.
Mix_Chunk* swapSound=NULL;
//Sound played when the player toggles a switch.
Mix_Chunk* toggleSound=NULL;
//The sound played when the player tries something that doesn't work.
//For example a broken portal or swapping the shadow into a shadow block.
Mix_Chunk* errorSound=NULL;
//Sound played when the player picks up a collectable.
Mix_Chunk* collectSound=NULL;
//Sound played when an achievement is achieved..
Mix_Chunk* achievementSound=NULL;
//Set the current stateID and the nextState.
int stateID=STATE_NULL;
int nextState=STATE_NULL;
//The name of the current level.
std::string levelName;
//Initialise the camera.
//Start location is 0, size is the same as the screen size.
SDL_Rect camera={0,0,SCREEN_WIDTH,SCREEN_HEIGHT};
//The SDL_Event object.
SDL_Event event;
//Themable colors
SDL_Color themeTextColor={0,0,0};
SDL_Color themeTextColorDialog={0,0,0};
//The current cursor type.
CursorType currentCursor=CURSOR_POINTER;
//Array containing the SDL_Cursors.
SDL_Cursor* cursors[CURSOR_MAX];
diff --git a/src/Globals.h b/src/Globals.h
index e8efba7..6acee17 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -1,262 +1,265 @@
/*
* Copyright (C) 2011-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/>.
*/
#ifndef GLOBALS_H
#define GLOBALS_H
#ifdef __APPLE__
#include <SDL_mixer/SDL_mixer.h>
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#endif
#include <string>
#include "libs/tinygettext/tinygettext.hpp"
#if defined (WIN32) || defined (__APPLE__)
//#define DATA_PATH
#else
#include "config.h"
#endif
#define TITLE_FONT_RAISE 19
#define GUI_FONT_RAISE 5
//Global constants
//The width of the screen.
extern int SCREEN_WIDTH;
//The height of the screen.
extern int SCREEN_HEIGHT;
//The depth of the screen.
const int SCREEN_BPP=32;
//SDL interprets each pixel as a 32-bit number,
// so our masks must depend on the endianness (byte order) of the machine.
//NOTE: We define them here so we only have to do it once.
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
const Uint32 RMASK=0xFF000000;
const Uint32 GMASK=0x00FF0000;
const Uint32 BMASK=0x0000FF00;
const Uint32 AMASK=0x000000FF;
#else
const Uint32 RMASK=0x000000FF;
const Uint32 GMASK=0x0000FF00;
const Uint32 BMASK=0x00FF0000;
const Uint32 AMASK=0xFF000000;
#endif
//String containing the version, used in the titelbar.
const std::string version="V0.5 Development version";
//The height of the current level.
extern int LEVEL_HEIGHT;
//The width of the current level.
extern int LEVEL_WIDTH;
//The target frames per seconds.
const int FPS=40;
//The language that in which the game should be translated.
extern std::string language;
//The DictionaryManager that is used to translate the game itself.
extern tinygettext::DictionaryManager* dictionaryManager;
//The screen surface, it's used to draw on before it's drawn to the real screen.
extern SDL_Surface* screen;
//SDL_Surface with the same dimensions as screen which can be used for all kinds of (temp) drawing.
extern SDL_Surface* tempSurface;
//Font that is used for titles.
//Knewave large.
extern TTF_Font* fontTitle;
//Font that is used for captions of buttons and other GUI elements.
//Knewave small.
extern TTF_Font* fontGUI;
//Font that is used for long captions of buttons and other GUI elements.
//Knewave smaller.
extern TTF_Font* fontGUISmall;
//Font that is used for (long) text.
//Blokletter-Viltstift small.
extern TTF_Font* fontText;
+//Font used for scripting editor.
+//Monospace.
+extern TTF_Font* fontMono;
//Small arrows used for GUI widgets.
//2 directions and 2 different/same colors depending on theme.
extern SDL_Surface* arrowLeft1;
extern SDL_Surface* arrowRight1;
extern SDL_Surface* arrowLeft2;
extern SDL_Surface* arrowRight2;
//Sound played when the player jumps.
extern Mix_Chunk* jumpSound;
//Sound played when the player dies.
extern Mix_Chunk* hitSound;
//Sound played when the saves a state.
extern Mix_Chunk* saveSound;
//Sound played when the player swaps.
extern Mix_Chunk* swapSound;
//Sound played when the player toggles a switch.
extern Mix_Chunk* toggleSound;
//The sound played when the player tries something that doesn't work.
//For example a broken portal or swapping the shadow into a shadow block.
extern Mix_Chunk* errorSound;
//Sound played when the player picks up a collectable.
extern Mix_Chunk* collectSound;
//Sound played when an achievement is achieved..
extern Mix_Chunk* achievementSound;
//Event, used for event handling.
extern SDL_Event event;
//GUI
class GUIObject;
extern GUIObject *GUIObjectRoot;
//The state id of the current state.
extern int stateID;
//Integer containing what the next state will be.
extern int nextState;
//String containing the name of the current level.
extern std::string levelName;
//SDL rectangle used to store the camera.
//x is the x location of the camera.
//y is the y location of the camera.
//w is the width of the camera. (equal to SCREEN_WIDTH)
//h is the height of the camera. (equal to SCREEN_HEIGHT)
extern SDL_Rect camera;
//Themable colors
extern SDL_Color themeTextColor;
extern SDL_Color themeTextColorDialog;
//Enumeration containing the different cursor types there are.
enum CursorType{
//The default pointer.
CURSOR_POINTER,
//The vertical ibeam, used to indicate text input.
CURSOR_CARROT,
//A closed hand, used for indicating a drag action.
CURSOR_DRAG,
//The different (window) size cursor icons.
CURSOR_SIZE_HOR,
CURSOR_SIZE_VER,
CURSOR_SIZE_FDIAG,
CURSOR_SIZE_BDIAG,
//Remove cursor used in level editor
CURSOR_REMOVE,
//The number of cursor types there are.
CURSOR_MAX
};
//Currently used cursor type.
extern CursorType currentCursor;
//Array containing the SDL_Cursors.
extern SDL_Cursor* cursors[CURSOR_MAX];
//Enumeration containing the ids of the game states.
enum GameStates{
//State null is a special state used to indicate no state.
//This is used when no next state is defined.
STATE_NULL,
//This state is before the actual leveleditor used to make levelpacks.
STATE_LEVEL_EDIT_SELECT,
//This state is for the level editor.
STATE_LEVEL_EDITOR,
//This state is for the main menu.
STATE_MENU,
//This state is for the actual game.
STATE_GAME,
//Special state used when exiting meandmyshadow.
STATE_EXIT,
//This state is for the help screen.
STATE_LEVEL_SELECT,
//This state is for the options screen.
STATE_OPTIONS,
//This state is for the addon screen.
STATE_ADDONS,
//This state is for credits screen
STATE_CREDITS,
//This state is for statistics screen
STATE_STATISTICS,
};
//Enumeration containing the ids of the different block types.
enum GameTileType{
//The normal solid block.
TYPE_BLOCK=0,
//Block representing the start location of the player.
TYPE_START_PLAYER,
//Block representing the start location of the shadow.
TYPE_START_SHADOW,
//The exit of the level.
TYPE_EXIT,
//The shadow block which is only solid for the shadow.
TYPE_SHADOW_BLOCK,
//Block that can kill both the player and the shadow.
TYPE_SPIKES,
//Special point where the player can save.
TYPE_CHECKPOINT,
//Block that will switch the location of the player and the shadow when invoked.
TYPE_SWAP,
//Block that will crumble to dust when stepped on it for the third time.
TYPE_FRAGILE,
//Normal block that moves along a path.
TYPE_MOVING_BLOCK,
//Shadow block that moves along a path.
TYPE_MOVING_SHADOW_BLOCK,
//A spike block that moves along a path.
TYPE_MOVING_SPIKES,
//Special block which, once entered, moves the player/shadow to a different portal.
TYPE_PORTAL,
//A block with a button which can activate or stop moving blocks, converyor belts
TYPE_BUTTON,
//A switch which can activate or stop moving blocks, converyor belts
TYPE_SWITCH,
//Solid block which works like
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
//Block that contains a message that can be read.
TYPE_NOTIFICATION_BLOCK,
//A collectable that is able to open locked doors
TYPE_COLLECTABLE,
//Block that can be pushed by the player and the shadow.
//Pushable blocks can push other pushable blocks.
TYPE_PUSHABLE,
//The (max) number of tiles.
TYPE_MAX
};
#endif
diff --git a/src/LevelEditor.cpp b/src/LevelEditor.cpp
index 88c5271..b6356f6 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,3046 +1,3047 @@
/*
* Copyright (C) 2011-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 "GameState.h"
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Objects.h"
#include "LevelPack.h"
#include "LevelEditor.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "GUIWindow.h"
#include "GUISpinBox.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sstream>
#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
#include "libs/tinyformat/tinyformat.h"
using namespace std;
static int levelTime,levelRecordings;
//Array containing translateble block names
static const char* blockNames[TYPE_MAX]={
__("Block"),__("Player Start"),__("Shadow Start"),
__("Exit"),__("Shadow Block"),__("Spikes"),
__("Checkpoint"),__("Swap"),__("Fragile"),
__("Moving Block"),__("Moving Shadow Block"),__("Moving Spikes"),
__("Teleporter"),__("Button"),__("Switch"),
__("Conveyor Belt"),__("Shadow Conveyor Belt"),__("Notification Block"),__("Collectable"),__("Puhsable")
};
//Array indicates if block is linkable
static const bool isLinkable[TYPE_MAX]={
false,false,false,
false,false,false,
false,false,false,
true,true,true,
true,true,true,
false,false,false,false
};
/////////////////LevelEditorActionsPopup/////////////////
class LevelEditorActionsPopup:private GUIEventCallback{
private:
//The parent object.
LevelEditor* parent;
//The position and size of window.
SDL_Rect rect;
//Array containing the actions in this popup.
GUIListBox* actions;
//GUI image.
SDL_Surface* bmGUI;
//Pointer to the object the actions apply to.
GameObject* target;
//The behaviour names.
vector<string> behaviour;
//The fragile block states.
vector<string> states;
public:
SDL_Rect getRect(){
return rect;
}
void dismiss(){
//Remove the actionsPopup from the parent.
if(parent!=NULL && parent->actionsPopup==this){
parent->actionsPopup=NULL;
}
//And delete ourself.
delete this;
}
SDL_Surface* createItem(const char* caption,int icon){
//FIXME: Add some sort of caching?
SDL_Color fg={0,0,0};
SDL_Surface* tip=TTF_RenderUTF8_Blended(fontText,caption,fg);
SDL_SetAlpha(tip,0,0xFF);
//Create the surface, we add 16px to the width for an icon.
SDL_Surface* item=SDL_CreateRGBSurface(SDL_SWSURFACE,tip->w+16,24,32,RMASK,GMASK,BMASK,AMASK);
SDL_Rect itemRect={0,0,item->w,item->h};
SDL_FillRect(item,&itemRect,0x00FFFFFF);
itemRect.y=3;
itemRect.h=16;
SDL_FillRect(item,&itemRect,0xFFFFFFFF);
//Draw the text on the item surface.
applySurface(16,0,tip,item,NULL);
//Check if we should draw an icon.
if(icon==1 || icon==2){
//Draw the check (or not).
SDL_Rect r={0,0,16,16};
r.x=(icon-1)*16;
applySurface(0,3,bmGUI,item,&r);
}
//Free the tip surface.
SDL_FreeSurface(tip);
//Update the height.
rect.h+=24;
//Check if we should update the width., 8px extra on the width is for four pixels spacing on either side.
if(item->w+8>rect.w)
rect.w=item->w+8;
return item;
}
void updateItem(int index,const char* action,const char* caption,int icon=0){
SDL_Surface* item=createItem(caption,icon);
actions->updateItem(index,action,item);
}
void addItem(const char* action,const char* caption,int icon=0){
SDL_Surface* item=createItem(caption,icon);
actions->addItem(action,item);
}
LevelEditorActionsPopup(LevelEditor* parent, GameObject* target, int x=0, int y=0){
this->parent=parent;
this->target=target;
//NOTE: The size gets set in the addItem method, height is already four to prevent a scrollbar.
rect.w=0;
rect.h=4;
//Load the gui images.
bmGUI=loadImage(getDataPath()+"gfx/gui.png");
//Create the behaviour vector.
behaviour.push_back(_("On"));
behaviour.push_back(_("Off"));
behaviour.push_back(_("Toggle"));
//Create the states list.
states.push_back(_("Complete"));
states.push_back(_("One step"));
states.push_back(_("Two steps"));
states.push_back(_("Gone"));
//TODO: The width should be based on the longest option.
//Get the type of the target.
int type=target->type;
//Create default actions.
//NOTE: Width and height are determined later on when the options are rendered.
actions=new GUIListBox(0,0,0,0);
actions->eventCallback=this;
addItem("Move",_("Move"));
addItem("Delete",_("Delete"));
//Determine what to do depending on the type.
if(isLinkable[type]){
//Check if it's a moving block type or trigger.
if(type==TYPE_BUTTON || type==TYPE_SWITCH || type==TYPE_PORTAL){
addItem("Link",_("Link"));
addItem("Remove Links",_("Remove Links"));
//Check if it's a portal, which contains a automatic option, and triggers a behaviour one.
if(type==TYPE_PORTAL){
addItem("Automatic",_("Automatic"),(target->getEditorProperty("automatic")=="1")?2:1);
}else{
//Get the current behaviour.
int currentBehaviour=2;
if(target->getEditorProperty("behaviour")=="on"){
currentBehaviour=0;
}else if(target->getEditorProperty("behaviour")=="off"){
currentBehaviour=1;
}
addItem("Behaviour",behaviour[currentBehaviour].c_str());
}
}else{
addItem("Path",_("Path"));
addItem("Remove Path",_("Remove Path"));
//FIXME: We use hardcoded indeces, if the order changes we have a problem.
addItem("Enabled",_("Enabled"),(target->getEditorProperty("disabled")=="0")?2:1);
addItem("Looping",_("Looping"),(target->getEditorProperty("loop")=="1")?2:1);
}
}
//Check for a conveyor belt.
if(type==TYPE_CONVEYOR_BELT || type==TYPE_SHADOW_CONVEYOR_BELT){
addItem("Enabled",_("Enabled"),(target->getEditorProperty("disabled")=="0")?2:1);
addItem("Speed",_("Speed"));
}
//Check if it's a fragile block.
if(type==TYPE_FRAGILE){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
addItem("State",states[currentState].c_str());
}
//Check if it's a notification block.
if(type==TYPE_NOTIFICATION_BLOCK)
addItem("Message",_("Message"));
//Finally add scripting to the bottom.
addItem("Scripting",_("Scripting"));
//Now set the size of the GUIListBox.
actions->width=rect.w;
actions->height=rect.h;
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
}
~LevelEditorActionsPopup(){
if(actions)
delete actions;
}
void render(){
//Draw the actions.
actions->render(rect.x,rect.y);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
}
void handleEvents(){
//Check if a mouse is pressed outside the popup.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
if(event.type==SDL_MOUSEBUTTONDOWN && !checkCollision(mouse,rect)){
dismiss();
return;
}
//Let the listbox handle its events.
actions->handleEvents(rect.x,rect.y);
}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//NOTE: There should only be one GUIObject, so we know what event is fired.
//Get the selected entry.
std::string action=actions->item[actions->value];
if(action=="Move"){
//TODO
dismiss();
return;
}else if(action=="Delete"){
parent->removeObject(target);
dismiss();
return;
}else if(action=="Link"){
parent->linking=true;
parent->linkingTrigger=target;
dismiss();
return;
}else if(action=="Remove Links"){
//Remove all the
std::map<GameObject*,vector<GameObject*> >::iterator it;
it=parent->triggers.find(target);
if(it!=parent->triggers.end()){
//Remove the targets.
(*it).second.clear();
}
//In case of a portal remove its destination field.
if(target->type==TYPE_PORTAL){
target->setEditorProperty("destination","");
}else{
//We give the trigger a new id to prevent activating unlinked targets.
char s[64];
- sprintf(s,"%d",parent->currentId);
+ sprintf(s,"%u",parent->currentId);
parent->currentId++;
target->setEditorProperty("id",s);
}
dismiss();
return;
}else if(action=="Path"){
parent->moving=true;
parent->movingBlock=target;
dismiss();
return;
}else if(action=="Remove Path"){
//Set the number of moving positions to zero.
target->setEditorProperty("MovingPosCount","0");
std::map<GameObject*,vector<MovingPosition> >::iterator it;
it=parent->movingBlocks.find(target);
if(it!=parent->movingBlocks.end()){
(*it).second.clear();
}
dismiss();
return;
}else if(action=="Message"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Notification block"));
root->name="notificationBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Enter message here:"));
root->addChild(obj);
- obj=new GUITextArea(50,90,500,100);
+ GUITextArea* textarea=new GUITextArea(50,90,500,100);
//Set the name of the text area, which is used to identify the object later on.
- obj->name="message";
- string tmp=target->getEditorProperty("message").c_str();
+ textarea->name="message";
+ string tmp=target->getEditorProperty("message");
//Change \n with the characters '\n'.
while(tmp.find("\\n")!=string::npos){
tmp=tmp.replace(tmp.find("\\n"),2,"\n");
}
- obj->caption=tmp.c_str();
- root->addChild(obj);
+ textarea->setString(tmp);
+ root->addChild(textarea);
obj=new GUIObject(root->width*0.3,250-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgNotificationBlockOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,250-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Enabled"){
//Get the previous state.
bool enabled=(target->getEditorProperty("disabled")=="0");
//Switch the state.
enabled=!enabled;
//NOTE: In case of enabled it is inverted since the property is actually disabled,
target->setEditorProperty("disabled",enabled?"0":"1");
updateItem(actions->value,"Enabled",_("Enabled"),enabled?2:1);
actions->value=-1;
return;
}else if(action=="Looping"){
//Get the previous state.
bool loop=(target->getEditorProperty("loop")=="1");
//Switch the state.
loop=!loop;
target->setEditorProperty("loop",loop?"1":"0");
updateItem(actions->value,"Looping",_("Looping"),loop?2:1);
actions->value=-1;
return;
}else if(action=="Automatic"){
//Get the previous state.
bool automatic=(target->getEditorProperty("automatic")=="1");
//Switch the state.
automatic=!automatic;
target->setEditorProperty("automatic",automatic?"1":"0");
updateItem(actions->value,"Automatic",_("Automatic"),automatic?2:1);
actions->value=-1;
return;
}else if(action=="Behaviour"){
//Get the current behaviour.
int currentBehaviour=2;
string behave=target->getEditorProperty("behaviour");
if(behave=="on"){
currentBehaviour=0;
}else if(behave=="off"){
currentBehaviour=1;
}
//Increase the behaviour.
currentBehaviour++;
if(currentBehaviour>2)
currentBehaviour=0;
//Update the data of the block.
target->setEditorProperty("behaviour",behaviour[currentBehaviour]);
//And update the item.
updateItem(actions->value,"Behaviour",behaviour[currentBehaviour].c_str());
actions->value=-1;
return;
}else if(action=="State"){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
//Increase the state.
currentState++;
if(currentState>3)
currentState=0;
//Update the data of the block.
char s[64];
sprintf(s,"%d",currentState);
target->setEditorProperty("state",s);
//And update the item.
updateItem(actions->value,"State",states[currentState].c_str());
actions->value=-1;
return;
}else if(action=="Speed"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Conveyor belt speed"));
root->name="conveyorBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUIObject(40,100,240,36,GUIObjectLabel,_("Enter speed here:"));
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(240,100,320,36);
//Set the name of the text area, which is used to identify the object later on.
obj2->name="speed";
obj2->number=atof(target->getEditorProperty("speed").c_str());
root->addChild(obj2);
obj=new GUIObject(root->width*0.3,250-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgConveyorBlockOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,250-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Scripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Scripting"));
root->name="scriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Script:"));
root->addChild(obj);
- obj=new GUITextArea(50,90,500,100);
- obj->name="script";
+ GUITextArea* text=new GUITextArea(50,90,500,100);
+ text->name="script";
+ text->setFont(fontMono);
//TODO: Support other events.
string tmp="";
if((dynamic_cast<Block*>(target))->scripts.find(GameObjectEvent_PlayerWalkOn)!=(dynamic_cast<Block*>(target))->scripts.end())
tmp=(dynamic_cast<Block*>(target))->scripts[GameObjectEvent_PlayerWalkOn];
- obj->caption=tmp;
- root->addChild(obj);
+ text->caption=tmp;
+ root->addChild(text);
obj=new GUIObject(root->width*0.3,250-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,250-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}
}
};
/////////////////LevelEditorSelectionPopup/////////////////
class LevelEditorSelectionPopup{
private:
//The parent object
LevelEditor* parent;
//The position of window
SDL_Rect rect;
//GUI image
SDL_Surface* bmGUI;
//The selection
std::vector<GameObject*> selection;
//The scrollbar
GUIScrollBar* scrollBar;
//Highlighted object
GameObject* highlightedObj;
//Highlighted button index. 0=none 1=select/deselect 2=delete 3=configure
int highlightedBtn;
public:
int startRow,showedRow;
//If selection is dirty
bool dirty;
public:
SDL_Rect getRect(){
return rect;
}
int width(){
return rect.w;
}
int height(){
return rect.h;
}
void updateScrollBar(){
int m=selection.size()-showedRow;
if(m>0){
if(startRow<0) startRow=0;
else if(startRow>m) startRow=m;
if(scrollBar==NULL){
scrollBar=new GUIScrollBar(0,0,16,rect.h-16,ScrollBarVertical,startRow,0,m,1,showedRow);
}
scrollBar->visible=true;
scrollBar->maxValue=m;
scrollBar->value=startRow;
}else{
startRow=0;
if(scrollBar){
scrollBar->visible=false;
scrollBar->value=0;
}
}
}
void updateSelection(){
if(parent!=NULL){
std::vector<GameObject*>& v=parent->levelObjects;
for(int i=selection.size()-1;i>=0;i--){
if(find(v.begin(),v.end(),selection[i])==v.end()){
selection.erase(selection.begin()+i);
}
}
updateScrollBar();
}
}
void dismiss(){
if(parent!=NULL && parent->selectionPopup==this){
parent->selectionPopup=NULL;
}
delete this;
}
LevelEditorSelectionPopup(LevelEditor* parent, std::vector<GameObject*>& selection, int x=0, int y=0){
this->parent=parent;
this->selection=selection;
dirty=false;
scrollBar=NULL;
highlightedObj=NULL;
highlightedBtn=0;
//calc window size
startRow=0;
showedRow=selection.size();
int m=SCREEN_HEIGHT/64-1;
if(showedRow>m) showedRow=m;
rect.w=320;
rect.h=showedRow*64+16;
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
updateScrollBar();
//Load the gui images.
bmGUI=loadImage(getDataPath()+"gfx/gui.png");
}
~LevelEditorSelectionPopup(){
if(scrollBar)
delete scrollBar;
}
void move(int x,int y){
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
}
void render(){
//Check dirty
if(dirty){
updateSelection();
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//background
drawGUIBox(rect.x,rect.y,rect.w,rect.h,screen,0xFFFFFFFFU);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//the tool tip of item
SDL_Rect tooltipRect;
string tooltip;
if(scrollBar && scrollBar->visible){
startRow=scrollBar->value;
}
highlightedObj=NULL;
highlightedBtn=0;
//draw avaliable item
for(int i=0;i<showedRow;i++){
int j=startRow+i;
if(j>=(int)selection.size()) break;
SDL_Rect r={rect.x+8,rect.y+i*64+8,rect.w-16,64};
if(scrollBar && scrollBar->visible) r.w-=24;
//check highlight
if(checkCollision(mouse,r)){
highlightedObj=selection[j];
SDL_FillRect(screen,&r,0xCCCCCC);
}
int type=selection[j]->type;
//draw tile picture
ThemeBlock* obj=objThemes.getBlock(type);
if(obj){
obj->editorPicture.draw(screen,r.x+7,r.y+7);
}
//draw name
SDL_Color fg={0,0,0};
SDL_Surface* txt=TTF_RenderUTF8_Blended(fontText,_(blockNames[type]),fg);
if(txt!=NULL){
SDL_Rect r2={r.x+64,r.y+(64-txt->h)/2,0,0};
SDL_BlitSurface(txt,NULL,screen,&r2);
SDL_FreeSurface(txt);
}
if(parent!=NULL){
//draw selected
{
std::vector<GameObject*> &v=parent->selection;
bool isSelected=find(v.begin(),v.end(),selection[j])!=v.end();
SDL_Rect r1={isSelected?16:0,0,16,16};
SDL_Rect r2={r.x+r.w-72,r.y+20,24,24};
if(checkCollision(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,screen,0x999999FFU);
tooltipRect=r2;
tooltip=_("Select");
highlightedBtn=1;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
//draw delete
{
SDL_Rect r1={112,0,16,16};
SDL_Rect r2={r.x+r.w-48,r.y+20,24,24};
if(checkCollision(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,screen,0x999999FFU);
tooltipRect=r2;
tooltip=_("Delete");
highlightedBtn=2;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
//draw configure
{
SDL_Rect r1={112,16,16,16};
SDL_Rect r2={r.x+r.w-24,r.y+20,24,24};
if(checkCollision(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,screen,0x999999FFU);
tooltipRect=r2;
tooltip=_("Configure");
highlightedBtn=3;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
}
}
//draw scrollbar
if(scrollBar && scrollBar->visible){
scrollBar->render(rect.x+rect.w-24,rect.y+8);
}
//draw tooltip
if(!tooltip.empty()){
//The back and foreground colors.
SDL_Color fg={0,0,0};
//Tool specific text.
SDL_Surface* tip=TTF_RenderUTF8_Blended(fontText,tooltip.c_str(),fg);
//Draw only if there's a tooltip available
if(tip!=NULL){
tooltipRect.y-=4;
tooltipRect.h+=8;
if(tooltipRect.y+tooltipRect.h+tip->h>SCREEN_HEIGHT-20)
tooltipRect.y-=tip->h;
else
tooltipRect.y+=tooltipRect.h;
if(tooltipRect.x+tip->w>SCREEN_WIDTH-20)
tooltipRect.x=SCREEN_WIDTH-20-tip->w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(tooltipRect.x-2,tooltipRect.y-2,tip->w+4,tip->h+4,screen,color);
//Draw tooltip's text
SDL_BlitSurface(tip,NULL,screen,&tooltipRect);
SDL_FreeSurface(tip);
}
}
}
void handleEvents(){
//Check dirty
if(dirty){
updateSelection();
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//Check scrollbar event
if(scrollBar && scrollBar->visible){
if(scrollBar->handleEvents(rect.x+rect.w-24,rect.y+8)) return;
}
if(event.type==SDL_MOUSEBUTTONDOWN){
//check mousewheel
if(event.button.button==SDL_BUTTON_WHEELUP){
startRow-=2;
updateScrollBar();
return;
}else if(event.button.button==SDL_BUTTON_WHEELDOWN){
startRow+=2;
updateScrollBar();
return;
}else if(event.button.button==SDL_BUTTON_LEFT){
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if close it
if(!checkCollision(mouse,rect)){
dismiss();
return;
}
//Check if item is clicked
if(highlightedObj!=NULL && highlightedBtn>0 && parent!=NULL){
std::vector<GameObject*>& v=parent->levelObjects;
if(find(v.begin(),v.end(),highlightedObj)!=v.end()){
switch(highlightedBtn){
case 1:
{
std::vector<GameObject*>& v2=parent->selection;
std::vector<GameObject*>::iterator it=find(v2.begin(),v2.end(),highlightedObj);
if(it==v2.end()){
v2.push_back(highlightedObj);
}else{
v2.erase(it);
}
}
break;
case 2:
parent->removeObject(highlightedObj);
break;
case 3:
if(parent->actionsPopup)
delete parent->actionsPopup;
parent->actionsPopup=new LevelEditorActionsPopup(parent,highlightedObj,mouse.x,mouse.y);
break;
}
}
}
}
}
}
};
/////////////////MovingPosition////////////////////////////
MovingPosition::MovingPosition(int x,int y,int time){
this->x=x;
this->y=y;
this->time=time;
}
MovingPosition::~MovingPosition(){}
void MovingPosition::updatePosition(int x,int y){
this->x=x;
this->y=y;
}
/////////////////LEVEL EDITOR//////////////////////////////
LevelEditor::LevelEditor():Game(true){
//Get the target time and recordings.
levelTime=levels->getLevel()->targetTime;
levelRecordings=levels->getLevel()->targetRecordings;
//This will set some default settings.
reset();
//The level is loaded by the game, so do postLoad.
postLoad();
//Create the GUI root.
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Load the toolbar.
toolbar=loadImage(getDataPath()+"gfx/menu/toolbar.png");
SDL_Rect tmp={(SCREEN_WIDTH-410)/2,SCREEN_HEIGHT-50,410,50};
toolbarRect=tmp;
selectionPopup=NULL;
actionsPopup=NULL;
movingSpeedWidth=-1;
//Load the selectionMark.
selectionMark=loadImage(getDataPath()+"gfx/menu/selection.png");
//Load the movingMark.
movingMark=loadImage(getDataPath()+"gfx/menu/moving.png");
//Create the semi transparent surface.
placement=SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,SCREEN_WIDTH,SCREEN_HEIGHT,32,RMASK,GMASK,BMASK,0);
SDL_SetColorKey(placement,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(placement->format,255,0,255));
SDL_SetAlpha(placement,SDL_SRCALPHA,125);
//Count the level editing time.
statsMgr.startLevelEdit();
}
LevelEditor::~LevelEditor(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
levelObjects.clear();
selection.clear();
//Free the placement surface.
SDL_FreeSurface(placement);
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Delete the popup
if(selectionPopup){
delete selectionPopup;
selectionPopup=NULL;
}
//Delete the popup
if(actionsPopup){
delete actionsPopup;
actionsPopup=NULL;
}
//Reset the camera.
camera.x=0;
camera.y=0;
//Count the level editing time.
statsMgr.endLevelEdit();
}
void LevelEditor::reset(){
//Set some default values.
playMode=false;
tool=ADD;
currentType=0;
pressedShift=false;
pressedLeftMouse=false;
dragging=false;
selectionDrag=false;
dragCenter=NULL;
if(LEVEL_WIDTH<SCREEN_WIDTH)
camera.x=-(SCREEN_WIDTH-LEVEL_WIDTH)/2;
else
camera.x=0;
if(LEVEL_HEIGHT<SCREEN_HEIGHT)
camera.y=-(SCREEN_HEIGHT-LEVEL_HEIGHT)/2;
else
camera.y=0;
cameraXvel=0;
cameraYvel=0;
linking=false;
linkingTrigger=NULL;
currentId=0;
movingBlock=NULL;
moving=false;
movingSpeed=10;
tooltip=-1;
//Set the player and shadow to their starting position.
player.setPosition(player.fx,player.fy);
shadow.setPosition(shadow.fx,shadow.fy);
selection.clear();
clipboard.clear();
triggers.clear();
movingBlocks.clear();
//Delete any gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Clear the GUIWindow object map.
objectWindows.clear();
}
void LevelEditor::loadLevelFromNode(TreeStorageNode* obj, const std::string& fileName){
//call the method of base class.
Game::loadLevelFromNode(obj,fileName);
//now do our own stuff.
string s=editorData["time"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=atoi(s.c_str());
}
s=editorData["recordings"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
}
void LevelEditor::saveLevel(string fileName){
//Create the output stream and check if it starts.
std::ofstream save(fileName.c_str());
if(!save) return;
//The dimensions of the level.
int maxX=0;
int maxY=0;
//The storageNode to put the level data in before writing it away.
TreeStorageNode node;
char s[64];
//The name of the level.
if(!levelName.empty()){
node.attributes["name"].push_back(levelName);
//Update the level name in the levelpack.
levels->getLevel()->name=levelName;
}
//The leveltheme.
if(!levelTheme.empty())
node.attributes["theme"].push_back(levelTheme);
//target time and recordings.
{
char c[32];
if(levelTime>=0){
sprintf(c,"%d",levelTime);
node.attributes["time"].push_back(c);
}
if(levelRecordings>=0){
sprintf(c,"%d",levelRecordings);
node.attributes["recordings"].push_back(c);
}
}
//The width of the level.
maxX=LEVEL_WIDTH;
sprintf(s,"%d",maxX);
node.attributes["size"].push_back(s);
//The height of the level.
maxY=LEVEL_HEIGHT;
sprintf(s,"%d",maxY);
node.attributes["size"].push_back(s);
//Loop through the gameObjects and save them.
for(int o=0;o<(signed)levelObjects.size();o++){
int objectType=levelObjects[o]->type;
//Check if it's a legal gameObject type.
if(objectType>=0 && objectType<TYPE_MAX){
TreeStorageNode* obj1=new TreeStorageNode;
node.subNodes.push_back(obj1);
//It's a tile so name the node tile.
obj1->name="tile";
//Write away the type of the gameObject.
obj1->value.push_back(blockName[objectType]);
//Get the box for the location of the gameObject.
SDL_Rect box=levelObjects[o]->getBox(BoxType_Base);
//Put the location in the storageNode.
sprintf(s,"%d",box.x);
obj1->value.push_back(s);
sprintf(s,"%d",box.y);
obj1->value.push_back(s);
//Loop through the editor data and save it also.
vector<pair<string,string> > obj;
levelObjects[o]->getEditorData(obj);
for(unsigned int i=0;i<obj.size();i++){
if((!obj[i].first.empty()) && (!obj[i].second.empty())){
obj1->attributes[obj[i].first].push_back(obj[i].second);
}
}
//Loop through the scripts and add them to the storage node of the game object.
map<int,string>::iterator it;
Block* object=(dynamic_cast<Block*>(levelObjects[o]));
for(it=object->scripts.begin();it!=object->scripts.end();++it){
TreeStorageNode* script=new TreeStorageNode;
obj1->subNodes.push_back(script);
script->name="script";
script->value.push_back(gameObjectEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
}
}
//Create a POASerializer and write away the level node.
POASerializer objSerializer;
objSerializer.writeNode(&node,save,true,true);
}
///////////////EVENT///////////////////
void LevelEditor::handleEvents(){
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//If playing/testing we should the game handle the events.
if(playMode){
Game::handleEvents();
//Also check if we should exit the playMode.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Reset the game and disable playMode.
Game::reset(true);
playMode=false;
GUIObjectRoot->visible=true;
camera.x=cameraSave.x;
camera.y=cameraSave.y;
//NOTE: To prevent the mouse to still "be pressed" we set it to false.
pressedLeftMouse=false;
}
}else{
//Also check if we should exit the editor.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Before we quit ask a make sure question.
if(msgBox(_("Are you sure you want to quit?"),MsgBoxYesNo,_("Quit prompt"))==MsgBoxYes){
//We exit the level editor.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
setNextState(STATE_LEVEL_EDIT_SELECT);
//Play the menu music again.
getMusicManager()->playMusic("menu");
//No need for handling other events, so return.
return;
}
}
//Check if we should redirect the event to the actions popup
if(actionsPopup!=NULL){
actionsPopup->handleEvents();
return;
}
//Check if we should redirect the event to selection popup
if(selectionPopup!=NULL){
if(event.type==SDL_MOUSEBUTTONDOWN
|| event.type==SDL_MOUSEBUTTONUP
|| event.type==SDL_MOUSEMOTION)
{
selectionPopup->handleEvents();
return;
}
}
//TODO: Don't handle any Events when GUIWindows process them.
{
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
for(int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(checkCollision(mouse,box))
return;
}
}
//Check if toolbar is clicked.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && tooltip>=0){
int t=tooltip;
if(t<NUMBER_TOOLS){
tool=(Tools)t;
}else{
//The selected button isn't a tool.
//Now check which button it is.
if(t==NUMBER_TOOLS){
playMode=true;
GUIObjectRoot->visible=false;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
//Also stop linking or moving.
if(linking){
linking=false;
linkingTrigger=NULL;
}
if(moving){
//Write the path to the moving block.
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(movingBlocks[movingBlock].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0;o<movingBlocks[movingBlock].size();o++){
- sprintf(s0+1,"%d",o);
+ sprintf(s0+1,"%u",o);
sprintf(s,"%d",movingBlocks[movingBlock][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].time);
s0[0]='t';
editorData[s0]=s;
}
movingBlock->setEditorData(editorData);
moving=false;
movingBlock=NULL;
}
}
if(t==NUMBER_TOOLS+2){
//Open up level settings dialog
levelSettings();
}
if(t==NUMBER_TOOLS+4){
//Go back to the level selection screen of Level Editor
setNextState(STATE_LEVEL_EDIT_SELECT);
//Change the music back to menu music.
getMusicManager()->playMusic("menu");
}
if(t==NUMBER_TOOLS+3){
//Save current level
saveLevel(levelFile);
//And give feedback to the user.
if(levelName.empty())
msgBox(tfm::format(_("Level \"%s\" saved"),fileNameFromPath(levelFile)),MsgBoxOKOnly,_("Saved"));
else
msgBox(tfm::format(_("Level \"%s\" saved"),levelName),MsgBoxOKOnly,_("Saved"));
}
}
return;
}
//check if shift is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_SHIFT)){
pressedShift=true;
}
if(inputMgr.isKeyUpEvent(INPUTMGR_SHIFT)){
pressedShift=false;
}
//Check if delete is pressed.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_DELETE){
if(!selection.empty()){
//Loop through the selected game objects.
while(!selection.empty()){
//Remove the objects in the selection.
removeObject(selection[0]);
}
//And clear the selection vector.
selection.clear();
dragCenter=NULL;
selectionDrag=false;
}
}
//Check for copy (Ctrl+c) or cut (Ctrl+x).
if(event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_c || event.key.keysym.sym==SDLK_x) && (event.key.keysym.mod & KMOD_CTRL)){
//Clear the current clipboard.
clipboard.clear();
//Check if the selection isn't empty.
if(!selection.empty()){
//Loop through the selection to find the left-top block.
int x=selection[0]->getBox().x;
int y=selection[0]->getBox().y;
for(unsigned int o=1; o<selection.size(); o++){
if(selection[o]->getBox().x<x || selection[o]->getBox().y<y){
x=selection[o]->getBox().x;
y=selection[o]->getBox().y;
}
}
//Loop through the selection for the actual copying.
for(unsigned int o=0; o<selection.size(); o++){
//Get the editor data of the object.
vector<pair<string,string> > obj;
selection[o]->getEditorData(obj);
//Loop through the editor data and convert it.
map<string,string> objMap;
for(unsigned int i=0;i<obj.size();i++){
objMap[obj[i].first]=obj[i].second;
}
//Add some entries to the map.
char s[64];
sprintf(s,"%d",selection[o]->getBox().x-x);
objMap["x"]=s;
sprintf(s,"%d",selection[o]->getBox().y-y);
objMap["y"]=s;
sprintf(s,"%d",selection[o]->type);
objMap["type"]=s;
//Overwrite the id to prevent triggers, portals, buttons, movingblocks, etc. from malfunctioning.
//We give an empty string as id, which is invalid and thus suitable.
objMap["id"]="";
//Do the same for destination if the type is portal.
if(selection[o]->type==TYPE_PORTAL){
objMap["destination"]="";
}
//And add the map to the clipboard vector.
clipboard.push_back(objMap);
if(event.key.keysym.sym==SDLK_x){
//Cutting means deleting the game object.
removeObject(selection[o]);
o--;
}
}
//Only clear the selection when Ctrl+x;
if(event.key.keysym.sym==SDLK_x){
selection.clear();
dragCenter=NULL;
selectionDrag=false;
}
}
}
//Check for paste (Ctrl+v).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_v && (event.key.keysym.mod & KMOD_CTRL)){
//First make sure that the clipboard isn't empty.
if(!clipboard.empty()){
//Clear the current selection.
selection.clear();
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Integers containing the diff of the x that occurs when placing a block outside the level size on the top or left.
//We use it to compensate the corrupted x and y locations of the other clipboard blocks.
int diffX=0;
int diffY=0;
//Loop through the clipboard.
for(unsigned int o=0;o<clipboard.size();o++){
Block* block=new Block(0,0,atoi(clipboard[o]["type"].c_str()),this);
block->setBaseLocation(atoi(clipboard[o]["x"].c_str())+x+diffX,atoi(clipboard[o]["y"].c_str())+y+diffY);
block->setEditorData(clipboard[o]);
if(block->getBox().x<0){
//A block on the left side of the level, meaning we need to shift everything.
//First calc the difference.
diffX+=(0-(block->getBox().x));
}
if(block->getBox().y<0){
//A block on the left side of the level, meaning we need to shift everything.
//First calc the difference.
diffY+=(0-(block->getBox().y));
}
//And add the object using the addObject method.
addObject(block);
//Also add the block to the selection.
selection.push_back(block);
}
}
}
//Check if the return button is pressed.
//If so run the configure tool.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_RETURN){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the selected game objects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check for collision.
if(checkCollision(mouse,levelObjects[o]->getBox())){
//Invoke the onEnterObject.
onEnterObject(levelObjects[o]);
//Break out of the for loop.
break;
}
}
}
//Check for the arrow keys, used for moving the camera when playMode=false.
cameraXvel=0;
cameraYvel=0;
if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
if(pressedShift){
cameraXvel+=10;
}else{
cameraXvel+=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
if(pressedShift){
cameraXvel-=10;
}else{
cameraXvel-=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_UP)){
if(pressedShift){
cameraYvel-=10;
}else{
cameraYvel-=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_DOWN)){
if(pressedShift){
cameraYvel+=10;
}else{
cameraYvel+=5;
}
}
//Check if the left mouse button is pressed/holded.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=true;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=false;
//We also need to check if dragging is true.
if(dragging){
//Set dragging false and call the onDrop event.
dragging=false;
int x,y;
SDL_GetMouseState(&x,&y);
//We call the drop event.
onDrop(x+camera.x,y+camera.y);
}
}
//Check if the mouse is dragging.
if(pressedLeftMouse && event.type==SDL_MOUSEMOTION){
if(abs(event.motion.xrel)+abs(event.motion.yrel)>=2){
//Check if this is the start of the dragging.
if(!dragging){
//The mouse is moved enough so let's set dragging true.
dragging=true;
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//We call the dragStart event.
onDragStart(x+camera.x,y+camera.y);
}else{
//Dragging was already true meaning we call onDrag() instead of onDragStart().
onDrag(event.motion.xrel,event.motion.yrel);
}
}
}
//Update cursor.
if(dragging){
if(tool==REMOVE)
currentCursor=CURSOR_REMOVE;
else
currentCursor=CURSOR_DRAG;
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
//Check if we scroll up, meaning the currentType++;
if((event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP) || inputMgr.isKeyDownEvent(INPUTMGR_NEXT)){
switch(tool){
case ADD:
//Only change the current type when using the add tool.
currentType++;
if(currentType>=EDITOR_ORDER_MAX){
currentType=0;
}
break;
case SELECT:
//When in configure mode.
movingSpeed++;
//The movingspeed is capped at 100.
if(movingSpeed>100){
movingSpeed=100;
}
break;
default:
//When in other mode, just scrolling the map
if(pressedShift)
camera.x-=200;
else camera.y-=200;
break;
}
}
//Check if we scroll down, meaning the currentType--;
if((event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN) || inputMgr.isKeyDownEvent(INPUTMGR_PREVIOUS)){
switch(tool){
case ADD:
//Only change the current type when using the add tool.
currentType--;
if(currentType<0){
currentType=EDITOR_ORDER_MAX-1;
}
break;
case SELECT:
//When in configure mode.
movingSpeed--;
if(movingSpeed<=0){
movingSpeed=1;
}
break;
default:
//When in other mode, just scrolling the map
if(pressedShift) camera.x+=200;
else camera.y+=200;
break;
}
}
//Check if we should enter playMode.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_p){
playMode=true;
GUIObjectRoot->visible=false;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
}
//Check for tool shortcuts.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_a){
tool=ADD;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s){
tool=SELECT;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_d){
//We clear the selection since that can't be used in the deletion tool.
selection.clear();
tool=REMOVE;
}
//Check for certain events.
//First make sure the mouse isn't above the toolbar.
if(checkCollision(mouse,toolbarRect)==false){
mouse.x+=camera.x;
mouse.y+=camera.y;
//Boolean if there's a click event fired.
bool clickEvent=false;
//Check if a mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN){
std::vector<GameObject*> clickObjects;
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
clickObjects.push_back(levelObjects[o]);
}
}
//Check if there are multiple objects above eachother or just one.
if(clickObjects.size()==1){
//We have collision meaning that the mouse is above an object.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickObject(clickObjects[0],isSelected);
}
}else if(clickObjects.size()>1){
//There are more than one object under the mouse
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
//Only show the selection popup when right clicking.
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
//Remove the selection popup if there's one.
if(selectionPopup!=NULL)
delete selectionPopup;
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
selectionPopup=new LevelEditorSelectionPopup(this,clickObjects,x,y);
}
}
}
//If event is false then we clicked on void.
if(!clickEvent){
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
//Left mouse button on void.
onClickVoid(mouse.x,mouse.y);
}else if(event.button.button==SDL_BUTTON_RIGHT && tool==SELECT){
//Stop linking.
linking=false;
linkingTrigger=NULL;
//Write the path to the moving block.
if(moving){
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(movingBlocks[movingBlock].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0;o<movingBlocks[movingBlock].size();o++){
- sprintf(s0+1,"%d",o);
+ sprintf(s0+1,"%u",o);
sprintf(s,"%d",movingBlocks[movingBlock][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].time);
s0[0]='t';
editorData[s0]=s;
}
movingBlock->setEditorData(editorData);
//Stop moving.
moving=false;
movingBlock=NULL;
}
}
}
}
}
//Check for backspace when moving to remove a movingposition.
if(moving && event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_BACKSPACE){
if(movingBlocks[movingBlock].size()>0){
movingBlocks[movingBlock].pop_back();
}
}
//Check for the tab key, level settings.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Show the levelSettings.
levelSettings();
}
//NOTE: Do we even need Ctrl+n?
//Check if we should a new level. (Ctrl+n)
//if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_n && (event.key.keysym.mod & KMOD_CTRL)){
// reset();
// //NOTE: We don't have anything to load from so we create an empty TreeStorageNode.
// Game::loadLevelFromNode(new TreeStorageNode,"");
// //Hide selection popup (if any)
// if(selectionPopup!=NULL){
// delete selectionPopup;
// selectionPopup=NULL;
// }
//}
//Check if we should save the level (Ctrl+s).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s && (event.key.keysym.mod & KMOD_CTRL)){
saveLevel(levelFile);
//And give feedback to the user.
if(levelName.empty())
msgBox(tfm::format(_("Level \"%s\" saved"),fileNameFromPath(levelFile)),MsgBoxOKOnly,_("Saved"));
else
msgBox(tfm::format(_("Level \"%s\" saved"),levelName),MsgBoxOKOnly,_("Saved"));
}
}
}
void LevelEditor::levelSettings(){
//It isn't so open a popup asking for a name.
GUIWindow* root=new GUIWindow((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-300)/2,600,300,true,true,_("Level settings"));
root->name="lvlSettingsWindow";
root->eventCallback=this;
GUIObject* obj;
//Create the two textboxes with a label.
obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Name:"));
root->addChild(obj);
obj=new GUIObject(140,50,410,36,GUIObjectTextBox,levelName.c_str());
obj->name="name";
root->addChild(obj);
obj=new GUIObject(40,100,240,36,GUIObjectLabel,_("Theme:"));
root->addChild(obj);
obj=new GUIObject(140,100,410,36,GUIObjectTextBox,levelTheme.c_str());
obj->name="theme";
root->addChild(obj);
//target time and recordings.
{
obj=new GUIObject(40,150,240,36,GUIObjectLabel,_("Target time (s):"));
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(290,150,260,36);
obj2->name="time";
obj2->number=levelTime/40.0f;
obj2->limitMin=0.0f;
obj2->change=0.1f;
root->addChild(obj2);
obj=new GUIObject(40,200,240,36,GUIObjectLabel,_("Target recordings:"));
root->addChild(obj);
obj2=new GUISpinBox(290,200,260,36);
obj2->number=levelRecordings;
obj2->limitMin=0.0f;
obj2->format="%1.0f";
obj2->name="recordings";
root->addChild(obj2);
}
//Ok and cancel buttons.
obj=new GUIObject(root->width*0.3,300-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="lvlSettingsOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,300-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="lvlSettingsCancel";
obj->eventCallback=root;
root->addChild(obj);
GUIObjectRoot->addChild(root);
}
void LevelEditor::postLoad(){
//We need to find the triggers.
for(unsigned int o=0;o<levelObjects.size();o++){
//Check for the highest id.
unsigned int id=atoi(levelObjects[o]->getEditorProperty("id").c_str());
if(id>=currentId)
currentId=id+1;
switch(levelObjects[o]->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//Now loop through the levelObjects in search for objects with the same id.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->id==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_PORTAL:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//If the destination is empty we return.
if((dynamic_cast<Block*>(levelObjects[o]))->destination.empty()){
break;
}
//Now loop through the levelObjects in search for objects with the same id as destination.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Get the editor data.
vector<pair<string,string> > objMap;
levelObjects[o]->getEditorData(objMap);
//Add the object to the movingBlocks vector.
vector<MovingPosition> positions;
movingBlocks[levelObjects[o]]=positions;
//Get the number of entries of the editor data.
int m=objMap.size();
//Check if the editor data isn't empty.
if(m>0){
//Integer containing the positions.
- int pos=0;
+ int pos;
int currentPos=0;
//Get the number of movingpositions.
pos=atoi(objMap[1].second.c_str());
while(currentPos<pos){
int x=atoi(objMap[currentPos*3+4].second.c_str());
int y=atoi(objMap[currentPos*3+5].second.c_str());
int t=atoi(objMap[currentPos*3+6].second.c_str());
//Create a new movingPosition.
MovingPosition position(x,y,t);
movingBlocks[levelObjects[o]].push_back(position);
//Increase currentPos by one.
currentPos++;
}
}
break;
}
default:
break;
}
}
}
void LevelEditor::snapToGrid(int* x,int* y){
//Check if the x location is negative.
if(*x<0){
*x=-((abs(*x-50)/50)*50);
}else{
*x=(*x/50)*50;
}
//Now the y location.
if(*y<0){
*y=-((abs(*y-50)/50)*50);
}else{
*y=(*y/50)*50;
}
}
void LevelEditor::onClickObject(GameObject* obj,bool selected){
switch(tool){
case SELECT:
{
//Check if we are linking.
if(linking){
//Check if the obj is valid to link to.
switch(obj->type){
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//It's only valid when not linking a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//You can't link a portal to moving blocks, etc.
//Stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
case TYPE_PORTAL:
{
//Make sure that the linkingTrigger is also a portal.
if(linkingTrigger->type!=TYPE_PORTAL){
//The linkingTrigger isn't a portal so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
default:
//It isn't valid so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
break;
}
//Check if the linkingTrigger can handle multiple or only one link.
switch(linkingTrigger->type){
case TYPE_PORTAL:
{
//Portals can only link to one so remove all existing links.
triggers[linkingTrigger].clear();
triggers[linkingTrigger].push_back(obj);
break;
}
default:
{
//The most can handle multiple links.
triggers[linkingTrigger].push_back(obj);
break;
}
}
//Check if it's a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//Portals need to get the id of the other instead of give it's own id.
char s[64];
sprintf(s,"%d",atoi(obj->getEditorProperty("id").c_str()));
linkingTrigger->setEditorProperty("destination",s);
}else{
//Give the object the same id as the trigger.
char s[64];
sprintf(s,"%d",atoi(linkingTrigger->getEditorProperty("id").c_str()));
obj->setEditorProperty("id",s);
}
//We return to prevent configuring stuff like conveyor belts, etc...
linking=false;
linkingTrigger=NULL;
return;
}
//If we're moving add a movingposition.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
x-=movingBlock->getBox().x;
y-=movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx,dy;
if(movingBlocks[movingBlock].empty()){
dx=x;
dy=y;
}else{
dx=x-movingBlocks[movingBlock].back().x;
dy=y-movingBlocks[movingBlock].back().y;
}
double length=sqrt(double(dx*dx+dy*dy));
movingBlocks[movingBlock].push_back(MovingPosition(x,y,(int)(length*(10/(double)movingSpeed))));
return;
}
}
case ADD:
{
//Check if object is already selected.
if(!selected){
//First check if shift is pressed or not.
if(!pressedShift){
//Clear the selection.
selection.clear();
}
//Add the object to the selection.
selection.push_back(obj);
}
break;
}
case REMOVE:
{
//Remove the object.
removeObject(obj);
break;
}
default:
break;
}
}
void LevelEditor::onRightClickObject(GameObject* obj,bool selected){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(this,obj,x,y);
return;
}
}
void LevelEditor::onClickVoid(int x,int y){
switch(tool){
case ADD:
{
//We need to clear the selection.
selection.clear();
//Now place an object.
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
addObject(new Block(x,y,editorTileOrder[currentType],this));
break;
}
case SELECT:
{
//We need to clear the selection.
selection.clear();
//If we're linking we should stop, user abort.
if(linking){
linking=false;
linkingTrigger=NULL;
//And return.
return;
}
//If we're moving we should add a point.
if(moving){
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
x-=movingBlock->getBox().x;
y-=movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx,dy;
if(movingBlocks[movingBlock].empty()){
dx=x;
dy=y;
}else{
dx=x-movingBlocks[movingBlock].back().x;
dy=y-movingBlocks[movingBlock].back().y;
}
double length=sqrt(double(dx*dx+dy*dy));
movingBlocks[movingBlock].push_back(MovingPosition(x,y,(int)(length*(10/(double)movingSpeed))));
//And return.
return;
}
break;
}
default:
break;
}
}
void LevelEditor::onDragStart(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//We can drag the selection so check if the selection isn't empty.
if(!selection.empty()){
//The selection isn't empty so search the dragCenter.
//Create a mouse rectangle.
SDL_Rect mouse={x,y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<selection.size(); o++){
if(checkCollision(selection[o]->getBox(),mouse)==true){
//We have collision so set the dragCenter.
dragCenter=selection[o];
selectionDrag=true;
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrag(int dx,int dy){
switch(tool){
case REMOVE:
{
//No matter what we delete the item the mouse is above.
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
currentCursor=CURSOR_REMOVE;
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//Remove the object.
removeObject(levelObjects[o]);
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrop(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//Check if the drag center isn't null.
if(dragCenter==NULL)
return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Loop through the selection.
for(unsigned int o=0; o<selection.size(); o++){
SDL_Rect r1=selection[o]->getBox();
//We need to place the object at his drop place.
moveObject(selection[o],(r1.x-r.x)+x,(r1.y-r.y)+y);
}
//Make sure the dragCenter is null and set selectionDrag false.
dragCenter=NULL;
selectionDrag=false;
break;
}
default:
break;
}
}
void LevelEditor::onCameraMove(int dx,int dy){
switch(tool){
case REMOVE:
{
//Only delete when the left mouse button is pressed.
if(pressedLeftMouse){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//Remove the object.
removeObject(levelObjects[o]);
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onEnterObject(GameObject* obj){
//NOTE: Function isn't used anymore.
}
void LevelEditor::addObject(GameObject* obj){
//Increase totalCollectables everytime we add a new collectable
if (obj->type==TYPE_COLLECTABLE) {
totalCollectables++;
}
//If it's a player or shadow start then we need to remove the previous one.
if(obj->type==TYPE_START_PLAYER || obj->type==TYPE_START_SHADOW){
//Loop through the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check if the type is the same.
if(levelObjects[o]->type==obj->type){
removeObject(levelObjects[o]);
}
}
}
//Add it to the levelObjects.
levelObjects.push_back(obj);
//Check if the object is inside the level dimensions, etc.
//Just call moveObject() to perform this.
moveObject(obj,obj->getBox().x,obj->getBox().y);
//GameObject type specific stuff.
switch(obj->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
case TYPE_PORTAL:
{
//Add the object to the triggers.
vector<GameObject*> linked;
triggers[obj]=linked;
//Give it it's own id.
char s[64];
- sprintf(s,"%d",currentId);
+ sprintf(s,"%u",currentId);
currentId++;
obj->setEditorProperty("id",s);
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Add the object to the moving blocks.
vector<MovingPosition> positions;
movingBlocks[obj]=positions;
//Get the editor data.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
//Get the number of entries of the editor data.
int m=objMap.size();
//Check if the editor data isn't empty.
if(m>0){
//Integer containing the positions.
int pos=0;
int currentPos=0;
//Get the number of movingpositions.
pos=atoi(objMap[1].second.c_str());
while(currentPos<pos){
int x=atoi(objMap[currentPos*3+4].second.c_str());
int y=atoi(objMap[currentPos*3+5].second.c_str());
int t=atoi(objMap[currentPos*3+6].second.c_str());
//Create a new movingPosition.
MovingPosition position(x,y,t);
movingBlocks[obj].push_back(position);
//Increase currentPos by one.
currentPos++;
}
}
//Give it it's own id.
std::map<std::string,std::string> editorData;
char s[64];
- sprintf(s,"%d",currentId);
+ sprintf(s,"%u",currentId);
currentId++;
editorData["id"]=s;
obj->setEditorData(editorData);
break;
}
default:
break;
}
}
void LevelEditor::moveObject(GameObject* obj,int x,int y){
//Set the obj at it's new position.
obj->setBaseLocation(x,y);
//Check if the object is inside the level dimensions.
//If not let the level grow.
if(obj->getBox().x+50>LEVEL_WIDTH){
LEVEL_WIDTH=obj->getBox().x+50;
}
if(obj->getBox().y+50>LEVEL_HEIGHT){
LEVEL_HEIGHT=obj->getBox().y+50;
}
if(obj->getBox().x<0 || obj->getBox().y<0){
//A block on the left (or top) side of the level, meaning we need to shift everything.
//First calc the difference.
int diffx=(0-(obj->getBox().x));
int diffy=(0-(obj->getBox().y));
if(diffx<0) diffx=0;
if(diffy<0) diffy=0;
//Change the level size first.
//The level grows with the difference, 0-(x+50).
LEVEL_WIDTH+=diffx;
LEVEL_HEIGHT+=diffy;
//cout<<"x:"<<diffx<<",y:"<<diffy<<endl; //debug
camera.x+=diffx;
camera.y+=diffy;
//Set the position of player and shadow
//(although it's unnecessary if there is player and shadow start)
player.setPosition(player.getBox().x+diffx,player.getBox().y+diffy);
shadow.setPosition(shadow.getBox().x+diffx,shadow.getBox().y+diffy);
for(unsigned int o=0; o<levelObjects.size(); o++){
//FIXME: shouldn't recuesive call me (to prevent stack overflow bugs)
moveObject(levelObjects[o],levelObjects[o]->getBox().x+diffx,levelObjects[o]->getBox().y+diffy);
}
}
//If the object is a player or shadow start then change the start position of the player or shadow.
if(obj->type==TYPE_START_PLAYER){
//Center the player horizontally.
player.fx=obj->getBox().x+(50-23)/2;
player.fy=obj->getBox().y;
//Now reset the player to get him to it's new start position.
player.reset(true);
}
if(obj->type==TYPE_START_SHADOW){
//Center the shadow horizontally.
shadow.fx=obj->getBox().x+(50-23)/2;
shadow.fy=obj->getBox().y;
//Now reset the shadow to get him to it's new start position.
shadow.reset(true);
}
}
void LevelEditor::removeObject(GameObject* obj){
std::vector<GameObject*>::iterator it;
std::map<GameObject*,vector<GameObject*> >::iterator mapIt;
//Increase totalCollectables everytime we add a new collectable
if(obj->type==TYPE_COLLECTABLE){
totalCollectables--;
}
//Check if the object is in the selection.
it=find(selection.begin(),selection.end(),obj);
if(it!=selection.end()){
//It is so we delete it.
selection.erase(it);
}
//Check if the object is in the triggers.
mapIt=triggers.find(obj);
if(mapIt!=triggers.end()){
//It is so we remove it.
triggers.erase(mapIt);
}
//Boolean if it could be a target.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES
|| obj->type==TYPE_CONVEYOR_BELT || obj->type==TYPE_SHADOW_CONVEYOR_BELT || obj->type==TYPE_PORTAL){
for(mapIt=triggers.begin();mapIt!=triggers.end();++mapIt){
//Now loop the target vector.
for(unsigned int o=0;o<(*mapIt).second.size();o++){
//Check if the obj is in the target vector.
if((*mapIt).second[o]==obj){
(*mapIt).second.erase(find((*mapIt).second.begin(),(*mapIt).second.end(),obj));
o--;
}
}
}
}
//Check if the object is in the movingObjects.
std::map<GameObject*,vector<MovingPosition> >::iterator movIt;
movIt=movingBlocks.find(obj);
if(movIt!=movingBlocks.end()){
//It is so we remove it.
movingBlocks.erase(movIt);
}
//Check if the block isn't being configured with a window one way or another.
std::map<GUIObject*,GameObject*>::iterator confIt;
for(confIt=objectWindows.begin();confIt!=objectWindows.end();++confIt){
if((*confIt).second==obj){
destroyWindow((*confIt).first);
}
}
//Now we remove the object from the levelObjects.
it=find(levelObjects.begin(),levelObjects.end(),obj);
if(it!=levelObjects.end()){
levelObjects.erase(it);
}
delete obj;
obj=NULL;
//Set dirty of selection popup
if(selectionPopup!=NULL) selectionPopup->dirty=true;
}
void LevelEditor::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//TODO: Add resize code for each GUIWindow.
if(name=="lvlSettingsWindow")
return;
if(name=="notificationBlockWindow")
return;
if(name=="conveyorBlockWindow")
return;
if(name=="scriptingWindow")
return;
//Check for GUI events.
//Notification block configure events.
if(name=="cfgNotificationBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the message textbox from the GUIWindow.
- GUIObject* message=obj->getChild("message");
+ GUITextArea* message=(GUITextArea*)obj->getChild("message");
if(message){
//Set the message of the notification block.
- configuredObject->setEditorProperty("message",message->caption);
+ configuredObject->setEditorProperty("message",message->getString());
}
}
}
//Conveyor belt block configure events.
if(name=="cfgConveyorBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the speed textbox from the GUIWindow.
GUISpinBox* speed=(GUISpinBox*)obj->getChild("speed");
if(speed){
//Set the speed of the conveyor belt.
ostringstream ss;
ss << speed->number;
configuredObject->setEditorProperty("speed",ss.str());
}
}
}
//LevelSetting events.
if(name=="lvlSettingsOK"){
GUIObject* object=obj->getChild("name");
if(object)
levelName=object->caption;
object=obj->getChild("theme");
if(object)
levelTheme=object->caption;
//target time and recordings.
GUISpinBox* object2=(GUISpinBox*)obj->getChild("time");
if(object2){
if(object2->number<=0){
levelTime=-1;
}else{
levelTime=int(object2->number*40.0+0.5);
}
}
object2=(GUISpinBox*)obj->getChild("recordings");
if(object){
if(object2->number<=0){
levelRecordings=-1;
}else{
levelRecordings=int(object2->number);
}
}
}
//Scripting window events.
if(name=="cfgScriptingOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the script textbox from the GUIWindow.
- GUIObject* script=obj->getChild("script");
+ GUITextArea* script=(GUITextArea*)obj->getChild("script");
if(script){
//Set the script for the target block.
//TODO: Support other event types.
- (dynamic_cast<Block*>(configuredObject))->scripts[GameObjectEvent_PlayerWalkOn]=script->caption.c_str();
+ (dynamic_cast<Block*>(configuredObject))->scripts[GameObjectEvent_PlayerWalkOn]=script->getString().c_str();
}
}
}
//NOTE: We assume every event came from a window so remove it.
destroyWindow(obj);
}
void LevelEditor::destroyWindow(GUIObject* window){
//Make sure the given pointer isn't null.
if(!window)
return;
//Remove the window from the GUIObject root.
if(GUIObjectRoot){
vector<GUIObject*>::iterator it;
it=find(GUIObjectRoot->childControls.begin(),GUIObjectRoot->childControls.end(),window);
if(it!=GUIObjectRoot->childControls.end()){
GUIObjectRoot->childControls.erase(it);
}
}
//Also remove the window from the objectWindows map.
map<GUIObject*,GameObject*>::iterator it;
it=objectWindows.find(window);
if(it!=objectWindows.end()){
objectWindows.erase(it);
}
//And delete the GUIWindow.
delete window;
}
////////////////LOGIC////////////////////
void LevelEditor::logic(){
if(playMode){
//PlayMode so let the game do it's logic.
Game::logic();
}else{
//In case of a selection or actions popup prevent the camera from moving.
if(selectionPopup || actionsPopup)
return;
//Move the camera.
if(cameraXvel!=0 || cameraYvel!=0){
camera.x+=cameraXvel;
camera.y+=cameraYvel;
//Call the onCameraMove event.
onCameraMove(cameraXvel,cameraYvel);
}
//Move the camera with the mouse.
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
{
//Check if the mouse isn't above a GUIObject (window).
bool inside=false;
for(int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(checkCollision(mouse,box))
inside=true;
}
if(!inside){
SDL_Rect r[3]={toolbarRect};
int m=1;
//TODO: Also call onCameraMove when moving using the mouse.
setCamera(r,m);
}
}
//It isn't playMode so the mouse should be checked.
tooltip=-1;
//We loop through the number of tools + the number of buttons.
for(int t=0; t<NUMBER_TOOLS+6; t++){
SDL_Rect toolRect={(SCREEN_WIDTH-410)/2+(t*40)+((t+1)*10),SCREEN_HEIGHT-45,40,40};
//Check for collision.
if(checkCollision(mouse,toolRect)==true){
//Set the tooltip tool.
tooltip=t;
}
}
}
}
/////////////////RENDER//////////////////////
void LevelEditor::render(){
//Always let the game render the game.
Game::render();
//Only render extra stuff like the toolbar, selection, etc.. when not in playMode.
if(!playMode){
//Render the selectionmarks.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
//Get the location to draw.
SDL_Rect r=selection[o]->getBox();
r.x-=camera.x;
r.y-=camera.y;
drawGUIBox(r.x,r.y,50,50,screen,0xFFFFFF33);
//Draw the selectionMarks.
applySurface(r.x,r.y,selectionMark,screen,NULL);
applySurface(r.x+r.w-5,r.y,selectionMark,screen,NULL);
applySurface(r.x,r.y+r.h-5,selectionMark,screen,NULL);
applySurface(r.x+r.w-5,r.y+r.h-5,selectionMark,screen,NULL);
}
//Clear the placement surface.
SDL_FillRect(placement,NULL,0x00FF00FF);
Uint32 color=SDL_MapRGB(placement->format,themeTextColor.r,themeTextColor.g,themeTextColor.b);
//Draw the dark areas marking the outside of the level.
SDL_Rect r;
if(camera.x<0){
//Draw left side.
r.x=0;
r.y=0;
r.w=0-camera.x;
r.h=SCREEN_HEIGHT;
SDL_FillRect(placement,&r,color);
}
if(camera.x>LEVEL_WIDTH-SCREEN_WIDTH){
//Draw right side.
r.x=LEVEL_WIDTH-camera.x;
r.y=0;
r.w=SCREEN_WIDTH-(LEVEL_WIDTH-camera.x);
r.h=SCREEN_HEIGHT;
SDL_FillRect(placement,&r,color);
}
if(camera.y<0){
//Draw the top.
r.x=0;
r.y=0;
r.w=SCREEN_WIDTH;
r.h=0-camera.y;
SDL_FillRect(placement,&r,color);
}
if(camera.y>LEVEL_HEIGHT-SCREEN_HEIGHT){
//Draw the bottom.
r.x=0;
r.y=LEVEL_HEIGHT-camera.y;
r.w=SCREEN_WIDTH;
r.h=SCREEN_HEIGHT-(LEVEL_HEIGHT-camera.y);
SDL_FillRect(placement,&r,color);
}
//Check if we should draw on the placement surface.
showConfigure();
if(selectionDrag){
showSelectionDrag();
}else{
if(tool==ADD){
showCurrentObject();
}
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Find a block where the mouse is hovering on.
for(unsigned int o=0; o<levelObjects.size(); o++){
SDL_Rect rect=levelObjects[o]->getBox();
if(checkCollision(rect,mouse)==true){
if(tool==REMOVE){
drawGUIBox(rect.x-camera.x,rect.y-camera.y,50,50,screen,0xFF000055);
currentCursor=CURSOR_REMOVE;
}else{
drawGUIBox(rect.x-camera.x,rect.y-camera.y,50,50,screen,0xFFFFFF33);
}
}
}
//Draw the level borders.
drawRect(-camera.x,-camera.y,LEVEL_WIDTH,LEVEL_HEIGHT,screen);
//Render the placement surface.
applySurface(0,0,placement,screen,NULL);
//Render the hud layer.
renderHUD();
//Render selection popup (if any).
if(selectionPopup!=NULL){
if(linking || moving){
//If we switch to linking mode then delete it
//FIXME: Logic in the render method.
delete selectionPopup;
selectionPopup=NULL;
}else{
selectionPopup->render();
}
}
//Render actions popup (if any).
if(actionsPopup!=NULL){
actionsPopup->render();
}
}
}
void LevelEditor::renderHUD(){
//If moving show the moving speed in the top right corner.
if(moving){
//Calculate width of text "Movespeed: 100" to keep the same position with every value
if (movingSpeedWidth==-1){
int w;
TTF_SizeUTF8(fontText,tfm::format(_("Movespeed: %s"),100).c_str(),&w,NULL);
movingSpeedWidth=w+4;
}
//Now render the text.
SDL_Color black={0,0,0,0};
SDL_Surface* bm=TTF_RenderUTF8_Blended(fontText,tfm::format(_("Movespeed: %s"),movingSpeed).c_str(),black);
//Draw the text in box and free the surface.
drawGUIBox(SCREEN_WIDTH-movingSpeedWidth-2,-2,movingSpeedWidth+8,bm->h+6,screen,0xDDDDDDDD);
applySurface(SCREEN_WIDTH-movingSpeedWidth,2,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//On top of all render the toolbar.
drawGUIBox(toolbarRect.x,toolbarRect.y,8*50+10,52,screen,0xEDEDEDFF);
//Draw the first four options.
SDL_Rect r={0,0,200,50};
applySurface(toolbarRect.x+5,toolbarRect.y,toolbar,screen,&r);
//And the last three.
r.x=200;
r.w=150;
applySurface(toolbarRect.x+255,toolbarRect.y,toolbar,screen,&r);
//Now render a tooltip.
if(tooltip>=0){
//The back and foreground colors.
SDL_Color fg={0,0,0};
//Tool specific text.
SDL_Surface* tip=NULL;
switch(tooltip){
case 0:
tip=TTF_RenderUTF8_Blended(fontText,_("Select"),fg);
break;
case 1:
tip=TTF_RenderUTF8_Blended(fontText,_("Add"),fg);
break;
case 2:
tip=TTF_RenderUTF8_Blended(fontText,_("Delete"),fg);
break;
case 3:
tip=TTF_RenderUTF8_Blended(fontText,_("Play"),fg);
break;
case 5:
tip=TTF_RenderUTF8_Blended(fontText,_("Level settings"),fg);
break;
case 6:
tip=TTF_RenderUTF8_Blended(fontText,_("Save level"),fg);
break;
case 7:
tip=TTF_RenderUTF8_Blended(fontText,_("Back to menu"),fg);
break;
default:
break;
}
//Draw only if there's a tooltip available
if(tip!=NULL){
SDL_Rect r={(SCREEN_WIDTH-390)/2+(tooltip*40)+(tooltip*10),SCREEN_HEIGHT-45,40,40};
r.y=SCREEN_HEIGHT-50-tip->h;
if(r.x+tip->w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-tip->w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(r.x-2,r.y-2,tip->w+4,tip->h+4,screen,color);
//Draw tooltip's text
SDL_BlitSurface(tip,NULL,screen,&r);
SDL_FreeSurface(tip);
}
}
//Draw a rectangle around the current tool.
Uint32 color=0xFFFFFF00;
drawGUIBox((SCREEN_WIDTH-390)/2+(tool*40)+(tool*10),SCREEN_HEIGHT-46,42,42,screen,color);
}
void LevelEditor::showCurrentObject(){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the currentType is a legal type.
if(currentType>=0 && currentType<EDITOR_ORDER_MAX){
ThemeBlock* obj=objThemes.getBlock(editorTileOrder[currentType]);
if(obj){
obj->editorPicture.draw(placement,x-camera.x,y-camera.y);
}
}
}
void LevelEditor::showSelectionDrag(){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the drag center isn't null.
if(dragCenter==NULL) return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
//Loop through the selection.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
ThemeBlock* obj=objThemes.getBlock(selection[o]->type);
if(obj){
SDL_Rect r1=selection[o]->getBox();
obj->editorPicture.draw(placement,(r1.x-r.x)+x-camera.x,(r1.y-r.y)+y-camera.y);
}
}
}
void LevelEditor::showConfigure(){
//arrow animation value. go through 0-65535 and loops.
static unsigned short arrowAnimation=0;
arrowAnimation++;
//By default use black color for arrows.
Uint32 color=0x00000000;
//Theme can change the color.
//TODO: use the actual color from the theme.
if(themeTextColor.r>128 && themeTextColor.g>128 && themeTextColor.b>128)
color=0xffffffff;
//Draw the trigger lines.
{
map<GameObject*,vector<GameObject*> >::iterator it;
for(it=triggers.begin();it!=triggers.end();++it){
//Check if the trigger has linked targets.
if(!(*it).second.empty()){
//The location of the trigger.
SDL_Rect r=(*it).first->getBox();
//Loop through the targets.
for(unsigned int o=0;o<(*it).second.size();o++){
//Get the location of the target.
SDL_Rect r1=(*it).second[o]->getBox();
//Draw the line from the center of the trigger to the center of the target.
drawLineWithArrow(r.x-camera.x+25,r.y-camera.y+25,r1.x-camera.x+25,r1.y-camera.y+25,placement,color,32,arrowAnimation%32);
//Also draw two selection marks.
applySurface(r.x-camera.x+25-2,r.y-camera.y+25-2,selectionMark,screen,NULL);
applySurface(r1.x-camera.x+25-2,r1.y-camera.y+25-2,selectionMark,screen,NULL);
}
}
}
//Draw a line to the mouse from the linkingTrigger when linking.
if(linking){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Draw the line from the center of the trigger to mouse.
drawLineWithArrow(linkingTrigger->getBox().x-camera.x+25,linkingTrigger->getBox().y-camera.y+25,x,y,placement,color,32,arrowAnimation%32);
}
}
//Draw the moving positions.
map<GameObject*,vector<MovingPosition> >::iterator it;
for(it=movingBlocks.begin();it!=movingBlocks.end();++it){
//Check if the block has positions.
if(!(*it).second.empty()){
//The location of the moving block.
SDL_Rect block=(*it).first->getBox();
block.x+=25-camera.x;
block.y+=25-camera.y;
//The location of the previous position.
//The first time it's the moving block's position self.
SDL_Rect r=block;
//Loop through the positions.
for(unsigned int o=0;o<(*it).second.size();o++){
//Draw the line from the center of the previous position to the center of the position.
//x and y are the coordinates for the current moving position.
int x=block.x+(*it).second[o].x;
int y=block.y+(*it).second[o].y;
//Check if we need to draw line
double dx=r.x-x;
double dy=r.y-y;
double d=sqrt(dx*dx+dy*dy);
if(d>0.001f){
if(it->second[o].time>0){
//Calculate offset to contain the moving speed.
int offset=int(d*arrowAnimation/it->second[o].time)%32;
drawLineWithArrow(r.x,r.y,x,y,placement,color,32,offset);
}else{
//time==0 ???? so don't draw arrow at all
drawLine(r.x,r.y,x,y,placement);
}
}
//And draw a marker at the end.
applySurface(x-13,y-13,movingMark,screen,NULL);
//Get the box of the previous position.
SDL_Rect tmp={x,y,0,0};
r=tmp;
}
}
}
//Draw a line to the mouse from the previous moving pos.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Check if we should snap the block to grid or not.
if(!pressedShift){
x+=camera.x;
y+=camera.y;
snapToGrid(&x,&y);
x-=camera.x;
y-=camera.y;
}else{
x-=25;
y-=25;
}
int posX,posY;
//Check if there are moving positions for the moving block.
if(!movingBlocks[movingBlock].empty()){
//Draw the line from the center of the previouse moving positions to mouse.
posX=movingBlocks[movingBlock].back().x;
posY=movingBlocks[movingBlock].back().y;
posX-=camera.x;
posY-=camera.y;
posX+=movingBlock->getBox().x;
posY+=movingBlock->getBox().y;
}else{
//Draw the line from the center of the movingblock to mouse.
posX=movingBlock->getBox().x-camera.x;
posY=movingBlock->getBox().y-camera.y;
}
//Calculate offset to contain the moving speed.
int offset=int(double(arrowAnimation)*movingSpeed/10.0)%32;
drawLineWithArrow(posX+25,posY+25,x+25,y+25,placement,color,32,offset);
applySurface(x+12,y+12,movingMark,screen,NULL);
}
}
void LevelEditor::resize(){
//Call the resize method of the Game.
Game::resize();
//Now update the placement surface.
if(placement)
SDL_FreeSurface(placement);
placement=SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,SCREEN_WIDTH,SCREEN_HEIGHT,32,RMASK,GMASK,BMASK,0);
SDL_SetColorKey(placement,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(placement->format,255,0,255));
SDL_SetAlpha(placement,SDL_SRCALPHA,125);
//Move the toolbar's position rect used for collision.
toolbarRect.x=(SCREEN_WIDTH-410)/2;
toolbarRect.y=SCREEN_HEIGHT-50;
}
//Filling the order array
const int LevelEditor::editorTileOrder[EDITOR_ORDER_MAX]={
TYPE_BLOCK,
TYPE_SHADOW_BLOCK,
TYPE_SPIKES,
TYPE_FRAGILE,
TYPE_MOVING_BLOCK,
TYPE_MOVING_SHADOW_BLOCK,
TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
TYPE_BUTTON,
TYPE_SWITCH,
TYPE_PORTAL,
TYPE_SWAP,
TYPE_CHECKPOINT,
TYPE_NOTIFICATION_BLOCK,
TYPE_START_PLAYER,
TYPE_START_SHADOW,
TYPE_EXIT,
TYPE_COLLECTABLE,
TYPE_PUSHABLE
};
diff --git a/src/Settings.cpp b/src/Settings.cpp
index 4bb2f2a..990524c 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -1,245 +1,248 @@
/*
* Copyright (C) 2011-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 "Settings.h"
#include <SDL/SDL.h>
#include <string>
#include <stdio.h>
using namespace std;
Settings::Settings(const string fileName): fileName(fileName){
char s[32];
settings["sound"]="128";
settings["music"]="128";
settings["musiclist"]="default";
settings["fullscreen"]="0";
settings["width"]="800";
settings["height"]="600";
settings["resizable"]="1";
#ifdef HARDWARE_ACCELERATION
settings["gl"]="1";
#else
settings["gl"]="0";
#endif
settings["theme"]="%DATA%/themes/Cloudscape";
settings["leveltheme"]="1";
settings["internet"]="1";
settings["lastlevelpack"]="tutorial";
settings["internet-proxy"]="";
settings["lang"]="";
//The record mode.
settings["quickrecord"]="0";
//Key settings.
sprintf(s,"%d",(int)SDLK_UP);
settings["key_up"]=settings["key_jump"]=s;
sprintf(s,"%d",(int)SDLK_DOWN);
settings["key_down"]=settings["key_action"]=s; //SDLK_DOWN
sprintf(s,"%d",(int)SDLK_LEFT);
settings["key_left"]=s; //SDLK_LEFT
sprintf(s,"%d",(int)SDLK_RIGHT);
settings["key_right"]=s; //SDLK_RIGHT
sprintf(s,"%d",(int)SDLK_SPACE);
settings["key_space"]=s; //SDLK_SPACE
settings["key_cancelRecording"]="0"; //not set by default
sprintf(s,"%d",(int)SDLK_ESCAPE);
settings["key_escape"]=s; //SDLK_ESCAPE
sprintf(s,"%d",(int)SDLK_r);
settings["key_restart"]=s; //SDLK_r
sprintf(s,"%d",(int)SDLK_TAB);
settings["key_tab"]=s;
sprintf(s,"%d",(int)SDLK_F2);
settings["key_save"]=s; //SDLK_F2
sprintf(s,"%d",(int)SDLK_F3);
settings["key_load"]=s; //SDLK_F3
sprintf(s,"%d",(int)SDLK_F4);
settings["key_swap"]=s; //SDLK_F4
sprintf(s,"%d",(int)SDLK_F5);
settings["key_teleport"]=s; //SDLK_F5
sprintf(s,"%d",(int)SDLK_F12);
settings["key_suicide"]=s;
sprintf(s,"%d",(int)SDLK_RSHIFT);
settings["key_shift"]=s; //SDLK_RSHIFT
sprintf(s,"%d",(int)SDLK_PAGEUP);
settings["key_next"]=s; //SDLK_PAGEUP
sprintf(s,"%d",(int)SDLK_PAGEDOWN);
settings["key_previous"]=s; //SDLK_PAGEDOWN
sprintf(s,"%d",(int)SDLK_RETURN);
settings["key_select"]=s; //SDLK_RETURN
settings["key_up2"]=settings["key_down2"]=settings["key_left2"]=settings["key_right2"]=
settings["key_jump2"]=settings["key_action2"]=
settings["key_space2"]=settings["key_cancelRecording2"]=
settings["key_escape2"]=settings["key_restart2"]=settings["key_tab2"]=
settings["key_save2"]=settings["key_load2"]=settings["key_swap2"]=settings["key_teleport2"]=
settings["key_suicide2"]=settings["key_shift2"]=settings["key_next2"]=settings["key_previous2"]=
settings["key_select2"]="0";
}
void Settings::parseFile(){
//We open the settings file.
ifstream file;
file.open(fileName.c_str());
if(!file){
cout<<"Can't find config file!"<<endl;
createFile();
}
//Now we're going to walk throught the file line by line.
string line;
while(getline(file,line)){
string temp = line;
unComment(temp);
if(temp.empty() || empty(temp))
continue;
//The line is good so we parse it.
parseLine(temp);
}
//And close the file.
file.close();
}
void Settings::parseLine(const string &line){
if((line.find('=') == line.npos) || !validLine(line))
cout<<"Warning illegal line in config file!"<<endl;
string temp = line;
temp.erase(0, temp.find_first_not_of("\t "));
int seperator = temp.find('=');
//Get the key and trim it.
string key, value;
key = line.substr(0, seperator);
if(key.find('\t')!=line.npos || key.find(' ')!=line.npos)
key.erase(key.find_first_of("\t "));
//Get the value and trim it.
value = line.substr(seperator + 1);
value.erase(0, value.find_first_not_of("\t "));
value.erase(value.find_last_not_of("\t ") + 1);
//Add the setting to the settings map.
setValue(key,value);
}
bool Settings::validLine(const string &line){
string temp = line;
temp.erase(0, temp.find_first_not_of("\t "));
if(temp[0] == '=')
return false;
for(size_t i = temp.find('=') + 1; i < temp.length(); i++)
return true;
return false;
}
void Settings::unComment(string &line){
if (line.find('#') != line.npos)
line.erase(line.find('#'));
}
bool Settings::empty(const string &line){
return (line.find_first_not_of(' ')==line.npos);
}
string Settings::getValue(const string &key){
if(settings.find(key) == settings.end()){
cout<<"Key "<<key<<" couldn't be found!"<<endl;
return "";
}
return settings[key];
}
bool Settings::getBoolValue(const string &key){
if(settings.find(key) == settings.end()){
cout<<"Key "<<key<<" couldn't be found!"<<endl;
return false;
}
return (settings[key] != "0");
}
void Settings::setValue(const string &key, const string &value){
if(settings.find(key) == settings.end()){
cout<<"Key "<<key<<" couldn't be found!"<<endl;
return;
}
settings[key]=value;
}
void Settings::createFile(){
ofstream file;
file.open(fileName.c_str());
//Default Config file.
file<<"#MeAndMyShadow config file. Created on "<<endl;
map<string, string>::iterator iter;
for(iter = settings.begin(); iter != settings.end(); ++iter){
file << iter->first << " = " << iter->second << endl;
}
//And close the file.
file.close();
}
void Settings::save(){
ofstream file;
file.open(fileName.c_str());
//First get the date and time.
time_t rawtime;
struct tm* timedate;
+ char str_time[80];
time(&rawtime);
timedate=localtime(&rawtime);
+ //Note: Function asctime() is marked obsolete in POSIX. So we're using strftime() instead.
+ strftime(str_time,80,"%a %b %d %H:%M:%S %Y",timedate);
+
//Now write it to the first line of the config file.
- //Note: There's no endl at the end since that's already in asctime(timeinfo).
- file<<"#MeAndMyShadow config file. Created on "<<asctime(timedate);
+ file<<"#MeAndMyShadow config file. Created on "<<str_time<<endl;
//Loop through the settings and save them.
map<string,string>::const_iterator iter;
for(iter=settings.begin(); iter!=settings.end(); ++iter){
file<<iter->first<<" = "<<iter->second<<endl;
}
file.close();
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, May 16, 7:11 PM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63418
Default Alt Text
(262 KB)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline