Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
244 KB
Referenced Files
None
Subscribers
None
diff --git a/data/themes/Cloudscape/theme.mnmstheme b/data/themes/Cloudscape/theme.mnmstheme
index c769898..81cd0d8 100644
--- a/data/themes/Cloudscape/theme.mnmstheme
+++ b/data/themes/Cloudscape/theme.mnmstheme
@@ -1,453 +1,462 @@
name="Cloudscape"
background(background.png){
repeat=0,0
}
block(Block){
editorPicture(tiles.png,0,0,50,50)
blockState(default){
object{
picture(tiles.png,0,0,50,50)
optionalPicture(tiles.png,50,0,50,50,0.15)
optionalPicture(tiles.png,100,0,50,50,0.15)
}
}
}
block(ShadowBlock){
editorPicture(tiles.png,0,50,50,50)
blockState(default){
object{
picture(tiles.png,0,50,50,50)
}
}
}
character(Player){
characterState(standleft){
object{
picture(player.png,115,0,23,40)
}
}
characterState(standright){
object{
picture(player.png,0,0,23,40)
}
}
characterState(walkleft){
object{
animation=20,0
pictureAnimation(player.png){
point(138,0,23,40,1,5)
point(230,0,23,40,4,5)
}
}
}
characterState(walkright){
object{
animation=20,0
pictureAnimation(player.png){
point(23,0,23,40,1,5)
point(115,0,23,40,4,5)
}
}
}
characterState(jumpleft){
object{
picture(player.png,276,0,23,40)
}
}
characterState(fallleft){
object{
picture(player.png,299,0,23,40)
}
}
characterState(jumpright){
object{
picture(player.png,230,0,23,40)
}
}
characterState(fallright){
object{
picture(player.png,253,0,23,40)
}
}
characterState(holding){
object{
picture(player.png,322,0,23,40)
}
}
characterState(line){
object{
picture(line.png,0,0,5,5)
}
}
characterState(dieright){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(deathright.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
characterState(dieleft){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(deathleft.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
characterState(dead){
object{
picture(player.png,0,0,23,40)
invisibleAtRunTime=1
}
}
}
character(Shadow){
characterState(standleft){
object{
picture(shadow.png,115,0,23,40)
}
}
characterState(standright){
object{
picture(shadow.png,0,0,23,40)
}
}
characterState(walkleft){
object{
animation=20,0
pictureAnimation(shadow.png){
point(138,0,23,40,1,5)
point(230,0,23,40,4,5)
}
}
}
characterState(walkright){
object{
animation=20,0
pictureAnimation(shadow.png){
point(23,0,23,40,1,5)
point(115,0,23,40,4,5)
}
}
}
characterState(jumpleft){
object{
picture(shadow.png,276,0,23,40)
}
}
characterState(fallleft){
object{
picture(shadow.png,299,0,23,40)
}
}
characterState(jumpright){
object{
picture(shadow.png,230,0,23,40)
}
}
characterState(fallright){
object{
picture(shadow.png,253,0,23,40)
}
}
characterState(holding){
object{
picture(shadow.png,322,0,23,40)
}
}
characterState(line){
object{
picture(line.png,0,0,5,5)
}
}
characterState(dieright){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(shadowdeathright.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
characterState(dieleft){
oneTimeAnimation=8,dead
object{
offset(0,-14)
animation=8,0
pictureAnimation(shadowdeathleft.png){
point(0,0,23,54,1,2)
point(92,0,23,54,4,2)
}
}
}
characterState(dead){
object{
picture(player.png,0,0,23,40)
invisibleAtRunTime=1
}
}
}
block(Fragile){
editorPicture(tiles.png,150,0,50,50)
blockState(default){
object{
picture(tiles.png,150,0,50,50)
}
}
blockState(fragile1){
object{
oneTimeAnimation=20,6
pictureAnimation(tiles.png){
point(200,0,50,50,1,5)
point(250,0,50,50,1,5)
}
}
}
blockState(fragile2){
object{
oneTimeAnimation=20,6
pictureAnimation(tiles.png){
point(300,0,50,50,1,5)
point(350,0,50,50,1,5)
}
}
}
blockState(fragile3){
oneTimeAnimation=6,fragile3_1
object{
animation=20,0
pictureAnimation(tiles.png){
point(150,50,50,100,1,2)
point(250,50,50,100,2,2)
}
}
}
blockState(fragile3_1){
object{
picture(tiles.png,250,50,50,100)
invisibleAtRunTime=1
}
}
}
block(MovingBlock){
editorPicture(tiles.png,350,200,50,50)
blockState(default){
object{
picture(tiles.png,0,0,50,50)
optionalPicture(tiles.png,50,0,50,50,0.15)
optionalPicture(tiles.png,100,0,50,50,0.15)
editorPicture(tiles.png,350,200,50,50)
}
}
}
block(MovingShadowBlock){
editorPicture(tiles.png,300,200,50,50)
blockState(default){
object{
picture(tiles.png,0,50,50,50)
editorPicture(tiles.png,300,200,50,50)
}
}
}
block(Exit){
editorPicture(tiles.png,150,200,50,50)
blockState(default){
oneTimeAnimation=6,open
object{
animation=80,0
pictureAnimation(tiles.png){
point(50,200,50,50,1,2)
point(150,200,50,50,2,2)
}
}
}
blockState(closed){
object{
picture(tiles.png,0,200,50,50)
}
}
blockState(open){
object{
picture(tiles.png,150,200,50,50)
}
}
}
block(Spikes){
editorPicture(tiles.png,450,0,50,50)
blockState(default){
object{
picture(tiles.png,450,0,50,50)
optionalPicture(tiles.png,450,50,50,50,0.5)
}
}
}
block(MovingSpikes){
editorPicture(tiles.png,400,200,50,50)
blockState(default){
object{
editorPicture(tiles.png,400,200,50,50)
picture(tiles.png,450,0,50,50)
optionalPicture(tiles.png,450,50,50,50,0.5)
}
}
}
block(Checkpoint){
editorPicture(tiles.png,50,50,50,50)
blockState(default){
object{
picture(tiles.png,50,50,50,50)
}
}
blockState(activated){
object{
animation=16,0
picture(tiles.png,100,50,50,50)
offsetAnimation{
point(0,0)
point(0,-4,4,1)
point(0,4,8,1)
point(0,0,4,1)
}
}
}
}
block(Swap){
editorPicture(swap.png,0,0,50,50)
blockState(default){
object{
picture(swap.png,0,0,50,50)
}
}
blockState(activated){
oneTimeAnimation=24,default
object{
animation=12,0
pictureAnimation(swap.png){
point(0,0,50,50)
point(600,0,50,50,12,1)
}
}
}
}
block(Teleporter){
editorPicture(tiles.png,300,50,50,50)
blockState(default){
object{
picture(tiles.png,300,50,50,50)
}
}
blockState(activated){
oneTimeAnimation=9,default
object{
animation=3,0
pictureAnimation(tiles.png){
point(300,50,50,50)
point(450,50,50,50,3,1)
}
}
}
}
block(Switch){
editorPicture(tiles.png,0,100,50,50)
blockState(default){
object{
picture(tiles.png,0,100,50,50)
}
}
blockState(done){
object{
picture(tiles.png,100,100,50,50)
}
}
blockState(activated){
oneTimeAnimation=3,done
object{
animation=3,0
pictureAnimation(tiles.png){
point(0,100,50,50)
point(150,100,50,50,3,1)
}
}
}
}
block(Button){
editorPicture(tiles.png,450,200,50,50)
blockState(default){
object{
picture(tiles.png,0,0,50,50)
optionalPicture(tiles.png,50,0,50,50,0.15)
optionalPicture(tiles.png,100,0,50,50,0.15)
}
}
blockState(button){
object{
# TODO:
picture(tiles.png,300,100,50,16)
}
}
}
block(NotificationBlock){
editorPicture(tiles.png,350,100,50,50)
blockState(default){
object{
picture(tiles.png,350,100,50,50)
}
}
}
block(ConveyorBelt){
editorPicture(tiles.png,450,100,50,50)
blockState(default){
object{
picture(tiles.png,450,100,50,50)
editorPicture(tiles.png,450,100,50,50)
}
}
}
block(ShadowConveyorBelt){
editorPicture(tiles.png,400,100,50,50)
blockState(default){
object{
picture(tiles.png,400,100,50,50)
}
}
}
block(PlayerStart){
editorPicture(tiles.png,250,200,50,50)
blockState(default){
object{
picture(tiles.png,250,200,50,50)
invisibleAtRunTime=1
}
}
}
block(ShadowStart){
editorPicture(tiles.png,200,200,50,50)
blockState(default){
object{
picture(tiles.png,200,200,50,50)
invisibleAtRunTime=1
}
}
}
block(Collectable){
editorPicture(collectable.png,0,0,50,50)
blockState(inactive){
object{
}
}
blockState(default){
object{
picture(collectable.png,0,0,50,50)
}
}
}
+block(Pushable){
+ editorPicture(tiles.png,350,200,50,50)
+ blockState(default){
+ object{
+ picture(tiles.png,350,200,50,50)
+ editorPicture(tiles.png,350,200,50,50)
+ }
+ }
+}
diff --git a/src/Block.cpp b/src/Block.cpp
index 41bc3f4..b1a13a2 100644
--- a/src/Block.cpp
+++ b/src/Block.cpp
@@ -1,683 +1,847 @@
/*
* 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;
//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;
+ xVel=yVel=xVelBase=yVelBase=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:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
+ case TYPE_PUSHABLE:
r.x=box.x-dx;
r.y=box.y-dy;
r.w=box.w;
r.h=box.h;
return r;
}
return box;
case BoxType_Delta:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
+ case TYPE_PUSHABLE:
r.x=dx;
r.y=dy;
break;
}
return r;
case BoxType_Velocity:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
+ case TYPE_PUSHABLE:
r.x=dx;
r.y=dy;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
r.x=(flags&1)?0:dx;
break;
}
return r;
case BoxType_Current:
return box;
}
return r;
}
void Block::setPosition(int x,int y){
box.x=x;
box.y=y;
boxBase.x=x;
boxBase.y=y;
}
void Block::saveState(){
tempSave=temp;
flagsSave=flags;
xSave=box.x-boxBase.x;
ySave=box.y-boxBase.y;
appearance.saveAnimation();
}
void Block::loadState(){
temp=tempSave;
flags=flagsSave;
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
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;
}
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
+ case TYPE_PUSHABLE:
box.x=boxBase.x;
box.y=boxBase.y;
break;
}
//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,Script>::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(scripts[eventType]);
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(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());
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);
}
}
}
}
}
/*//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.
+ if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,NULL))
+ 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;
+
+ if(objCurrentStand!=NULL){
+ xVelBase=objCurrentStand->getBox(BoxType_Velocity).x;
+ box.x+=xVelBase;
+ yVelBase=objCurrentStand->getBox(BoxType_Velocity).y;
+ box.y+=yVelBase;
+ }
+ }
+ break;
}
}
diff --git a/src/Block.h b/src/Block.h
index 2651e6b..bb162f7 100644
--- a/src/Block.h
+++ b/src/Block.h
@@ -1,135 +1,143 @@
/*
* 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 BLOCK_H
#define BLOCK_H
#include "GameObjects.h"
#include "Globals.h"
#include "ThemeManager.h"
#include "ScriptExecutor.h"
#include <vector>
#include <SDL/SDL.h>
class Block: public GameObject{
private:
//The Appearance of the block.
ThemeBlockInstance appearance;
//Integer that a block can use for all sorts of things.
int temp;
//The save for temp when the state of the block is saved.
int tempSave;
//flags:
//moving object 0x1=disabled
//button bit0-1=behavior 0x4=pressed
//switch bit0-1=behavior
int flags;
//The save for flags when the state of the block is saved.
int flagsSave;
//Following is for moving blocks.
//The starting place for moving blocks.
SDL_Rect boxBase;
//Vector containing the poisitions of the moving block.
std::vector<SDL_Rect> movingPos;
int dx;
int xSave;
int dy;
int ySave;
//Boolean if the moving block loops his movement.
//Default value is true.
bool loop;
-
+
+ //Following is for the pushable block.
+ GameObject* objCurrentStand;
+
//Flags of the block for the editor.
//moving object 0x1=disabled
//portal 0x1=automatic
//fragile =state
int editorFlags;
public:
+ //Follwing is for pushable block.
+ bool inAir;
+ int xVel,yVel;
+ int xVelBase,yVelBase;
+
//The id of the block.
std::string id;
//String containing the id of the destination for portals.
std::string destination;
//String containing the message of the notification block.
std::string message;
//The map that holds a script for every event.
map<int,Script> scripts;
//Constructor.
//x: The x location of the block.
//y: The y location of the block.
//objParent: Pointer to the Game object.
Block(int x,int y,int type,Game* objParent);
//Desturctor.
~Block();
//Method used to draw the block.
void show();
//Returns the box of a given type.
//boxType: The type of box that should be returned.
//See GameObjects.h for the types.
//Returns: The box.
virtual SDL_Rect getBox(int boxType=BoxType_Current);
//Method used to set the location of the block.
//It will set the base box x and y location.
//x: The new x location.
//y: The new y location.
virtual void setPosition(int x,int y);
//Save the state of the block so we can load it later on.
virtual void saveState();
//Load the saved state of the block so.
virtual void loadState();
//Reset the block.
//save: Boolean if the saved state should also be deleted.
virtual void reset(bool save);
//Play an animation.
//flags: TODO???
virtual void playAnimation(int flags);
//Method called when there's an event.
//eventType: The type of event.
//See GameObjects.h for the eventtypes.
virtual void onEvent(int eventType);
//Method used to retrieve a property from the block.
//propertyType: The type of property requested.
//See GameObjects.h for the properties.
//obj: Pointer to the player.
//Returns: Integer containing the value of the property.
virtual int queryProperties(int propertyType,Player* obj);
//Get the editor data of the block.
//obj: The vector that will be filled with the editorData.
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj);
//Set the editor data of the block.
//obj: The new editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj);
//Method used for updating moving blocks or elements of blocks.
virtual void move();
};
#endif
diff --git a/src/Functions.h b/src/Functions.h
index bfcd4f3..e9c637c 100644
--- a/src/Functions.h
+++ b/src/Functions.h
@@ -1,252 +1,254 @@
/*
* 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 FUNCTIONS_H
#define FUNCTIONS_H
#include "Settings.h"
#include "MusicManager.h"
#include "LevelPackManager.h"
#include "ScriptExecutor.h"
#include "Globals.h"
#include <SDL/SDL.h>
#include <string>
#include <vector>
//gettext function
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _(message) (dictionaryManager!=NULL?dictionaryManager->get_dictionary().translate(message).c_str():std::string(message).c_str())
//gettext function
//dictionaryManager: Pointer to the dictionaryManager to use for the translation.
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _C(dictionaryManager, message) ((dictionaryManager)!=NULL?(dictionaryManager)->get_dictionary().translate(message).c_str():std::string(message).c_str())
//dummy function for xgettext
//message: The message to translate.
//Returns: message parameter
#define __(message) (message)
//Loads an image.
//file: The image file to load.
//Returns: The SDL_surface containing the image.
SDL_Surface* loadImage(std::string file);
//Method for drawing an SDL_Surface onto another.
//x: The x location to draw the source on the desination.
//y: The y location to draw the source on the desination.
//source: The SDL_Surface to draw.
//dest: The SDL_Surface to draw on.
//clip: Rectangle which part of the source should be drawn.
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip);
//Method used to draw an rectangle.
//x: The top left x location of the rectangle.
//y: The top left y location of the rectangle.
//w: The width of the rectangle,
//h: The height of the rectangle.
//dest: The SDL_Surface to draw on.
//color: The color of the rectangle border to draw.
void drawRect(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color=0);
//Method used to draw filled boxes with an anti-alliased border.
//Mostly used for GUI components.
//x: The top left x location of the box.
//y: The top left y location of the box.
//w: The width of the box,
//h: The height of the box.
//dest: The SDL_Surface to draw on.
//color: The color of the rectangle background to draw.
void drawGUIBox(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color);
//Method used to draw a line.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
void drawLine(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color=0);
//Method used to draw a line with some arrows on it.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
//spacing: The spacing between arrows.
//offset: Offset of first arrow relative to the start point.
//xize, ysize: The size of arrow.
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color=0,int spacing=16,int offset=0,int xsize=5,int ysize=5);
//Method that will load the fonts needed for the game.
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing the language.
bool loadFonts();
//Method that will load the default theme again.
//name: name of the theme to load or empty for scaling background
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing resolution.
bool loadTheme(string name);
//This method will attempt to create the screen/window.
//NOTE: It's separate from init(), since it might get called separatly from the code when changing resolution.
bool createScreen();
//Method that is called when a fullscreen window is created.
//It will choose the resolution that is closest to the configured one.
void pickFullscreenResolution();
//This method is used to configure the window that is created by createScreen.
//NOTE: It will do it in a WM specific way, so if the wm is unkown it will do nothing.
//initial: Boolean that is true if it's the first time the window is configured.
void configureWindow(bool initial);
//Call this method when receive SDL_VIDEORESIZE event.
void onVideoResize();
//Initialises the game. This is done almost at the beginning of the program.
//It initialises: SDL, SDL_Mixer, SDL_ttf, the screen and the block types.
//Returns: True if everything goes well.
bool init();
//Loads some important files, like the background music and the default theme.
//Returns: True if everything goes well.
bool loadFiles();
//This method will load the settings from the settings file.
//Returns: False if there's an error while loading.
bool loadSettings();
//This method will save the settings to the settings file.
//Returns: False if there's an error while saving.
bool saveSettings();
//Method used to get a pointer to the settings object.
//Returns: A pointer to the settings object.
Settings* getSettings();
//Method used to get a pointer to the MusicManager object.
//Returns: A pointer to the MusicManager object.
MusicManager* getMusicManager();
//Method used to get a pointer to the LevelPackManager object.
//Returns: A pointer to the LevelPackManager object.
LevelPackManager* getLevelPackManager();
+//Method used to get a pointer to the ScriptExecutor object.
+//Returns: A pointer to the ScriptExecutor object.
ScriptExecutor* getScriptExecutor();
//Method that will, depending on the rendering backend, draw the screen surface to the screen.
void flipScreen();
//Method used to clean up before quiting meandmyshadow.
void clean();
//Sets what the nextState will be.
//newstate: Integer containing the id of the newstate.
void setNextState(int newstate);
//Method that will perform the state change.
//It will fade out and in.
void changeState();
//This method is called when music is stopped.
//NOTE: This method is outside the MusicManager because it couldn't be called otherwise.
//Do not call this method anywhere in the code!
void musicStoppedHook();
//Checks collision between two SDL_Rects.
//a: The first rectangle.
//b: The second rectangle.
//Returns: True if the two rectangles collide.
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b);
//This method will check if the mouse is near a screen edge.
//r: An array of SDL_Rect, does nothing if mouse inside these rectange(s).
//count: Number of rectangles.
//If so it will move the camera.
//Note: This function only works with the leveleditor.
void setCamera(const SDL_Rect* r,int count);
//Parse the commandline arguments.
//argc: Integer containing the number of aruguments there are.
//argv: The arguments.
//Returns: -1 if something goes wrong while parsing,
// 0 if version is shown,
// 1 if everything is alright
int parseArguments(int argc, char** argv);
//From http://en.wikipedia.org/wiki/Clamping_(graphics)
//x: The value to clamp.
//min: The minimum x can be.
//max: The maximum x can be.
//Returns: Integer containing the clamped value.
int inline clamp(int x,int min,int max){
return (x>max)?max:(x<min)?min:x;
}
//Enumeration containing the different messagebox button combinations.
enum msgBoxButtons{
//Only one button with the text OK.
MsgBoxOKOnly=0,
//Two buttons, one saying OK, the other Cancel.
MsgBoxOKCancel=1,
//Three buttons, Abort, Retry, Ignore.
MsgBoxAbortRetryIgnore=2,
//Three buttons, Yes, No or Cancel.
MsgBoxYesNoCancel=3,
//Two buttons, one saying Yes, the other No.
MsgBoxYesNo=4,
//Two buttons, one saying Retry, the other Cancel.
MsgBoxRetryCancel=5,
};
//Enumeration containing the different result that can be retrieved from a messagebox.
//It represents the button that has been pressed.
enum msgBoxResult{
//The OK button.
MsgBoxOK=1,
//The cancel button.
MsgBoxCancel=2,
//The abort button.
MsgBoxAbort=3,
//The retry button.
MsgBoxRetry=4,
//The ignore button.
MsgBoxIgnore=5,
//The yes button.
MsgBoxYes=6,
//The no button.
MsgBoxNo=7,
};
//Method that prompts the user with a notification and/or question.
//prompt: The message the user is prompted with.
//buttons: Which buttons the messagebox should have.
//title: The title of the message box.
//Returns: A msgBoxResult which button has been pressed.
msgBoxResult msgBox(std::string prompt,msgBoxButtons buttons,const std::string& title);
//This method will show a file dialog in which the user can select a file.
//NOTE: It doesn't support entering folders.
//fileName: String that will contain the result, it can also be used to already chose the file.
//title: The title of the fileDialog window.
//extension: The extension the files must have, leave empty for all files.
//path: The path to list the files of.
//isSave: If the dialog is for saving files, and not loading.
//verifyFile: Boolean if the selected should be verified.
//files: Boolean if the fileDialog should display files, if not it will display directories.
bool fileDialog(std::string& fileName,const char* title=NULL,const char* extension=NULL,const char* path=NULL,bool isSave=false,bool verifyFile=false,bool files=true);
#endif
diff --git a/src/Game.cpp b/src/Game.cpp
index 7d4392d..e3fb5ac 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1487 +1,1487 @@
/*
* 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 "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <locale>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
-"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable"
+"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable", "Pushable"
};
map<string,int> Game::blockNameMap;
string Game::recordFile;
Game::Game(bool loadLevel):isReset(false)
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,shadowCam(false)
,player(this),shadow(this),objLastCheckPoint(NULL){
saveStateNextTime=false;
loadStateNextTime=false;
//Reserve the memory for the GameObject tips.
memset(bmTips,0,sizeof(bmTips));
action=loadImage(getDataPath()+"gfx/actions.png");
medals=loadImage(getDataPath()+"gfx/medals.png");
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
ThemeBlockInstance appearance;
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&appearance);
collectable=appearance.currentState->parent->themeObjects[0]->picture.picture;
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
//Check if we should load record file.
if(!recordFile.empty()){
loadRecord(recordFile.c_str());
recordFile.clear();
return;
}
//If we should load the level then load it.
if(loadLevel){
this->loadLevel(levels->getLevelpackPath()+levels->getLevelFile());
levels->saveLevelProgress();
}
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
//Done now clear the levelObjects vector.
levelObjects.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Loop through the tips.
for(int i=0;i<TYPE_MAX;i++){
//If it exist free the SDL_Surface.
if(bmTips[i])
SDL_FreeSurface(bmTips[i]);
}
memset(bmTips,0,sizeof(bmTips));
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
}
void Game::loadLevelFromNode(TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
destroy();
//set current level to loaded one.
currentLevelNode=obj;
//Temp var used for block locations.
SDL_Rect box;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
currentCollectables=0;
totalCollectables=0;
currentCollectablesSaved=0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//If a theme is configured then load it.
string theme=processFileName(getSettings()->getValue("theme"));
//Check if level themes are enabled.
if(getSettings()->getBoolValue("leveltheme")) {
string &s=editorData["theme"];
if(!s.empty()){
customTheme=objThemes.appendThemeFromFile(processFileName(theme)+"/theme.mnmstheme");
}
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance);
objThemes.getCharacter(true)->createInstance(&shadow.appearance);
}
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile" && obj1->value.size()>=3){
int objectType=blockNameMap[obj1->value[0]];
box.x=atoi(obj1->value[1].c_str());
box.y=atoi(obj1->value[2].c_str());
map<string,string> obj;
for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
if(i->second.size()>0) obj[i->first]=i->second[0];
}
//If the type is collectable, increase the number of totalCollectables
if(objectType==TYPE_COLLECTABLE)
totalCollectables++;
levelObjects.push_back(new Block(box.x,box.y,objectType,this));
levelObjects.back()->setEditorData(obj);
//Check for subnodes on the tile node.
//FIXME: Extend setEditorData to accept a TreeStorageNode so it can be handled there.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
//FIXME: Ugly variable naming.
TreeStorageNode* obj2=obj1->subNodes[j];
if(obj2==NULL) continue;
//Check for a script block.
if(obj2->name=="script" && obj2->value.size()>=1){
int eventType=atoi(obj2->value[0].c_str());
Script script;
script.script=obj2->attributes["script"][0].c_str();
dynamic_cast<Block*>(levelObjects.back())->scripts[eventType]=script;
}
}
}
}
//Close exits if there are collectables
if(totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if (levels->getLevelCount()>1){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_C(levels->getDictionaryManager(),editorData["name"]));
}
SDL_Color fg={0,0,0,0};
bmTips[0]=TTF_RenderUTF8_Blended(fontText,s.c_str(),fg);
}
//Get the background
background=objThemes.getBackground(false);
if(background)
background->resetAnimation(true);
}
void Game::loadLevel(string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cout<<"Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cout<<"Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cout<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'r' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Only set isReset true if this isn't a replay.
if(!(player.isPlayFromRecord() && !interlevel))
isReset=true;
}
//Check for the next level buttons when in the interlevel popup.
if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || (event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_RETURN || event.key.keysym.sym==SDLK_RCTRL))){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel();
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
shadowCam=!shadowCam;
}
}
/////////////////LOGIC///////////////////
void Game::logic(){
//Check if we should save/load state.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Add one tick to the time.
time++;
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//Let the player jump.
player.jump();
//Let him move.
player.move(levelObjects);
//And let the camera follow him.
if(!shadowCam){
player.setMyCamera();
}else{
shadow.setMyCamera();
}
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Let the shadow jump.
shadow.jump();
//Let the shadow move.
shadow.move(levelObjects);
//Some levelObjects can move so update them.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->move();
}
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.flags|1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL && obj->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded();
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
//Get current level
LevelPack::Level *level=levels->getLevel();
//Now check if we should update statistics
{
//Get previous and current medal
int oldMedal=level->won?1:0,newMedal=1;
int bestTime=level->time;
int targetTime=level->targetTime;
int bestRecordings=level->recordings;
int targetRecordings=level->targetRecordings;
if(oldMedal){
if(targetTime<0){
oldMedal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
oldMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
oldMedal++;
}
}else{
bestTime=time;
bestRecordings=recordings;
}
if(bestTime==-1 || bestTime>time) bestTime=time;
if(bestRecordings==-1 || bestRecordings>recordings) bestRecordings=recordings;
if(targetTime<0){
newMedal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
newMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
newMedal++;
}
//Check if we need to update statistics
if(newMedal>oldMedal){
switch(oldMedal){
case 0:
statsMgr.completedLevels++;
break;
case 2:
statsMgr.silverLevels--;
break;
}
switch(newMedal){
case 2:
statsMgr.silverLevels++;
break;
case 3:
statsMgr.goldLevels++;
break;
}
}
}
//Set the current level won.
level->won=true;
if(level->time==-1 || level->time>time){
level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
if(level->recordings==-1 || level->recordings>recordings){
level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay();
//Update achievements
if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
statsMgr.updateLevelAchievements();
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
}
}
won=false;
//Check if we should reset.
if(isReset)
reset(false);
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(screen);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_Rect r={0,0,SCREEN_WIDTH,SCREEN_HEIGHT};
SDL_FillRect(screen,&r,-1);
}
}
//Now we draw the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
levelObjects[o]->show();
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow second because he needs to be behind the player.
shadow.show();
player.show();
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
drawGUIBox(-2,SCREEN_HEIGHT-bmTips[0]->h-4,bmTips[0]->w+8,bmTips[0]->h+6,screen,0xDDDDDDDD);
applySurface(2,SCREEN_HEIGHT-bmTips[0]->h,bmTips[0],screen,NULL);
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode=_(inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_ACTION,false)));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
SDL_Color fg={0,0,0,0};
bmTips[gameTipIndex]=TTF_RenderUTF8_Blended(fontText,s.c_str(),fg);
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
drawGUIBox(-2,-2,bmTips[gameTipIndex]->w+8,bmTips[gameTipIndex]->h+6,screen,0xDDDDDDDD);
applySurface(2,2,bmTips[gameTipIndex],screen,NULL);
}
}
//Set the gameTip to 0.
gameTipIndex=0;
//Pointer to the sdl surface that will contain a message, if any.
SDL_Surface* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart=inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_RESTART,false));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad=inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_LOAD,false));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[3]=TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
fg);
}
bm=bmTips[3];
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
SDL_Color fg={0,0,0,0};
bmTips[2]=TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
fg);
}
bm=bmTips[2];
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[1]=TTF_RenderUTF8_Blended(fontText,
_("Your shadow has died."),
fg);
}
bm=bmTips[1];
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
shadowCam=false;
}
//Draw the tip.
if(bm!=NULL){
int x=(SCREEN_WIDTH-bm->w)/2;
int y=32;
drawGUIBox(x-8,y-8,bm->w+16,bm->h+14,screen,0xDDDDDDDD);
applySurface(x,y,bm,screen,NULL);
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if(currentCollectables<=totalCollectables && totalCollectables!=0 && !interlevel && time>0){
//Temp stringstream just to addup all the text nicely
stringstream temp;
temp << currentCollectables << "/" << totalCollectables;
SDL_Rect r;
SDL_Surface* bm=TTF_RenderText_Blended(fontText,temp.str().c_str(),themeTextColorDialog);
//Align the text properly
r.x=SCREEN_WIDTH-collectable->w-bm->w+22;
r.y=SCREEN_HEIGHT-bm->h;
//Draw background
drawGUIBox(SCREEN_WIDTH-bm->w-34,SCREEN_HEIGHT-bm->h-4,bm->w+34+2,bm->h+4+2,screen,0xDDDDDDDD);
//Draw the collectable icon
applySurface(SCREEN_WIDTH-collectable->w+12,SCREEN_HEIGHT-collectable->h+10,collectable,screen,NULL);
//Draw text
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
//show time and records used in level editor.
if(stateID==STATE_LEVEL_EDITOR && time>0){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
SDL_Surface *bm;
int y=SCREEN_HEIGHT;
bm=TTF_RenderUTF8_Shaded(fontText,tfm::format(_("%d recordings"),recordings).c_str(),fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
char c[32];
sprintf(c,"%-.2fs",time/40.0f);
bm=TTF_RenderUTF8_Shaded(fontText,c,fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//Draw the current action in the upper right corner.
if(player.record){
applySurface(SCREEN_WIDTH-50,0,action,screen,NULL);
}else if(shadow.state!=0){
SDL_Rect r={50,0,50,50};
applySurface(SCREEN_WIDTH-50,0,action,screen,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if(interlevel){
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA,220);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
//Check if the GUI isn't null.
if(GUIObjectRoot){
//==Create first box==
//Create the title
SDL_Rect r;
/// TRANSLATORS: This is caption for finished level
SDL_Surface* bm=TTF_RenderUTF8_Blended(fontGUI,_("You've finished:"),themeTextColorDialog);
//Recreate the level string.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_C(levels->getDictionaryManager(),levelName));
}
SDL_Surface* bm2=TTF_RenderUTF8_Blended(fontText,s.c_str(),themeTextColorDialog);
//Now draw the first gui box so that it's bigger than longer text.
int width;
if(bm->w>bm2->w)
width=bm->w+32;
else
width=bm2->w+32;
drawGUIBox((SCREEN_WIDTH-width)/2,4,width,68,screen,0xDDDDDDA1);
// Now draw title.
r.x=(SCREEN_WIDTH-bm->w)/2;
r.y=8-GUI_FONT_RAISE;
SDL_BlitSurface(bm,NULL,screen,&r);
// And then level name.
r.x=(SCREEN_WIDTH-bm2->w)/2;
r.y=44;
SDL_BlitSurface(bm2,NULL,screen,&r);
//Free drawed texts
SDL_FreeSurface(bm);
SDL_FreeSurface(bm2);
//==Create second box==
//Now draw the second gui box.
drawGUIBox(GUIObjectRoot->left,GUIObjectRoot->top,GUIObjectRoot->width,GUIObjectRoot->height,screen,0xDDDDDDA1);
//Draw the medal.
int medal=GUIObjectRoot->value;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
applySurface(GUIObjectRoot->left+16,GUIObjectRoot->top+92,medals,screen,&r);
applySurface(GUIObjectRoot->left+medalX,GUIObjectRoot->top+92,medals,screen,&r);
}
}else if((time & 0x10)==0x10){
SDL_Rect r={50,0,50,50};
applySurface(0,0,action,screen,&r);
applySurface(0,SCREEN_HEIGHT-50,action,screen,&r);
applySurface(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,action,screen,&r);
}
}else if(player.objNotificationBlock){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
std::string &untranslated_message=(dynamic_cast<Block*>(player.objNotificationBlock))->message;
std::string message=_C(levels->getDictionaryManager(),untranslated_message);
std::vector<char> string_data(message.begin(), message.end());
string_data.push_back('\0');
int maxWidth = 0;
int y = 20;
vector<SDL_Surface*> lines;
//Now process the prompt.
{
//Pointer to the string.
char* lps=&string_data[0];
//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;
//Integer used to center the sentence horizontally.
int x;
TTF_SizeText(fontText,lps,&x,NULL);
//Find out largest width
if(x>maxWidth)
maxWidth=x;
x=(SCREEN_WIDTH-x)/2;
lines.push_back(TTF_RenderUTF8_Blended(fontText,lps,themeTextColorDialog));
//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;
}
}
maxWidth+=SCREEN_WIDTH*0.15;
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,screen,0xDDDDDDA1);
while(!lines.empty()){
SDL_Surface* bm=lines[0];
if(bm!=NULL){
applySurface(100+((SCREEN_WIDTH-200-bm->w)/2),SCREEN_HEIGHT-y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
y-=25;
lines.erase(lines.begin());
}
}
}
void Game::resize(){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(){
//Reset the number of collectables
currentCollectables=currentCollectablesSaved=0;
//We show the interlevel popup so interlevel must be true.
interlevel=true;
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
GUIObjectRoot=new GUIObject(0,SCREEN_HEIGHT-140,570,135,GUIObjectNone);
//NOTE: We put the medal in the value of the GUIObjectRoot.
//Make child widgets change color properly according to theme.
GUIObjectRoot->inDialog=true;
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
if(targetTime<0){
medal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
medal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
medal++;
}
//Add it to the GUIObjectRoot.
GUIObjectRoot->value=medal;
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
if(targetTime<=0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
GUIObject* obj=new GUIObject(x,10+timeY,-1,36,GUIObjectLabel,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
obj=new GUIObject(x,34+timeY,-1,36,GUIObjectLabel,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
obj=new GUIObject(x,58,-1,36,GUIObjectLabel,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUIObject(x,10+recsY,-1,36,GUIObjectLabel,tfm::format(_("Recordings: %d"),recordings).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUIObject(x,34+recsY,-1,36,GUIObjectLabel,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
obj=new GUIObject(x,58,-1,36,GUIObjectLabel,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
obj=new GUIObject(50,92,-1,36,GUIObjectLabel,s1.c_str(),0,true,true,GUIGravityCenter);
GUIObjectRoot->addChild(obj);
obj->render(0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
medalX=x-24;
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
GUIObject* b1=new GUIObject(x,10,-1,36,GUIObjectButton,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
GUIObjectRoot->addChild(b1);
b1->render(0,0,true);
/// TRANSLATORS: used as restart level
GUIObject* b2=new GUIObject(x,50,-1,36,GUIObjectButton,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
GUIObjectRoot->addChild(b2);
b2->render(0,0,true);
/// TRANSLATORS: used as next level
GUIObject* b3=new GUIObject(x,90,-1,36,GUIObjectButton,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
GUIObjectRoot->addChild(b3);
b3->render(0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
GUIObjectRoot->width=x;
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
//We only need to reset a few things so we don't call reset().
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(true);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(true);
//Close exit(s) if there are any collectables
if(totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
}
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
player.reset(true);
shadow.reset(true);
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
}
void Game::recordingEnded(){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(player.canSaveState() && shadow.canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
//Save the current collectables
currentCollectablesSaved=currentCollectables;
//Save other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->saveState();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
//Update statistics.
if(!player.isPlayFromRecord() && !interlevel){
statsMgr.saveTimes++;
//TODO: achievements
}
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
//Load the current collactbles
currentCollectables=currentCollectablesSaved;
//Load other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->loadState();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
//Update statistics.
if(!player.isPlayFromRecord() && !interlevel){
statsMgr.loadTimes++;
//TODO: achievements
}
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save){
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats.
time=0;
recordings=0;
//Reset the number of collectables
currentCollectables=0;
if(save)
currentCollectablesSaved=0;
//There is no last checkpoint so set it to NULL.
if(save)
objLastCheckPoint=NULL;
//Reset other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
}
//Check if interlevel is true, if so we might need to delete the gui.
if(interlevel){
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Also set interlevel to false.
interlevel=false;
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
//Don't forget the music.
getMusicManager()->pickMusic();
}else{
if(!levels->congratulationText.empty()){
msgBox(_C(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And reset the game.
//new: we don't need to clear the save game because
//in level replay the game won't be saved
//TODO: it doesn't work; better leave for next release
reset(/*false*/ true);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel();
}
}
diff --git a/src/Globals.h b/src/Globals.h
index 79d469b..cbb78e2 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -1,231 +1,235 @@
/*
* 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;
//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 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 8db414e..dcab537 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,3455 +1,3456 @@
/*
* 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 "GUIOverlay.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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;
static GUIObject *levelTimeProperty,*levelRecordingsProperty;
//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")
+ __("Conveyor Belt"),__("Shadow Conveyor Belt"),__("Notification Block"),__("Collectable"),__("Puhsable")
};
//Array indicates if block is configurable
static const bool isConfigurable[TYPE_MAX]={
false,false,false,
false,false,false,
false,false,true,
true,true,true,
true,true,true,
true,true,true,false
};
//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
};
/////////////////LevelEditorToolbox////////////////////////
class LevelEditorToolbox{
private:
//The parent object
LevelEditor* parent;
//The position of window
SDL_Rect rect;
//Background surface
SDL_Surface *background;
//GUI image
SDL_Surface *bmGUI;
public:
int startRow,maxRow;
bool visible;
bool dragging;
public:
SDL_Rect getRect(){
return rect;
}
int width(){
return 320;
}
int height(){
return 180;
}
LevelEditorToolbox(LevelEditor* parent){
this->parent=parent;
visible=false;
dragging=false;
//calc row count
startRow=0;
maxRow=(LevelEditor::EDITOR_ORDER_MAX+4)/5;
//set size
rect.w=width();
rect.h=height();
//Load the gui images.
bmGUI=loadImage(getDataPath()+"gfx/gui.png");
//create background and draw something on it
background=SDL_CreateRGBSurface(SDL_HWSURFACE,
rect.w,rect.h,screen->format->BitsPerPixel,
screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);
//background
drawGUIBox(0,0,rect.w,rect.h,background,0xFFFFFFFFU);
//caption
{
SDL_Rect captionRect={6,8,width()-16,32};
//SDL_FillRect(background,&captionRect,0xCCCCCCU);
SDL_Color fg={0,0,0};
SDL_Surface *caption=TTF_RenderUTF8_Blended(fontGUI,_("Toolbox"),fg);
applySurface(captionRect.x+(captionRect.w-caption->w)/2,
captionRect.y+(captionRect.h-caption->h)/2,caption,background,NULL);
SDL_FreeSurface(caption);
}
}
~LevelEditorToolbox(){
SDL_FreeSurface(background);
}
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(){
if(visible){
applySurface(rect.x,rect.y,background,screen,NULL);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//draw close button
{
//check highlight
SDL_Rect r={rect.x+rect.w-36,rect.y+12,24,24};
if(checkCollision(mouse,r)){
drawGUIBox(r.x,r.y,r.w,r.h,screen,0x999999FFU);
}
SDL_Rect r1={112,0,16,16};
applySurface(rect.x+rect.w-32,rect.y+16,bmGUI,screen,&r1);
}
//the tool tip of item
SDL_Rect tooltipRect;
string tooltip;
//draw avaliable item
for(int i=0;i<2;i++){
int j=startRow+i;
if(j>=maxRow) j-=maxRow;
for(int k=0;k<5;k++){
int idx=j*5+k;
if(idx<0 || idx>=LevelEditor::EDITOR_ORDER_MAX) break;
SDL_Rect r={rect.x+k*60+10,rect.y+i*60+50,60,60};
//check highlight
if(checkCollision(mouse,r)){
tooltipRect=r;
tooltip=_(blockNames[LevelEditor::editorTileOrder[idx]]);
if(parent->currentType==idx){
drawGUIBox(r.x,r.y,r.w,r.h,screen,0x999999FFU);
}else{
SDL_FillRect(screen,&r,0xCCCCCC);
}
}else if(parent->currentType==idx){
drawGUIBox(r.x,r.y,r.w,r.h,screen,0xCCCCCCFFU);
}
ThemeBlock* obj=objThemes.getBlock(LevelEditor::editorTileOrder[idx]);
if(obj){
obj->editorPicture.draw(screen,r.x+5,r.y+5);
}
}
}
//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){
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(){
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if item is clicked
for(int i=0;i<2;i++){
int j=startRow+i;
if(j>=maxRow) j-=maxRow;
for(int k=0;k<5;k++){
int idx=j*5+k;
if(idx<0 || idx>=LevelEditor::EDITOR_ORDER_MAX) break;
SDL_Rect r={rect.x+k*60+10,rect.y+i*60+50,60,60};
//check highlight
if(checkCollision(mouse,r)){
parent->currentType=idx;
return;
}
}
}
//Now begin drag the toolbox
dragging=true;
}
else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//Stop dragging
dragging=false;
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if close button clicked
{
SDL_Rect r={rect.x+rect.w-36,rect.y+12,24,24};
if(checkCollision(mouse,r)){
visible=false;
return;
}
}
}
else if(event.type==SDL_MOUSEMOTION){
if((event.motion.state & SDL_BUTTON_LMASK)==0){
dragging=false;
}else if(dragging){
move(rect.x+event.motion.xrel,rect.y+event.motion.yrel);
}
}
}
};
/////////////////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=configure 3=link 4=delete
int highlightedBtn;
public:
int startRow,showedRow;
bool dragging;
//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;
dragging=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-96,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 configure
if(isConfigurable[type]){
SDL_Rect r1={112,16,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=_("Configure");
highlightedBtn=2;
}
r2.x+=4;
r2.y+=4;
SDL_BlitSurface(bmGUI,&r1,screen,&r2);
}
//draw link
if(isLinkable[type]){
SDL_Rect r1={112,32,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=_("Link");
highlightedBtn=3;
}
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-24,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=4;
}
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;
}
//begin drag
if(event.button.button==SDL_BUTTON_LEFT) dragging=true;
}
else if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//end drag
dragging=false;
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->tool=LevelEditor::CONFIGURE;
parent->onEnterObject(highlightedObj);
break;
case 3:
{
std::vector<GameObject*>& v2=parent->selection;
parent->tool=LevelEditor::CONFIGURE;
parent->onRightClickObject(highlightedObj,find(v2.begin(),v2.end(),highlightedObj)!=v2.end());
dismiss();
}
break;
case 4:
parent->removeObject(highlightedObj);
break;
}
}
}
}
else if(event.type==SDL_MOUSEMOTION){
if((event.motion.state & SDL_BUTTON_LMASK)==0){
dragging=false;
}else if(dragging){
move(rect.x+event.motion.xrel,rect.y+event.motion.yrel);
}
}
}
};
/////////////////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();
//Load the toolbar.
toolbar=loadImage(getDataPath()+"gfx/menu/toolbar.png");
SDL_Rect tmp={(SCREEN_WIDTH-460)/2,SCREEN_HEIGHT-50,460,50};
toolbarRect=tmp;
toolbox=NULL;
selectionPopup=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 toolbox (if any)
if(toolbox){
delete toolbox;
toolbox=NULL;
}
//Delete the popup
if(selectionPopup){
delete selectionPopup;
selectionPopup=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;
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
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();
}
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.
sprintf(s,"%d",objectType);
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);
}
}
}
}
//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;
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");
}
}
//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;
}
}
//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;
//Check if we should show tool box
if(tool==ADD){
//show the tool box
if(toolbox==NULL){
toolbox=new LevelEditorToolbox(this);
toolbox->move(event.button.x,event.button.y-toolbox->height()-20);
}
if(!toolbox->visible){
toolbox->visible=true;
}
}
}else{
//The selected button isn't a tool.
//Now check which button it is.
if(t==NUMBER_TOOLS){
playMode=true;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
if(tool==CONFIGURE){
//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(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->setPosition(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())){
tool=CONFIGURE;
//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);
}
}
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
//Check if mouse is in the tool box
bool mouseInToolbox=(toolbox!=NULL && !playMode && tool==ADD && toolbox->visible
&& (toolbox->dragging || checkCollision(mouse,toolbox->getRect())));
//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.
if(mouseInToolbox){
if((--toolbox->startRow)<0){
toolbox->startRow=toolbox->maxRow-1;
}
}else{
currentType++;
if(currentType>=EDITOR_ORDER_MAX){
currentType=0;
}
}
break;
case CONFIGURE:
//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.
if(mouseInToolbox){
if((++toolbox->startRow)>=toolbox->maxRow){
toolbox->startRow=0;
}
}else{
currentType--;
if(currentType<0){
currentType=EDITOR_ORDER_MAX-1;
}
}
break;
case CONFIGURE:
//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;
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;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_w){
tool=CONFIGURE;
}
//Check for certain events.
//First make sure the mouse isn't above the toolbar.
if(checkCollision(mouse,toolbarRect)==false){
//Check if mouse is in the tool box
if(mouseInToolbox){
toolbox->handleEvents();
}else{
//We didn't hit the toolbar so convert the mouse location to ingame location.
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_MOUSEBUTTONUP){
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]);
}
}
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
clickEvent=true;
SDL_Rect r=clickObjects[0]->getBox();
if(selectionPopup!=NULL) delete selectionPopup;
selectionPopup=new LevelEditorSelectionPopup(this,clickObjects,
r.x-camera.x,r.y-camera.y);
}
}
//If event is false then we clicked on void.
if(!clickEvent){
if(event.type==SDL_MOUSEBUTTONUP){
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==CONFIGURE){
//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(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();
}
//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) or save levelpack (Ctrl+Shift+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.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-300)/2,600,300,GUIObjectFrame,_("Level settings"));
GUIObject* obj;
//NOTE: We reuse the objectProperty and secondProperty.
obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Name:"));
root->addChild(obj);
obj=new GUIObject(140,50,410,36,GUIObjectTextBox,levelName.c_str());
objectProperty=obj;
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());
secondObjectProperty=obj;
root->addChild(obj);
//target time and recordings.
{
char c[32];
if(levelTime>=0){
sprintf(c,"%-.2f",levelTime/40.0f);
}else{
c[0]='\0';
}
obj=new GUIObject(40,150,240,36,GUIObjectLabel,_("Target time (s):"));
root->addChild(obj);
obj=new GUIObject(290,150,260,36,GUIObjectTextBox,c);
levelTimeProperty=obj;
root->addChild(obj);
if(levelRecordings>=0){
sprintf(c,"%d",levelRecordings);
}else{
c[0]='\0';
}
obj=new GUIObject(40,200,240,36,GUIObjectLabel,_("Target recordings:"));
root->addChild(obj);
obj=new GUIObject(290,200,260,36,GUIObjectTextBox,c);
levelRecordingsProperty=obj;
root->addChild(obj);
}
//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=this;
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=this;
root->addChild(obj);
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
void LevelEditor::postLoad(){
//We need to find the triggers.
for(unsigned int o=0;o<levelObjects.size();o++){
//Get the editor data.
vector<pair<string,string> > objMap;
levelObjects[o]->getEditorData(objMap);
//Check for the highest id.
for(unsigned int i=0;i<objMap.size();i++){
if(objMap[i].first=="id"){
unsigned int id=atoi(objMap[i].second.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:
{
//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 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){
//NOTE: We put CONFIGURE above ADD and SELECT to use the same method of selection.
//Meaning there's no break at the end of CONFIGURE.
case CONFIGURE:
{
//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.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",atoi(objMap[0].second.c_str()));
editorData["destination"]=s;
linkingTrigger->setEditorData(editorData);
}
}else{
//Give the object the same id as the trigger.
vector<pair<string,string> > objMap;
linkingTrigger->getEditorData(objMap);
int m=objMap.size();
if(m>0){
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",atoi(objMap[0].second.c_str()));
editorData["id"]=s;
obj->setEditorData(editorData);
}
}
//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;
}
//Now handle it as if the user pressed enter (show block properties dialog).
onEnterObject(obj);
}
case SELECT:
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){
switch(tool){
case CONFIGURE:
{
//Make sure we aren't doing anything special.
if(moving || linking)
break;
//Check if it's a trigger.
if(obj->type==TYPE_PORTAL || obj->type==TYPE_BUTTON || obj->type==TYPE_SWITCH){
//Set linking true.
linking=true;
linkingTrigger=obj;
}
//Check if it's a moving block.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES){
//Set moving true.
moving=true;
movingBlock=obj;
}
break;
}
case SELECT:
case ADD:
{
//We deselect the object if it's selected.
if(selected){
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),obj);
//Remove the object from selection.
if(it!=selection.end()){
selection.erase(it);
}
}else{
//It wasn't a selected object so switch to configure mode.
//Check if it's the right type of object.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES ||
obj->type==TYPE_PORTAL || obj->type==TYPE_BUTTON || obj->type==TYPE_SWITCH){
tool=CONFIGURE;
onRightClickObject(obj,selected);
}
}
break;
}
default:
break;
}
}
void LevelEditor::onClickVoid(int x,int y){
switch(tool){
case SELECT:
{
//We need to clear the selection.
selection.clear();
break;
}
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 CONFIGURE:
{
//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:
case CONFIGURE:
{
//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};
//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:
case CONFIGURE:
{
//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){
switch(tool){
case CONFIGURE:
{
//Check if the type is an moving block.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Check if the moving block has a path..
string s1;
bool path=false;
if(!movingBlocks[obj].empty()){
s1=_("Defined");
path=true;
}else{
s1=_("None");
}
//Now create the GUI.
string s;
switch(obj->type){
case TYPE_MOVING_BLOCK:
s=_("Moving block");
break;
case TYPE_MOVING_SHADOW_BLOCK:
s=_("Moving shadow block");
break;
case TYPE_MOVING_SPIKES:
s=_("Moving spikes");
break;
}
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,s.c_str());
GUIObject* obj;
obj=new GUIObject(70,50,280,36,GUIObjectCheckBox,_("Enabled"),(objMap[2].second!="1"));
obj->name="cfgMovingBlockEnabled";
obj->eventCallback=this;
objectProperty=obj;
root->addChild(obj);
obj=new GUIObject(70,80,280,36,GUIObjectCheckBox,_("Loop"),(objMap[3].second!="0"));
obj->name="cfgMovingBlockLoop";
obj->eventCallback=this;
secondObjectProperty=obj;
root->addChild(obj);
obj=new GUIObject(70,110,280,36,GUIObjectLabel,_("Path"));
root->addChild(obj);
GUIObject* label=new GUIObject(330,110,-1,36,GUIObjectLabel,s1.c_str());
root->addChild(label);
label->render(0,0,false);
if(path){
obj=new GUIObject(label->left+label->width,110,36,36,GUIObjectButton,"x");
obj->name="cfgMovingBlockClrPath";
obj->eventCallback=this;
root->addChild(obj);
}else{
//NOTE: The '+' is translated 5 pixels down to align with the 'x'.
obj=new GUIObject(label->left+label->width,115,36,36,GUIObjectButton,"+");
obj->name="cfgMovingBlockMakePath";
obj->eventCallback=this;
root->addChild(obj);
}
obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgMovingBlockOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the GUI overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
}
//Check which type of object it is.
if(obj->type==TYPE_NOTIFICATION_BLOCK){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,GUIObjectFrame,_("Notification block"));
GUIObject* obj;
obj=new GUIObject(40,50,240,36,GUIObjectLabel,_("Enter message here:"));
root->addChild(obj);
obj=new GUITextArea(50,90,500,100);
string tmp=objMap[1].second.c_str();
//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();
//Set the textField.
objectProperty=obj;
root->addChild(obj);
obj=new GUIObject(root->width*0.3,250-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgNotificationBlockOK";
obj->eventCallback=this;
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=this;
root->addChild(obj);
//Create the GUI overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
}
if(obj->type==TYPE_CONVEYOR_BELT || obj->type==TYPE_SHADOW_CONVEYOR_BELT){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
string s;
if(obj->type==TYPE_CONVEYOR_BELT){
s=_("Shadow Conveyor belt");
}else{
s=_("Conveyor belt");
}
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,s.c_str());
GUIObject* obj;
obj=new GUIObject(40,60,220,36,GUIObjectCheckBox,_("Enabled"),(objMap[1].second!="1"));
obj->name="cfgConveyorBlockEnabled";
obj->eventCallback=this;
objectProperty=obj;
root->addChild(obj);
obj=new GUIObject(40,100,240,36,GUIObjectLabel,_("Enter speed here:"));
root->addChild(obj);
obj=new GUIObject(240,100,320,36,GUIObjectTextBox,objMap[2].second.c_str());
//Set the textField.
secondObjectProperty=obj;
root->addChild(obj);
obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgConveyorBlockOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the GUI overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
}
if(obj->type==TYPE_PORTAL){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Check how many targets there are for this object.
string s1;
bool target=false;
if(!triggers[obj].empty()){
s1=_("Defined");
target=true;
}else{
s1=_("None");
}
//Now create the GUI.
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,_("Portal"));
GUIObject* obj;
obj=new GUIObject(70,60,310,36,GUIObjectCheckBox,_("Activate on touch"),(objMap[1].second=="1"));
obj->name="cfgPortalAutomatic";
obj->eventCallback=this;
objectProperty=obj;
root->addChild(obj);
obj=new GUIObject(70,100,240,36,GUIObjectLabel,_("Targets:"));
root->addChild(obj);
GUIObject* label=new GUIObject(360,100,-1,36,GUIObjectLabel,s1.c_str());
root->addChild(label);
label->render(0,0,false);
//Check if there are targets defined.
if(target){
obj=new GUIObject(label->left+label->width,100,36,36,GUIObjectButton,"x");
obj->name="cfgPortalUnlink";
obj->eventCallback=this;
root->addChild(obj);
}else{
//NOTE: The '+' is translated 5 pixels down to align with the 'x'.
obj=new GUIObject(label->left+label->width,105,36,36,GUIObjectButton,"+");
obj->name="cfgPortalLink";
obj->eventCallback=this;
root->addChild(obj);
}
obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgPortalOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the GUI overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
}
if(obj->type==TYPE_BUTTON || obj->type==TYPE_SWITCH){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Check how many targets there are for this object.
string s1;
bool targets=false;
if(!triggers[obj].empty()){
s1=tfm::format(_("%d Defined"),(int)triggers[obj].size());
targets=true;
}else{
s1=_("None");
}
//Now create the GUI.
string s;
if(obj->type==TYPE_BUTTON){
s=_("Button");
}else{
s=_("Switch");
}
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,s.c_str());
GUIObject* obj;
obj=new GUIObject(70,60,240,36,GUIObjectLabel,_("Behaviour:"));
root->addChild(obj);
obj=new GUISingleLineListBox(250,60,300,36);
obj->name="lstBehaviour";
vector<string> v;
v.push_back(_("On"));
v.push_back(_("Off"));
v.push_back(_("Toggle"));
(dynamic_cast<GUISingleLineListBox*>(obj))->item=v;
//Get the current behaviour.
if(objMap[1].second=="on"){
obj->value=0;
}else if(objMap[1].second=="off"){
obj->value=1;
}else{
//There's no need to check for the last one, since it's also the default.
obj->value=2;
}
objectProperty=obj;
root->addChild(obj);
obj=new GUIObject(70,100,240,36,GUIObjectLabel,_("Targets:"));
root->addChild(obj);
GUIObject* label=new GUIObject(250,100,-1,36,GUIObjectLabel,s1.c_str());
root->addChild(label);
label->render(0,0,false);
//NOTE: The '+' is translated 5 pixels down to align with the 'x'.
obj=new GUIObject(label->left+label->width,105,36,36,GUIObjectButton,"+");
obj->name="cfgTriggerLink";
obj->eventCallback=this;
root->addChild(obj);
//Check if there are targets defined.
if(targets){
obj=new GUIObject(label->left+label->width+40,100,36,36,GUIObjectButton,"x");
obj->name="cfgTriggerUnlink";
obj->eventCallback=this;
root->addChild(obj);
}
obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgTriggerOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the GUI overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
}
if(obj->type==TYPE_FRAGILE){
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if it contains the state data.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Create the GUI.
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,_("Fragile"));
GUIObject* obj;
obj=new GUIObject(70,60,240,36,GUIObjectLabel,_("State:"));
root->addChild(obj);
obj=new GUISingleLineListBox(250,60,300,36);
obj->name="lstBehaviour";
vector<string> v;
v.push_back(_("Complete"));
v.push_back(_("One step"));
v.push_back(_("Two steps"));
v.push_back(_("Gone"));
(dynamic_cast<GUISingleLineListBox*>(obj))->item=v;
//Get the current state.
obj->value=atoi(objMap[1].second.c_str());
objectProperty=obj;
root->addChild(obj);
obj=new GUIObject(root->width*0.3,200-44,-1,36,GUIObjectButton,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgFragileOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIObject(root->width*0.7,200-44,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the GUI overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
}
break;
}
default:
break;
}
}
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.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",currentId);
currentId++;
editorData["id"]=s;
obj->setEditorData(editorData);
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);
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->setPosition(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);
}
//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){
//Check for GUI events.
//Notification block configure events.
if(name=="cfgNotificationBlockOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["message"]=objectProperty->caption;
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Conveyor belt block configure events.
if(name=="cfgConveyorBlockOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["speed"]=secondObjectProperty->caption;
editorData["disabled"]=(objectProperty->value==0)?"1":"0";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Moving block configure events.
if(name=="cfgMovingBlockOK"){
if(GUIObjectRoot){
//Set if the moving block is enabled/disabled.
std::map<std::string,std::string> editorData;
editorData["disabled"]=(objectProperty->value==0)?"1":"0";
editorData["loop"]=(secondObjectProperty->value==1)?"1":"0";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgMovingBlockClrPath"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["MovingPosCount"]="0";
configuredObject->setEditorData(editorData);
std::map<GameObject*,vector<MovingPosition> >::iterator it;
it=movingBlocks.find(configuredObject);
if(it!=movingBlocks.end()){
(*it).second.clear();
}
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgMovingBlockMakePath"){
if(GUIObjectRoot){
//Set moving.
moving=true;
movingBlock=configuredObject;
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Portal block configure events.
if(name=="cfgPortalOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["automatic"]=(objectProperty->value==1)?"1":"0";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgPortalLink"){
//We set linking true.
linking=true;
linkingTrigger=configuredObject;
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
if(name=="cfgPortalUnlink"){
std::map<GameObject*,vector<GameObject*> >::iterator it;
it=triggers.find(configuredObject);
if(it!=triggers.end()){
//Remove the targets.
(*it).second.clear();
}
//We reset the destination.
std::map<std::string,std::string> editorData;
editorData["destination"]="";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
//Trigger block configure events.
if(name=="cfgTriggerOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["behaviour"]=(dynamic_cast<GUISingleLineListBox*>(objectProperty))->item[objectProperty->value];
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgTriggerLink"){
//We set linking true.
linking=true;
linkingTrigger=configuredObject;
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
if(name=="cfgTriggerUnlink"){
std::map<GameObject*,vector<GameObject*> >::iterator it;
it=triggers.find(configuredObject);
if(it!=triggers.end()){
//Remove the targets.
(*it).second.clear();
}
//We give the trigger a new id to prevent activating unlinked targets.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",currentId);
currentId++;
editorData["id"]=s;
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
//Fragile configuration.
if(name=="cfgFragileOK"){
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",objectProperty->value);
editorData["state"]=s;
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
//Cancel.
if(name=="cfgCancel"){
if(GUIObjectRoot){
//Delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//LevelSetting events.
if(name=="lvlSettingsOK"){
levelName=objectProperty->caption;
levelTheme=secondObjectProperty->caption;
//target time and recordings.
string s=levelTimeProperty->caption;
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=int(atof(s.c_str())*40.0+0.5);
}
s=levelRecordingsProperty->caption;
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
//And delete the GUI.
if(GUIObjectRoot){
objectProperty=NULL;
secondObjectProperty=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="lvlSettingsCancel"){
if(GUIObjectRoot){
//Delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
////////////////LOGIC////////////////////
void LevelEditor::logic(){
if(playMode){
//PlayMode so let the game do it's logic.
Game::logic();
}else{
//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.
{
SDL_Rect r[3]={toolbarRect};
int m=1;
if(toolbox!=NULL && tool==ADD && toolbox->visible)
r[m++]=toolbox->getRect();
if(selectionPopup!=NULL)
r[m++]=selectionPopup->getRect();
setCamera(r,m);
}
//It isn't playMode so the mouse should be checked.
tooltip=-1;
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//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-460)/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;
//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.
if(selectionDrag){
showSelectionDrag();
}else{
if(tool==ADD){
showCurrentObject();
}
if(tool==CONFIGURE){
showConfigure();
}
}
//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 tool box (if any)
if(toolbox!=NULL && tool==ADD && toolbox->visible){
toolbox->render();
}
//On top of all render the toolbar.
applySurface(toolbarRect.x,toolbarRect.y,toolbar,screen,NULL);
//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,_("Configure"),fg);
break;
case 4:
tip=TTF_RenderUTF8_Blended(fontText,_("Play"),fg);
break;
case 6:
tip=TTF_RenderUTF8_Blended(fontText,_("Level settings"),fg);
break;
case 7:
tip=TTF_RenderUTF8_Blended(fontText,_("Save level"),fg);
break;
case 8:
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-440)/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.
color=0xFFFFFF00;
drawGUIBox((SCREEN_WIDTH-440)/2+(tool*40)+(tool*10),SCREEN_HEIGHT-46,42,42,screen,color);
//Render selection popup (if any)
if(selectionPopup!=NULL){
if(linking){
//If we switch to linking mode then delete it
delete selectionPopup;
selectionPopup=NULL;
}else{
selectionPopup->render();
}
}
}
}
void LevelEditor::renderHUD(){
//Switch the tool.
switch(tool){
case CONFIGURE:
//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);
}
break;
default:
break;
}
}
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-460)/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_COLLECTABLE,
+ TYPE_PUSHABLE
};
diff --git a/src/LevelEditor.h b/src/LevelEditor.h
index 731d6dd..280c52b 100644
--- a/src/LevelEditor.h
+++ b/src/LevelEditor.h
@@ -1,287 +1,288 @@
/*
* 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 LEVELEDITOR_H
#define LEVELEDITOR_H
#include "GameState.h"
#include "GameObjects.h"
#include "Player.h"
#include "Game.h"
#include "GUIObject.h"
#include <vector>
#include <map>
#include <string>
#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
//Class that represents a moving position for moving blocks.
class MovingPosition{
public:
//Integer containing the relative time used to store in the level.
int time;
//The x location.
int x;
//The x location.
int y;
//Constructor.
//x: The x position relative to the moving block's position.
//y: The y position relative to the moving block's position.
//time: The time it takes from the previous position to here.
MovingPosition(int x,int y,int time);
//Destructor.
~MovingPosition();
//This will update the moving position.
//x: The x position relative to the moving block's position.
//y: The y position relative to the moving block's position.
void updatePosition(int x,int y);
};
//internal tool box class
class LevelEditorToolbox;
//internal selection popup class
class LevelEditorSelectionPopup;
//The LevelEditor state, it's based on the Game state.
class LevelEditor: public Game{
friend class LevelEditorToolbox;
friend class LevelEditorSelectionPopup;
private:
//Boolean if the user isplaying/testing the level.
bool playMode;
//Enumaration containing the tools.
//SELECT: The select tool, for selecting/dragging blocks.
//ADD: For adding blocks.
//REMOVE: For removing blocks.
//CONFIGURE: Used to configure special blocks.
enum Tools{
SELECT,
ADD,
REMOVE,
CONFIGURE,
NUMBER_TOOLS
};
//The tool the user has selected.
Tools tool;
//The toolbar surface.
SDL_Surface* toolbar;
//Rectangle the size and location of the toolbar on screen.
SDL_Rect toolbarRect;
//The editor tool box (if any)
LevelEditorToolbox* toolbox;
//The selection popup (if any)
LevelEditorSelectionPopup* selectionPopup;
//Vector containing pointers to the selected GameObjects.
vector<GameObject*> selection;
//The selection square.
SDL_Surface* selectionMark;
//Surface used for drawing transparent selection/dragging.
SDL_Surface* placement;
//A circle at the location of moving positions in configure mode.
SDL_Surface* movingMark;
//The current type of block to place in Add mode.
int currentType;
//Boolean if the shift button is pressed.
bool pressedShift;
//Boolean if the left mouse button is pressed.
bool pressedLeftMouse;
//Boolean if the mouse is dragged. (Left button pressed and moved)
bool dragging;
//The camera x velocity.
int cameraXvel;
int cameraYvel;
//SDL_Rect used to store the camera's location when entering playMode.
SDL_Rect cameraSave;
//Boolean if the selection is dragged.
bool selectionDrag;
//Pointer to the gameobject that's the center of the drag.
GameObject* dragCenter;
//Integer containing a unique id.
//Everytime a new id is needed it will increase by one.
unsigned int currentId;
//Vector containing the trigger GameObjects.
map<GameObject*,vector<GameObject*> > triggers;
//Boolean used in configure mode when linking triggers with their targets.
bool linking;
//Pointer to the trigger that's is being linked.
GameObject* linkingTrigger;
//Vector containing the moving GameObjects.
map<GameObject*,vector<MovingPosition> > movingBlocks;
//Integer containing the speed the block is moving for newly added blocks.
//The movingSpeed is capped at 100.
int movingSpeed;
//Boolean used in configure mode when configuring moving blocks.
bool moving;
//Pointer to the moving block that's is being configured.
GameObject* movingBlock;
//Value used for placing the Movespeed label
int movingSpeedWidth;
//The clipboard.
vector<map<string,string> > clipboard;
//Pointer to a GUIObject for a property of the object.
//Only used in the configure tool.
GUIObject* objectProperty;
//Pointer to a GUIObject for a property of the object.
//Only used in the configure tool.
GUIObject* secondObjectProperty;
//Pointer to the object that is being configured.
GameObject* configuredObject;
//String containing the levelTheme.
std::string levelTheme;
//Integer containing the button of which a tool tip should be shown.
int tooltip;
//GUI event handling is done here.
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType);
//Method that will let you configure the levelSettings.
void levelSettings();
//Method used to save the level.
//fileName: Thge filename to write the level to.
void saveLevel(string fileName);
//Method used to convert a given x and y to snap to grid.
//x: Pointer to the x location.
//y: Pointer to the y location.
void snapToGrid(int* x,int* y);
public:
//Array containing the ids of different block types in a wanted order
//Maybe also useful to disable deprecated block types in the editor
//PLEASE NOTE: Must be updated for new block types
//Ordered for Edward Liis proposal:
//Normal->Shadow->Spikes->Fragile
//Normal moving->Shadow moving->Moving spikes
//Conveyor belt->Shadow conveyor belt
//Button->Switch->Portal->Swap->Checkpoint->Notification block
//Player start->Shadow start->Exit
+ //Collectable->Pushable
- static const int EDITOR_ORDER_MAX=19;
+ static const int EDITOR_ORDER_MAX=20;
static const int editorTileOrder[EDITOR_ORDER_MAX];
protected:
//Inherits the function loadLevelFromNode from Game class.
virtual void loadLevelFromNode(TreeStorageNode* obj, const std::string& fileName);
public:
//Constructor.
LevelEditor();
//Destructor.
~LevelEditor();
//Method that will reset some default values.
void reset();
//Inherited from Game(State).
void handleEvents();
void logic();
void render();
void resize();
//Method used to draw the currentType on the placement surface.
//This will only be called when the tool is ADD.
void showCurrentObject();
//Method used to draw the selection that's being dragged.
void showSelectionDrag();
//Method used to draw configure tool specific things like moving positions, teleport lines.
void showConfigure();
//Method that will render the HUD.
//It will be rendered after the placement suface but before the toolbar.
void renderHUD();
//Method called after loading a level.
//It will fill the triggers vector.
void postLoad();
//Event that is invoked when there's a mouse click on an object.
//obj: Pointer to the GameObject clicked on.
//selected: Boolean if the GameObject that has been clicked on was selected.
void onClickObject(GameObject* obj,bool selected);
//Event that is invoked when there's a right mouse button click on an object.
//obj: Pointer to the GameObject clicked on.
//selected: Boolean if the GameObject that has been clicked on was selected.
void onRightClickObject(GameObject* obj,bool selected);
//Event that is invoked when there's a mouse click but not on any object.
//x: The x location of the click on the game field (+= camera.x).
//y: The y location of the click on the game field (+= camera.y).
void onClickVoid(int x,int y);
//Event that is invoked when the dragging starts.
//x: The x location the drag started. (mouse.x+camera.x)
//y: The y location the drag started. (mouse.y+camera.y)
void onDragStart(int x,int y);
//Event that is invoked when the mouse is dragged.
//dx: The relative x distance the mouse dragged.
//dy: The relative y distance the mouse dragged.
void onDrag(int dx,int dy);
//Event that is invoked when the mouse stopped dragging.
//x: The x location the drag stopped. (mouse.x+camera.x)
//y: The y location the drag stopped. (mouse.y+camera.y)
void onDrop(int x,int y);
//Event that is invoked when the camera is moved.
//dx: The relative x distance the camera moved.
//dy: The relative y distance the camera moved.
void onCameraMove(int dx,int dy);
//Event that is invoked when enter is pressed above an object.
//obj: Pointer to the GameObject entered above.
void onEnterObject(GameObject* obj);
//Method used to add a GameObject to the level.
//obj: Pointer to the gameobject to add.
void addObject(GameObject* obj);
//Method used to move a GameObject from the level.
//obj: Pointer to the gameobject to move.
//x: The new x location of the GameObject.
//y: The new y location of the GameObject.
void moveObject(GameObject* obj,int x,int y);
//Method used to remove a GameObject from the level.
//obj: Pointer to the gameobject to remove.
void removeObject(GameObject* obj);
};
#endif
diff --git a/src/Player.cpp b/src/Player.cpp
index 9cb1d96..8dc222a 100644
--- a/src/Player.cpp
+++ b/src/Player.cpp
@@ -1,1470 +1,1478 @@
/*
* 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 "Player.h"
#include "Game.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include "MD5.h"
#include <iostream>
#include <fstream>
#include <SDL/SDL.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
using namespace std;
#ifdef RECORD_FILE_DEBUG
string recordKeyPressLog,recordKeyPressLog_saved;
vector<SDL_Rect> recordPlayerPosition,recordPlayerPosition_saved;
#endif
Player::Player(Game* objParent):xVelBase(0),yVelBase(0),objParent(objParent),recordSaved(false),
inAirSaved(false),isJumpSaved(false),onGroundSaved(false),canMoveSaved(false),holdingOtherSaved(false){
//Set the dimensions of the player.
//The size of the player is 21x40.
box.x=0;
box.y=0;
box.w=21;
box.h=40;
//Set his velocity to zero.
xVel=0;
yVel=0;
//Set the start position.
fx=0;
fy=0;
//Set some default values.
inAir=true;
isJump=false;
onGround=true;
shadowCall=false;
shadow=false;
canMove=true;
holdingOther=false;
dead=false;
record=false;
downKeyPressed=false;
spaceKeyPressed=false;
recordIndex=-1;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog.clear();
recordKeyPressLog_saved.clear();
recordPlayerPosition.clear();
recordPlayerPosition_saved.clear();
#endif
objNotificationBlock=NULL;
//Some default values for animation variables.
direction=0;
jumpTime=0;
state=stateSaved=0;
//xVelSaved is used to store if there's a state saved or not.
xVelSaved=yVelSaved=0x80000000;
objCurrentStand=objLastStand=objLastTeleport=objShadowBlock=NULL;
}
Player::~Player(){
//Do nothing here
}
bool Player::isPlayFromRecord(){
return recordIndex>=0; // && recordIndex<(int)recordButton.size();
}
//get the game record object.
std::vector<int>* Player::getRecord(){
return &recordButton;
}
#ifdef RECORD_FILE_DEBUG
string& Player::keyPressLog(){
return recordKeyPressLog;
}
vector<SDL_Rect>& Player::playerPosition(){
return recordPlayerPosition;
}
#endif
//play the record.
void Player::playRecord(){
//TODO:
recordIndex=0;
}
void Player::spaceKeyDown(class Shadow* shadow){
//Start recording or stop, depending on the recording state.
if(record==false){
//We start recording.
if(shadow->called==true){
//The shadow is still busy so first stop him before we can start recording.
shadowCall=false;
shadow->called=false;
shadow->playerButton.clear();
}else if(!dead){
//The shadow isn't moving and we aren't dead so start recording.
record=true;
//We start a recording meaning we need to increase recordings by one.
objParent->recordings++;
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
statsMgr.recordTimes++;
if(statsMgr.recordTimes==100) statsMgr.newAchievement("record100");
if(statsMgr.recordTimes==1000) statsMgr.newAchievement("record1k");
}
}
}else{
//The player is recording so stop recording and call the shadow.
record=false;
shadowCall=true;
}
}
void Player::handleInput(class Shadow* shadow){
//Check if we should read the input from record file.
//Actually, we read input from record file in
//another function shadowSetState.
bool readFromRecord=false;
if(recordIndex>=0 && recordIndex<(int)recordButton.size()) readFromRecord=true;
if(!readFromRecord){
//Reset horizontal velocity.
xVel=0;
if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
//Walking to the right.
xVel+=7;
}
if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
//Walking to the left.
if(xVel!=0 && !dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
//Horizontal confusion achievement :)
statsMgr.newAchievement("horizontal");
}
xVel-=7;
}
//Check if a key has been released.
if(/*event.type==SDL_KEYUP || */!inputMgr.isKeyDown(INPUTMGR_ACTION)){
//It has so downKeyPressed can't be true.
downKeyPressed=false;
}
/*
//Don't reset spaceKeyPressed or when you press the space key
//and release another key then the bug occurs. (ticket #44)
if(event.type==SDL_KEYUP || !inputMgr.isKeyDown(INPUTMGR_SPACE)){
spaceKeyPressed=false;
}*/
}
//Check if a key is pressed (down).
if(inputMgr.isKeyDownEvent(INPUTMGR_JUMP) && !readFromRecord){
//The up key, if we aren't in the air we start jumping.
//Fixed a potential bug
if(!inAir && !isJump){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Jump key pressed\n",objParent->time);
cout<<c;
recordKeyPressLog+=c;
#endif
isJump=true;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) && !readFromRecord){
//Fixed a potential bug
if(!spaceKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Space key pressed\n",objParent->time);
cout<<c;
recordKeyPressLog+=c;
#endif
spaceKeyDown(shadow);
spaceKeyPressed=true;
}
}else if(inputMgr.isKeyUpEvent(INPUTMGR_SPACE) && !readFromRecord){
if(record && getSettings()->getBoolValue("quickrecord")){
spaceKeyDown(shadow);
spaceKeyPressed=true;
}
}else if(record && !readFromRecord && inputMgr.isKeyDownEvent(INPUTMGR_CANCELRECORDING)){
//Cancel current recording
//Search the recorded button and clear the last space key press
int i=recordButton.size()-1;
for(;i>=0;i--){
if(recordButton[i] & PlayerButtonSpace){
recordButton[i] &= ~PlayerButtonSpace;
break;
}
}
if(i>=0){
//Clear the recording at the player's side.
playerButton.clear();
line.clear();
//reset the record flag
record=false;
//decrese the record count
objParent->recordings--;
}else{
cout<<"Failed to find last recording"<<endl;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_ACTION)){
//Downkey is pressed.
//Fixed a potential bug
if(!downKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Action key pressed\n",objParent->time);
cout<<c;
recordKeyPressLog+=c;
#endif
downKeyPressed=true;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SAVE)){
//F2 only works in the level editor.
if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
//Save the state. (delayed)
if(objParent)
objParent->saveStateNextTime=true;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_LOAD) && !readFromRecord){
//F3 is used to load the last state.
if(objParent)
objParent->loadStateNextTime=true;
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SWAP)){
//F4 will swap the player and the shadow, but only in the level editor.
if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
swapState(shadow);
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_TELEPORT)){
//F5 will revive and teleoprt the player to the cursor. Only works in the level editor.
//Shift+F5 teleports the shadow.
if(stateID==STATE_LEVEL_EDITOR){
//get the position of the cursor.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
if(inputMgr.isKeyDown(INPUTMGR_SHIFT)){
//teleports the shadow.
shadow->dead=false;
shadow->box.x=x;
shadow->box.y=y;
}else{
//teleports the player.
dead=false;
box.x=x;
box.y=y;
}
//play sound?
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,swapSound,0);
}
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SUICIDE)){
//F12 is suicide and only works in the leveleditor.
if(stateID==STATE_LEVEL_EDITOR){
die();
shadow->die();
}
}
}
void Player::setPosition(int x,int y){
box.x=x;
box.y=y;
}
void Player::move(vector<GameObject*> &levelObjects){
//Pointer to a checkpoint.
GameObject* objCheckPoint=NULL;
//Pointer to a swap.
GameObject* objSwap=NULL;
//Set the objShadowBlock to NULL.
//Only for swapping to prevent the shadow from swapping in a shadow block.
objShadowBlock=NULL;
//Set the objNotificationBlock to NULL.
objNotificationBlock=NULL;
//Set the object the player is currently standing to NULL.
objCurrentStand=NULL;
//Check if the player is still alive.
if(dead==false){
//Add gravity acceleration to the vertical velocity.
//NOTE: The x velocity is already set during the input handling, so is the jumping.
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<levelObjects.size(); o++){
//Check if the player can collide with this game object.
if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
continue;
//Check if the block is inside the frame.
if(checkCollision(frame,levelObjects[o]->getBox())){
objects.push_back(levelObjects[o]);
continue;
}
//Additional checks need to be made for moving blocks.
if(levelObjects[o]->type==TYPE_MOVING_BLOCK || levelObjects[o]->type==TYPE_MOVING_SHADOW_BLOCK || levelObjects[o]->type==TYPE_MOVING_SPIKES) {
//Check the movement of these blocks to see if they will collide.
SDL_Rect v=levelObjects[o]->getBox(BoxType_Velocity);
SDL_Rect r=levelObjects[o]->getBox();
r.x+=v.x;
r.y+=v.y;
if(checkCollision(frame,r)) {
objects.push_back(levelObjects[o]);
}
}
}
//Boolean if the player is moved, used for squash detection.
bool playerMoved=false;
//Indicates player or shadow is traveling, we should add it to traveling distance
bool isTraveling=true;
//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;
//Check if the player can move.
if(canMove==true){
//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){
- box.x=r.x-box.w;
-
playerMoved=true;
+
+ //In case of a pushable block we give it velocity.
+ if(objects[o]->type==TYPE_PUSHABLE){
+ (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){
- box.x=r.x+r.w;
-
playerMoved=true;
+
+ //In case of a pushable block we give it velocity.
+ if(objects[o]->type==TYPE_PUSHABLE){
+ (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;
//Pointer to the object the player standed on.
GameObject* lastStand=NULL;
//Assume we are in air and are able to move unless proven otherwise (???).
inAir=true;
canMove=true;
//Boolean if the player can teleport.
bool canTeleport=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;
lastStand=objects[o];
lastStand->onEvent(GameObjectEvent_PlayerIsOn);
//The player is moved, if it's a moving block check for squating.
if(v.y!=0){
playerMoved=true;
}
}
}else{
//FIXME: The player can have a yVel of 0 and get squashed if he is standing on the other.
bool holding=objParent->shadow.holdingOther;
if(shadow)
holding=objParent->player.holdingOther;
if(yVel<=v.y+1 || holding){
yVel=v.y>0?v.y:0;
if(box.y<r.y+r.h){
if(!holding)
box.y=r.y+r.h;
//The player is moved, if it's a moving block check for squating.
if(v.y!=0){
playerMoved=true;
}
}
}
}
}
}
//Check if the player was moved, if so check if the player is squashed.
if(playerMoved){
for(unsigned int o=0;o<levelObjects.size();o++){
SDL_Rect r2=levelObjects[o]->getBox();
if(levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this) && checkCollision(box,r2)){
//The player is squashed so first move him back.
box.x=lastX;
box.y=lastY;
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(shadow) statsMgr.shadowSquashed++;
else statsMgr.playerSquashed++;
switch(statsMgr.playerSquashed+statsMgr.shadowSquashed){
case 1:
statsMgr.newAchievement("squash1");
break;
case 50:
statsMgr.newAchievement("squash50");
break;
}
}
//Now call the die method.
die();
}
}
}
//Check if the player fell of the level, if so let him die but without animation.
if(box.y>LEVEL_HEIGHT)
die(false);
//Check if the player changed blocks, meaning stepped onto a block.
objCurrentStand=lastStand;
if(lastStand!=objLastStand){
objLastStand=lastStand;
if(lastStand){
//Call the walk on event of the laststand.
lastStand->onEvent(GameObjectEvent_PlayerWalkOn);
//Bugfix for Fragile blocks.
if(lastStand->type==TYPE_FRAGILE && !lastStand->queryProperties(GameObjectProperty_PlayerCanWalkOn,this)){
inAir=true;
onGround=false;
isJump=false;
}
}
}
//Now check the functional blocks.
for(unsigned int o=0;o<levelObjects.size();o++){
//Check for collision.
if(checkCollision(box,levelObjects[o]->getBox())){
//Now switch the type.
switch(levelObjects[o]->type){
case TYPE_CHECKPOINT:
{
//If we're not the shadow set the gameTip to Checkpoint.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_CHECKPOINT;
//And let objCheckPoint point to this object.
objCheckPoint=levelObjects[o];
break;
}
case TYPE_SWAP:
{
//If we're not the shadow set the gameTip to swap.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_SWAP;
//And let objSwap point to this object.
objSwap=levelObjects[o];
break;
}
case TYPE_EXIT:
{
//Make sure we're not in the leveleditor.
if(stateID==STATE_LEVEL_EDITOR)
break;
//Check to see if we have enough keys to finish the level
if(objParent->currentCollectables>=objParent->totalCollectables){
//We can't just handle the winning here (in the middle of the update cycle)/
//So set won in Game true.
objParent->won=true;
}
break;
}
case TYPE_PORTAL:
{
//Check if the teleport id isn't empty.
if((dynamic_cast<Block*>(levelObjects[o]))->id.empty()){
cerr<<"Warning: Invalid teleport id!"<<endl;
canTeleport=false;
}
//If we're not the shadow set the gameTip to portal.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_PORTAL;
//Check if we can teleport and should (downkey -or- auto).
if(canTeleport && (downKeyPressed || (levelObjects[o]->queryProperties(GameObjectProperty_Flags,this)&1))){
canTeleport=false;
if(downKeyPressed || levelObjects[o]!=objLastTeleport){
downKeyPressed=false;
//Loop the levelobjects again to find the destination.
for(unsigned int oo=o+1;;){
//We started at our index+1.
//Meaning that if we reach the end of the vector then we need to start at the beginning.
if(oo>=levelObjects.size())
oo-=(int)levelObjects.size();
//It also means that if we reach the same index we need to stop.
//If the for loop breaks this way then we have no succes.
if(oo==o){
//Couldn't teleport so play the error sound.
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,errorSound,0);
}
break;
}
//Check if the second (oo) object is a portal.
if(levelObjects[oo]->type==TYPE_PORTAL){
//Check the id against the destination of the first portal.
if((dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Call the event.
levelObjects[o]->onEvent(GameObjectEvent_OnToggle);
objLastTeleport=levelObjects[oo];
//Get the destination location and teleport the player.
SDL_Rect r=levelObjects[oo]->getBox();
box.x=r.x+5;
box.y=r.y+2;
//We don't count it to traveling distance.
isTraveling=false;
//Check if music/sound is enabled.
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,swapSound,0);
}
break;
}
}
//Increase oo.
oo++;
}
}
}
break;
}
case TYPE_SWITCH:
{
//If we're not the shadow set the gameTip to switch.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_SWITCH;
//If the down key is pressed then invoke an event.
if(downKeyPressed){
//Play the animation.
levelObjects[o]->playAnimation(1);
//Check if sound is enabled, if so play the toggle sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,toggleSound,0);
}
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
statsMgr.switchTimes++;
//TODO: achievements
}
if(objParent!=NULL){
//Make sure that the id isn't emtpy.
if(!(dynamic_cast<Block*>(levelObjects[o]))->id.empty()){
objParent->broadcastObjectEvent(0x10000 | (levelObjects[o]->queryProperties(GameObjectProperty_Flags,this)&3),
-1,(dynamic_cast<Block*>(levelObjects[o]))->id.c_str());
}else{
cerr<<"Warning: invalid switch id!"<<endl;
}
}
}
break;
}
case TYPE_SHADOW_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
{
//This only applies to the player.
if(!shadow)
objShadowBlock=levelObjects[o];
break;
}
case TYPE_NOTIFICATION_BLOCK:
{
//This only applies to the player.
if(!shadow)
objNotificationBlock=levelObjects[o];
break;
}
case TYPE_COLLECTABLE:
{
//Check if collectable is active (if it's not it's equal to 1(inactive))
if(levelObjects[o]->queryProperties(GameObjectProperty_Flags, this)==0) {
//Toggle an event
levelObjects[o]->onEvent(GameObjectEvent_OnToggle);
//Increase the current number of collectables
objParent->currentCollectables++;
if(getSettings()->getBoolValue("sound"))
Mix_PlayChannel(-1,collectSound,0);
//Open exit(s)
if(objParent->currentCollectables>=objParent->totalCollectables){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOn);
}
}
}
}
}
break;
}
}
//Now check for the spike property.
if(levelObjects[o]->queryProperties(GameObjectProperty_IsSpikes,this)){
//It is so get the collision box.
SDL_Rect r=levelObjects[o]->getBox();
//TODO: pixel-accuracy hit test.
//For now we shrink the box.
r.x+=2;
r.y+=2;
r.w-=4;
r.h-=4;
//Check collision, if the player collides then let him die.
if(checkCollision(box,r)){
die();
}
}
}
}
//Check if the player can teleport.
if(canTeleport)
objLastTeleport=NULL;
//Check the checkpoint pointer only if the downkey is pressed.
//new: don't save the game if playing game record
if(objParent!=NULL && downKeyPressed && objCheckPoint!=NULL && !isPlayFromRecord()){
//Checkpoint thus save the state.
if(objParent->canSaveState()){
objParent->saveStateNextTime=true;
objParent->objLastCheckPoint=objCheckPoint;
}
}
//Check the swap pointer only if the down key is pressed.
if(objSwap!=NULL && downKeyPressed && objParent!=NULL){
//Now check if the shadow we're the shadow or not.
if(shadow){
if(!(dead || objParent->player.dead)){
//Check if the player isn't in front of a shadow block.
if(!objParent->player.objShadowBlock){
objParent->player.swapState(this);
objSwap->playAnimation(1);
//We don't count it to traveling distance.
isTraveling=false;
//Note: Statistics updated in swapState() function.
}else{
//We can't swap so play the error sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,errorSound,0);
}
}
}
}else{
if(!(dead || objParent->shadow.dead)){
//Check if the player isn't in front of a shadow block.
if(!objShadowBlock){
swapState(&objParent->shadow);
objSwap->playAnimation(1);
//We don't count it to traveling distance.
isTraveling=false;
//Note: Statistics updated in swapState() function.
}else{
//We can't swap so play the error sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,errorSound,0);
}
}
}
}
}
//Determine the correct theme state.
if(!dead){
//Set the direction depending on the velocity.
if(xVel>0)
direction=0;
else if(xVel<0)
direction=1;
//Check if the player is in the air.
if(!inAir){
//On the ground so check the direction and movement.
if(xVel>0){
if(appearance.currentStateName!="walkright"){
appearance.changeState("walkright");
}
}else if(xVel<0){
if(appearance.currentStateName!="walkleft"){
appearance.changeState("walkleft");
}
}else if(xVel==0){
if(direction==1){
appearance.changeState("standleft");
}else{
appearance.changeState("standright");
}
}
}else{
//Check for jump appearance (inAir).
if(direction==1){
if(yVel>0){
if(appearance.currentStateName!="fallleft")
appearance.changeState("fallleft");
}else{
if(appearance.currentStateName!="jumpleft")
appearance.changeState("jumpleft");
}
}else{
if(yVel>0){
if(appearance.currentStateName!="fallright")
appearance.changeState("fallright");
}else{
if(appearance.currentStateName!="jumpright")
appearance.changeState("jumpright");
}
}
}
}
//Update traveling distance statistics.
if(isTraveling && (lastX!=box.x || lastY!=box.y) && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
float dx=float(lastX-box.x),dy=float(lastY-box.y);
float d0=statsMgr.playerTravelingDistance+statsMgr.shadowTravelingDistance,
d=sqrtf(dx*dx+dy*dy)/50.0f;
if(shadow) statsMgr.shadowTravelingDistance+=d;
else statsMgr.playerTravelingDistance+=d;
//Update achievement
d+=d0;
if(d0<=100.0f && d>=100.0f) statsMgr.newAchievement("travel100");
if(d0<=1000.0f && d>=1000.0f) statsMgr.newAchievement("travel1k");
if(d0<=10000.0f && d>=10000.0f) statsMgr.newAchievement("travel10k");
if(d0<=42195.0f && d>=42195.0f) statsMgr.newAchievement("travel42k");
}
}
//Finally we reset some stuff.
downKeyPressed=false;
xVelBase=0;
yVelBase=0;
}
void Player::jump(){
//Check if the player is dead or not.
if(dead==true){
//The player can't jump if he's dead.
isJump=false;
}
//Check if the player can jump.
if(isJump==true && inAir==false){
//Set the jump velocity.
yVel=-13;
inAir=true;
isJump=false;
jumpTime++;
//Update statistics
if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(shadow) statsMgr.shadowJumps++;
else statsMgr.playerJumps++;
if(statsMgr.playerJumps+statsMgr.shadowJumps==1000) statsMgr.newAchievement("frog");
}
//Check if sound is enabled, if so play the jump sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,jumpSound,0);
}
}
}
void Player::show(){
//Check if we should render the recorded line.
//Only do this when we're recording and we're not the shadow.
if(shadow==false && record==true){
//FIXME: Adding an entry not in update but in render?
line.push_back(SDL_Rect());
line[line.size()-1].x=box.x+11;
line[line.size()-1].y=box.y+20;
//Loop through the line dots and draw them.
for(int l=0; l<(signed)line.size(); l++){
appearance.drawState("line",screen,line[l].x-camera.x,line[l].y-camera.y,NULL);
}
}
//NOTE: We do logic here, because it's only needed by the appearance.
appearance.updateAnimation();
appearance.draw(screen, box.x-camera.x, box.y-camera.y, NULL);
}
void Player::shadowSetState(){
int currentKey=0;
/*//debug
extern int block_test_count;
extern bool block_test_only;
if(SDL_GetKeyState(NULL)[SDLK_p]){
block_test_count=recordButton.size();
}
if(block_test_count==(int)recordButton.size()){
block_test_only=true;
}*/
//Check if we should read the input from record file.
if(recordIndex>=0){ // && recordIndex<(int)recordButton.size()){
//read the input from record file
if(recordIndex<(int)recordButton.size()){
currentKey=recordButton[recordIndex];
recordIndex++;
}
//Reset horizontal velocity.
xVel=0;
if(currentKey&PlayerButtonRight){
//Walking to the right.
xVel+=7;
}
if(currentKey&PlayerButtonLeft){
//Walking to the left.
xVel-=7;
}
if(currentKey&PlayerButtonJump){
//The up key, if we aren't in the air we start jumping.
if(inAir==false){
isJump=true;
}else{
//Shouldn't go here
cout<<"Replay BUG"<<endl;
}
}
//check the down key
downKeyPressed=(currentKey&PlayerButtonDown)!=0;
//check the space key
if(currentKey&PlayerButtonSpace){
spaceKeyDown(&objParent->shadow);
}
}else{
//read the input from keyboard.
recordIndex=-1;
//Check for xvelocity.
if(xVel>0)
currentKey|=PlayerButtonRight;
if(xVel<0)
currentKey|=PlayerButtonLeft;
//Check for jumping.
if(isJump){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Jump key recorded\n",objParent->time-1);
cout<<c;
recordKeyPressLog+=c;
#endif
currentKey|=PlayerButtonJump;
}
//Check if the downbutton is pressed.
if(downKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Action key recorded\n",objParent->time-1);
cout<<c;
recordKeyPressLog+=c;
#endif
currentKey|=PlayerButtonDown;
}
if(spaceKeyPressed){
#ifdef RECORD_FILE_DEBUG
char c[64];
sprintf(c,"[%05d] Space key recorded\n",objParent->time-1);
cout<<c;
recordKeyPressLog+=c;
#endif
currentKey|=PlayerButtonSpace;
}
//Record it.
recordButton.push_back(currentKey);
}
#ifdef RECORD_FILE_DEBUG
if(recordIndex>=0){
if(recordIndex>0 && recordIndex<=int(recordPlayerPosition.size())/2){
SDL_Rect &r1=recordPlayerPosition[recordIndex*2-2];
SDL_Rect &r2=recordPlayerPosition[recordIndex*2-1];
if(r1.x!=box.x || r1.y!=box.y || r2.x!=objParent->shadow.box.x || r2.y!=objParent->shadow.box.y){
char c[192];
sprintf(c,"Replay ERROR [%05d] %d %d %d %d Expected: %d %d %d %d\n",
objParent->time-1,box.x,box.y,objParent->shadow.box.x,objParent->shadow.box.y,r1.x,r1.y,r2.x,r2.y);
cout<<c;
}
}
}else{
recordPlayerPosition.push_back(box);
recordPlayerPosition.push_back(objParent->shadow.box);
}
#endif
//reset spaceKeyPressed.
spaceKeyPressed=false;
//Only add an entry if the player is recording.
if(record){
//Add the action.
if(!dead){
playerButton.push_back(currentKey);
//Change the state.
state++;
}
}
}
void Player::shadowGiveState(Shadow* shadow){
//Check if the player calls the shadow.
if(shadowCall==true){
//Clear any recording still with the shadow.
shadow->playerButton.clear();
//Loop the recorded moves and add them to the one of the shadow.
for(unsigned int s=0;s<playerButton.size();s++){
shadow->playerButton.push_back(playerButton[s]);
}
//Reset the state of both the player and the shadow.
stateReset();
shadow->stateReset();
//Clear the recording at the player's side.
playerButton.clear();
line.clear();
//Set shadowCall false
shadowCall=false;
//And let the shadow know that the player called him.
shadow->meCall();
}
}
void Player::stateReset(){
//Reset the state by setting it to 0.
state=0;
}
void Player::otherCheck(class Player* other){
//First make sure the player isn't dead.
//And check for velocity of the block the player is standing on.
if(!dead){
if(objCurrentStand!=NULL){
//Now get the velocity of the object the player is standing on.
SDL_Rect v=objCurrentStand->getBox(BoxType_Velocity);
//Set the base velocity to the velocity of the object.
xVelBase=v.x;
yVelBase=v.y;
//Already move the player box.
box.x+=v.x;
box.y+=v.y;
}
}
//Now do the same for the shadow.
if(!other->dead){
if(other->objCurrentStand!=NULL){
//Now get the velocity of the object the shadow is standing on.
SDL_Rect v=other->objCurrentStand->getBox(BoxType_Velocity);
//Set the base velocity to the velocity of the object.
other->xVelBase=v.x;
other->yVelBase=v.y;
//Already move the shadow box.
other->box.x+=v.x;
other->box.y+=v.y;
}
}
//Now check if the player is on the shadow.
//First make sure they are both alive.
if(!dead && !other->dead){
//Get the box of the shadow.
SDL_Rect boxShadow=other->getBox();
//Check if the player is on top of the shadow.
if(checkCollision(box,boxShadow)==true){
//We have collision now check if the other is standing on top of you.
if(box.y+box.h<=boxShadow.y+13 && !other->inAir){
int yVelocity=yVel-1;
if(yVelocity>0){
box.y-=yVel;
box.y+=boxShadow.y-(box.y+box.h);
inAir=false;
canMove=false;
onGround=true;
other->holdingOther=true;
other->appearance.changeState("holding");
//Change our own appearance to standing.
if(direction==1){
appearance.changeState("standleft");
}else{
appearance.changeState("standright");
}
//Apply the velocity the shadow has.
box.x+=other->xVelBase;
box.y+=other->yVelBase;
}
}else if(boxShadow.y+boxShadow.h<=box.y+13 && !inAir){
int yVelocity=other->yVel-1;
if(yVelocity>0){
other->box.y-=other->yVel;
other->box.y+=box.y-(other->box.y+other->box.h);
other->inAir=false;
other->canMove=false;
other->onGround=true;
holdingOther=true;
appearance.changeState("holding");
//Change our own appearance to standing.
if(other->direction==1){
other->appearance.changeState("standleft");
}else{
other->appearance.changeState("standright");
}
//Apply the velocity the shadow has.
other->box.x+=xVelBase;
other->box.y+=yVelBase;
}
}
}else{
holdingOther=false;
other->holdingOther=false;
}
}
//And set currentStand to null.
objCurrentStand=NULL;
other->objCurrentStand=NULL;
}
SDL_Rect Player::getBox(){
return box;
}
void Player::setMyCamera(){
//Only change the camera when the player isn't dead.
if(dead)
return;
//Check if the level fit's horizontally inside the camera.
if(camera.w>LEVEL_WIDTH){
camera.x=-(camera.w-LEVEL_WIDTH)/2;
}else{
//Check if the player is halfway pass the halfright of the screen.
if(box.x>camera.x+(SCREEN_WIDTH/2+50)){
//It is so ease the camera to the right.
camera.x+=(box.x-camera.x-(SCREEN_WIDTH/2))>>4;
//Check if the camera isn't going too far.
if(box.x<camera.x+(SCREEN_WIDTH/2+50)){
camera.x=box.x-(SCREEN_WIDTH/2+50);
}
}
//Check if the player is halfway pass the halfleft of the screen.
if(box.x<camera.x+(SCREEN_WIDTH/2-50)){
//It is so ease the camera to the left.
camera.x+=(box.x-camera.x-(SCREEN_WIDTH/2))>>4;
//Check if the camera isn't going too far.
if(box.x>camera.x+(SCREEN_WIDTH/2-50)){
camera.x=box.x-(SCREEN_WIDTH/2-50);
}
}
//If the camera is too far to the left we set it to 0.
if(camera.x<0){
camera.x=0;
}
//If the camera is too far to the right we set it to the max right.
if(camera.x+camera.w>LEVEL_WIDTH){
camera.x=LEVEL_WIDTH-camera.w;
}
}
//Check if the level fit's vertically inside the camera.
if(camera.h>LEVEL_HEIGHT){
//We don't centre vertical because the bottom line of the level (deadly) will be mid air.
camera.y=-(camera.h-LEVEL_HEIGHT);
}else{
//Check if the player is halfway pass the lower half of the screen.
if(box.y>camera.y+(SCREEN_HEIGHT/2+50)){
//If is so ease the camera down.
camera.y+=(box.y-camera.y-(SCREEN_HEIGHT/2))>>4;
//Check if the camera isn't going too far.
if(box.y<camera.y+(SCREEN_HEIGHT/2+50)){
camera.y=box.y-(SCREEN_HEIGHT/2+50);
}
}
//Check if the player is halfway pass the upper half of the screen.
if(box.y<camera.y+(SCREEN_HEIGHT/2-50)){
//It is so ease the camera up.
camera.y+=(box.y-camera.y-(SCREEN_HEIGHT/2))>>4;
//Check if the camera isn't going too far.
if(box.y>camera.y+(SCREEN_HEIGHT/2-50)){
camera.y=box.y-(SCREEN_HEIGHT/2-50);
}
}
//If the camera is too far up we set it to 0.
if(camera.y<0){
camera.y=0;
}
//If the camera is too far down we set it to the max down.
if(camera.y+camera.h>LEVEL_HEIGHT){
camera.y=LEVEL_HEIGHT-camera.h;
}
}
}
void Player::reset(bool save){
//Set the location of the player to it's initial state.
box.x=fx;
box.y=fy;
//Reset back to default value.
inAir=true;
isJump=false;
onGround=true;
shadowCall=false;
canMove=true;
holdingOther=false;
dead=false;
record=false;
downKeyPressed=false;
spaceKeyPressed=false;
//Some animation variables.
appearance.resetAnimation(save);
appearance.changeState("standright");
direction=0;
state=0;
xVel=0; //??? fixed a strange bug in game replay
yVel=0;
objCurrentStand=NULL;
objNotificationBlock=NULL;
//Clear the recording.
line.clear();
playerButton.clear();
recordButton.clear();
recordIndex=-1;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog.clear();
recordPlayerPosition.clear();
#endif
//xVelSaved is used to indicate if there's a state saved or not.
if(save){
xVelSaved=0x80000000;
}
}
void Player::saveState(){
//We can only save the state when the player isn't dead.
if(!dead){
boxSaved.x=box.x;
boxSaved.y=box.y;
xVelSaved=xVel;
yVelSaved=yVel;
inAirSaved=inAir;
isJumpSaved=isJump;
onGroundSaved=onGround;
canMoveSaved=canMove;
holdingOtherSaved=holdingOther;
stateSaved=state;
//Let the appearance save.
appearance.saveAnimation();
//Save any recording stuff.
recordSaved=record;
playerButtonSaved=playerButton;
lineSaved=line;
//Save the record
savedRecordButton=recordButton;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog_saved=recordKeyPressLog;
recordPlayerPosition_saved=recordPlayerPosition;
#endif
//Only play the sound when it's enabled.
if(getSettings()->getBoolValue("sound")==true){
//To prevent playing the sound twice, only the player can cause the sound.
if(!shadow)
Mix_PlayChannel(-1,saveSound,0);
}
}
}
void Player::loadState(){
//Check with xVelSaved if there's a saved state.
if(xVelSaved==int(0x80000000)){
//There isn't so reset the game to load the first initial state.
//NOTE: There's no need in removing the saved state since there is none.
reset(false);
return;
}
//Restore the saved values.
box.x=boxSaved.x;
box.y=boxSaved.y;
//xVel is set to 0 since it's saved counterpart is used to indicate a saved state.
xVel=0;
yVel=yVelSaved;
//Restore the saved values.
inAir=inAirSaved;
isJump=isJumpSaved;
onGround=onGroundSaved;
canMove=canMoveSaved;
holdingOther=holdingOtherSaved;
dead=false;
record=false;
shadowCall=false;
state=stateSaved;
//Restore the appearance.
appearance.loadAnimation();
//Restore any recorded stuff.
record=recordSaved;
playerButton=playerButtonSaved;
line=lineSaved;
//Load the previously saved record
recordButton=savedRecordButton;
#ifdef RECORD_FILE_DEBUG
recordKeyPressLog=recordKeyPressLog_saved;
recordPlayerPosition=recordPlayerPosition_saved;
#endif
}
void Player::swapState(Player* other){
//We need to swap the values of the player with the ones of the given player.
swap(box.x,other->box.x);
swap(box.y,other->box.y);
//NOTE: xVel isn't there since it's used for something else.
swap(yVel,other->yVel);
swap(inAir,other->inAir);
swap(isJump,other->isJump);
swap(onGround,other->onGround);
swap(canMove,other->canMove);
swap(holdingOther,other->holdingOther);
swap(dead,other->dead);
//Also reset the state of the other.
other->stateReset();
//Play the swap sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,swapSound,0);
}
//Update statistics.
if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
statsMgr.swapTimes++;
//TODO: achievements
}
}
bool Player::canSaveState(){
//We can only save the state if the player isn't dead.
return !dead;
}
bool Player::canLoadState(){
//We use xVelSaved to indicate if a state is saved or not.
return xVelSaved != int(0x80000000);
}
void Player::die(bool animation){
//Make sure the player isn't already dead.
if(!dead){
dead=true;
//If sound is enabled run the hit sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,hitSound,0);
}
//Change the apearance to die (if animation is true).
if(animation){
if(direction==1){
appearance.changeState("dieleft");
}else{
appearance.changeState("dieright");
}
}
//Update statistics
if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
if(shadow) statsMgr.shadowDies++;
else statsMgr.playerDies++;
switch(statsMgr.playerDies+statsMgr.shadowDies){
case 1:
statsMgr.newAchievement("die1");
break;
case 50:
statsMgr.newAchievement("die50");
break;
case 1000:
statsMgr.newAchievement("die1000");
break;
}
if(objParent->player.dead && objParent->shadow.dead) statsMgr.newAchievement("doubleKill");
}
}
//We set the jumpTime to 120 when this is the shadow.
//That's the countdown for the "Your shadow has died." message.
if(shadow){
jumpTime=80;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, May 15, 11:36 PM (1 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63988
Default Alt Text
(244 KB)

Event Timeline