Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F118891
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
127 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Block.cpp b/src/Block.cpp
index 4ac8a57..d7b6951 100644
--- a/src/Block.cpp
+++ b/src/Block.cpp
@@ -1,1280 +1,1272 @@
/*
* 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>
#include <assert.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),
movingPosTime(-1),
speed(0),
speedSave(0),
editorSpeed(0),
editorFlags(0)
{
//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(){}
int Block::getPathMaxTime() {
if (movingPosTime < 0) {
movingPosTime = 0;
for (const SDL_Rect& p : movingPos) {
movingPosTime += p.w;
}
}
return movingPosTime;
}
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 (default) appearance.
objThemes.getBlock(type)->createInstance(&appearance);
}
void Block::show(SDL_Renderer& renderer){
//Make sure we are visible.
if ((flags & 0x80000000) != 0 && (stateID != STATE_LEVEL_EDITOR || dynamic_cast<LevelEditor*>(parent)->isPlayMode()))
return;
//Check if the block is visible.
if(checkCollision(camera,box)==true || (stateID==STATE_LEVEL_EDITOR && checkCollision(camera,boxBase)==true)){
//Some type of block needs additional state check.
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;
}
//Always draw the base. (This is only supposed to show in editor.)
appearance.drawState("base", renderer, boxBase.x - camera.x, boxBase.y - camera.y, boxBase.w, boxBase.h);
//What we need to draw depends on the type of block.
switch (type) {
default:
//Draw normal.
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
//Draw conveyor belt.
if (animation) {
// FIXME: ad-hoc code. Should add a new animation type in theme system.
const int a = animation / 10;
const SDL_Rect r = { box.x - camera.x, box.y - camera.y, box.w, box.h };
appearance.draw(renderer, box.x - camera.x - 50 + a, box.y - camera.y, box.w + 50, box.h, &r);
} else {
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
}
break;
}
//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, box.w, box.h);
break;
}
//Draw some stupid icons during edit mode.
if (stateID == STATE_LEVEL_EDITOR) {
auto bmGUI = static_cast<LevelEditor*>(parent)->getGuiTexture();
if (!bmGUI) {
return;
}
int x = box.x - camera.x + 2;
//Scripted blocks
if (!scripts.empty()){
const SDL_Rect r = { 0, 32, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Invisible blocks
if (editorFlags & 0x80000000) {
const SDL_Rect r = { 16, 48, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Block with custom appearance
if (!customAppearanceName.empty()) {
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
}
}
}
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;
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;
//Invalidates the cache.
movingPosTime = -1;
//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;
}
//Invalidates the cache.
movingPosTime = -1;
//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;
//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 int f = flags & 0x3;
const char* s=(f==0)?"default":((f==1)?"fragile1":((f==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 visible, otherwise no events should be handled except for 'OnCreate'.
if ((flags & 0x80000000) != 0 && eventType != GameObjectEvent_OnCreate)
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=parent->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:
if ((flags & 0x3) < 3) {
flags++;
const int f = flags & 0x3;
const char* s = (f <= 0) ? "default" : ((f == 1) ? "fragile1" : ((f == 2) ? "fragile2" : "fragile3"));
appearance.changeState(s);
}
break;
}
break;
case GameObjectEvent_PlayerIsOn:
switch(type){
case TYPE_BUTTON:
temp=1;
break;
}
break;
case GameObjectEvent_OnPlayerInteraction:
switch (type) {
case TYPE_SWITCH:
//Make sure that the id isn't emtpy.
if (!id.empty()) {
parent->broadcastObjectEvent(0x10000 | (flags & 3),
-1, id.c_str());
} else {
cerr << "WARNING: invalid switch id!" << endl;
}
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 & 0x3) < 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));
//And visibility.
obj.push_back(pair<string, string>("visible", (editorFlags & 0x80000000) == 0 ? "1" : "0"));
//And custom appearance.
obj.push_back(pair<string, string>("appearance", customAppearanceName));
//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",(editorFlags&0x2)?"0":"1"));
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>("speed10",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&0x3);
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 appearance.
it = obj.find("appearance");
if (it != obj.end()) {
std::string newAppearanceName;
if (it->second.empty() || it->second == std::string(Game::blockName[type]) + "_Scenery") {
//Use the default appearance. (Do nothing since newAppearanceName is already empty)
} else {
//Use the custom appearance.
newAppearanceName = it->second;
}
if (newAppearanceName != customAppearanceName) {
//Try to find the custom appearance.
ThemeBlock *themeBlock = NULL;
if (!newAppearanceName.empty()) {
themeBlock = objThemes.getScenery(newAppearanceName);
if (themeBlock == NULL) {
std::cerr << "ERROR: failed to load custom appearance '" << newAppearanceName << "' for block " << Game::blockName[type] << std::endl;
}
} else {
themeBlock = objThemes.getBlock(type);
if (themeBlock == NULL) {
std::cerr << "ERROR: failed to load default appearance for block " << Game::blockName[type] << std::endl;
}
}
if (themeBlock) {
//Update the custom appearance name.
customAppearanceName = newAppearanceName;
//Recreate the theme block instance.
themeBlock->createInstance(&appearance);
//Do some block specific stuff,
//e.g. reset the state according to block type,
//or load some missing part of block states from default appearance.
switch (type) {
case TYPE_FRAGILE:
{
const int f = flags & 0x3;
const char* s = (f == 0) ? "default" : ((f == 1) ? "fragile1" : ((f == 2) ? "fragile2" : "fragile3"));
appearance.changeState(s);
}
break;
case TYPE_BUTTON:
if (appearance.blockStates.find("button") == appearance.blockStates.end()) {
//Try to load the "button" state from default appearance
ThemeBlockInstance defaultAppearance;
objThemes.getBlock(type)->createInstance(&defaultAppearance);
auto it = defaultAppearance.blockStates.find("button");
if (it != defaultAppearance.blockStates.end()) {
appearance.blockStates[it->first] = it->second;
}
}
break;
}
}
}
}
//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"];
}
//Check if the data contains the visibility
it = obj.find("visible");
if (it != obj.end()) {
//Set the visibility.
const string& s = it->second;
flags = flagsSave = editorFlags = (editorFlags & ~0x80000000) | ((s == "true" || atoi(s.c_str())) ? 0 : 0x80000000);
}
//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()){
const string& s=it->second;
editorFlags&=~0x1;
if(!(s=="true" || atoi(s.c_str()))) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
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()){
const string& s=it->second;
editorFlags |= 0x2;
if (s == "true" || atoi(s.c_str())) editorFlags &= ~0x2;
flags = flagsSave = editorFlags;
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
//Check if there's a speed key in the editor data.
//NOTE: 'speed' is obsolete in V0.5.
it=obj.find("speed10");
if(it!=obj.end()){
editorSpeed=atoi(it->second.c_str());
speed=speedSave=editorSpeed;
}else{
it = obj.find("speed");
if (it != obj.end()){
editorSpeed = atoi(it->second.c_str()) * 10;
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()){
const string& s=it->second;
editorFlags&=~0x1;
if(!(s=="true" || atoi(s.c_str()))) editorFlags|=0x1;
flags=flagsSave=editorFlags;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x1;
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()){
const string& s=it->second;
editorFlags&=~0x1;
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=it->second;
}
}
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Check if the behaviour key is in the data.
it=obj.find("behaviour");
if(it!=obj.end()){
const string& s=it->second;
editorFlags&=~0x3;
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=it->second;
//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=(editorFlags&~0x3)|(atoi(it->second.c_str())&0x3);
flags=flagsSave=editorFlags;
{
const int f = flags & 0x3;
const char* s=(f==0)?"default":((f==1)?"fragile1":((f==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
}
}
break;
}
}
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 = 0;
{
auto it = Game::blockNameMap.find(objNode->value[0]);
if (it != Game::blockNameMap.end()) {
type = it->second;
} else {
cerr << "WARNING: Unknown block type '" << objNode->value[0] << "'!" << endl;
}
}
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;
-}
-
void Block::move(){
bool isPlayMode = stateID != STATE_LEVEL_EDITOR || dynamic_cast<LevelEditor*>(parent)->isPlayMode();
//Make sure we are visible, if not return.
if ((flags & 0x80000000) != 0 && isPlayMode)
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:
//Only move block when we are in play mode.
if (isPlayMode) {
//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){ // time == 0 means the block deactivates at this point automatically
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 && i == movingPos.size() - 1) {
//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((flags & 0x2) == 0){
//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:
//Only move block when we are in play mode.
if (isPlayMode) {
//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:
//NOTE: we update conveyor belt animation even in edit mode.
//Increase the conveyor belt animation.
if((flags&1)==0){
//Since now 1 speed = 0.1 pixel/s we need some more sophisticated calculation.
int a = animation + speed, d = 0;
if (a < 0) {
//Add a delta value to make it positive
d = (((-a) / 500) + 1) * 500;
}
//Set the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel = (a + d) / 10 - (animation + d) / 10;
//Update animation value
animation = (a + d) % 500;
assert(animation >= 0);
} 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:
//Only move block when we are in play mode.
if (isPlayMode) {
//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(delta.y>0)
yVelBase=delta.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 visible blocks.
if(parent->levelObjects[o]->flags & 0x80000000)
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 visible.
if(parent->levelObjects[o]->flags & 0x80000000)
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 right so the left edge of the player must be greater 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 greater 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/Block.h b/src/Block.h
index 63a1e29..72d117e 100644
--- a/src/Block.h
+++ b/src/Block.h
@@ -1,212 +1,210 @@
/*
* 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/>.
*/
#ifndef BLOCK_H
#define BLOCK_H
#include "GameObjects.h"
#include "ThemeManager.h"
#include "ScriptUserData.h"
#include "ScriptExecutor.h"
#include <vector>
#include <SDL.h>
class LevelEditor;
class AddRemoveGameObjectCommand;
class AddRemovePathCommand;
class BlockScriptAPI;
class Block: public GameObject, public ScriptUserClass<'B','L','O','K',Block>{
friend class LevelEditor;
friend class AddRemoveGameObjectCommand;
friend class AddRemovePathCommand;
friend class BlockScriptAPI;
private:
//Integer that a block can use for all animation or visual related things.
int animation;
//The save for animation when the state of the block is saved.
int animationSave;
//flags:
//all: 0x80000000=invisible (If it's not visible it will not collide with anything or execute any scripts except for 'onCreate'.)
//moving object: 0x1=disabled 0x2=NOT loop
//button: bit0-1=behavior 0x4=pressed
//switch: bit0-1=behavior
//portal: 0x1=automatic
//fragile: bit0-1 state
//collectible: 0x1=collected
int flags;
//The save for flags when the state of the block is saved.
int flagsSave;
//Temp variables used to keep track of time/state.
int temp;
//The save for temp when the state of the block is saved.
int tempSave;
//Save variables for the current location and size of the block.
SDL_Rect boxSave;
//Delta variables, if the block moves these must be set to the delta movement.
int dx,dy;
int dxSave,dySave;
//Vector containing the poisitions of the moving block.
std::vector<SDL_Rect> movingPos;
//Cached variable for total time ot moving positions. -1 means uninitialized
//NOTE: should reset it when editing movingPos (except for edit mode since it will reset when level starts)
int movingPosTime;
//Integer containing the speed for conveyorbelts.
//NOTE: in V0.5 the speed 1 means 0.1 pixel/frame = 0.08 block/s
//which is 1/10 of the old speed, and named "speed10" in the level file to keep compatibility
int speed;
int speedSave;
int editorSpeed;
//Following is for the pushable block.
Block* objCurrentStand;
//Flags of the block for the editor.
int editorFlags;
public:
// The custom appearance name, whose meaning is the same as Scenery::sceneryName_. "" means using default one
std::string customAppearanceName;
//The Appearance of the block.
ThemeBlockInstance appearance;
//Velocity variables for the block, if the block moves these must be set for collision/movement of the player.
int xVel,yVel;
//Save variables for the velocity.
int xVelSave,yVelSave;
//Follwing is for pushable block.
bool inAir;
int xVelBase,yVelBase;
//The save variables for each of the above.
bool inAirSave;
int xVelBaseSave,yVelBaseSave;
//The id of the block.
std::string id;
//String containing the id of the destination for portals.
std::string destination;
//String containing the message of the notification block.
std::string message;
//The map that holds a script for every event.
map<int,std::string> scripts;
//Compiled scripts. Use lua_rawgeti(L, LUA_REGISTRYINDEX, r) to get the function.
std::map<int, int> compiledScripts, savedCompiledScripts, initialCompiledScripts;
//Constructor.
//objParent: Pointer to the Game object.
//x: The x location of the block.
//y: The y location of the block.
//w: The width of the block.
//h: The height of the block.
//type: The block type.
Block(Game* objParent,int x=0,int y=0,int w=50,int h=50,int type=-1);
//Desturctor.
~Block();
//Method for initializing the block.
//x: The x location of the block.
//y: The y location of the block.
//w: The width of the block.
//h: The height of the block.
//type: The block type.
void init(int x,int y,int w,int h,int type);
//Method used to draw the block.
void show(SDL_Renderer &renderer) override;
//Returns the box of a given type.
//boxType: The type of box that should be returned.
//See GameObjects.h for the types.
//Returns: The box.
virtual SDL_Rect getBox(int boxType=BoxType_Current);
//Method for setting the block to a given location as if it moved there.
//x: The new x location.
//y: The new y location.
void moveTo(int x,int y);
//Method for setting a new size as if the block grew,
//w: The new width of the block.
//h: The new height of the block.
void growTo(int w,int h);
//Save the state of the block so we can load it later on.
virtual void saveState();
//Load the saved state of the block so.
virtual void loadState();
//Reset the block.
//save: Boolean if the saved state should also be deleted.
virtual void reset(bool save);
//Play an animation.
virtual void playAnimation();
//Method called when there's an event.
//eventType: The type of event.
//See GameObjects.h for the eventtypes.
virtual void onEvent(int eventType);
//Method used to retrieve a property from the block.
//propertyType: The type of property requested.
//See GameObjects.h for the properties.
//obj: Pointer to the player.
//Returns: Integer containing the value of the property.
virtual int queryProperties(int propertyType,Player* obj);
//Get the editor data of the block.
//obj: The vector that will be filled with the editorData.
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj);
//Set the editor data of the block.
//obj: The new editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj);
//Get a single property of the block.
//property: The property to return.
//Returns: The value for the requested property.
virtual std::string getEditorProperty(std::string property);
//Set a single property of the block.
//property: The property to set.
//value: The new value for the property.
virtual void setEditorProperty(std::string property,std::string value);
//Method for loading the Block from a node.
//objNode: Pointer to the storage node to load from.
//Returns: True if it succeeds without errors.
virtual bool loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode* objNode) override;
- //Method used for resetting the dx/dy and xVel/yVel variables.
- virtual void prepareFrame();
//Method used for updating moving blocks or elements of blocks.
virtual void move();
//Get total time ot moving positions.
int getPathMaxTime();
};
#endif
diff --git a/src/Game.cpp b/src/Game.cpp
index 310e9b3..8dd1f6b 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1770 +1,1764 @@
/*
* 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 "GameState.h"
#include "Functions.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include "MusicManager.h"
#include "Render.h"
#include "StatisticsManager.h"
#include "ScriptExecutor.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <locale>
#include <stdio.h>
#include <SDL_ttf.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable", "Pushable"
};
map<string,int> Game::blockNameMap;
map<int,string> Game::gameObjectEventTypeMap;
map<string,int> Game::gameObjectEventNameMap;
map<int,string> Game::levelEventTypeMap;
map<string,int> Game::levelEventNameMap;
string Game::recordFile;
//An internal function.
static void copyCompiledScripts(lua_State *state, const std::map<int, int>& src, std::map<int, int>& dest) {
//Clear the existing scripts.
for (auto it = dest.begin(); it != dest.end(); ++it) {
luaL_unref(state, LUA_REGISTRYINDEX, it->second);
}
dest.clear();
//Copy the source to the destination.
for (auto it = src.begin(); it != src.end(); ++it) {
lua_rawgeti(state, LUA_REGISTRYINDEX, it->second);
dest[it->first] = luaL_ref(state, LUA_REGISTRYINDEX);
}
}
Game::Game(SDL_Renderer &renderer, ImageManager &imageManager):isReset(false)
, scriptExecutor(new ScriptExecutor())
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,cameraMode(CAMERA_PLAYER),cameraModeSaved(CAMERA_PLAYER)
,player(this),shadow(this),objLastCheckPoint(NULL)
,currentCollectables(0),totalCollectables(0),currentCollectablesSaved(0){
saveStateNextTime=false;
loadStateNextTime=false;
recentSwap=recentSwapSaved=-10000;
recentLoad=recentSave=0;
action=imageManager.loadTexture(getDataPath()+"gfx/actions.png", renderer);
medals=imageManager.loadTexture(getDataPath()+"gfx/medals.png", renderer);
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&collectable);
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
delete scriptExecutor;
scriptExecutor = NULL;
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
//Done now clear the levelObjects vector.
levelObjects.clear();
//Loop through the sceneryLayers and delete them.
for(auto it=sceneryLayers.begin();it!=sceneryLayers.end();++it){
delete it->second;
}
sceneryLayers.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//If there's a (partial) theme bundled with the levelpack remove that as well.
if(levels->customTheme)
objThemes.removeTheme();
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
recentSwap=recentSwapSaved=-10000;
//Set the music list back to the configured list.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
}
void Game::reloadMusic() {
//NOTE: level music is always enabled.
//Check if the levelpack has a prefered music list.
if (levels && !levels->levelpackMusicList.empty())
getMusicManager()->setMusicList(levels->levelpackMusicList);
//Check for the music to use.
string &s = editorData["music"];
if (!s.empty()) {
getMusicManager()->playMusic(s);
} else {
getMusicManager()->pickMusic();
}
}
void Game::loadLevelFromNode(ImageManager& imageManager,SDL_Renderer& renderer,TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
//Not needed since loadLevelFromNode is only called from the changeState method, meaning it's a new instance of Game.
//destroy();
//set current level to loaded one.
currentLevelNode=obj;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
currentCollectables=0;
totalCollectables=0;
currentCollectablesSaved=0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//NOTE: level themes are always enabled.
//Check for bundled (partial) themes for level pack.
if (levels->customTheme){
if (objThemes.appendThemeFromFile(levels->levelpackPath + "/theme/theme.mnmstheme", imageManager, renderer) == NULL){
//The theme failed to load so set the customTheme boolean to false.
levels->customTheme = false;
}
}
//Check for the theme to use for this level. This has higher priority.
//Examples: %DATA%/themes/classic or %USER%/themes/Orange
string &s = editorData["theme"];
if (!s.empty()){
customTheme = objThemes.appendThemeFromFile(processFileName(s) + "/theme.mnmstheme", imageManager, renderer);
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance);
objThemes.getCharacter(true)->createInstance(&shadow.appearance);
}
//Get the music.
reloadMusic();
//Load the data from the level node.
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile"){
Block* block=new Block(this);
if(!block->loadFromNode(imageManager,renderer,obj1)){
delete block;
continue;
}
//If the type is collectable, increase the number of totalCollectables
if(block->type==TYPE_COLLECTABLE)
totalCollectables++;
//Add the block to the levelObjects vector.
levelObjects.push_back(block);
}else if(obj1->name=="scenerylayer" && obj1->value.size()==1){
//Check if the layer exists.
if (sceneryLayers[obj1->value[0]] == NULL) {
sceneryLayers[obj1->value[0]] = new SceneryLayer();
}
//Load contents from node.
sceneryLayers[obj1->value[0]]->loadFromNode(this, imageManager, renderer, obj1);
}else if(obj1->name=="script" && !obj1->value.empty()){
map<string,int>::iterator it=Game::levelEventNameMap.find(obj1->value[0]);
if(it!=Game::levelEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj1->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if(levels->getLevelCount()>1 && levels->type!=COLLECTION){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),editorData["name"]));
} else {
s = _CC(levels->getDictionaryManager(), editorData["name"]);
}
bmTips[0]=textureFromText(renderer, *fontText,s.c_str(),objThemes.getTextColor(true));
}
//Get the background
background=objThemes.getBackground(false);
//Now the loading is finished, we reset all objects to their initial states.
reset(true, stateID == STATE_LEVEL_EDITOR);
}
void Game::loadLevel(ImageManager& imageManager,SDL_Renderer& renderer,std::string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cerr<<"ERROR: Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(imageManager,renderer,obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(ImageManager& imageManager, SDL_Renderer& renderer, const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cerr<<"ERROR: Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(imageManager,renderer,obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cerr<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(stateID != STATE_LEVEL_EDITOR && inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'R' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Restart game only if we are not watching a replay.
if (!player.isPlayFromRecord() || interlevel) {
//Reset the game at next frame.
isReset = true;
//Also delete any gui (most likely the interlevel gui). Only in game mode.
if (GUIObjectRoot && stateID != STATE_LEVEL_EDITOR){
delete GUIObjectRoot;
GUIObjectRoot = NULL;
}
//And set interlevel to false.
interlevel = false;
}
}
//Check for the next level buttons when in the interlevel popup.
if (inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || inputMgr.isKeyDownEvent(INPUTMGR_SELECT)){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Switch the camera mode.
switch(cameraMode){
case CAMERA_PLAYER:
cameraMode=CAMERA_SHADOW;
break;
case CAMERA_SHADOW:
case CAMERA_CUSTOM:
cameraMode=CAMERA_PLAYER;
break;
}
}
}
/////////////////LOGIC///////////////////
void Game::logic(ImageManager& imageManager, SDL_Renderer& renderer){
//Add one tick to the time.
time++;
- //FIXME:Resetting dx/dy and xVel/yVel every loop interferes with movement logic of player and blocks.
- //First prepare each gameObject for the new frame.
- //This includes resetting dx/dy and xVel/yVel.
- //for(unsigned int o=0;o<levelObjects.size();o++)
- //levelObjects[o]->prepareFrame();
-
//Process delay execution scripts.
getScriptExecutor()->processDelayExecution();
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.target){
//NOTE: Should we check if the target still exists???
e.target->onEvent(e.eventType);
}else if(e.flags&1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
if(levelObjects[i]->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Check if we should save/load state.
//NOTE: This happens after event handling so no eventQueue has to be saved/restored.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Loop through the gameobjects to update them.
for(unsigned int i=0;i<levelObjects.size();i++){
//Send GameObjectEvent_OnEnterFrame event to the script
levelObjects[i]->onEvent(GameObjectEvent_OnEnterFrame);
}
for(unsigned int i=0;i<levelObjects.size();i++){
//Let the gameobject handle movement.
levelObjects[i]->move();
}
//Also update the scenery.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it){
it->second->updateAnimation();
}
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//NOTE: to fix bugs regarding player/shadow swap, we should first process collision of player/shadow then move them
SDL_Rect playerLastPosition = player.getBox();
SDL_Rect shadowLastPosition = shadow.getBox();
//Check collision for player.
player.collision(levelObjects);
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow.
shadow.collision(levelObjects);
//Let the player move.
player.move(levelObjects, playerLastPosition.x, playerLastPosition.y);
//Let the shadow move.
shadow.move(levelObjects, shadowLastPosition.x, shadowLastPosition.y);
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Update the camera.
switch(cameraMode){
case CAMERA_PLAYER:
player.setMyCamera();
break;
case CAMERA_SHADOW:
shadow.setMyCamera();
break;
case CAMERA_CUSTOM:
//NOTE: The target is (should be) screen size independent so calculate the real target x and y here.
int targetX=cameraTarget.x-(SCREEN_WIDTH/2);
int targetY=cameraTarget.y-(SCREEN_HEIGHT/2);
//Move the camera to the cameraTarget.
if(camera.x>targetX){
camera.x-=(camera.x-targetX)>>4;
//Make sure we don't go too far.
if(camera.x<targetX)
camera.x=targetX;
}else if(camera.x<targetX){
camera.x+=(targetX-camera.x)>>4;
//Make sure we don't go too far.
if(camera.x>targetX)
camera.x=targetX;
}
if(camera.y>targetY){
camera.y-=(camera.y-targetY)>>4;
//Make sure we don't go too far.
if(camera.y<targetY)
camera.y=targetY;
}else if(camera.y<targetY){
camera.y+=(targetY-camera.y)>>4;
//Make sure we don't go too far.
if(camera.y>targetY)
camera.y=targetY;
}
break;
}
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded(imageManager,renderer);
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
//Get current level
LevelPack::Level *level=levels->getLevel();
//Now check if we should update statistics
{
//Get previous and current medal
int oldMedal=level->won?1:0,newMedal=1;
int bestTime=level->time;
int targetTime=level->targetTime;
int bestRecordings=level->recordings;
int targetRecordings=level->targetRecordings;
if(oldMedal){
if(targetTime<0){
oldMedal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
oldMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
oldMedal++;
}
}else{
bestTime=time;
bestRecordings=recordings;
}
if(bestTime==-1 || bestTime>time) bestTime=time;
if(bestRecordings==-1 || bestRecordings>recordings) bestRecordings=recordings;
if(targetTime<0){
newMedal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
newMedal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
newMedal++;
}
//Check if we need to update statistics
if(newMedal>oldMedal){
switch(oldMedal){
case 0:
statsMgr.completedLevels++;
break;
case 2:
statsMgr.silverLevels--;
break;
}
switch(newMedal){
case 2:
statsMgr.silverLevels++;
break;
case 3:
statsMgr.goldLevels++;
break;
}
}
}
//Check the achievement "Complete a level with checkpoint, but without saving"
if (objLastCheckPoint == NULL) {
for (auto obj : levelObjects) {
if (obj->type == TYPE_CHECKPOINT) {
statsMgr.newAchievement("withoutsave");
break;
}
}
}
//Set the current level won.
level->won=true;
if(level->time==-1 || level->time>time){
level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
if(level->recordings==-1 || level->recordings>recordings){
level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay(imageManager,renderer);
//Update achievements
if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
statsMgr.updateLevelAchievements();
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
isReset = false;
}
}
won=false;
//Check if we should reset.
if (isReset) {
//NOTE: we don't need to reset save ??? it looks like that there are no bugs
reset(false, false);
}
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(ImageManager&,SDL_Renderer &renderer){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(renderer);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_SetRenderDrawColor(&renderer, 255,255,255,255);
SDL_RenderClear(&renderer);
}
}
//Now draw the blackground layers.
auto it = sceneryLayers.begin();
for (; it != sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
it->second->show(renderer);
}
//Now we draw the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
levelObjects[o]->show(renderer);
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow first, because he needs to be behind the player.
shadow.show(renderer);
player.show(renderer);
//Now draw the foreground layers.
for (; it != sceneryLayers.end(); ++it){
it->second->show(renderer);
}
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
withTexture(*bmTips[0], [&](SDL_Rect r){
drawGUIBox(-2,SCREEN_HEIGHT-r.h-4,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,SCREEN_HEIGHT-r.h,*bmTips[0],renderer,NULL);
});
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_ACTION, false), inputMgr.getKeyCode(INPUTMGR_ACTION, true));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
bmTips[gameTipIndex]=textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true));
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
withTexture(*bmTips[gameTipIndex], [&](SDL_Rect r){
drawGUIBox(-2,-2,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,2,*bmTips[gameTipIndex],renderer);
});
}
}
//Set the gameTip to 0.
gameTipIndex=0;
// Limit the scope of bm, as it's a borrowed pointer.
{
//Pointer to the sdl texture that will contain a message, if any.
SDL_Texture* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_RESTART, false), inputMgr.getKeyCode(INPUTMGR_RESTART, true));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_LOAD, false), inputMgr.getKeyCode(INPUTMGR_LOAD, true));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
bmTips[3]=textureFromText(renderer, *fontText,//TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[3].get();
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
bmTips[2]=textureFromText(renderer, *fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[2].get();
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
bmTips[1]=textureFromText(renderer, *fontText,
_("Your shadow has died."),
objThemes.getTextColor(true));
}
bm=bmTips[1].get();
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
cameraMode=CAMERA_PLAYER;
}
//Draw the tip.
if(bm!=NULL){
const SDL_Rect textureSize = rectFromTexture(*bm);
int x=(SCREEN_WIDTH-textureSize.w)/2;
int y=32;
drawGUIBox(x-8,y-8,textureSize.w+16,textureSize.h+14,renderer,0xFFFFFFFF);
applyTexture(x,y,*bm,renderer);
}
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if(currentCollectables<=totalCollectables && totalCollectables!=0 && !interlevel && time>0){
if(collectablesTexture.needsUpdate(currentCollectables)) {
//Temp stringstream just to addup all the text nicely
std::stringstream temp;
temp << currentCollectables << "/" << totalCollectables;
collectablesTexture.update(currentCollectables,
textureFromText(renderer,
*fontText,
temp.str().c_str(),
objThemes.getTextColor(true)));
}
SDL_Rect bmSize = rectFromTexture(*collectablesTexture.get());
//Draw background
drawGUIBox(SCREEN_WIDTH-bmSize.w-34,SCREEN_HEIGHT-bmSize.h-4,bmSize.w+34+2,bmSize.h+4+2,renderer,0xFFFFFFFF);
//Draw the collectable icon
collectable.draw(renderer,SCREEN_WIDTH-50+12,SCREEN_HEIGHT-50+10);
//Draw text
applyTexture(SCREEN_WIDTH-50-bmSize.w+22,SCREEN_HEIGHT-bmSize.h,collectablesTexture.getTexture(),renderer);
}
//show time and records used in level editor.
if(stateID==STATE_LEVEL_EDITOR && time>0){
const SDL_Color fg=objThemes.getTextColor(true),bg={255,255,255,255};
const int alpha = 160;
if (recordingsTexture.needsUpdate(recordings)) {
recordingsTexture.update(recordings,
textureFromTextShaded(
renderer,
*fontText,
tfm::format(_("%d recordings"),recordings).c_str(),
fg,
bg
));
SDL_SetTextureAlphaMod(recordingsTexture.get(),alpha);
}
int y=SCREEN_HEIGHT - textureHeight(*recordingsTexture.get());
applyTexture(0,y,*recordingsTexture.get(), renderer);
if(timeTexture.needsUpdate(time)) {
const size_t len = 32;
char c[len];
SDL_snprintf(c,len,"%-.2fs",time/40.0f);
timeTexture.update(time,
textureFromTextShaded(
renderer,
*fontText,
c,
fg,
bg
));
y-=textureHeight(*timeTexture.get());
}
applyTexture(0,y,*timeTexture.get(), renderer);
}
//Draw the current action in the upper right corner.
if(player.record){
const SDL_Rect r = { 0, 0, 50, 50 };
applyTexture(SCREEN_WIDTH - 50, 0, *action, renderer, &r);
} else if (shadow.state != 0){
const SDL_Rect r={50,0,50,50};
applyTexture(SCREEN_WIDTH-50,0,*action,renderer,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if( interlevel){
dimScreen(renderer,191);
}else if((time & 0x10)==0x10){
// FIXME: replace this ugly ad-hoc animation by a better one
const SDL_Rect r={50,0,50,50};
applyTexture(0,0,*action,renderer,&r);
applyTexture(0,SCREEN_HEIGHT-50,*action,renderer,&r);
applyTexture(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,*action,renderer,&r);
}
}else if(player.objNotificationBlock){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
//Check if we need to update the notification message texture.
const auto& blockId = player.objNotificationBlock;
int maxWidth = 0;
int y = 20;
//We check against blockId rather than the full message, as blockId is most likely shorter.
if(notificationTexture.needsUpdate(blockId)) {
const std::string &untranslated_message=player.objNotificationBlock->message;
std::string message=_CC(levels->getDictionaryManager(),untranslated_message);
std::vector<std::string> string_data;
//Trim the message.
{
size_t lps = message.find_first_not_of("\n\r \t");
if (lps == string::npos) {
message.clear(); // it's completely empty
} else {
message = message.substr(lps, message.find_last_not_of("\n\r \t") - lps + 1);
}
}
//Split the message into lines.
for (int lps = 0;;) {
// determine the end of line
int lpe = lps;
for (; message[lpe] != '\n' && message[lpe] != '\r' && message[lpe] != '\0'; lpe++);
string_data.push_back(message.substr(lps, lpe - lps));
// break if the string ends
if (message[lpe] == '\0') break;
// skip "\r\n" for Windows line ending
if (message[lpe] == '\r' && message[lpe + 1] == '\n') lpe++;
// point to the start of next line
lps = lpe + 1;
}
vector<SurfacePtr> lines;
//Create the image for each lines
for (int i = 0; i < (int)string_data.size(); i++) {
//Integer used to center the sentence horizontally.
int x = 0;
TTF_SizeUTF8(fontText, string_data[i].c_str(), &x, NULL);
//Find out largest width
if (x>maxWidth)
maxWidth = x;
lines.emplace_back(TTF_RenderUTF8_Blended(fontText, string_data[i].c_str(), objThemes.getTextColor(true)));
//Increase y with 25, about the height of the text.
y += 25;
}
maxWidth+=SCREEN_WIDTH*0.15;
SurfacePtr surf = createSurface(maxWidth, y);
int y1 = y;
for(SurfacePtr &s : lines) {
if(s) {
applySurface((surf->w-s->w)/2,surf->h - y1,s.get(),surf.get(),NULL);
}
y1 -= 25;
}
notificationTexture.update(blockId, textureUniqueFromSurface(renderer,std::move(surf)));
} else {
auto texSize = rectFromTexture(*notificationTexture.get());
maxWidth=texSize.w;
y=texSize.h;
}
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,renderer,0xFFFFFFBF);
applyTexture((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y,notificationTexture.getTexture(),renderer);
}
}
void Game::resize(ImageManager&, SDL_Renderer& /*renderer*/){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(ImageManager& imageManager,SDL_Renderer& renderer){
//Set interlevel true.
interlevel=true;
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
//Reset the game.
//NOTE: We don't reset the saves. I'll see that if it will introduce bugs.
reset(false, false);
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Set the copy of playerButtons back.
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
//Create a new GUIObjectRoot the size of the screen.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Make child widgets change color properly according to theme.
GUIObjectRoot->inDialog=true;
//Create a GUIFrame for the upper frame.
GUIFrame* upperFrame=new GUIFrame(imageManager,renderer,0,4,0,68);
GUIObjectRoot->addChild(upperFrame);
//Render the You've finished: text and add it to a GUIImage.
//NOTE: The texture is managed by the GUIImage so no need to free it ourselfs.
auto bm = SharedTexture(textureFromText(renderer, *fontGUI,_("You've finished:"),objThemes.getTextColor(true)));
const SDL_Rect textureSize = rectFromTexture(*bm);
GUIImage* title=new GUIImage(imageManager,renderer,0,4-GUI_FONT_RAISE,textureSize.w,textureSize.h,bm);
upperFrame->addChild(title);
//Create the sub title.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),levelName));
}
GUIObject* obj=new GUILabel(imageManager,renderer,0,40,0,28,s.c_str(),0,true,true,GUIGravityCenter);
upperFrame->addChild(obj);
obj->render(renderer,0,0,false);
//Determine the width the upper frame should have.
int width;
if(textureSize.w>obj->width)
width=textureSize.w+32;
else
width=obj->width+32;
//Set the left of the title.
title->left=(width-title->width)/2;
//Set the width of the level label to the width of the frame for centering.
obj->width=width;
//Now set the position and width of the frame.
upperFrame->width=width;
upperFrame->left=(SCREEN_WIDTH-width)/2;
//Now create a GUIFrame for the lower frame.
GUIFrame* lowerFrame=new GUIFrame(imageManager,renderer,0,SCREEN_HEIGHT-140,570,135);
GUIObjectRoot->addChild(lowerFrame);
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
if(targetTime<0){
medal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
medal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
medal++;
}
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
if(targetTime<=0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
obj=new GUILabel(imageManager,renderer,x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
obj=new GUILabel(imageManager,renderer,x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,10+recsY,-1,36,tfm::format(_("Recordings: %d"),recordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,34+recsY,-1,36,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
obj=new GUILabel(imageManager,renderer,50,92,-1,36,s1.c_str(),0,true,true,GUIGravityCenter);
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
//Create the rectangle for the earned medal.
SDL_Rect r;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
//Create the medal on the left side.
obj=new GUIImage(imageManager,renderer,16,92,30,30,medals,r);
lowerFrame->addChild(obj);
//And the medal on the right side.
obj=new GUIImage(imageManager,renderer,x-24,92,30,30,medals,r);
lowerFrame->addChild(obj);
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
GUIObject* b1=new GUIButton(imageManager,renderer,x,10,-1,36,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
lowerFrame->addChild(b1);
b1->render(renderer,0,0,true);
/// TRANSLATORS: used as restart level
GUIObject* b2=new GUIButton(imageManager,renderer,x,50,-1,36,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
lowerFrame->addChild(b2);
b2->render(renderer,0,0,true);
/// TRANSLATORS: used as next level
GUIObject* b3=new GUIButton(imageManager,renderer,x,90,-1,36,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
lowerFrame->addChild(b3);
b3->render(renderer,0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
lowerFrame->width=x;
lowerFrame->left=(SCREEN_WIDTH-lowerFrame->width)/2;
}
}
void Game::recordingEnded(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(imageManager,renderer,_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
recentSwapSaved=recentSwap;
//Save the camera mode and target.
cameraModeSaved=cameraMode;
cameraTargetSaved=cameraTarget;
//Save the current collectables
currentCollectablesSaved=currentCollectables;
//Save scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), compiledScripts, savedCompiledScripts);
//Save other state, for example moving blocks.
for (auto block : levelObjects){
block->saveState();
copyCompiledScripts(getScriptExecutor()->getLuaState(), block->compiledScripts, block->savedCompiledScripts);
}
//Also save states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->saveAnimation();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentSave+1000>t){
statsMgr.newAchievement("panicSave");
}
recentSave=t;
//Update statistics.
statsMgr.saveTimes++;
//Update achievements
switch(statsMgr.saveTimes){
case 1000:
statsMgr.newAchievement("save1k");
break;
}
}
//Save the state for script executor.
getScriptExecutor()->saveState();
//Execute the onSave event.
executeScript(LevelEvent_OnSave);
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
recentSwap=recentSwapSaved;
//Load the camera mode and target.
cameraMode=cameraModeSaved;
cameraTarget=cameraTargetSaved;
//Load the current collactbles
currentCollectables=currentCollectablesSaved;
//Load scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), savedCompiledScripts, compiledScripts);
//Load other state, for example moving blocks.
for(auto block:levelObjects){
block->loadState();
copyCompiledScripts(getScriptExecutor()->getLuaState(), block->savedCompiledScripts, block->compiledScripts);
}
//Also load states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->loadAnimation();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements.
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentLoad+1000>t){
statsMgr.newAchievement("panicLoad");
}
recentLoad=t;
//Update statistics.
statsMgr.loadTimes++;
//Update achievements
switch(statsMgr.loadTimes){
case 1000:
statsMgr.newAchievement("load1k");
break;
}
}
//Load the state for script executor.
getScriptExecutor()->loadState();
//Execute the onLoad event, if any.
executeScript(LevelEvent_OnLoad);
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save,bool noScript){
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats if interlevel isn't true.
if(!interlevel){
time=0;
recordings=0;
}
recentSwap=-10000;
if(save) recentSwapSaved=-10000;
//Reset the camera.
cameraMode=CAMERA_PLAYER;
if(save) cameraModeSaved=CAMERA_PLAYER;
cameraTarget.x=cameraTarget.y=cameraTarget.w=cameraTarget.h=0;
if(save) cameraTargetSaved.x=cameraTargetSaved.y=cameraTargetSaved.w=cameraTargetSaved.h=0;
//Reset the number of collectables
currentCollectables=0;
if(save)
currentCollectablesSaved=0;
//There is no last checkpoint so set it to NULL.
if(save)
objLastCheckPoint=NULL;
//Clear the event queue, since all the events are from before the reset.
eventQueue.clear();
//Reset other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(save);
}
//Also reset states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->resetAnimation(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Reset the cached notification block
notificationTexture.update(NULL, NULL);
//Reset the script environment if necessary.
if (noScript) {
//Destroys the script environment completely.
getScriptExecutor()->destroy();
//Clear the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
//Clear the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
block->savedCompiledScripts.clear();
block->initialCompiledScripts.clear();
}
} else {
if (save || getScriptExecutor()->getLuaState() == NULL) {
//Create a new script environment.
getScriptExecutor()->reset(true);
//Recompile the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
for (auto it = scripts.begin(); it != scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
compiledScripts[it->first] = index;
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
initialCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
}
//Recompile the block script.
for (auto block : levelObjects) {
block->compiledScripts.clear();
block->savedCompiledScripts.clear();
block->initialCompiledScripts.clear();
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
block->initialCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
}
}
} else {
//Do a soft reset.
getScriptExecutor()->reset(false);
//Restore the level script to initial state.
copyCompiledScripts(getScriptExecutor()->getLuaState(), initialCompiledScripts, compiledScripts);
//Restore the block script to initial state.
for (auto block : levelObjects) {
copyCompiledScripts(getScriptExecutor()->getLuaState(), block->initialCompiledScripts, block->compiledScripts);
}
}
}
//Call the level's onCreate event.
executeScript(LevelEvent_OnCreate);
//Send GameObjectEvent_OnCreate event to the script
for (auto block : levelObjects) {
block->onEvent(GameObjectEvent_OnCreate);
}
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for (auto block : levelObjects){
if (block->type == TYPE_EXIT){
block->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::executeScript(int eventType){
map<int,int>::iterator it;
//Check if there's a script for the given event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is one so execute it.
getScriptExecutor()->executeScript(it->second);
}
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id,GameObject* target){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Or there's a target given.
if(target)
e.target=target;
else
e.target=NULL;
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(ImageManager& imageManager, SDL_Renderer& renderer){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
}else{
if(!levels->congratulationText.empty()){
msgBox(imageManager,renderer,_CC(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(imageManager,renderer,_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(ImageManager& imageManager,SDL_Renderer& renderer, string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
interlevel=false;
//And reset the game.
//NOTE: We don't need to clear the save game because in level replay the game won't be saved (??)
//TODO: it seems work (??); I'll see if it introduce bugs
reset(false, false);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
diff --git a/src/GameObjects.cpp b/src/GameObjects.cpp
index 422bb53..186be56 100644
--- a/src/GameObjects.cpp
+++ b/src/GameObjects.cpp
@@ -1,83 +1,82 @@
/*
* 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 "GameObjects.h"
#include "Functions.h"
#include "Globals.h"
GameObject::GameObject(Game* parent):type(0),parent(parent){}
GameObject::~GameObject(){}
SDL_Rect GameObject::getBox(int boxType){
//This is the default implementation of getBox(int) method.
switch(boxType){
case BoxType_Current:
case BoxType_Previous:
return box;
case BoxType_Base:
return boxBase;
}
//Return an empty SDL_Rect.
SDL_Rect tmp={0,0,0,0};
return tmp;
}
void GameObject::setLocation(int x,int y){
box.x=x;
box.y=y;
}
void GameObject::setBaseLocation(int x,int y){
box.x=x;
box.y=y;
boxBase.x=x;
boxBase.y=y;
}
void GameObject::setSize(int w,int h){
box.w=w;
box.h=h;
}
void GameObject::setBaseSize(int w,int h){
box.w=w;
box.h=h;
boxBase.w=w;
boxBase.h=h;
}
void GameObject::saveState(){}
void GameObject::loadState(){}
void GameObject::reset(bool save){}
void GameObject::playAnimation(){}
void GameObject::onEvent(int eventType){}
int GameObject::queryProperties(int propertyType,Player* obj){
return 0;
}
void GameObject::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){}
void GameObject::setEditorData(std::map<std::string,std::string>& obj){}
std::string GameObject::getEditorProperty(std::string property){return "";}
void GameObject::setEditorProperty(std::string property,std::string value){}
bool GameObject::loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode*){return true;}
-void GameObject::prepareFrame(){}
void GameObject::move(){}
diff --git a/src/GameObjects.h b/src/GameObjects.h
index 420f7a0..79353f0 100644
--- a/src/GameObjects.h
+++ b/src/GameObjects.h
@@ -1,177 +1,174 @@
/*
* 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/>.
*/
#ifndef GAME_OBJECTS_H
#define GAME_OBJECTS_H
#include "Globals.h"
#include "TreeStorageNode.h"
#include "Player.h"
#include <SDL.h>
#include <string>
#include <vector>
#include <utility>
#include <map>
class Game;
class Player;
//The different gameObject events.
enum GameObjectEventType{
//Event called when the player walks on the gameObject.
GameObjectEvent_PlayerWalkOn=1,
//Event called when the player is on the gameObject.
GameObjectEvent_PlayerIsOn,
//Event called when the player leaves the gameObject.
GameObjectEvent_PlayerLeave,
//Event called when the gameObject is created.
//Only used for scripting purpose.
GameObjectEvent_OnCreate,
//Event called every frame.
//Only used for scripting purpose.
GameObjectEvent_OnEnterFrame,
//Event called when the player press DOWN key.
//Currently this event only fires when the block type is TYPE_SWITCH.
GameObjectEvent_OnPlayerInteraction,
//Event called when the block receives "toggle" from a switch/button.
GameObjectEvent_OnToggle=0x10000,
//Event called when the block receives "switch on" from a switch/button.
GameObjectEvent_OnSwitchOn=0x10001,
//Event called when the block receives "switch off" from a switch/button.
GameObjectEvent_OnSwitchOff=0x10002,
};
//The different gameObject properties.
enum GameObjectPropertyType{
//If the player can walk on the gameObject.
GameObjectProperty_PlayerCanWalkOn=1,
//If the object is spiked.
GameObjectProperty_IsSpikes,
//If the gameObject has some flags.
GameObjectProperty_Flags,
};
//The different box types that can be requested using the getBox(int boxType) method.
enum GameObjectBoxType{
//Box of the current position.
BoxType_Current=0,
//Box of the base/start position.
BoxType_Base,
//Box of the previous position.
BoxType_Previous,
//The movement of the block since last position.
BoxType_Delta,
//The velocity for when the player is standing on it.
BoxType_Velocity,
};
//The GameObject class.
class GameObject{
protected:
//The box of the gameObject.
//It's used for the current location of the gameObject and its size.
SDL_Rect box;
//The base location of the game object.
SDL_Rect boxBase;
public:
//The type of the GameObject.
int type;
//Pointer to the Game state.
Game* parent;
//Constructor.
//parent: Pointer to the Game state.
GameObject(Game* parent);
//Destructor.
virtual ~GameObject();
//Method used to retrieve a certain box from the GameObject.
//boxType: The type of box that is requested. (default=0)
//Returns: An SDL_Rect.
virtual SDL_Rect getBox(int boxType=0);
//This method is used to place the location on a given location.
//x: The x location to place the gameObject.
//y: The y location to place the gameObject.
virtual void setLocation(int x,int y);
//This method is used to set the base of an object to a given location.
//x: The x location to place the gameObject.
//y: The y location to place the gameObject.
virtual void setBaseLocation(int x,int y);
//This method sets the size of the object to a given size.
//w: The new width of the gameObject.
//h: The new height the gameObject.
virtual void setSize(int w,int h);
//This method sets the size of the base of the object to a given size.
//w: The new width of the gameObject.
//h: The new height of the gameObject.
virtual void setBaseSize(int w,int h);
//Method used to draw the GameObject.
virtual void show(SDL_Renderer& renderer)=0;
//Save the state of the GameObject, used for moving blocks, etc.
virtual void saveState();
//Load the state of the GameObject, used for moving blocks, etc.
virtual void loadState();
//Reset the state of the GameObject, used for moving blocks, etc.
//save: Boolean if the saved state should also be reset.
virtual void reset(bool save);
//Play an animation.
virtual void playAnimation();
//Invoke an event of the GameObject.
//eventType: The event type.
virtual void onEvent(int eventType);
//Method used to request certain properties of the GameObject.
//propertyType: The property that is requested.
//obj: Pointer to the player.
virtual int queryProperties(int propertyType,Player* obj);
//Method used to retrieve the additional editor data for the GameObject.
//Used for messages, moving positions, etc...
//obj: Vector containing the editorData pairs. (key, value)
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj);
//Set the editorData.
//obj: Map containing the key/value for the editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj);
//Get a single property of the block.
//property: The property to return.
//Returns: The value for the requested property.
virtual std::string getEditorProperty(std::string property);
//Set a single property of the block.
//property: The property to set.
//value: The new value for the property.
virtual void setEditorProperty(std::string property,std::string value);
//Method for loading the GameObject from a node.
//objNode: Pointer to the storage node to load from.
//Returns: True if it succeeds without errors.
virtual bool loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode*);
- //Method that is called before the move method.
- //It can be used to reset variables like delta movement and velocity.
- virtual void prepareFrame();
//Update method for GameObjects, used for moving blocks.
virtual void move();
};
#endif
diff --git a/src/Scenery.cpp b/src/Scenery.cpp
index c6a6a37..164758b 100644
--- a/src/Scenery.cpp
+++ b/src/Scenery.cpp
@@ -1,401 +1,396 @@
/*
* Copyright (C) 2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me And My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GameObjects.h"
#include "Scenery.h"
#include "Functions.h"
#include "LevelEditor.h"
#include "POASerializer.h"
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
#include "libs/tinyformat/tinyformat.h"
Scenery::Scenery(Game* objParent) :
GameObject(objParent),
xSave(0),
ySave(0),
dx(0),
dy(0),
themeBlock(NULL),
repeatMode(0)
{}
Scenery::Scenery(Game* objParent, int x, int y, int w, int h, const std::string& sceneryName) :
GameObject(objParent),
xSave(0),
ySave(0),
dx(0),
dy(0),
themeBlock(NULL),
repeatMode(0)
{
box.x = boxBase.x = x;
box.y = boxBase.y = y;
box.w = boxBase.w = w;
box.h = boxBase.h = h;
if (sceneryName.empty()) {
themeBlock = &internalThemeBlock;
} else {
// Load the appearance.
themeBlock = objThemes.getScenery(sceneryName);
if (themeBlock) {
sceneryName_ = sceneryName;
} else {
fprintf(stderr, "ERROR: Can't find scenery with name '%s'.\n", sceneryName.c_str());
themeBlock = &internalThemeBlock;
}
}
themeBlock->createInstance(&appearance);
}
Scenery::~Scenery(){
//Destroy the themeBlock since it isn't needed anymore.
internalThemeBlock.destroy();
}
static inline int getNewCoord(unsigned char rm, int default_, int cameraX, int cameraW, int levelW, int offset) {
switch (rm) {
case Scenery::NEGATIVE_INFINITY:
return cameraX;
case Scenery::ZERO:
return std::max(cameraX, offset);
case Scenery::LEVEL_SIZE:
return std::min(cameraX + cameraW, levelW + offset);
case Scenery::POSITIVE_INFINITY:
return cameraX + cameraW;
default:
return default_;
}
}
void Scenery::show(SDL_Renderer& renderer) {
showScenery(renderer, 0, 0);
}
void Scenery::showScenery(SDL_Renderer& renderer, int offsetX, int offsetY) {
//The box which is offset by the input.
const SDL_Rect box = {
this->box.x + offsetX,
this->box.y + offsetY,
this->box.w,
this->box.h,
};
//The real box according to repeat mode.
SDL_Rect theBox = {
getNewCoord(repeatMode, box.x, camera.x, camera.w, LEVEL_WIDTH, offsetX),
getNewCoord(repeatMode >> 16, box.y, camera.y, camera.h, LEVEL_HEIGHT, offsetX),
getNewCoord(repeatMode >> 8, box.x + box.w, camera.x, camera.w, LEVEL_WIDTH, offsetY),
getNewCoord(repeatMode >> 24, box.y + box.h, camera.y, camera.h, LEVEL_HEIGHT, offsetY),
};
theBox.w -= theBox.x;
theBox.h -= theBox.y;
//Check if the scenery is visible.
if (theBox.w > 0 && theBox.h > 0 && checkCollision(camera, theBox)) {
//Snap the size to integral multiple of box.w and box.h
if (box.w > 1) {
theBox.w += theBox.x;
if (repeatMode & 0xFFu) {
theBox.x = box.x + int(floor(float(theBox.x - box.x) / float(box.w))) * box.w;
}
if (repeatMode & 0xFF00u) {
theBox.w = box.x + int(ceil(float(theBox.w - box.x) / float(box.w))) * box.w;
}
theBox.w -= theBox.x;
}
if (box.h > 1) {
theBox.h += theBox.y;
if (repeatMode & 0xFF0000u) {
theBox.y = box.y + int(floor(float(theBox.y - box.y) / float(box.h))) * box.h;
}
if (repeatMode & 0xFF000000u) {
theBox.h = box.y + int(ceil(float(theBox.h - box.y) / float(box.h))) * box.h;
}
theBox.h -= theBox.y;
}
//Now draw normal.
if (theBox.w > 0 && theBox.h > 0) {
appearance.draw(renderer, theBox.x - camera.x, theBox.y - camera.y, theBox.w, theBox.h);
}
}
//Draw some stupid icons in edit mode.
if (stateID == STATE_LEVEL_EDITOR && checkCollision(camera, box)) {
auto bmGUI = static_cast<LevelEditor*>(parent)->getGuiTexture();
if (!bmGUI) {
return;
}
int x = box.x - camera.x + 2;
//Draw a stupid icon for custom scenery.
if (themeBlock == &internalThemeBlock) {
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Draw a stupid icon for horizonal repeat.
if (repeatMode & 0x0000FFFFu) {
const SDL_Rect r = { 64, 32, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Draw a stupid icon for vertical repeat.
if (repeatMode & 0xFFFF0000u) {
const SDL_Rect r = { 64, 48, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
}
}
SDL_Rect Scenery::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:
return r;
case BoxType_Current:
return box;
}
return r;
}
void Scenery::setLocation(int x,int y){
//The scenery has moved so calculate the delta.
dx=x-box.x;
dy=y-box.y;
//And set its new location.
box.x=x;
box.y=y;
}
void Scenery::saveState(){
//Store the location.
xSave=box.x-boxBase.x;
ySave=box.y-boxBase.y;
//And any animations.
appearance.saveAnimation();
}
void Scenery::loadState(){
//Restore the location.
box.x=boxBase.x+xSave;
box.y=boxBase.y+ySave;
//And load the animation.
appearance.loadAnimation();
}
void Scenery::reset(bool save){
//Reset the scenery to its original location.
box.x=boxBase.x;
box.y=boxBase.y;
if(save)
xSave=ySave=0;
//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();
}
void Scenery::playAnimation(){}
void Scenery::onEvent(int eventType){
//NOTE: Scenery should not interact with the player or vice versa.
}
int Scenery::queryProperties(int propertyType,Player* obj){
//NOTE: Scenery doesn't have any properties.
return 0;
}
void Scenery::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){
obj.push_back(pair<string, string>("sceneryName", sceneryName_));
obj.push_back(pair<string, string>("customScenery", customScenery_));
obj.push_back(pair<string, string>("repeatMode", tfm::format("%d", repeatMode)));
}
void Scenery::setEditorData(std::map<std::string,std::string>& obj){
// NOTE: currently the sceneryName cannot be changed by this method.
auto it = obj.find("customScenery");
if (it != obj.end()) {
customScenery_ = it->second;
}
it = obj.find("repeatMode");
if (it != obj.end()) {
repeatMode = atoi(it->second.c_str());
}
}
std::string Scenery::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 Scenery::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 Scenery::loadFromNode(ImageManager& imageManager, SDL_Renderer& renderer, TreeStorageNode* objNode){
sceneryName_.clear();
customScenery_.clear();
repeatMode = 0;
if (objNode->name == "object") {
//Make sure there are enough arguments.
if (objNode->value.size() < 2)
return false;
//Load position and size.
box.x = boxBase.x = atoi(objNode->value[0].c_str());
box.y = boxBase.y = atoi(objNode->value[1].c_str());
box.w = boxBase.w = (objNode->value.size() >= 3) ? atoi(objNode->value[2].c_str()) : 50;
box.h = boxBase.h = (objNode->value.size() >= 4) ? atoi(objNode->value[3].c_str()) : 50;
//Dump the current TreeStorageNode.
//NOTE: we temporarily remove all attributes since they are not related to theme.
std::map<std::string, std::vector<std::string> > tmpAttributes;
std::swap(objNode->attributes, tmpAttributes);
std::ostringstream o;
POASerializer().writeNode(objNode, o, false, true);
customScenery_ = o.str();
//restore old attributes
std::swap(objNode->attributes, tmpAttributes);
//Load the appearance.
if (!internalThemeBlock.loadFromNode(objNode, levels->levelpackPath, imageManager, renderer)) return false;
themeBlock = &internalThemeBlock;
themeBlock->createInstance(&appearance);
} else if (objNode->name == "scenery") {
//Make sure there are enough arguments.
if (objNode->value.size() < 3)
return false;
//Load position and size.
box.x = boxBase.x = atoi(objNode->value[1].c_str());
box.y = boxBase.y = atoi(objNode->value[2].c_str());
box.w = boxBase.w = (objNode->value.size() >= 4) ? atoi(objNode->value[3].c_str()) : 50;
box.h = boxBase.h = (objNode->value.size() >= 5) ? atoi(objNode->value[4].c_str()) : 50;
//Load the appearance.
themeBlock = objThemes.getScenery(objNode->value[0]);
if (!themeBlock) {
fprintf(stderr, "ERROR: Can't find scenery with name '%s'.\n", objNode->value[0].c_str());
return false;
}
themeBlock->createInstance(&appearance);
//Save the scenery name.
sceneryName_ = objNode->value[0];
} else {
//Unsupported node name for scenery block
fprintf(stderr, "ERROR: Unsupported node name '%s' for scenery block.\n", objNode->name.c_str());
return false;
}
auto it = objNode->attributes.find("repeatMode");
if (it != objNode->attributes.end() && it->second.size() >= 4) {
repeatMode = atoi(it->second[0].c_str())
| (atoi(it->second[1].c_str()) << 8)
| (atoi(it->second[2].c_str()) << 16)
| (atoi(it->second[3].c_str()) << 24);
}
return true;
}
bool Scenery::updateCustomScenery(ImageManager& imageManager, SDL_Renderer& renderer) {
POASerializer serializer;
std::istringstream i(customScenery_);
TreeStorageNode objNode;
//Load the node from text dump
if (!serializer.readNode(i, &objNode, true)) return false;
//Load the appearance.
if (!internalThemeBlock.loadFromNode(&objNode, levels->levelpackPath, imageManager, renderer)) return false;
themeBlock = &internalThemeBlock;
themeBlock->createInstance(&appearance);
// Clear the scenery name since we are using custom scenery
sceneryName_.clear();
return true;
}
-void Scenery::prepareFrame(){
- //Reset the delta variables.
- dx=dy=0;
-}
-
void Scenery::move(){
//Update our appearance.
appearance.updateAnimation();
}
diff --git a/src/Scenery.h b/src/Scenery.h
index 1f01f44..d52d43b 100644
--- a/src/Scenery.h
+++ b/src/Scenery.h
@@ -1,154 +1,152 @@
/*
* Copyright (C) 2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCENERY_H
#define SCENERY_H
#include "GameObjects.h"
#include "ThemeManager.h"
#include <vector>
#include <SDL.h>
class Scenery: public GameObject{
private:
//Save variables for the current location of the scenery.
int xSave,ySave;
//Delta variables, if the scenery moves these must be set to the delta movement.
int dx,dy;
public:
//The ThemeBlock, kept so it can be deleted later on.
ThemeBlock internalThemeBlock;
// The pointer points to the real ThemeBlock, either point to internalThemeBlock, or a ThemeBlock in ThemeManager, or NULL.
ThemeBlock* themeBlock;
//The Appearance of the scenery.
//NOTE: We use a ThemeBlockInstance since it allows for all sorts of things like animations.
ThemeBlockInstance appearance;
// The scenery name. "" means custom scenery, in this case themeBlock is pointing to internalThemeBlock
std::string sceneryName_;
// The custom scenery description, which is the text dump of the TreeStorageNode.
std::string customScenery_;
// The repeat mode.
enum RepeatMode {
DEFAULT, // Starts or ends at the position of this block (default)
NEGATIVE_INFINITY, // Starts at negative infinity
ZERO, // Starts or ends at 0
LEVEL_SIZE, // Starts or ends at level size
POSITIVE_INFINITY, // Ends at positive infinity
REPEAT_MODE_MAX,
};
// The repeat mode of this block. The value is Scenery::RepeatMode left shifted by appropriate value
// bit 0-7: x start
// bit 8-15: x end
// bit 16-23: y start
// bit 24-31: y end
unsigned int repeatMode;
//Constructor.
//objParent: Pointer to the Game object.
Scenery(Game* objParent);
//Constructor.
//objParent: Pointer to the Game object.
//x: the x coordinate
//y: the y coordinate
//w: the width
//h: the height
//sceneryName: the scenery name, "" means custom scenery block
Scenery(Game* objParent, int x, int y, int w, int h, const std::string& sceneryName);
//Desturctor.
~Scenery();
//Method to load custom scenery from customScenery_ member variable.
bool updateCustomScenery(ImageManager& imageManager, SDL_Renderer& renderer);
//Method used to draw the scenery.
//NOTE: To enable parallax scrolling, etc. use showScenery() instead.
void show(SDL_Renderer& renderer) override;
//Method used to draw the scenery.
//offsetX/Y: the offset apply to the scenery block before considering camera position.
void showScenery(SDL_Renderer& renderer, int offsetX, int offsetY);
//Returns the box of a given type.
//boxType: The type of box that should be returned.
//See GameObjects.h for the types.
//Returns: The box.
virtual SDL_Rect getBox(int boxType=BoxType_Current);
//Method used to set the location of the scenery.
//NOTE: The new location isn't stored as base location.
//x: The new x location.
//y: The new y location.
virtual void setLocation(int x,int y);
//Save the state of the scenery so we can load it later on.
virtual void saveState();
//Load the saved state of the scenery.
virtual void loadState();
//Reset the scenery.
//save: Boolean if the saved state should also be deleted.
virtual void reset(bool save);
//Play an animation.
virtual void playAnimation();
//Method called when there's an event.
//eventType: The type of event.
//See GameObjects.h for the eventtypes.
virtual void onEvent(int eventType);
//Method used to retrieve a property from the scenery.
//propertyType: The type of property requested.
//See GameObjects.h for the properties.
//obj: Pointer to the player.
//Returns: Integer containing the value of the property.
virtual int queryProperties(int propertyType,Player* obj);
//Get the editor data of the scenery.
//obj: The vector that will be filled with the editorData.
virtual void getEditorData(std::vector<std::pair<std::string,std::string> >& obj);
//Set the editor data of the scenery.
//obj: The new editor data.
virtual void setEditorData(std::map<std::string,std::string>& obj);
//Get a single property of the scenery.
//property: The property to return.
//Returns: The value for the requested property.
virtual std::string getEditorProperty(std::string property);
//Set a single property of the scenery.
//property: The property to set.
//value: The new value for the property.
virtual void setEditorProperty(std::string property,std::string value);
//Method for loading the Scenery object from a node.
//objNode: Pointer to the storage node to load from.
//Returns: True if it succeeds without errors.
virtual bool loadFromNode(ImageManager& imageManager,SDL_Renderer& renderer,TreeStorageNode* objNode) override;
- //Method used for resetting the dx/dy and xVel/yVel variables.
- virtual void prepareFrame();
//Method used for updating any animations.
virtual void move();
};
#endif
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, May 16, 8:21 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63662
Default Alt Text
(127 KB)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline