Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
101 KB
Referenced Files
None
Subscribers
None
diff --git a/docs/ThemeDescription.txt b/docs/ThemeDescription.txt
index f7718fe..5cd118d 100644
--- a/docs/ThemeDescription.txt
+++ b/docs/ThemeDescription.txt
@@ -1,162 +1,193 @@
Me and My Shadow Theme File Description
=======================================
(draft)
The theme file contains:
name=<theme name>
1 block subnode
---------------
block(<block name>){...} //subnode specifies the block's appearance
In this subnode:
editorPicture(<file name>,<x>,<y>,<w>,<h>) //specifies the picture shows in editor
1.1 state/characterState/blockState subnode
----------------------
NOTE: blockState and characterState are for backwards compatibility, use state instead.
blockState(<state name>){...} //subnode specifies the appearance of each state of the block
the state name: for example "default" or "activated", detailed information to be announced...
optional attributes:
oneTimeAnimation=<length>,<next state> //if this state is one-time animation only
1.1.1 object subnode
--------------------
object{...} //subnode specifies (multiple) objects to display in each state
optional attributes:
animation=<length>,<loop point> //if object has looped animation
oneTimeAnimation=<length>,<end point>
invisibleAtRunTime=1 //if this object is invisible when playing game
invisibleAtDesignTime=1 //if this object is invisible when editing the map
optional nodes specifies object to display:
1.1.1.1 picture subnode
-----------------------
picture(<file name>,<x>,<y>,<w>,<h>)
+The (x,y,w,h) defines the source rectangle.
+
+NOTE: picture and pictureAnimation are mutually exclusive. (?)
+
1.1.1.2 optionalPicture subnode
-------------------------------
optionalPicture(<file name>,<x>,<y>,<w>,<h>,<probability>)
+If this subnode is set, the picture will be randomly used according to the given probability.
+
1.1.1.3 editorPicture subnode
-----------------------------
editorPicture(<file name>,<x>,<y>,<w>,<h>)
If this subnode is set, the picture will be used in the level editor.
-1.1.1.4 offset subnode
+1.1.1.4 positioning subnode
+---------------------------
+
+positioning(<xalign>,<yalign>)
+
+xalign: 'left', 'centre' (NOTE: not 'center'), 'right' or 'repeat'
+yalign: 'top', 'middle', 'bottom' or 'repeat'
+
+1.1.1.5 offset subnode
----------------------
-offset(<x>,<y>)
+offset(<x>,<y>[,<w>[,<h>]])
+
+Shift the top,left,right,bottom of the destination rectangle by x,y,-w,-h.
-1.1.1.5 pictureAnimation subnode
+NOTE: w,h are only used when the corresponding positioning modes are 'repeat'.
+
+1.1.1.6 pictureAnimation subnode
--------------------------------
pictureAnimation(<file name>){
- point(<x>,<y>,<w>,<h>)
- point(<x>,<y>,<w>,<h>,<frame count>,<display time of each frame>)
+ point(<x>,<y>,<w>,<h>[,<frame count>[,<display time of each frame>]])
+ point(<x>,<y>,<w>,<h>[,<frame count>[,<display time of each frame>]])
...
}
-1.1.1.6 offsetAnimation subnode
+NOTE: picture and pictureAnimation are mutually exclusive. (?)
+
+NOTE: The default value of frame_count and the display_time_of_each_frame are 1.
+
+The source rectangle (x,y,w,h) is animated by the following way:
+
+- (x,y,w,h)=point[0] is displayed for time display_time[0]
+- for i=1,2,3... the following sequences of pictures are displayed:
+ - for r=1/frame_count[i],2/frame_count[i],...,1
+ the (x,y,w,h)=point[i-1]*(1-r)+point[i]*r is displayed for time display_time[i]
+
+1.1.1.7 offsetAnimation subnode
-------------------------------
offsetAnimation{
- point(<x>,<y>)
- point(<x>,<y>,<frame count>,<display time of each frame>)
+ point(<x>,<y>[,<frame count>[,<display time of each frame>]])
+ point(<x>,<y>[,<frame count>[,<display time of each frame>]])
...
}
+This is similar to pictureAnimation subnode, but this time it animates the offset.
+
2 background subnode
--------------------
Specifies the background of level.
There can be multiple background subnodes.
Each subnode is a layer of background.
Syntax:
background(<file name>){
srcSize=<x>,<y>,<w>,<h> //Specifies the source size and offset of picture (optional, default value=image size)
destSize=<x>,<y>,<w>,<h> //Specifies the destination size and offset of picture (optional, default value=source size)
repeat=<repeat x>,<repeat y> //Repeat in x,y direction? (0 or 1) (optional, default value=1,1)
speed=<speed x>,<speed y> //Specifies the moving speed (pixel/frame, a real number) (optional, default=0,0)
cameraSpeed=<x>,<y> //The speed of following camera (a real number, typically in 0-1) (optional, default=0,0)
}
3 character subnode
-------------------
Specifies the appearance of player and shadow.
Syntax:
character(Player){
...
}
or
character(Shadow){
...
}
The other format is the same as the block subnode.
4 menuBackground subnode (optional)
----------------
Specifies the background of main menu.
The format is the same as the background subnode.
5 menu block (optional)
------------
Specifies the appearance of blocks used in level selection screen.
Syntax:
menu(Block){
...
}
or
menu(ShadowBlock){
...
}
The other format is the same as the block subnode.
NOTE: if you defined menu(Block) but not defined menu(ShadowBlock),
then all blocks used in level selection screen will be menu(Block)
regardless of locked or not.
6 scenery block
---------------
Defines new scenery block type.
Syntax:
scenery(<name>){
...
}
The other format is the same as the block subnode.
diff --git a/src/Block.cpp b/src/Block.cpp
index 68f409e..af906a5 100644
--- a/src/Block.cpp
+++ b/src/Block.cpp
@@ -1,1136 +1,1130 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "Functions.h"
#include "LevelEditor.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
Block::Block(Game* parent,int x,int y,int w,int h,int type):
GameObject(parent),
animation(0),
animationSave(0),
flags(0),
flagsSave(0),
temp(0),
tempSave(0),
dx(0),
dxSave(0),
dy(0),
dySave(0),
loop(true),
speed(0),
speedSave(0),
editorSpeed(0),
editorFlags(0),
enabled(true),
enabledSave(true)
{
//Make sure the type is set, if not init should be called somewhere else with this information.
if(type>=0 && type<TYPE_MAX)
init(x,y,w,h,type);
}
Block::~Block(){}
void Block::init(int x,int y,int w,int h,int type){
//First set the location and size of the box.
//The default size is 50x50.
box.x=boxBase.x=x;
box.y=boxBase.y=y;
box.w=boxBase.w=w;
box.h=boxBase.h=h;
//Set the save values.
boxSave.x=x;
boxSave.y=y;
boxSave.w=w;
boxSave.h=h;
//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.setLocation(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.setLocation(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=inAirSave=true;
xVel=yVel=xVelBase=yVelBase=0;
xVelSave=yVelSave=xVelBaseSave=yVelBaseSave=0;
//And load the appearance.
objThemes.getBlock(type)->createInstance(&appearance);
}
void Block::show(SDL_Renderer& renderer){
//Make sure we are enabled.
if(!enabled)
return;
//Check if the block is visible.
if(checkCollision(camera,box)==true || (stateID==STATE_LEVEL_EDITOR && checkCollision(camera,boxBase)==true)){
- SDL_Rect r={0,0,box.w,box.h};
-
//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(animation){
// FIXME: ad-hoc code
- r.x = box.w - animation;
- r.w = animation;
- appearance.draw(renderer, box.x - camera.x - box.w + animation, box.y - camera.y, box.w, box.h, &r);
- r.x = 0;
- r.w = box.w - animation;
- appearance.draw(renderer, box.x - camera.x + animation, box.y - camera.y, box.w - animation, box.h, &r);
+ const SDL_Rect r = { box.x - camera.x, box.y - camera.y, box.w, box.h };
+ appearance.draw(renderer, box.x - camera.x - 50 + animation, box.y - camera.y, box.w + 50 - animation, box.h, &r);
return;
}
break;
}
//Always draw the base.
appearance.drawState("base", renderer, boxBase.x - camera.x, boxBase.y - camera.y, boxBase.w, boxBase.h);
//Now draw normal.
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
//Some types need to draw something on top of the base/default.
switch(type){
case TYPE_BUTTON:
if(flags&4){
if(animation<5) animation++;
}else{
if(animation>0) animation--;
}
appearance.drawState("button",renderer,box.x-camera.x,box.y-camera.y-5+animation);
break;
}
//Draw a stupid icon for scrpited blocks in edit mode.
if(stateID==STATE_LEVEL_EDITOR && !scripts.empty()){
auto bmGUI = static_cast<LevelEditor*>(parent)->getGuiTexture();
if(!bmGUI) {
return;
}
const SDL_Rect r={0,32,16,16};
const SDL_Rect dstRect={box.x - camera.x + 2,box.y - camera.y + 2,16,16};
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
}
}
}
SDL_Rect Block::getBox(int boxType){
SDL_Rect r={0,0,0,0};
switch(boxType){
case BoxType_Base:
return boxBase;
case BoxType_Previous:
r.x=box.x-dx;
r.y=box.y-dy;
r.w=box.w;
r.h=box.h;
return r;
case BoxType_Delta:
r.x=dx;
r.y=dy;
return r;
case BoxType_Velocity:
r.x=xVel;
r.y=yVel;
//NOTE: In case of the pushable block we sometimes need to substract one from the vertical velocity.
//The yVel is set to one when it's resting, but should be handled as zero in collision.
if(type==TYPE_PUSHABLE && !inAir)
r.y=0;
return r;
case BoxType_Current:
return box;
}
return r;
}
void Block::moveTo(int x,int y){
//The block has moved so calculate the delta.
//NOTE: Every delta is summed since they all happened within one frame and for collision/movement we need the resulting delta.
int delta=(x-box.x);
dx+=delta;
xVel+=delta;
delta=(y-box.y);
dy+=delta;
yVel+=delta;
//And set the new location.
box.x=x;
box.y=y;
}
void Block::growTo(int w,int h){
//The block has changed size
//NOTE: Every delta is summed since they all happened within one frame and for collision/movement we need the resulting delta.
int delta=(w-box.w);
dx+=delta;
xVel+=delta;
delta=(h-box.h);
dy+=delta;
yVel+=delta;
//And set the new location.
box.w=w;
box.h=h;
}
void Block::saveState(){
animationSave=animation;
flagsSave=flags;
tempSave=temp;
dxSave=dx;
dySave=dy;
boxSave.x=box.x-boxBase.x;
boxSave.y=box.y-boxBase.y;
boxSave.w=box.w-boxBase.w;
boxSave.h=box.h-boxBase.h;
xVelSave=xVel;
yVelSave=yVel;
enabledSave=enabled;
appearance.saveAnimation();
//In case of a certain blocks we need to save some more.
switch(type){
case TYPE_PUSHABLE:
xVelBaseSave=xVelBase;
yVelBaseSave=yVelBase;
inAirSave=inAir;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
speedSave=speed;
break;
}
}
void Block::loadState(){
//Restore the flags and animation var.
animation=animationSave;
flags=flagsSave;
temp=tempSave;
dx=dxSave;
dy=dySave;
//Restore the location.
box.x=boxBase.x+boxSave.x;
box.y=boxBase.y+boxSave.y;
box.w=boxBase.w+boxSave.w;
box.h=boxBase.h+boxSave.h;
//And the velocity.
xVel=xVelSave;
yVel=yVelSave;
//The enabled status.
enabled=enabledSave;
//Handle block type specific variables.
switch(type){
case TYPE_PUSHABLE:
xVelBase=xVelBaseSave;
yVelBase=yVelBaseSave;
inAir=inAirSave;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
speed=speedSave;
break;
}
//And load the animation.
appearance.loadAnimation();
}
void Block::reset(bool save){
//We need to reset so we clear the animation and saves.
if(save){
animation=animationSave=0;
boxSave.x=boxSave.y=boxSave.w=boxSave.h=0;
flags=flagsSave=editorFlags;
temp=tempSave=0;
dx=dxSave=0;
dy=dySave=0;
}else{
animation=0;
flags=editorFlags;
temp=0;
dx=0;
dy=0;
}
//Reset the block to its original location.
box.x=boxBase.x;
box.y=boxBase.y;
box.w=boxBase.w;
box.h=boxBase.h;
//Reset any velocity.
xVel=yVel=xVelBase=yVelBase=0;
if(save)
xVelSave=yVelSave=xVelBaseSave=yVelBaseSave=0;
//Reset the enabled status.
enabled=true;
if(save)
enabledSave=true;
//Also reset the appearance.
appearance.resetAnimation(save);
appearance.changeState("default");
//NOTE: We load the animation right after changing it to prevent a transition.
if(save)
appearance.loadAnimation();
//Some types of block requires type specific code.
switch(type){
case TYPE_FRAGILE:
{
const char* s=(flags==0)?"default":((flags==1)?"fragile1":((flags==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
break;
case TYPE_PUSHABLE:
inAir=false;
if(save)
inAirSave=false;
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
if(save)
speed=speedSave=editorSpeed;
else
speed=editorSpeed;
break;
}
}
void Block::playAnimation(){
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){
//Make sure we are enabled, otherwise no events should be handled.
if(!enabled)
return;
//Iterator used to check if the map contains certain entries.
map<int,int>::iterator it;
//Check if there's a script for the event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is a script so execute it and check return value.
int ret=getScriptExecutor()->executeScript(it->second,this);
//Return value 1 means do default event process.
//Other values are coming soon...
if(ret!=1) 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:
temp=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>("activated",(editorFlags&0x1)?"0":"1"));
obj.push_back(pair<string,string>("loop",loop?"1":"0"));
for(unsigned int i=0;i<movingPos.size();i++){
sprintf(s0+1,"%u",i);
sprintf(s,"%d",movingPos[i].x);
s0[0]='x';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].y);
s0[0]='y';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].w);
s0[0]='t';
obj.push_back(pair<string,string>(s0,s));
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
char s[64];
obj.push_back(pair<string,string>("activated",(editorFlags&0x1)?"0":"1"));
sprintf(s,"%d",editorSpeed);
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=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 activated or disabled key is in the data.
//NOTE: 'disabled' is obsolete in V0.5.
it=obj.find("activated");
if(it!=obj.end()){
string s=it->second;
editorFlags=0;
if(!(s=="true" || atoi(s.c_str()))) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
string s=it->second;
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()){
editorSpeed=atoi(obj["speed"].c_str());
speed=speedSave=editorSpeed;
}
//Check if the activated or disabled key is in the data.
//NOTE: 'disabled' is obsolete in V0.5.
it=obj.find("activated");
if(it!=obj.end()){
string s=it->second;
editorFlags=0;
if(!(s=="true" || atoi(s.c_str()))) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
string s=it->second;
editorFlags=0;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
}
}
break;
case TYPE_PORTAL:
{
//Check if the automatic key is in the data.
it=obj.find("automatic");
if(it!=obj.end()){
string s=obj["automatic"];
editorFlags=0;
if(s=="true" || atoi(s.c_str())) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}
//Check if the destination key is in the data.
it=obj.find("destination");
if(it!=obj.end()){
destination=obj["destination"];
}
}
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Check if the behaviour key is in the data.
it=obj.find("behaviour");
if(it!=obj.end()){
string s=obj["behaviour"];
editorFlags=0;
if(s=="on" || s==_("On")) editorFlags|=1;
else if(s=="off" || s==_("Off")) editorFlags|=2;
flags=flagsSave=editorFlags;
}
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
//Check if the message key is in the data.
it=obj.find("message");
if(it!=obj.end()){
message=obj["message"];
//Change the characters '\n' to a real \n
while(message.find("\\n")!=string::npos){
message=message.replace(message.find("\\n"),2,"\n");
}
}
}
break;
case TYPE_FRAGILE:
{
//Check if the status is in the data.
it=obj.find("state");
if(it!=obj.end()){
editorFlags=atoi(obj["state"].c_str());
flags=editorFlags;
{
const char* s=(flags==0)?"default":((flags==1)?"fragile1":((flags==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
}
}
}
}
std::string Block::getEditorProperty(std::string property){
//First get the complete editor data.
vector<pair<string,string> > objMap;
vector<pair<string,string> >::iterator it;
getEditorData(objMap);
//Loop through the entries.
for(it=objMap.begin();it!=objMap.end();++it){
if(it->first==property)
return it->second;
}
//Nothing found.
return "";
}
void Block::setEditorProperty(std::string property,std::string value){
//Create a map to hold the property.
std::map<std::string,std::string> editorData;
editorData[property]=value;
//And call the setEditorData method.
setEditorData(editorData);
}
bool Block::loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode* objNode){
//Make sure there are enough parameters.
if(objNode->value.size()<3)
return false;
//Load the type and location.
int type=Game::blockNameMap[objNode->value[0]];
int x=atoi(objNode->value[1].c_str());
int y=atoi(objNode->value[2].c_str());
int w=50;
int h=50;
if(objNode->value.size()>3)
w=atoi(objNode->value[3].c_str());
if(objNode->value.size()>4)
h=atoi(objNode->value[4].c_str());
//Call the init method.
init(x,y,w,h,type);
//Loop through the attributes as editorProperties.
map<string,string> obj;
for(map<string,vector<string> >::iterator i=objNode->attributes.begin();i!=objNode->attributes.end();++i){
if(i->second.size()>0) obj[i->first]=i->second[0];
}
setEditorData(obj);
//Loop through the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
//FIXME: Ugly variable naming.
TreeStorageNode* obj=objNode->subNodes[i];
if(obj==NULL) continue;
//Check for a script block.
if(obj->name=="script" && !obj->value.empty()){
map<string,int>::iterator it=Game::gameObjectEventNameMap.find(obj->value[0]);
if(it!=Game::gameObjectEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
return true;
}
void Block::prepareFrame(){
//Reset the delta variables.
dx=dy=0;
//Also reset the velocity, these should be set in the move method.
if(type!=TYPE_PUSHABLE)
xVel=yVel=0;
}
/*//debug
int block_test_count=-1;
bool block_test_only=false;*/
void Block::move(){
//Make sure we are enabled, if not return.
if(!enabled)
return;
//First update the animation of the appearance.
appearance.updateAnimation();
//Block specific move code.
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;
}*/
//Make sure the block is enabled, if so increase the time.
if(!(flags&0x1)) temp++;
int t=temp;
SDL_Rect r0={0,0,0,0},r1;
dx=0;
dy=0;
//Loop through the moving positions.
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));
//Calculate the delta and velocity.
xVel=dx=newX-box.x;
yVel=dy=newY-box.y;
//Set the new location of the moving block.
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;
xVel=dx=newX-box.x;
yVel=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){
//Set the time back to zero.
temp=0;
//Calculate the delta movement.
if(!movingPos.empty() && movingPos.back().x==0 && movingPos.back().y==0){
dx=boxBase.x-box.x;
dy=boxBase.y-box.y;
}
//Set the movingblock back to it's initial location.
box.x=boxBase.x;
box.y=boxBase.y;
}
}
break;
case TYPE_BUTTON:
{
//Check the third bit of flags to see if temp changed.
int new_flags=temp?4:0;
if((flags^new_flags)&4){
//The button has been pressed or unpressed so change the third bit on flags.
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;
}
}
}
temp=0;
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
//Increase the conveyor belt animation.
if((flags&1)==0){
- animation=(animation+speed)%box.w;
- if(animation<0) animation+=box.w;
+ animation=(animation+speed)%50;
+ if(animation<0) animation+=50;
//Set the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel=speed;
} else {
//Clear the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel = 0;
}
break;
case TYPE_PUSHABLE:
{
//Update the vertical velocity, horizontal is set by the player.
if(inAir==true){
yVel+=1;
//Cap fall speed to 13.
if(yVel>13)
yVel=13;
}
if(objCurrentStand!=NULL){
//Now get the velocity and delta of the object the player is standing on.
SDL_Rect v=objCurrentStand->getBox(BoxType_Velocity);
SDL_Rect delta=objCurrentStand->getBox(BoxType_Delta);
switch(objCurrentStand->type){
//For conveyor belts the velocity is transfered.
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
xVelBase+=v.x;
}
break;
//In other cases, such as, player on shadow, player on crate... the change in x position must be considered.
default:
{
if(delta.x != 0)
xVelBase+=delta.x;
}
break;
}
//NOTE: Only copy the velocity of the block when moving down.
//Upwards is automatically resolved before the player is moved.
if(v.y>0)
yVelBase=v.y;
else
yVelBase=0;
}
//Set the object the player is currently standing to NULL.
objCurrentStand=NULL;
//Store the location of the player.
int lastX=box.x;
int lastY=box.y;
//An array that will hold all the GameObjects that are involved in the collision/movement.
vector<Block*> objects;
//All the blocks have moved so if there's collision with the player, the block moved into him.
for(unsigned int o=0;o<parent->levelObjects.size();o++){
//Make sure to only check enabled blocks.
if(!parent->levelObjects[o]->enabled)
continue;
//Make sure we aren't the block.
if(parent->levelObjects[o]==this)
continue;
//Make sure the object is solid for the player.
if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
continue;
//Check for collision.
if(checkCollision(box,parent->levelObjects[o]->getBox()))
objects.push_back(parent->levelObjects[o]);
}
//There was collision so try to resolve it.
if(!objects.empty()){
//FIXME: When multiple moving blocks are overlapping the player can be "bounced" off depending on the block order.
for(unsigned int o=0;o<objects.size();o++){
SDL_Rect r=objects[o]->getBox();
SDL_Rect delta=objects[o]->getBox(BoxType_Delta);
//Check on which side of the box the player is.
if(delta.x!=0){
if(delta.x>0){
if((r.x+r.w)-box.x<=delta.x)
box.x=r.x+r.w;
}else{
if((box.x+box.w)-r.x<=-delta.x)
box.x=r.x-box.w;
}
}
if(delta.y!=0){
if(delta.y>0){
if((r.y+r.h)-box.y<=delta.y)
box.y=r.y+r.h;
}else{
if((box.y+box.h)-r.y<=-delta.y)
box.y=r.y-box.h;
}
}
}
}
//Reuse the objects array, this time for blocks the block moves into.
objects.clear();
//Determine the collision frame.
SDL_Rect frame={box.x,box.y,box.w,box.h};
//Keep the horizontal movement of the block 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++){
//Make sure the object is enabled.
if(!parent->levelObjects[o]->enabled)
continue;
//Make sure we aren't the block.
if(parent->levelObjects[o]==this)
continue;
//Check if the player can collide with this game object.
if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
continue;
//Check if the block is inside the frame.
if(checkCollision(frame,parent->levelObjects[o]->getBox()))
objects.push_back(parent->levelObjects[o]);
}
//Horizontal pass.
if(xVel+xVelBase!=0){
box.x+=xVel+xVelBase;
for(unsigned int o=0;o<objects.size();o++){
SDL_Rect r=objects[o]->getBox();
if(!checkCollision(box,r))
continue;
if(xVel+xVelBase>0){
//We came from the left so the right edge of the player must be less or equal than xVel+xVelBase.
if((box.x+box.w)-r.x<=xVel+xVelBase)
box.x=r.x-box.w;
}else{
//We came from the left so the right edge of the player must be less or equal than xVel+xVelBase.
if(box.x-(r.x+r.w)<=-(xVel+xVelBase))
box.x=r.x+r.w;
}
}
}
//Some variables that are used in vertical movement.
Block* lastStand=NULL;
inAir=true;
//Vertical pass.
if(yVel+yVelBase!=0){
box.y+=yVel+yVelBase;
for(unsigned int o=0;o<objects.size();o++){
SDL_Rect r=objects[o]->getBox();
if(!checkCollision(box,r))
continue;
//Now check how we entered the block (vertically or horizontally).
if(yVel+yVelBase>0){
//We came from the top so the bottom edge of the player must be less or equal than yVel+yVelBase.
if((box.y+box.h)-r.y<=yVel+yVelBase){
//NOTE: lastStand is handled later since the player can stand on only one block at the time.
//Check if there's already a lastStand.
if(lastStand){
//There is one, so check 'how much' the player is on the blocks.
SDL_Rect r=objects[o]->getBox();
int w=0;
if(box.x+box.w>r.x+r.w)
w=(r.x+r.w)-box.x;
else
w=(box.x+box.w)-r.x;
//Do the same for the other box.
r=lastStand->getBox();
int w2=0;
if(box.x+box.w>r.x+r.w)
w2=(r.x+r.w)-box.x;
else
w2=(box.x+box.w)-r.x;
//NOTE: It doesn't matter which block the player is on if they are both stationary.
SDL_Rect v=objects[o]->getBox(BoxType_Velocity);
SDL_Rect v2=lastStand->getBox(BoxType_Velocity);
if(v.y==v2.y){
if(w>w2)
lastStand=objects[o];
}else if(v.y<v2.y){
lastStand=objects[o];
}
}else{
lastStand=objects[o];
}
}
}else{
//We came from the bottom so the upper edge of the player must be less or equal than yVel+yVelBase.
if(box.y-(r.y+r.h)<=-(yVel+yVelBase)){
box.y=r.y+r.h;
yVel=0;
}
}
}
}
if(lastStand){
inAir=false;
yVel=1;
SDL_Rect r=lastStand->getBox();
box.y=r.y-box.h;
}
//Block will currently be standing on whatever it was last standing on.
objCurrentStand=lastStand;
dx=box.x-lastX;
dy=box.y-lastY;
xVel=0;
xVelBase=0;
}
break;
}
}
diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp
index 804016d..73da204 100644
--- a/src/ThemeManager.cpp
+++ b/src/ThemeManager.cpp
@@ -1,1000 +1,1022 @@
/*
* 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 "ThemeManager.h"
#include "POASerializer.h"
#include "Functions.h"
#include "FileManager.h"
#include "Game.h"
#include "ImageManager.h"
#include <iostream>
using namespace std;
//The ThemeStack that is be used by the GameState.
ThemeStack objThemes;
bool ThemeManager::loadFile(const string& fileName, ImageManager &imageManager, SDL_Renderer &renderer){
POASerializer objSerializer;
TreeStorageNode objNode;
//First we destroy the current ThemeManager.
destroy();
//Now we try to load the file, if it fails we return false.
if(!objSerializer.loadNodeFromFile(fileName.c_str(),&objNode,true)){
cerr<<"ERROR: Unable to open theme file: "<<fileName<<endl;
return false;
}
//Set the themePath.
themePath=pathFromFileName(fileName);
//Retrieve the name of the theme from the file.
{
vector<string> &v=objNode.attributes["name"];
if(!v.empty()) themeName=v[0];
}
//Reset themeable colors to default
themeTextColor.r=themeTextColor.g=themeTextColor.b=0;
themeTextColorDialog.r=themeTextColorDialog.g=themeTextColorDialog.b=0;
//Read themeable colors if any
vector<string> &ct=objNode.attributes["textColor"];
if(!ct.empty()){
themeTextColor.r=atoi(ct[0].c_str());
themeTextColor.g=atoi(ct[1].c_str());
themeTextColor.b=atoi(ct[2].c_str());
}
vector<string> &ct2=objNode.attributes["textColorDialog"];
if(!ct2.empty()){
themeTextColorDialog.r=atoi(ct2[0].c_str());
themeTextColorDialog.g=atoi(ct2[1].c_str());
themeTextColorDialog.b=atoi(ct2[2].c_str());
}
//Loop the subnodes of the theme.
for(unsigned int i=0;i<objNode.subNodes.size();i++){
TreeStorageNode *obj=objNode.subNodes[i];
//Check if it's a block or a background.
if (obj->name == "block" && !obj->value.empty()){
map<string, int>::iterator it = Game::blockNameMap.find(obj->value[0]);
if (it != Game::blockNameMap.end()){
int idx = it->second;
if (!objBlocks[idx]) objBlocks[idx] = new ThemeBlock;
if (!objBlocks[idx]->loadFromNode(obj, themePath, imageManager, renderer)){
cerr << "ERROR: Unable to load " << Game::blockName[idx] << " for theme " << fileName << endl;
delete objBlocks[idx];
objBlocks[idx] = NULL;
return false;
}
}
} else if (obj->name == "scenery" && !obj->value.empty()){
std::string& name = obj->value[0];
if (!objScenery[name]) objScenery[name] = new ThemeBlock;
if (!objScenery[name]->loadFromNode(obj, themePath, imageManager, renderer)){
cerr << "ERROR: Unable to load scenery '" << name << "' for theme " << fileName << endl;
delete objScenery[name];
objScenery[name] = NULL;
return false;
}
}else if(obj->name=="background" && !obj->value.empty()){
if(!objBackground) objBackground=new ThemeBackground();
if(!objBackground->addPictureFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load background for theme "<<fileName<<endl;
delete objBackground;
objBackground=NULL;
return false;
}
}else if(obj->name=="character" && !obj->value.empty()){
if(obj->value[0]=="Shadow"){
if(!shadow) shadow=new ThemeBlock();
if(!shadow->loadFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load shadow for theme "<<fileName<<endl;
delete shadow;
shadow=NULL;
return false;
}
}else if(obj->value[0]=="Player"){
if(!player) player=new ThemeBlock();
if(!player->loadFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load player for theme "<<fileName<<endl;
delete player;
player=NULL;
return false;
}
}
}else if(obj->name=="menuBackground" && !obj->value.empty()){
if(!menuBackground) menuBackground=new ThemeBackground();
if(!menuBackground->addPictureFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load background for theme "<<fileName<<endl;
delete menuBackground;
menuBackground=NULL;
return false;
}
}else if(obj->name=="menu" && obj->value[0]=="Block"){
if(!menuBlock) menuBlock=new ThemeBlock;
if(!menuBlock->loadFromNode(obj,themePath, imageManager, renderer)){
cerr<<"ERROR: Unable to load menu block for theme "<<fileName<<endl;
delete menuBlock;
menuBlock=NULL;
return false;
}
} else if (obj->name == "menu" && obj->value[0] == "ShadowBlock"){
if (!menuShadowBlock) menuShadowBlock = new ThemeBlock;
if (!menuShadowBlock->loadFromNode(obj, themePath, imageManager, renderer)){
cerr << "ERROR: Unable to load menu shadow block for theme " << fileName << endl;
delete menuShadowBlock;
menuShadowBlock = NULL;
return false;
}
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemeBlock::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager &imageManager, SDL_Renderer &renderer){
destroy();
//Loop the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode *obj=objNode->subNodes[i];
//Check if the subnode is an editorPicture or a blockState.
if(obj->name=="editorPicture"){
if(!editorPicture.loadFromNode(obj,themePath, imageManager, renderer)) return false;
//NOTE: blockState and characterState are for backwards compatability, use state instead.
}else if((obj->name=="blockState" || obj->name=="characterState" || obj->name=="state") && !obj->value.empty()){
string& s=obj->value[0];
map<string,ThemeBlockState*>::iterator it=blockStates.find(s);
if(it==blockStates.end()) blockStates[s]=new ThemeBlockState;
if(!blockStates[s]->loadFromNode(obj,themePath, imageManager, renderer)) return false;
}else if(obj->name=="transitionState" && obj->value.size()==2){
pair<string,string> s=pair<string,string>(obj->value[0],obj->value[1]);
map<pair<string,string>,ThemeBlockState*>::iterator it=transitions.find(s);
if(it==transitions.end()) transitions[s]=new ThemeBlockState;
if(!transitions[s]->loadFromNode(obj,themePath, imageManager, renderer)) return false;
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemeBlockState::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
destroy();
//Retrieve the oneTimeAnimation attribute.
{
vector<string> &v=objNode->attributes["oneTimeAnimation"];
//Check if there are enough values for the oneTimeAnimation attribute.
if(v.size()>=2 && !v[0].empty()){
oneTimeAnimationLength=atoi(v[0].c_str());
nextState=v[1];
}
}
//Loop the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode *obj=objNode->subNodes[i];
if(obj->name=="object"){
ThemeObject *obj1=new ThemeObject();
if(!obj1->loadFromNode(obj,themePath, imageManager, renderer)){
delete obj1;
return false;
}
themeObjects.push_back(obj1);
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemeObject::loadFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
destroy();
//Retrieve the animation attribute.
{
vector<string> &v=objNode->attributes["animation"];
if(v.size()>=2){
animationLength=atoi(v[0].c_str());
animationLoopPoint=atoi(v[1].c_str());
}
}
//Retrieve the oneTimeAnimation attribute.
{
vector<string> &v=objNode->attributes["oneTimeAnimation"];
if(v.size()>=2){
animationLength=atoi(v[0].c_str());
animationLoopPoint=atoi(v[1].c_str())|0x80000000;
}
}
//Retrieve the invisibleAtRunTime attribute.
{
vector<string> &v=objNode->attributes["invisibleAtRunTime"];
if(!v.empty() && !v[0].empty()){
invisibleAtRunTime=atoi(v[0].c_str())?true:false;
}
}
//Retrieve the invisibleAtDesignTime attribute.
{
vector<string> &v=objNode->attributes["invisibleAtDesignTime"];
if(!v.empty() && !v[0].empty()){
invisibleAtDesignTime=atoi(v[0].c_str())?true:false;
}
}
//Loop the subnodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode *obj=objNode->subNodes[i];
if(obj->name=="picture" || obj->name=="pictureAnimation"){
if(!picture.loadFromNode(obj,themePath, imageManager, renderer)){
return false;
}
}else if(obj->name=="editorPicture"){
if(!editorPicture.loadFromNode(obj,themePath, imageManager, renderer)){
return false;
}
}else if(obj->name=="optionalPicture" && obj->value.size()>=6){
ThemePicture *objPic=new ThemePicture();
double f=atof(obj->value[5].c_str());
if(!objPic->loadFromNode(obj,themePath, imageManager, renderer)){
delete objPic;
return false;
}
optionalPicture.push_back(pair<double,ThemePicture*>(f,objPic));
}else if(obj->name=="offset" || obj->name=="offsetAnimation"){
if(!offset.loadFromNode(obj)) return false;
}else if(obj->name=="positioning"){
if(!positioning.loadFromNode(obj)) return false;
}
}
//Done and nothing went wrong so return true.
return true;
}
bool ThemePicture::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager &imageManager, SDL_Renderer &renderer){
destroy();
//Check if the node has enough values.
if(!objNode->value.empty()){
//Load the texture.
texture=imageManager.loadTexture(themePath+objNode->value[0], renderer);
if(!texture) {
return false;
}
//Check if it's an animation.
if(objNode->name=="pictureAnimation"){
if(!offset.loadFromNode(objNode)) return false;
return true;
}else if(objNode->value.size()>=5){
typeOffsetPoint r={atoi(objNode->value[1].c_str()),
atoi(objNode->value[2].c_str()),
atoi(objNode->value[3].c_str()),
atoi(objNode->value[4].c_str()),0,0};
offset.offsetData.push_back(r);
offset.length=0;
return true;
}
}
//Done and nothing went wrong so return true.
return false;
}
bool ThemeOffsetData::loadFromNode(TreeStorageNode* objNode){
destroy();
//Check what kind of offset it is.
if(objNode->name=="pictureAnimation"){
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode* obj=objNode->subNodes[i];
if(obj->name=="point" && obj->value.size()>=4){
typeOffsetPoint r={atoi(obj->value[0].c_str()),
atoi(obj->value[1].c_str()),
atoi(obj->value[2].c_str()),
atoi(obj->value[3].c_str()),1,1};
if(obj->value.size()>=5) r.frameCount=atoi(obj->value[4].c_str());
if(obj->value.size()>=6) r.frameDisplayTime=atoi(obj->value[5].c_str());
offsetData.push_back(r);
length+=r.frameCount*r.frameDisplayTime;
}
}
return true;
}else if(objNode->name=="offsetAnimation"){
for(unsigned int i=0;i<objNode->subNodes.size();i++){
TreeStorageNode* obj=objNode->subNodes[i];
if(obj->name=="point" && obj->value.size()>=2){
typeOffsetPoint r={atoi(obj->value[0].c_str()),
atoi(obj->value[1].c_str()),0,0,1,1};
if(obj->value.size()>=3) r.frameCount=atoi(obj->value[2].c_str());
if(obj->value.size()>=4) r.frameDisplayTime=atoi(obj->value[3].c_str());
offsetData.push_back(r);
length+=r.frameCount*r.frameDisplayTime;
}
}
return true;
}else if(objNode->name=="offset" && objNode->value.size()>=2){
typeOffsetPoint r={atoi(objNode->value[0].c_str()),
atoi(objNode->value[1].c_str()),0,0,0,0};
if(objNode->value.size()>2)
r.w=atoi(objNode->value[2].c_str());
if(objNode->value.size()>3)
r.h=atoi(objNode->value[3].c_str());
offsetData.push_back(r);
length=0;
return true;
}
//Done and nothing went wrong so return true.
return false;
}
bool ThemePositioningData::loadFromNode(TreeStorageNode* objNode){
destroy();
//Check if enough values are set.
if(objNode->value.size()>=2){
//Check horizontal alignment.
if(objNode->value[0]=="left"){
horizontalAlign=LEFT;
}else if(objNode->value[0]=="centre"){
horizontalAlign=CENTRE;
}else if(objNode->value[0]=="right"){
horizontalAlign=RIGHT;
}else if(objNode->value[0]=="repeat"){
horizontalAlign=REPEAT;
}
//Check vertical alignment.
if(objNode->value[1]=="top"){
verticalAlign=TOP;
}else if(objNode->value[1]=="middle"){
verticalAlign=MIDDLE;
}else if(objNode->value[1]=="bottom"){
verticalAlign=BOTTOM;
}else if(objNode->value[1]=="repeat"){
verticalAlign=REPEAT;
}
//Done and nothing went wrong so return true.
return true;
}
return false;
}
-void ThemeObjectInstance::draw(SDL_Renderer& renderer,int x,int y,int w,int h,SDL_Rect *clipRect){
+void ThemeObjectInstance::draw(SDL_Renderer& renderer,int x,int y,int w,int h,const SDL_Rect *clipRect){
//Get the picture.
//SDL_Surface *src=picture->picture;
SDL_Texture* src = picture->texture.get();
if(src==NULL) return;
- int ex=0,ey=0,ew=0,eh=0;
+
+ //The offset to the left and top of the destination rectangle.
+ int ex = 0, ey = 0;
+
+ //The offset to the right and bottom of the destination rectangle. Only used when the position mode is REPEAT.
+ int ew = 0, eh = 0;
+
+ //The x,y,width,height of the source rectangle.
int xx=0,yy=0,ww=0,hh=0;
+
int animationNew=animation&0x7FFFFFFF;
+
+ //Get the source rectangle.
{
const vector<typeOffsetPoint> &v=picture->offset.offsetData;
if(picture->offset.length==0 || animationNew<v[0].frameDisplayTime){
xx=v[0].x;
yy=v[0].y;
ww=v[0].w;
hh=v[0].h;
}else if(animationNew>=picture->offset.length){
int i=v.size()-1;
xx=v[i].x;
yy=v[i].y;
ww=v[i].w;
hh=v[i].h;
}else{
int t=animationNew-v[0].frameDisplayTime;
for(unsigned int i=1;i<v.size();i++){
int tt=t/v[i].frameDisplayTime;
if(tt>=0 && tt<v[i].frameCount){
xx=(int)((float)v[i-1].x+(float)(v[i].x-v[i-1].x)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
yy=(int)((float)v[i-1].y+(float)(v[i].y-v[i-1].y)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ww=(int)((float)v[i-1].w+(float)(v[i].w-v[i-1].w)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
hh=(int)((float)v[i-1].h+(float)(v[i].h-v[i-1].h)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
break;
}else{
t-=v[i].frameCount*v[i].frameDisplayTime;
}
}
}
}
+
//Get the offset.
{
vector<typeOffsetPoint> &v=parent->offset.offsetData;
if(v.empty()){
ex=0;
ey=0;
}else if(parent->offset.length==0 || animationNew<v[0].frameDisplayTime){
ex=v[0].x;
ey=v[0].y;
ew=v[0].w;
eh=v[0].h;
}else if(animationNew>=parent->offset.length){
int i=v.size()-1;
ex=v[i].x;
ey=v[i].y;
ew=v[i].w;
eh=v[i].h;
}else{
int t=animationNew-v[0].frameDisplayTime;
for(unsigned int i=1;i<v.size();i++){
int tt=t/v[i].frameDisplayTime;
if(tt>=0 && tt<v[i].frameCount){
ex=(int)((float)v[i-1].x+(float)(v[i].x-v[i-1].x)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ey=(int)((float)v[i-1].y+(float)(v[i].y-v[i-1].y)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ew=(int)((float)v[i-1].w+(float)(v[i].w-v[i-1].w)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
eh=(int)((float)v[i-1].h+(float)(v[i].h-v[i-1].h)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
break;
}else{
t-=v[i].frameCount*v[i].frameDisplayTime;
}
}
}
}
//And finally draw the ThemeObjectInstance.
- if(clipRect){
- int d;
- d=clipRect->x-ex;
- if(d>0){
- ex+=d;
- xx+=d;
- ww-=d;
+ if(ww>0&&hh>0){
+ Alignment hAlign = parent->positioning.horizontalAlign;
+ Alignment vAlign = parent->positioning.verticalAlign;
+
+ //If the destination size is not set then assume it's the same as the source size.
+ //In this case we also disable the align.
+ if (w <= 0) {
+ w = ww;
+ hAlign = LEFT;
}
- d=clipRect->y-ey;
- if(d>0){
- ey+=d;
- yy+=d;
- hh-=d;
+ if (h <= 0) {
+ h = hh;
+ vAlign = TOP;
}
- if(ww>clipRect->w) ww=clipRect->w;
- if(hh>clipRect->h) hh=clipRect->h;
- }
- if(ww>0&&hh>0){
- SDL_Rect r1={xx,yy,ww,hh};
+
+ //The destination rectangle (NOTE: the w,h is actually the right and bottom)
SDL_Rect r2={x+ex,y+ey,0,0};
- //Only align horizontally when there's a width.
- if(w!=0){
- switch(parent->positioning.horizontalAlign){
- case LEFT:
- //NOTE: No need to change the x location, left is default.
- break;
- case CENTRE:
- r2.x+=(w-r1.w)/2;
- break;
- case RIGHT:
- r2.x+=w-ww;
- break;
- }
+
+ //Align horizontally.
+ switch (hAlign){
+ case CENTRE:
+ r2.x += (w - ww) / 2;
+ break;
+ case RIGHT:
+ r2.x += w - ww;
+ break;
}
- //Only align vertically when there's a height.
- if(h!=0){
- switch(parent->positioning.verticalAlign){
- case TOP:
- //NOTE: No need to change the y location, top is default.
- break;
- case MIDDLE:
- r2.y+=(h-r1.h)/2;
- break;
- case BOTTOM:
- r2.y+=h-hh;
- break;
- }
+
+ //Align vertically.
+ switch (vAlign){
+ case MIDDLE:
+ r2.y += (h - hh) / 2;
+ break;
+ case BOTTOM:
+ r2.y += h - hh;
+ break;
+ }
+
+ //Calculate the correct right and bottom of the destination rectangle (esp. in REPEAT mode)
+ r2.w = (hAlign == REPEAT) ? (x + w - ew) : r2.x + ww;
+ r2.h = (vAlign == REPEAT) ? (y + h - eh) : r2.y + hh;
+
+ if (clipRect) {
+ //Clip the right and bottom
+ if (r2.w > clipRect->x + clipRect->w) r2.w = clipRect->x + clipRect->w;
+ if (r2.h > clipRect->y + clipRect->h) r2.h = clipRect->y + clipRect->h;
+
+ //Clip the left and top (ad-hoc code)
+ if (r2.x < clipRect->x) r2.x += ((clipRect->x - r2.x) / ww) * ww;
+ if (r2.y < clipRect->y) r2.y += ((clipRect->y - r2.y) / hh) * hh;
}
- //Set the targets to the draw location plus one to ensure it gets drawn at least once.
- int targetX=r2.x+1;
- int targetY=r2.y+1;
- if(w!=0 && parent->positioning.horizontalAlign==REPEAT)
- targetX=x+w-ew;
- if(h!=0 && parent->positioning.verticalAlign==REPEAT)
- targetY=y+h-eh;
//As long as we haven't exceeded the horizontal target keep drawing.
- while(r2.x<targetX){
+ while (r2.x < r2.w){
//Store the y position for when more than one column has to be drawn.
- const int y2=r2.y;
+ const int y2 = r2.y;
//As long as we haven't exceeded the vertical target keep drawing.
- while(r2.y<targetY){
+ while (r2.y < r2.h){
//Check if we should clip.
- SDL_Rect srcrect={r1.x,r1.y,r1.w,r1.h};
- if(w!=0 && r2.x+r1.w>x+w-ew)
- srcrect.w-=(r2.x+r1.w)-(x+w-ew);
- if(h!=0 && r2.y+r1.h>y+h-eh)
- srcrect.h-=(r2.y+r1.h)-(y+h-eh);
-
+ SDL_Rect srcrect = { xx, yy, ww, hh };
+ if (r2.x + ww > r2.w) srcrect.w = r2.w - r2.x;
+ if (r2.y + hh > r2.w) srcrect.h = r2.h - r2.y;
+
//NOTE: dstrect will hold the blit rectangle after calling SDL_BlitSurface, so we can't use r2.
- const SDL_Rect dstrect={r2.x,r2.y,srcrect.w,srcrect.h};
- SDL_RenderCopy(&renderer, src, &srcrect, &dstrect);
- r2.y+=r1.h;
+ SDL_Rect dstrect = { r2.x, r2.y, 0, 0 };
+
+ //Clip the left and top
+ if (clipRect) {
+ int d = clipRect->x - dstrect.x;
+ if (d > 0) {
+ srcrect.x += d; srcrect.w -= d; dstrect.x += d;
+ }
+ d = clipRect->y - dstrect.y;
+ if (d > 0) {
+ srcrect.y += d; srcrect.h -= d; dstrect.y += d;
+ }
+ }
+
+ if (srcrect.w > 0 && srcrect.h > 0) {
+ dstrect.w = srcrect.w;
+ dstrect.h = srcrect.h;
+ SDL_RenderCopy(&renderer, src, &srcrect, &dstrect);
+ }
+ r2.y += hh;
}
- r2.x+=r1.w;
+ r2.x += ww;
//Reset the y position before drawing a new column.
- r2.y=y2;
+ r2.y = y2;
}
}
}
void ThemeObjectInstance::updateAnimation(){
//First get the animation length.
int m;
m=parent->animationLength;
//If it's higher than 0 then we have an animation.
if(m>0 && animation>=0){
//Increase the animation frame.
animation++;
//Check if the animation is beyond the length, if so set it to the looppoint.
if(animation>=m)
animation=parent->animationLoopPoint;
}
}
void ThemeBlockInstance::updateAnimation(){
//Make sure the currentState isn't null.
if(currentState!=NULL){
//Call the updateAnimation method of the currentState.
currentState->updateAnimation();
//Get the length of the animation.
int m=currentState->parent->oneTimeAnimationLength;
//If it's higher than 0 then we have an animation.
//Also check if it's past the lenght, meaning done.
if(m>0 && currentState->animation>=m){
//Now we can change the state to the nextState.
changeState(currentState->parent->nextState);
}
}
}
void ThemeBlock::createInstance(ThemeBlockInstance* obj){
//Make sure the given ThemeBlockInstance is ready.
obj->blockStates.clear();
obj->transitions.clear();
obj->currentState=NULL;
//Loop through the blockstates.
for(map<string,ThemeBlockState*>::iterator it=blockStates.begin();it!=blockStates.end();++it){
//Get the themeBlockStateInstance of the given ThemeBlockInstance.
ThemeBlockStateInstance &obj1=obj->blockStates[it->first];
//Set the parent of the state instance.
obj1.parent=it->second;
//Create the state instance.
createStateInstance(&obj1);
}
//Loop through the transitions.
for(map<pair<string,string>,ThemeBlockState*>::iterator it=transitions.begin();it!=transitions.end();++it){
//Get the themeBlockStateInstance of the given ThemeBlockInstance.
ThemeBlockStateInstance &obj1=obj->transitions[it->first];
//Set the parent of the state instance.
obj1.parent=it->second;
//Create the state instance.
createStateInstance(&obj1);
}
//Change the state to the default one.
//FIXME: Is that needed?
obj->changeState("default");
}
void ThemeBlock::createStateInstance(ThemeBlockStateInstance* obj){
//Get the vector with themeObjects.
vector<ThemeObject*> &v=obj->parent->themeObjects;
//Loop through them.
for(unsigned int i=0;i<v.size();i++){
//Create an instance for every one.
ThemeObjectInstance p;
//Set the parent.
p.parent=v[i];
//Choose the picture.
if(stateID==STATE_LEVEL_EDITOR){
if(p.parent->invisibleAtDesignTime)
continue;
if(p.parent->editorPicture.texture!=NULL)
p.picture=&p.parent->editorPicture;
}else{
if(p.parent->invisibleAtRunTime)
continue;
}
//Get the number of optional Pictures.
int m=p.parent->optionalPicture.size();
//If p.picture is null, not an editor picture, and there are optional pictures then give one random.
if(p.picture==NULL && m>0){
double f=0.0,f1=1.0/256.0;
for(int j=0;j<8;j++){
f+=f1*(double)(rand()&0xff);
f1*=(1.0/256.0);
}
for(int j=0;j<m;j++){
f-=p.parent->optionalPicture[j].first;
if(f<0.0){
p.picture=p.parent->optionalPicture[j].second;
break;
}
}
}
//If random turned out to give nothing then give the non optional picture.
if(p.picture==NULL && p.parent->picture.texture!=NULL)
p.picture=&p.parent->picture;
//If the picture isn't null then can we give it to the ThemeBlockStateInstance.
if(p.picture!=NULL)
obj->objects.push_back(p);
}
}
void ThemePicture::draw(SDL_Renderer& renderer,int x,int y,int animation,SDL_Rect *clipRect){
//Get the Picture.
if(texture==NULL) return;
int ex=0,ey=0,xx,yy,ww,hh;
{
const vector<typeOffsetPoint> &v=offset.offsetData;
if(offset.length==0 || animation<v[0].frameDisplayTime){
xx=v[0].x;
yy=v[0].y;
ww=v[0].w;
hh=v[0].h;
}else if(animation>=offset.length){
int i=v.size()-1;
xx=v[i].x;
yy=v[i].y;
ww=v[i].w;
hh=v[i].h;
}else{
int t=animation-v[0].frameDisplayTime;
for(unsigned int i=1;i<v.size();i++){
int tt=t/v[i].frameDisplayTime;
if(tt>=0 && tt<v[i].frameCount){
xx=(int)((float)v[i-1].x+(float)(v[i].x-v[i-1].x)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
yy=(int)((float)v[i-1].y+(float)(v[i].y-v[i-1].y)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
ww=(int)((float)v[i-1].w+(float)(v[i].w-v[i-1].w)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
hh=(int)((float)v[i-1].h+(float)(v[i].h-v[i-1].h)*(float)(tt+1)/(float)v[i].frameCount+0.5f);
break;
}else{
t-=v[i].frameCount*v[i].frameDisplayTime;
}
}
}
}
//Draw the Picture.
if(clipRect){
int d;
d=clipRect->x-ex;
if(d>0){
ex+=d;
xx+=d;
ww-=d;
}
d=clipRect->y-ey;
if(d>0){
ey+=d;
yy+=d;
hh-=d;
}
if(ww>clipRect->w) ww=clipRect->w;
if(hh>clipRect->h) hh=clipRect->h;
}
if(ww>0&&hh>0){
SDL_Rect r1={xx,yy,ww,hh};
SDL_Rect r2={x+ex,y+ey,ww,hh};
SDL_RenderCopy(&renderer, texture.get(), &r1, &r2);
}
}
//This method will scale the background picture (if needed and configured) to the current SCREEN_WIDTH and SCREEN_HEIGHT.
void ThemeBackgroundPicture::scaleToScreen(){
//Only scale if needed.
if(scale){
// SDL2 allows us to scale the texture when rendering, so
// we only need to adjust the size of the destination rect.
destSize.w = SCREEN_WIDTH;
destSize.h = SCREEN_HEIGHT;
}
}
void ThemeBackgroundPicture::draw(SDL_Renderer &dest){
//Check if the picture is visible.
if(!(texture&&srcSize.w>0&&srcSize.h>0&&destSize.w>0&&destSize.h>0))
return;
//Calculate the draw area.
int sx=(int)((float)destSize.x+currentX-cameraX*(float)camera.x+0.5f);
int sy=(int)((float)destSize.y+currentY-cameraY*(float)camera.y+0.5f);
int ex,ey;
//Include repeating.
if(repeatX){
sx%=destSize.w;
if(sx>0) sx-=destSize.w;
ex=SCREEN_WIDTH;
}else{
if(sx<=-(int)destSize.w || sx>=SCREEN_WIDTH) return;
ex=sx+1;
}
if(repeatY){
sy%=destSize.h;
if(sy>0) sy-=destSize.h;
ey=SCREEN_HEIGHT;
}else{
if(sy<=-(int)destSize.h || sy>=SCREEN_HEIGHT) return;
ey=sy+1;
}
//And finally draw the ThemeBackgroundPicture.
for(int x=sx;x<ex;x+=destSize.w){
for(int y=sy;y<ey;y+=destSize.h){
// NOTE: Rendercopy cares about w/h here
// so had to add it for SDL2 port.
SDL_Rect r={x,y,destSize.w,destSize.h};
//SDL_BlitSurface(picture,&srcSize,dest,&r);
SDL_RenderCopy(&dest, texture.get(), &srcSize, &r);
}
}
}
bool ThemeBackgroundPicture::loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager &imageManager, SDL_Renderer& renderer){
//Load the picture directly into a texture.
texture = imageManager.loadTexture(themePath+objNode->value[0], renderer);
if (!texture) {
return false;
}
//Retrieve the source size.
{
vector<string> &v=objNode->attributes["srcSize"];
if(v.size()>=4){
srcSize.x=atoi(v[0].c_str());
srcSize.y=atoi(v[1].c_str());
srcSize.w=atoi(v[2].c_str());
srcSize.h=atoi(v[3].c_str());
}else{
srcSize.x=0;
srcSize.y=0;
// This gets the width and height of the texture.
SDL_QueryTexture(texture.get(), NULL, NULL, &srcSize.w, &srcSize.h);
}
//Cache the sourcesize.
cachedSrcSize=srcSize;
}
//Retrieve the destination size.
{
vector<string> &v=objNode->attributes["destSize"];
if(v.size()>=4){
destSize.x=atoi(v[0].c_str());
destSize.y=atoi(v[1].c_str());
destSize.w=atoi(v[2].c_str());
destSize.h=atoi(v[3].c_str());
}else{
destSize.x=0;
destSize.y=0;
destSize.w=SCREEN_WIDTH;
destSize.h=SCREEN_HEIGHT;
}
//Cache the destsize.
cachedDestSize=destSize;
}
//Retrieve if we should scale to screen.
{
//Get scaleToScreen.
vector<string> &v=objNode->attributes["scaleToScreen"];
//Boolean if the image should be scaled, default is true.
scale=true;
if(!v.empty()){
scale=atoi(v[0].c_str());
}
//Now scaleToScreen.
//NOTE: We don't check if scaleToScreen is true or false since that is done in scaleToScreen();
scaleToScreen();
}
//Retrieve if it should be repeated.
{
vector<string> &v=objNode->attributes["repeat"];
if(v.size()>=2){
repeatX=atoi(v[0].c_str())?true:false;
repeatY=atoi(v[1].c_str())?true:false;
}else{
repeatX=true;
repeatY=true;
}
}
//Retrieve the speed.
{
vector<string> &v=objNode->attributes["speed"];
if(v.size()>=2){
speedX=atof(v[0].c_str());
speedY=atof(v[1].c_str());
}else{
speedX=0.0f;
speedY=0.0f;
}
}
//Retrieve the camera speed.
{
vector<string> &v=objNode->attributes["cameraSpeed"];
if(v.size()>=2){
cameraX=atof(v[0].c_str());
cameraY=atof(v[1].c_str());
}else{
cameraX=0.0f;
cameraY=0.0f;
}
}
//Done and nothing went wrong so return true.
return true;
}
//Constructor.
ThemeStack::ThemeStack(){
}
//Destructor.
ThemeStack::~ThemeStack(){
//Loop through the themes and delete them.
for(unsigned int i=0;i<objThemes.size();i++)
delete objThemes[i];
}
//Method that will destroy the ThemeStack.
void ThemeStack::destroy(){
//Loop through the themes and delete them.
for(unsigned int i=0;i<objThemes.size();i++)
delete objThemes[i];
//Clear the vector to prevent dangling pointers.
objThemes.clear();
}
//Method that will append a theme to the stack.
//obj: The ThemeManager to add.
void ThemeStack::appendTheme(ThemeManager* obj){
objThemes.push_back(obj);
//debug
#if defined(DEBUG) || defined(_DEBUG)
cout<<"ThemeStack::appendTheme(): theme count="<<objThemes.size()<<endl;
#endif
}
//Method that will remove the last theme added to the stack.
void ThemeStack::removeTheme(){
//Make sure that the stack isn't empty.
if(!objThemes.empty()){
delete objThemes.back();
objThemes.pop_back();
}
}
//Method that will append a theme that will be loaded from file.
//fileName: The file to load the theme from.
//Returns: Pointer to the newly added theme, NULL if failed.
ThemeManager* ThemeStack::appendThemeFromFile(const string& fileName, ImageManager &imageManager, SDL_Renderer &renderer){
//Create a new themeManager.
ThemeManager* obj=new ThemeManager();
//Let it load from the given file.
if(!obj->loadFile(fileName, imageManager, renderer)){
//Failed thus delete the theme and return null.
cerr<<"ERROR: Failed loading theme "<<fileName<<endl;
delete obj;
return NULL;
}else{
//Succeeded, add it to the stack and return it.
objThemes.push_back(obj);
return obj;
}
}
//Method that is used to let the themes scale.
void ThemeStack::scaleToScreen(){
//Loop through the themes and call their scaleToScreen method.
for(unsigned int i=0;i<objThemes.size();i++)
objThemes[i]->scaleToScreen();
}
//Get a pointer to the ThemeBlock of a given block type.
//index: The type of block.
//Returns: Pointer to the ThemeBlock.
ThemeBlock* ThemeStack::getBlock(int index,bool menu){
//Loop through the themes from top to bottom.
for(int i=objThemes.size()-1;i>=0;i--){
//Get the block from the theme.
ThemeBlock* obj=objThemes[i]->getBlock(index,menu);
//Check if it isn't null.
if(obj)
return obj;
}
//Nothing found.
return NULL;
}
ThemeBlock* ThemeStack::getScenery(const std::string& name) {
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--){
//Get the block from the theme.
ThemeBlock* obj = objThemes[i]->getScenery(name);
//Check if it isn't null.
if (obj)
return obj;
}
//Nothing found.
return NULL;
}
void ThemeStack::getSceneryBlockNames(std::set<std::string> &s) {
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--){
objThemes[i]->getSceneryBlockNames(s);
}
}
//Get a pointer to the ThemeBlock of the shadow or the player.
//isShadow: Boolean if it's the shadow
//Returns: Pointer to the ThemeBlock.
ThemeBlock* ThemeStack::getCharacter(bool isShadow){
//Loop through the themes from top to bottom.
for(int i=objThemes.size()-1;i>=0;i--){
//Get the ThemeBlock from the theme.
ThemeBlock* obj=objThemes[i]->getCharacter(isShadow);
//Check if it isn't null.
if(obj)
return obj;
}
//Nothing found.
return NULL;
}
//Get a pointer to the ThemeBackground of the theme.
//Returns: Pointer to the ThemeBackground.
ThemeBackground* ThemeStack::getBackground(bool menu){
//Loop through the themes from top to bottom.
for(int i=objThemes.size()-1;i>=0;i--){
//Get the ThemeBackground from the theme.
ThemeBackground* obj=objThemes[i]->getBackground(menu);
//Check if it isn't null.
if(obj)
return obj;
}
//Nothing found.
return NULL;
}
diff --git a/src/ThemeManager.h b/src/ThemeManager.h
index 280fe13..3d3bb90 100644
--- a/src/ThemeManager.h
+++ b/src/ThemeManager.h
@@ -1,949 +1,949 @@
/*
* 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 THEMEMANAGER_H
#define THEMEMANAGER_H
#include "Globals.h"
#include <string>
#include <vector>
#include <utility>
#include <iostream>
using namespace std;
class ImageManager;
class TreeStorageNode;
//Structure containing offset data for one frame.
struct typeOffsetPoint{
//The location (x,y) and size (w,h).
int x,y,w,h;
//The frame to which this offset applies.
int frameCount;
//The number of frames this offset is shown.
int frameDisplayTime;
};
//We already need the classes so declare them here.
class ThemeOffsetData;
class ThemePicture;
class ThemeObject;
class ThemeBlockState;
class ThemeBlock;
//Instance class of a ThemeObject, this is used by the other Instance classes.
class ThemeObjectInstance{
public:
//Pointer to the picture.
ThemePicture* picture;
//Pointer to the parent the object an instance os is.
ThemeObject* parent;
//Integer containing the current animation frame.
int animation;
//Integer containing the saved animation frame.
int savedAnimation;
public:
//Constructor.
ThemeObjectInstance():picture(NULL),parent(NULL),animation(0),savedAnimation(0){}
//Method used to draw the ThemeObject.
//dest: The destination surface to draw the ThemeObject on.
//x: The x location of the area to draw in.
//y: The y location of the area to draw in.
//w: The width of the area to draw in.
//h: The height of the area to draw in.
//clipRect: Rectangle used to clip.
- void draw(SDL_Renderer& renderer,int x,int y,int w=0,int h=0,SDL_Rect* clipRect=NULL);
+ void draw(SDL_Renderer& renderer,int x,int y,int w=0,int h=0,const SDL_Rect* clipRect=NULL);
//Method that will update the animation.
void updateAnimation();
//Method that will reset the animation.
//save: Boolean if the saved animation should be deleted.
void resetAnimation(bool save){
animation=0;
if(save){
savedAnimation=0;
}
}
//Method that will save the animation.
void saveAnimation(){
savedAnimation=animation;
}
//Method that will load a saved animation.
void loadAnimation(){
animation=savedAnimation;
}
};
//Instance class of a ThemeBlockState, this is used by the ThemeBlockInstance.
class ThemeBlockStateInstance{
public:
//Pointer to the parent the state an instance of is.
ThemeBlockState *parent;
//Vector containing the ThemeObjectInstances.
vector<ThemeObjectInstance> objects;
//Integer containing the current animation frame.
int animation;
//Integer containing the saved animation frame.
int savedAnimation;
public:
//Constructor.
ThemeBlockStateInstance():parent(NULL),animation(0),savedAnimation(0){}
//Method used to draw the ThemeBlockState.
//dest: The destination surface to draw the ThemeBlockState on.
//x: The x location of the area to draw in.
//y: The y location of the area to draw in.
//w: The width of the area to draw in.
//h: The height of the area to draw in.
//clipRect: Rectangle used to clip.
- void draw(SDL_Renderer& renderer,int x,int y,int w=0,int h=0,SDL_Rect *clipRect=NULL){
+ void draw(SDL_Renderer& renderer,int x,int y,int w=0,int h=0,const SDL_Rect *clipRect=NULL){
for(unsigned int i=0;i<objects.size();i++){
objects[i].draw(renderer,x,y,w,h,clipRect);
}
}
//Method that will update the animation.
void updateAnimation(){
for(unsigned int i=0;i<objects.size();i++){
objects[i].updateAnimation();
}
animation++;
}
//Method that will reset the animation.
//save: Boolean if the saved state should be deleted.
void resetAnimation(bool save){
for(unsigned int i=0;i<objects.size();i++){
objects[i].resetAnimation(save);
}
animation=0;
if(save){
savedAnimation=0;
}
}
//Method that will save the animation.
void saveAnimation(){
for(unsigned int i=0;i<objects.size();i++){
objects[i].saveAnimation();
}
savedAnimation=animation;
}
//Method that will load a saved animation.
void loadAnimation(){
for(unsigned int i=0;i<objects.size();i++){
objects[i].loadAnimation();
}
animation=savedAnimation;
}
};
//Instance of a ThemeBlock, this is used by blocks in the game to prevent changing the theme in game.
//It also allows animation to run independently.
class ThemeBlockInstance{
public:
//Pointer to the current state.
ThemeBlockStateInstance* currentState;
//The name of the current state.
string currentStateName;
//Map containing the blockStates.
map<string,ThemeBlockStateInstance> blockStates;
//Map containing the blockTransitionStates.
map<pair<string,string>,ThemeBlockStateInstance> transitions;
//String containing the name of the saved state.
string savedStateName;
public:
//Constructor.
ThemeBlockInstance():currentState(NULL){}
//Method used to draw the ThemeBlock.
//renderer: The destination renderer to draw the ThemeBlock on.
//x: The x location of the area to draw in.
//y: The y location of the area to draw in.
//w: The width of the area to draw in.
//h: The height of the area to draw in.
//clipRect: Rectangle used to clip.
//Returns: True if it succeeds.
- bool draw(SDL_Renderer& renderer,int x,int y,int w=0,int h=0,SDL_Rect *clipRect=NULL){
+ bool draw(SDL_Renderer& renderer,int x,int y,int w=0,int h=0,const SDL_Rect *clipRect=NULL){
if(currentState!=NULL){
currentState->draw(renderer,x,y,w,h,clipRect);
return true;
}
return false;
}
//Method that will draw a specific state.
//s: The name of the state to draw.
//dest: The destination surface to draw the ThemeBlock on.
//x: The x location of the area to draw in.
//y: The y location of the area to draw in.
//w: The width of the area to draw in.
//h: The height of the area to draw in.
//clipRect: Rectangle used to clip.
//Returns: True if it succeeds.
bool drawState(const string& s,SDL_Renderer& renderer,int x,int y,int w=0,int h=0,SDL_Rect *clipRect=NULL){
map<string,ThemeBlockStateInstance>::iterator it=blockStates.find(s);
if(it!=blockStates.end()){
it->second.draw(renderer,x,y,w,h,clipRect);
return true;
}
return false;
}
//Method that will change the current state.
//s: The name of the state to change to.
//reset: Boolean if the animation should reset.
//Returns: True if it succeeds (exists).
bool changeState(const string& s,bool reset=true){
bool newState=false;
//First check if there's a transition.
{
pair<string,string> s1=pair<string,string>(currentStateName,s);
map<pair<string,string>,ThemeBlockStateInstance>::iterator it=transitions.find(s1);
if(it!=transitions.end()){
currentState=&it->second;
//NOTE: We set the currentState name to target state name.
//Worst case senario is that the animation is skipped when saving/loading at a checkpoint.
currentStateName=s;
newState=true;
}
}
//If there isn't a transition go directly to the state.
if(!newState){
//Get the new state.
map<string,ThemeBlockStateInstance>::iterator it=blockStates.find(s);
//Check if it exists.
if(it!=blockStates.end()){
currentState=&it->second;
currentStateName=it->first;
newState=true;
}
}
//Check if a state has been found.
if(newState){
//FIXME: Is it needed to set the savedStateName here?
if(savedStateName.empty())
savedStateName=currentStateName;
//If reset then reset the animation.
if(reset)
currentState->resetAnimation(true);
return true;
}
//It doesn't so return false.
return false;
}
//Method that will update the animation.
void updateAnimation();
//Method that will reset the animation.
//save: Boolean if the saved state should be deleted.
void resetAnimation(bool save){
for(map<string,ThemeBlockStateInstance>::iterator it=blockStates.begin();it!=blockStates.end();++it){
it->second.resetAnimation(save);
}
if(save){
savedStateName.clear();
}
}
//Method that will save the animation.
void saveAnimation(){
for(map<string,ThemeBlockStateInstance>::iterator it=blockStates.begin();it!=blockStates.end();++it){
it->second.saveAnimation();
}
savedStateName=currentStateName;
}
//Method that will restore a saved animation.
void loadAnimation(){
for(map<string,ThemeBlockStateInstance>::iterator it=blockStates.begin();it!=blockStates.end();++it){
it->second.loadAnimation();
}
changeState(savedStateName,false);
}
};
//Class containing the offset data.
class ThemeOffsetData{
public:
//Vector containing the offsetDatas.
vector<typeOffsetPoint> offsetData;
//The length of the "animation" in frames.
int length;
public:
//Constructor.
ThemeOffsetData():length(0){}
//Destructor.
~ThemeOffsetData(){}
//Method used to destroy the offsetData.
void destroy(){
//Set length to zero.
length=0;
//And clear the offsetData vector.
offsetData.clear();
}
//Method that will load the offsetData from a node.
//objNode: Pointer to the TreeStorageNode to read the data from.
//Returns: True if it succeeds without errors.
bool loadFromNode(TreeStorageNode* objNode);
};
enum Alignment{
//Horizontal alignments
LEFT,
CENTRE,
RIGHT,
//Vertical alignments
TOP,
MIDDLE,
BOTTOM,
//NOTE: Repeat can be used for both horizontal and vertical alignments.
REPEAT
};
//Class containing the positioning and repeat data.
class ThemePositioningData{
public:
//Horizontal and vertical alignment data.
Alignment horizontalAlign,verticalAlign;
public:
//Constructor.
ThemePositioningData(){}
//Destructor.
~ThemePositioningData(){}
//Method used to destroy the positioningData.
void destroy(){
horizontalAlign=REPEAT;
verticalAlign=REPEAT;
}
//Method that will load the positioningData from a node.
//objNode: Pointer to the TreeStorageNode to read the data from.
//Returns: True if it succeeds without errors.
bool loadFromNode(TreeStorageNode* objNode);
};
//This is the lowest level of the theme system.
//It's a picture with offset data.
class ThemePicture{
public:
//Pointer to actual texture. Handled by ImageManager.
SharedTexture texture;
//Offset data for the picture.
ThemeOffsetData offset;
int x;
int y;
public:
//Constructor.
ThemePicture():texture(NULL), x(0), y(0){}
//Destructor.
~ThemePicture(){}
//Method used to destroy the picture.
void destroy(){
//Freeing handled by ImageManager.
//TODO: Unload unused images
texture=NULL;
//Destroy the offset data.
offset.destroy();
}
bool loadFromNode(TreeStorageNode* objNode, string themePath, ImageManager& imageManager, SDL_Renderer& renderer);
//Method that will draw the ThemePicture.
//dest: The destination surface.
//x: The x location on the dest to draw the picture.
//y: The y location on the dest to draw the picture.
//animation: The frame of the animation to draw.
//clipRect: Rectangle to clip the picture.
void draw(SDL_Renderer& renderer,int x,int y,int animation=0, SDL_Rect* clipRect=NULL);
};
//The ThemeObject class is used to contain a basic theme element.
//Contains the picture, animation information, etc...
class ThemeObject{
public:
//Integer containing the length of the animation.
int animationLength;
//Integer containing the frame from where the animation is going to loop.
int animationLoopPoint;
//Boolean if the animation is invisible at run time (Game state).
bool invisibleAtRunTime;
//Boolean if the animation is invisible at design time (Level editor).
bool invisibleAtDesignTime;
//Picture of the ThemeObject.
ThemePicture picture;
//Picture of the ThemeObject shown when in the level editor.
ThemePicture editorPicture;
//Vector containing optionalPicture for the ThemeObject.
vector<pair<double,ThemePicture*> > optionalPicture;
//ThemeOffsetData for the ThemeObject.
ThemeOffsetData offset;
//ThemePositionData for the ThemeObject.
ThemePositioningData positioning;
public:
//Constructor.
ThemeObject():animationLength(0),animationLoopPoint(0),invisibleAtRunTime(false),invisibleAtDesignTime(false){}
//Destructor.
~ThemeObject(){
//Loop through the optionalPicture and delete them.
for(unsigned int i=0;i<optionalPicture.size();i++){
delete optionalPicture[i].second;
}
}
//Method that will destroy the ThemeObject.
void destroy(){
//Loop through the optionalPicture and delete them.
for(unsigned int i=0;i<optionalPicture.size();i++){
delete optionalPicture[i].second;
}
optionalPicture.clear();
animationLength=0;
animationLoopPoint=0;
invisibleAtRunTime=false;
invisibleAtDesignTime=false;
picture.destroy();
editorPicture.destroy();
offset.destroy();
positioning.destroy();
}
//Method that will load a ThemeObject from a node.
//objNode: The TreeStorageNode to read the object from.
//themePath: Path to the theme.
//Returns: True if it succeeds.
bool loadFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer);
};
//Class containing a single state of a themed block.
class ThemeBlockState{
public:
//The length in frames of the oneTimeAnimation.
int oneTimeAnimationLength;
//String containing the name of the next state.
string nextState;
//Vector containing the themeObjects that make up this state.
vector<ThemeObject*> themeObjects;
public:
//Constructor.
ThemeBlockState():oneTimeAnimationLength(0){}
//Destructor.
~ThemeBlockState(){
//Loop through the ThemeObjects and delete them.
for(unsigned int i=0;i<themeObjects.size();i++){
delete themeObjects[i];
}
}
//Method that will destroy the ThemeBlockState.
void destroy(){
//Loop through the ThemeObjects and delete them.
for(unsigned int i=0;i<themeObjects.size();i++){
delete themeObjects[i];
}
//Clear the themeObjects vector.
themeObjects.clear();
//Set the length to 0.
oneTimeAnimationLength=0;
//Clear the nextState string.
nextState.clear();
}
//Method that will load a ThemeBlockState from a node.
//objNode: The TreeStorageNode to read the state from.
//themePath: Path to the theme.
//Returns: True if it succeeds.
bool loadFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer);
};
//Class containing the needed things for a themed block.
class ThemeBlock{
public:
//Picture that is shown only in the level editor.
ThemePicture editorPicture;
//Map containing ThemeBlockStates for the different states of a block.
map<string,ThemeBlockState*> blockStates;
//Map containing the transition states between blocks states.
map<pair<string,string>,ThemeBlockState*> transitions;
public:
//Constructor.
ThemeBlock(){}
//Destructor/
~ThemeBlock(){
//Loop through the ThemeBlockStates and delete them,
for(map<string,ThemeBlockState*>::iterator i=blockStates.begin();i!=blockStates.end();++i){
delete i->second;
}
//Loop through the ThemeBlockStates and delete them,
for(map<pair<string,string>,ThemeBlockState*>::iterator i=transitions.begin();i!=transitions.end();++i){
delete i->second;
}
}
//Method that will destroy the ThemeBlock.
void destroy(){
//Loop through the ThemeBlockStates and delete them,
for(map<string,ThemeBlockState*>::iterator i=blockStates.begin();i!=blockStates.end();++i){
delete i->second;
}
//Loop through the ThemeBlockStates transitions and delete them,
for(map<pair<string,string>,ThemeBlockState*>::iterator i=transitions.begin();i!=transitions.end();++i){
delete i->second;
}
//Clear the blockStates map.
blockStates.clear();
transitions.clear();
editorPicture.destroy();
}
//Method that will load a ThemeBlock from a node.
//objNode: The TreeStorageNode to load the ThemeBlock from.
//themePath: The path to the theme.
//Returns: True if it succeeds.
bool loadFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer);
//Method that will create a ThemeBlockInstance.
//obj: Pointer that will be filled with the instance.
void createInstance(ThemeBlockInstance* obj);
private:
//Method that will create a ThemeBlockStateInstance.
//obj: Pointer that will be filled with the instance.
void createStateInstance(ThemeBlockStateInstance* obj);
};
//ThemeBackgroundPicture is a class containing the picture for the background.
class ThemeBackgroundPicture{
private:
//Rectangle that should be taken from the picture.
//NOTE The size is pixels of the image.
SDL_Rect cachedSrcSize;
//Rectangle with the size it will have on the destination (screen).
//NOTE The size is in pixels or in precentages (if scaleToScreen is true).
SDL_Rect cachedDestSize;
//Pointer to the SDL_Texture containing the picture. (Creation/destruction handled by ImageManager)
SharedTexture texture;
//Rectangle that should be taken from the picture.
//NOTE The size is pixels of the image.
SDL_Rect srcSize;
//Rectangle with the size it will have on the destination (screen).
//NOTE The size is in pixels even though the loaded value from the theme description file can be in precentages (if scaleToScreen is true).
SDL_Rect destSize;
//Boolean if the background picture should be scaled to screen.
bool scale;
//Boolean if the image should be repeated over the x-axis.
bool repeatX;
//Boolean if the image should be repeated over the y-axis.
bool repeatY;
//Float containing the speed the background picture moves along the x-axis.
float speedX;
//Float containing the speed the background picture moves along the y-axis.
float speedY;
//Float containing the horizontal speed the picture will have when moving the camera (horizontally).
float cameraX;
//Float containing the vertical speed the picture will have when moving the camera (vertically).
float cameraY;
private:
//Float with the current x position.
float currentX;
//Float with the current y position.
float currentY;
//Stored x location for when loading a state.
float savedX;
//Stored y location for when loading a state.
float savedY;
public:
//Constructor.
ThemeBackgroundPicture(){
//Set some default values.
texture=NULL;
memset(&srcSize,0,sizeof(srcSize));
memset(&destSize,0,sizeof(destSize));
memset(&cachedSrcSize,0,sizeof(cachedSrcSize));
memset(&cachedDestSize,0,sizeof(cachedDestSize));
scale=true;
repeatX=true;
repeatY=true;
speedX=0.0f;
speedY=0.0f;
cameraX=0.0f;
cameraY=0.0f;
currentX=0.0f;
currentY=0.0f;
savedX=0.0f;
savedY=0.0f;
}
//Method that will update the animation.
void updateAnimation(){
//Move the picture along the x-axis.
currentX+=speedX;
if(repeatX && destSize.w>0){
float f=(float)destSize.w;
if(currentX>f || currentX<-f) currentX-=f*floor(currentX/f);
}
//Move the picture along the y-axis.
currentY+=speedY;
if(repeatY && destSize.h>0){
float f=(float)destSize.h;
if(currentY>f || currentY<-f) currentY-=f*floor(currentY/f);
}
}
//Method that will reset the animation.
//save: Boolean if the saved state should be deleted.
void resetAnimation(bool save){
currentX=0.0f;
currentY=0.0f;
if(save){
savedX=0.0f;
savedY=0.0f;
}
}
//Method that will save the animation.
void saveAnimation(){
savedX=currentX;
savedY=currentY;
}
//Method that will load the animation.
void loadAnimation(){
currentX=savedX;
currentY=savedY;
}
//Method used to draw the ThemeBackgroundPicture.
//dest: Pointer to the SDL_Renderer the picture should be drawn on.
void draw(SDL_Renderer& dest);
//Method used to load the ThemeBackgroundPicture from a node.
//objNode: The TreeStorageNode to load the picture from.
//themePath: The path to the theme.
bool loadFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer);
//This method will scale the background picture (if needed and configured) to the current SCREEN_WIDTH and SCREEN_HEIGHT.
void scaleToScreen();
};
//Class that forms the complete background of a theme.
//It is in fact nothing more than a vector containing multiple ThemeBackgroundPictures.
class ThemeBackground{
private:
//Vector containing the ThemeBackgroundPictures.
vector<ThemeBackgroundPicture> picture;
public:
//Method that will update the animation of all the background pictures.
void updateAnimation(){
for(unsigned int i=0;i<picture.size();i++){
picture[i].updateAnimation();
}
}
//Method that will reset the animation of all the background pictures.
//save: Boolean if the saved state should be deleted.
void resetAnimation(bool save){
for(unsigned int i=0;i<picture.size();i++){
picture[i].resetAnimation(save);
}
}
//Method that will save the animation of all the background pictures.
void saveAnimation(){
for(unsigned int i=0;i<picture.size();i++){
picture[i].saveAnimation();
}
}
//Method that will load the animation of all the background pictures.
void loadAnimation(){
for(unsigned int i=0;i<picture.size();i++){
picture[i].loadAnimation();
}
}
//Method that will scale the background pictures (if set) to the current screen resolution.
void scaleToScreen(){
for(unsigned int i=0;i<picture.size();i++){
picture[i].scaleToScreen();
}
}
//This method will draw all the background pictures.
//dest: Pointer to the SDL_Renderer to draw them on.
void draw(SDL_Renderer& renderer){
for(unsigned int i=0;i<picture.size();i++){
picture[i].draw(renderer);
}
}
//Method that will add a ThemeBackgroundPicture to the ThemeBackground.
//objNode: The treeStorageNode to read from.
//themePath: The path to the theme.
//Returns: True if it succeeds.
bool addPictureFromNode(TreeStorageNode* objNode,string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
picture.push_back(ThemeBackgroundPicture());
return picture.back().loadFromNode(objNode,themePath, imageManager, renderer);
}
};
//The ThemeManager is actually a whole theme, filled with ThemeBlocks and ThemeBackground.
class ThemeManager{
private:
//The ThemeBlock of the shadow.
ThemeBlock* shadow;
//The ThemeBlock of the player.
ThemeBlock* player;
//Array containing a ThemeBlock for every block type.
ThemeBlock* objBlocks[TYPE_MAX];
//Map containing all scenery blocks.
std::map<std::string, ThemeBlock*> objScenery;
//The ThemeBackground.
ThemeBackground* objBackground;
//ThemeBackground for menu.
ThemeBackground* menuBackground;
//Level selection background block.
ThemeBlock* menuBlock;
//Level selection background block for locked level.
ThemeBlock* menuShadowBlock;
public:
//String containing the path to the string.
string themePath;
//String containing the theme name.
string themeName;
public:
//Constructor.
ThemeManager(){
//Make sure the pointers are set to NULL.
objBackground=NULL;
//Reserve enough memory for the ThemeBlocks.
memset(objBlocks,0,sizeof(objBlocks));
shadow=NULL;
player=NULL;
menuBackground=NULL;
menuBlock=NULL;
menuShadowBlock = NULL;
}
//Destructor.
~ThemeManager(){
//Just call destroy().
destroy();
}
//Method used to destroy the ThemeManager.
void destroy(){
//Delete the ThemeBlock of the shadow.
if (shadow) {
delete shadow;
shadow = NULL;
}
//Delete the ThemeBlock of the player.
if (player) {
delete player;
player = NULL;
}
//Loop through the ThemeBlocks and delete them.
for(int i=0;i<TYPE_MAX;i++){
if (objBlocks[i]) {
delete objBlocks[i];
objBlocks[i] = NULL;
}
}
//Delete all scenery blocks
for (auto it = objScenery.begin(); it != objScenery.end(); ++it) {
delete it->second;
}
objScenery.clear();
//Delete the ThemeBackgrounds, etc.
if (objBackground) {
delete objBackground;
objBackground = NULL;
}
if (menuBackground) {
delete menuBackground;
menuBackground = NULL;
}
if (menuBlock) {
delete menuBlock;
menuBlock = NULL;
}
if (menuShadowBlock) {
delete menuShadowBlock;
menuShadowBlock = NULL;
}
//And clear the themeName, etc.
themeName.clear();
themePath.clear();
}
//Method that will load the theme from a file.
//fileName: The file to load the theme from.
//Returns: True if it succeeds.
bool loadFile(const string& fileName, ImageManager& imageManager, SDL_Renderer& renderer);
//Method that will scale the theme to the current SCREEN_WIDTH and SCREEN_HEIGHT.
void scaleToScreen(){
//We only need to scale the background.
if(objBackground)
objBackground->scaleToScreen();
}
//Get a pointer to the ThemeBlock of a given block type.
//index: The type of block.
//menu: Boolean if get spefial blocks for menu
//Returns: Pointer to the ThemeBlock.
ThemeBlock* getBlock(int index,bool menu){
if (!menu)
return objBlocks[index];
else
if (index == TYPE_BLOCK)
if (menuBlock)
return menuBlock;
else
return objBlocks[TYPE_BLOCK];
else if (index == TYPE_SHADOW_BLOCK)
if (menuShadowBlock)
return menuShadowBlock;
else if (menuBlock)
return menuBlock;
else
return objBlocks[TYPE_SHADOW_BLOCK];
else
return objBlocks[index];
}
//Get a pointer to the ThemeBlock of a given scenery type.
//name: The name of scenery block.
ThemeBlock* getScenery(const std::string& name){
auto it = objScenery.find(name);
if (it == objScenery.end())
return NULL;
else
return it->second;
}
// Add all names of available scenery blocks to a given set.
void getSceneryBlockNames(std::set<std::string> &s) {
for (auto it = objScenery.begin(); it != objScenery.end(); ++it) {
s.insert(it->first);
}
}
//Get a pointer to the ThemeBlock of the shadow or the player.
//isShadow: Boolean if it's the shadow
//Returns: Pointer to the ThemeBlock.
ThemeBlock* getCharacter(bool isShadow){
if(isShadow)
return shadow;
return player;
}
//Get a pointer to the ThemeBackground of the theme.
//bool: Boolean if get menu background
//Returns: Pointer to the ThemeBackground.
ThemeBackground* getBackground(bool menu){
if(menu&&menuBackground)
return menuBackground;
else
return objBackground;
}
};
//Class that combines multiple ThemeManager into one stack.
//If a file is not in a certain theme it will use one of a lower theme.
class ThemeStack{
private:
//Vector containing the themes in the stack.
vector<ThemeManager*> objThemes;
public:
//Constructor.
ThemeStack();
//Destructor.
~ThemeStack();
//Method that will destroy the ThemeStack.
void destroy();
//Method that will append a theme to the stack.
//obj: The ThemeManager to add.
void appendTheme(ThemeManager* obj);
//Method that will remove the last theme added to the stack.
void removeTheme();
//Method that will append a theme that will be loaded from file.
//fileName: The file to load the theme from.
//Returns: Pointer to the newly added theme, NULL if failed.
ThemeManager* appendThemeFromFile(const string& fileName, ImageManager& imageManager, SDL_Renderer& renderer);
//Method that is used to let the themes scale.
void scaleToScreen();
//Get the number of themes in the stack.
//Returns: The theme count.
int themeCount(){
return (int)objThemes.size();
}
//Operator overloading so that the themes can be accesed using the [] operator.
//i: The index.
ThemeManager* operator[](int i){
return objThemes[i];
}
//Get a pointer to the ThemeBlock of a given block type.
//index: The type of block.
//Returns: Pointer to the ThemeBlock.
ThemeBlock* getBlock(int index,bool menu=false);
//Get a pointer to the ThemeBlock of a given scenery type.
//name: The name of scenery block.
ThemeBlock* getScenery(const std::string& name);
// Add all names of available scenery blocks to a given set.
void getSceneryBlockNames(std::set<std::string> &s);
//Get a pointer to the ThemeBlock of the shadow or the player.
//isShadow: Boolean if it's the shadow
//Returns: Pointer to the ThemeBlock.
ThemeBlock* getCharacter(bool isShadow);
//Get a pointer to the ThemeBackground of the theme.
//Returns: Pointer to the ThemeBackground.
ThemeBackground* getBackground(bool menu);
};
//The ThemeStack that is be used by the GameState.
extern ThemeStack objThemes;
#endif

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 17, 9:29 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
70190
Default Alt Text
(101 KB)

Event Timeline