Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
55 KB
Referenced Files
None
Subscribers
None
diff --git a/docs/ThemeDescription.md b/docs/ThemeDescription.md
index 6c38359..57001ad 100644
--- a/docs/ThemeDescription.md
+++ b/docs/ThemeDescription.md
@@ -1,269 +1,273 @@
Me and My Shadow Theme File Description
=======================================
(draft)
The theme file contains:
~~~
-name=<theme name>
+name=<theme name> //The theme name
+textColor=<r>,<g>,<b> //Optional theme text color. Should be dark or light depending on the theme background is light or dark.
+textColorDialog=<r>,<g>,<b> //Optional theme text color in dialog. Should be dark since the dialog background is light.
~~~
and the following subnode.
1 block subnode
---------------
~~~
block(<block name>){...} //subnode specifies the block's appearance
~~~
block name:
"Block", "PlayerStart", "ShadowStart",
"Exit", "ShadowBlock", "Spikes",
"Checkpoint", "Swap", "Fragile",
"MovingBlock", "MovingShadowBlock", "MovingSpikes",
"Teleporter", "Button", "Switch",
"ConveyorBelt", "ShadowConveyorBelt", "NotificationBlock", "Collectable", "Pushable".
For the newest version of this list, see `Game::blockName` in `Game.cpp`.
In this subnode:
### 1.1 state/characterState/blockState/transitionState 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
state(<state name>){...}
characterState(<state name>){...}
transitionState(<current state>,<new state>){...}
~~~
the state name:
* for all blocks
- "base": Always draw the base before other states (intended to be used in level editor).
- "default": The default state.
* for "Checkpoint","Swap","Switch"
- "activated": The activated state.
* for "Collectable"
- "inactive": The collected state.
* for "Exit"
- "closed": The closed state.
* for "Button"
- "button": The button (which is a separated, movable part of the button).
* for "Fragile"
- "fragile1": The stepped once state.
- "fragile2": The stepped twice state.
- "fragile3": The broken state.
transition state:
If we found a transition state which matches the current state and the next state,
then we switch to this transition state instead of the new state.
NOTE: the transition state should have the oneTimeAnimation attribute and set the correct next state.
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 instead in the level editor.
##### 1.1.1.4 positioning subnode
~~~
positioning(<xalign>,<yalign>)
~~~
* xalign: 'left', 'centre' (NOTE: not 'center'), 'right' or 'repeat' or 'stretch'
* yalign: 'top', 'middle', 'bottom' or 'repeat' or 'stretch'
##### 1.1.1.5 offset subnode
~~~
offset(<x>,<y>[,<w>[,<h>]])
~~~
Shift the top,left,right,bottom of the destination rectangle by x,y,-w,-h.
NOTE: w,h are only used when the corresponding positioning modes are 'repeat' or 'stretch'.
##### 1.1.1.6 pictureAnimation subnode
~~~
pictureAnimation(<file name>){
point(<x>,<y>,<w>,<h>[,<frame count>[,<display time of each frame>]])
point(<x>,<y>,<w>,<h>[,<frame count>[,<display time of each frame>]])
...
}
~~~
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>[,<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.
### 1.2 editorPicture subnode
Syntax:
~~~
editorPicture(<file name>,<x>,<y>,<w>,<h>) //specifies the picture shows in editor
~~~
This subnode is required (?) for block and scenery.
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)
+ destSize=<x>,<y>,<w>,<h> //Specifies the destination size and offset of picture (optional, default value=size of game window WHEN THE THEME FILE IS LOADED (!!!))
+ scaleToScreen=<0 or 1> //Specifies the destination should be scale to screen. (optional, default is 1)
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.
+There can be multiple menuBackground subnodes.
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/ThemeManager.cpp b/src/ThemeManager.cpp
index bafe6d8..67a292b 100644
--- a/src/ThemeManager.cpp
+++ b/src/ThemeManager.cpp
@@ -1,1605 +1,1605 @@
/*
* 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;
ThemeObjectInstance::ThemeObjectInstance()
: picture(NULL), parent(NULL), animation(0), savedAnimation(0)
{
}
void ThemeObjectInstance::resetAnimation(bool save){
animation = 0;
if (save){
savedAnimation = 0;
}
}
void ThemeObjectInstance::saveAnimation(){
savedAnimation = animation;
}
void ThemeObjectInstance::loadAnimation(){
animation = savedAnimation;
}
ThemeBlockStateInstance::ThemeBlockStateInstance()
: parent(NULL), animation(0), savedAnimation(0)
{
}
void ThemeBlockStateInstance::draw(SDL_Renderer& renderer, int x, int y, int w, int h, const SDL_Rect *clipRect){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].draw(renderer, x, y, w, h, clipRect);
}
}
void ThemeBlockStateInstance::updateAnimation(){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].updateAnimation();
}
animation++;
}
void ThemeBlockStateInstance::resetAnimation(bool save){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].resetAnimation(save);
}
animation = 0;
if (save){
savedAnimation = 0;
}
}
void ThemeBlockStateInstance::saveAnimation(){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].saveAnimation();
}
savedAnimation = animation;
}
void ThemeBlockStateInstance::loadAnimation(){
for (unsigned int i = 0; i<objects.size(); i++){
objects[i].loadAnimation();
}
animation = savedAnimation;
}
ThemeBlockInstance::ThemeBlockInstance()
: currentState(NULL)
{
}
bool ThemeBlockInstance::draw(SDL_Renderer& renderer, int x, int y, int w, int h, const SDL_Rect *clipRect){
if (currentState != NULL){
currentState->draw(renderer, x, y, w, h, clipRect);
return true;
}
return false;
}
bool ThemeBlockInstance::drawState(const string& s, SDL_Renderer& renderer, int x, int y, int w, int h, SDL_Rect *clipRect){
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;
}
bool ThemeBlockInstance::changeState(const string& s, bool reset){
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;
}
void ThemeBlockInstance::resetAnimation(bool save){
for (map<string, ThemeBlockStateInstance>::iterator it = blockStates.begin(); it != blockStates.end(); ++it){
it->second.resetAnimation(save);
}
if (save){
savedStateName.clear();
}
}
void ThemeBlockInstance::saveAnimation(){
for (map<string, ThemeBlockStateInstance>::iterator it = blockStates.begin(); it != blockStates.end(); ++it){
it->second.saveAnimation();
}
savedStateName = currentStateName;
}
void ThemeBlockInstance::loadAnimation(){
for (map<string, ThemeBlockStateInstance>::iterator it = blockStates.begin(); it != blockStates.end(); ++it){
it->second.loadAnimation();
}
changeState(savedStateName, false);
}
ThemeOffsetData::ThemeOffsetData()
: length(0)
{
}
void ThemeOffsetData::destroy(){
//Set length to zero.
length = 0;
//And clear the offsetData vector.
offsetData.clear();
}
//Constructor.
ThemePositioningData::ThemePositioningData()
: horizontalAlign(REPEAT), verticalAlign(REPEAT)
{
}
//Method used to destroy the positioningData.
void ThemePositioningData::destroy(){
horizontalAlign = REPEAT;
verticalAlign = REPEAT;
}
ThemePicture::ThemePicture()
:texture(NULL)//, x(0), y(0)
{
}
void ThemePicture::destroy(){
//Freeing handled by ImageManager.
//TODO: Unload unused images
texture = NULL;
//Destroy the offset data.
offset.destroy();
}
ThemeObject::ThemeObject()
:animationLength(0), animationLoopPoint(0), invisibleAtRunTime(false), invisibleAtDesignTime(false)
{
}
ThemeObject::~ThemeObject(){
//Loop through the optionalPicture and delete them.
for (unsigned int i = 0; i<optionalPicture.size(); i++){
delete optionalPicture[i].second;
}
}
void ThemeObject::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();
}
ThemeBlockState::ThemeBlockState()
:oneTimeAnimationLength(0)
{
}
ThemeBlockState::~ThemeBlockState(){
//Loop through the ThemeObjects and delete them.
for (unsigned int i = 0; i<themeObjects.size(); i++){
delete themeObjects[i];
}
}
void ThemeBlockState::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();
}
ThemeBlock::ThemeBlock()
{
}
ThemeBlock::~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;
}
}
void ThemeBlock::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();
}
ThemeBackgroundPicture::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;
}
void ThemeBackgroundPicture::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);
}
}
void ThemeBackgroundPicture::resetAnimation(bool save){
currentX = 0.0f;
currentY = 0.0f;
if (save){
savedX = 0.0f;
savedY = 0.0f;
}
}
void ThemeBackgroundPicture::saveAnimation(){
savedX = currentX;
savedY = currentY;
}
void ThemeBackgroundPicture::loadAnimation(){
currentX = savedX;
currentY = savedY;
}
void ThemeBackground::updateAnimation(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].updateAnimation();
}
}
void ThemeBackground::resetAnimation(bool save){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].resetAnimation(save);
}
}
void ThemeBackground::saveAnimation(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].saveAnimation();
}
}
void ThemeBackground::loadAnimation(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].loadAnimation();
}
}
void ThemeBackground::scaleToScreen(){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].scaleToScreen();
}
}
void ThemeBackground::draw(SDL_Renderer& renderer){
for (unsigned int i = 0; i<picture.size(); i++){
picture[i].draw(renderer);
}
}
bool ThemeBackground::addPictureFromNode(TreeStorageNode* objNode, string themePath, ImageManager& imageManager, SDL_Renderer& renderer){
picture.push_back(ThemeBackgroundPicture());
return picture.back().loadFromNode(objNode, themePath, imageManager, renderer);
}
ThemeManager::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;
hasThemeTextColor = hasThemeTextColorDialog = false;
}
ThemeManager::~ThemeManager(){
//Just call destroy().
destroy();
}
void ThemeManager::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();
}
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
hasThemeTextColor = hasThemeTextColorDialog = false;
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()){
hasThemeTextColor = true;
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()){
hasThemeTextColorDialog = true;
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;
}
void ThemeManager::scaleToScreen(){
//We only need to scale the background.
if (objBackground)
objBackground->scaleToScreen();
}
ThemeBlock* ThemeManager::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];
}
ThemeBlock* ThemeManager::getScenery(const std::string& name){
auto it = objScenery.find(name);
if (it == objScenery.end())
return NULL;
else
return it->second;
}
void ThemeManager::getSceneryBlockNames(std::set<std::string> &s) {
for (auto it = objScenery.begin(); it != objScenery.end(); ++it) {
s.insert(it->first);
}
}
ThemeBlock* ThemeManager::getCharacter(bool isShadow){
if (isShadow)
return shadow;
return player;
}
ThemeBackground* ThemeManager::getBackground(bool menu){
if (menu&&menuBackground)
return menuBackground;
else
return objBackground;
}
bool ThemeManager::getTextColor(bool isDialog, SDL_Color& color) {
if (isDialog) {
if (hasThemeTextColorDialog) color = themeTextColorDialog;
return hasThemeTextColorDialog;
} else {
if (hasThemeTextColor) color = themeTextColor;
return hasThemeTextColor;
}
}
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){
ThemeOffsetPoint 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;
}
}
cerr << "ERROR: The structure of theme picture node '" << objNode->name << "' is incorrect" << endl;
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){
ThemeOffsetPoint 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){
ThemeOffsetPoint 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){
ThemeOffsetPoint 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;
}
cerr << "ERROR: The structure of theme offset data node '" << objNode->name << "' is incorrect" << endl;
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;
} else if (objNode->value[0] == "stretch") {
horizontalAlign = STRETCH;
} else {
cerr << "ERROR: Unknown horizontal align mode: " << objNode->value[0] << endl;
return false;
}
//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;
} else if (objNode->value[1] == "stretch") {
verticalAlign = STRETCH;
} else {
cerr << "ERROR: Unknown vertical align mode: " << objNode->value[1] << endl;
return false;
}
//Done and nothing went wrong so return true.
return true;
}
cerr << "ERROR: The structure of theme positioning data node '" << objNode->name << "' is incorrect" << endl;
return false;
}
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;
//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 or STRETCH.
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<ThemeOffsetPoint> &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<ThemeOffsetPoint> &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(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;
}
if (h <= 0) {
h = hh;
vAlign = TOP;
}
//The destination rectangle (NOTE: the w,h are actually the right and bottom)
SDL_Rect r2={x+ex,y+ey,0,0};
//Align horizontally.
switch (hAlign){
case CENTRE:
r2.x += (w - ww) / 2;
break;
case RIGHT:
r2.x += w - ww;
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 and STRETCH mode)
r2.w = (hAlign == REPEAT || hAlign == STRETCH) ? (x + w - ew) : r2.x + ww;
r2.h = (vAlign == REPEAT || vAlign == STRETCH) ? (y + h - eh) : r2.y + hh;
//For STRETCH mode we have to use SDL builtin clip rect function
//otherwise the texture coordinate is hard to calculate
bool useSDLClipRect = false;
if (clipRect) {
//Clip the right and bottom
if (r2.w > clipRect->x + clipRect->w) {
if (hAlign == STRETCH) useSDLClipRect = true;
else r2.w = clipRect->x + clipRect->w;
}
if (r2.h > clipRect->y + clipRect->h) {
if (vAlign == STRETCH) useSDLClipRect = true;
else r2.h = clipRect->y + clipRect->h;
}
//Clip the left and top (ad-hoc code)
if (r2.x < clipRect->x) {
if (hAlign == STRETCH) useSDLClipRect = true;
else r2.x += ((clipRect->x - r2.x) / ww) * ww;
}
if (r2.y < clipRect->y) {
if (vAlign == STRETCH) useSDLClipRect = true;
else r2.y += ((clipRect->y - r2.y) / hh) * hh;
}
}
//Set the SDL clip rect if necessary
if (useSDLClipRect) {
SDL_RenderSetClipRect(&renderer, clipRect);
}
//As long as we haven't exceeded the horizontal target keep drawing.
while (r2.x < r2.w){
//Store the y position for when more than one column has to be drawn.
const int y2 = r2.y;
//As long as we haven't exceeded the vertical target keep drawing.
while (r2.y < r2.h){
//The source rectangle which will be modified by clipping.
SDL_Rect srcrect = { xx, yy, ww, hh };
//Check if we should clip the right and bottom.
if (r2.x + ww > r2.w && hAlign != STRETCH) srcrect.w = r2.w - r2.x;
if (r2.y + hh > r2.h && vAlign != STRETCH) srcrect.h = r2.h - r2.y;
//The destination rectangle which will be modified by clipping.
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 && hAlign != STRETCH) {
srcrect.x += d; srcrect.w -= d; dstrect.x += d;
}
d = clipRect->y - dstrect.y;
if (d > 0 && vAlign != STRETCH) {
srcrect.y += d; srcrect.h -= d; dstrect.y += d;
}
}
if (srcrect.w > 0 && srcrect.h > 0) {
dstrect.w = (hAlign == STRETCH) ? (r2.w - r2.x) : srcrect.w;
dstrect.h = (vAlign == STRETCH) ? (r2.h - r2.y) : srcrect.h;
SDL_RenderCopy(&renderer, src, &srcrect, &dstrect);
}
if (vAlign == STRETCH) break; //For STRETCH mode draw once is enough
r2.y += hh;
}
if (hAlign == STRETCH) break; //For STRETCH mode draw once is enough
r2.x += ww;
//Reset the y position before drawing a new column.
r2.y = y2;
}
//Reset the SDL clip rect if necessary
if (useSDLClipRect) {
SDL_RenderSetClipRect(&renderer, NULL);
}
}
}
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<ThemeOffsetPoint> &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());
+ scale=atoi(v[0].c_str())?true:false;
}
//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(){
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//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();
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//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
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//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();
}
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
}
//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){
//Invalidates the cache.
hasThemeTextColor = hasThemeTextColorDialog = false;
//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;
}
SDL_Color ThemeStack::getTextColor(bool isDialog) {
if (isDialog) {
if (hasThemeTextColorDialog) return themeTextColorDialog;
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--) {
if (objThemes[i]->getTextColor(isDialog, themeTextColorDialog)) {
hasThemeTextColorDialog = true;
return themeTextColorDialog;
}
}
hasThemeTextColorDialog = true;
themeTextColorDialog = BLACK;
return themeTextColorDialog;
} else {
if (hasThemeTextColor) return themeTextColor;
//Loop through the themes from top to bottom.
for (int i = objThemes.size() - 1; i >= 0; i--) {
if (objThemes[i]->getTextColor(isDialog, themeTextColor)) {
hasThemeTextColor = true;
return themeTextColor;
}
}
hasThemeTextColor = true;
themeTextColor = BLACK;
return themeTextColor;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 3:38 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
64175
Default Alt Text
(55 KB)

Event Timeline