Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F133768
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
63 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Game.cpp b/src/Game.cpp
index 5a6a825..5a97cf3 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,710 +1,757 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "GameState.h"
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Objects.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string.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",
};
map<string,int> Game::blockNameMap;
string Game::recordFile;
Game::Game(bool loadLevel):isReset(false)
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,shadowCam(false)
,player(this),shadow(this),objLastCheckPoint(NULL){
//Reserve the memory for the GameObject tips.
memset(bmTips,0,sizeof(bmTips));
action=loadImage(getDataPath()+"gfx/actions.png");
//Check if we should load record file.
if(!recordFile.empty()){
loadRecord(recordFile.c_str());
recordFile.clear();
return;
}
//If we should load the level then load it.
if(loadLevel){
this->loadLevel(levels.getLevelpackPath()+levels.getLevelFile());
levels.saveLevelProgress();
}
}
Game::~Game(){
//Simply call our destroy method.
destroy();
}
void Game::destroy(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
//Done now clear the levelObjects vector.
levelObjects.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Loop through the tips.
for(int i=0;i<TYPE_MAX;i++){
//If it exist free the SDL_Surface.
if(bmTips[i])
SDL_FreeSurface(bmTips[i]);
}
memset(bmTips,0,sizeof(bmTips));
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
}
void Game::loadLevelFromNode(TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
destroy();
//set current level to loaded one.
currentLevelNode=obj;
//Temp var used for block locations.
SDL_Rect box;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();i++){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//If a theme is configured then load it.
string theme=processFileName(getSettings()->getValue("theme"));
//Check if it isn't the default theme, because if it is it's already loaded.
if(fileNameFromPath(theme)!="Cloudscape") {
customTheme=objThemes.appendThemeFromFile(theme+"/theme.mnmstheme");
}
//Check if level themes are enabled.
if(getSettings()->getBoolValue("leveltheme")) {
string &s=editorData["theme"];
if(!s.empty()){
customTheme=objThemes.appendThemeFromFile(processFileName(theme)+"/theme.mnmstheme");
}
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance);
objThemes.getCharacter(true)->createInstance(&shadow.appearance);
}
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile" && obj1->value.size()>=3){
int objectType=blockNameMap[obj1->value[0]];
box.x=atoi(obj1->value[1].c_str());
box.y=atoi(obj1->value[2].c_str());
map<string,string> obj;
for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();i++){
if(i->second.size()>0) obj[i->first]=i->second[0];
}
levelObjects.push_back( new Block ( box.x, box.y, objectType, this) );
levelObjects.back()->setEditorData(obj);
}
}
//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.
stringstream s;
if(levels.getLevelCount()>1){
s<<"Level "<<(levels.getCurrentLevel()+1)<<" ";
}
s<<editorData["name"];
SDL_Color fg={0,0,0,0};
SDL_Color bg={255,255,255,0};
bmTips[0]=TTF_RenderText_Shaded(fontText,s.str().c_str(),fg,bg);
if(bmTips[0])
SDL_SetAlpha(bmTips[0],SDL_SRCALPHA,160);
}
//Get the background
background=objThemes.getBackground();
if(background)
background->resetAnimation(true);
}
void Game::loadLevel(string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cout<<"Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(obj,fileName);
}
//save current game record to the file.
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",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();
}
//load game record (and its level) from file and play it.
void Game::loadRecord(const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cout<<"Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cout<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
- vector<int> *record=player.getRecord();
- record->clear();
- vector<string> &v=obj.attributes["record"];
- for(unsigned int i=0;i<v.size();i++){
- string &s=v[i];
- string::size_type pos=s.find_first_of('*');
- if(pos==string::npos){
- //1 item only.
- int i=atoi(s.c_str());
- record->push_back(i);
- }else{
- //contains many items.
- int i=atoi(s.substr(0,pos).c_str());
- int j=atoi(s.substr(pos+1).c_str());
- for(;j>0;j--){
+ {
+ vector<int> *record=player.getRecord();
+ record->clear();
+ vector<string> &v=obj.attributes["record"];
+ for(unsigned int i=0;i<v.size();i++){
+ string &s=v[i];
+ string::size_type pos=s.find_first_of('*');
+ if(pos==string::npos){
+ //1 item only.
+ int i=atoi(s.c_str());
record->push_back(i);
+ }else{
+ //contains many items.
+ int i=atoi(s.substr(0,pos).c_str());
+ int j=atoi(s.substr(pos+1).c_str());
+ for(;j>0;j--){
+ record->push_back(i);
+ }
+ }
+ }
+ }
+
+#ifdef RECORD_FILE_DEBUG
+ //load the debug data
+ {
+ vector<string> &v=obj.attributes["recordPlayerPosition"];
+ vector<SDL_Rect> &playerPosition=player.playerPosition();
+ playerPosition.clear();
+ if(!v.empty()){
+ if(!v[0].empty()){
+ stringstream st(v[0]);
+ int m;
+ st>>m;
+ for(int i=0;i<m;i++){
+ SDL_Rect r;
+ st>>r.x>>r.y;
+ r.w=0;
+ r.h=0;
+ playerPosition.push_back(r);
+ }
}
}
}
+#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels.saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'r' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART) && !player.isPlayFromRecord()){
//Set reset true.
isReset=true;
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
shadowCam=!shadowCam;
}
}
/////////////////LOGIC///////////////////
void Game::logic(){
//Add one tick to the time.
//if(stateID!=STATE_LEVEL_EDITOR)
time++;
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//Let the player jump.
player.jump();
//Let him move.
player.move(levelObjects);
//And let the camera follow him.
if(!shadowCam){
player.setMyCamera();
}else{
shadow.setMyCamera();
}
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Let the shadow jump.
shadow.jump();
//Let the shadow move.
shadow.move(levelObjects);
//Some levelObjects can move so update them.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->move();
}
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.flags|1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL && obj->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
shadow.otherCheck(&player);
//Check if we should reset.
if(isReset)
reset(false);
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground();
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(screen);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_Rect r={0,0,SCREEN_WIDTH,SCREEN_HEIGHT};
SDL_FillRect(screen,&r,-1);
}
}
//Now we draw the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
levelObjects[o]->show();
}
//Followed by the player and the shadow.
player.show();
shadow.show();
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL){
applySurface(0,SCREEN_HEIGHT-bmTips[0]->h,bmTips[0],screen,NULL);
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for restart text, 2 for restart+checkpoint.
if(gameTipIndex>2 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
const char* s=NULL;
switch(gameTipIndex){
case TYPE_CHECKPOINT:
s="Press DOWN key to save the game.";
break;
case TYPE_SWAP:
s="Press DOWN key to swap the position of player and shadow.";
break;
case TYPE_SWITCH:
s="Press DOWN key to activate the switch.";
break;
case TYPE_PORTAL:
s="Press DOWN key to teleport.";
break;
case TYPE_NOTIFICATION_BLOCK:
s="Press DOWN key to read the message.";
break;
}
//If we have a string then it's a supported GameObject type.
if(s!=NULL){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[gameTipIndex]=TTF_RenderText_Shaded(fontText,s,fg,bg);
SDL_SetAlpha(bmTips[gameTipIndex],SDL_SRCALPHA,160);
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
applySurface(0,0,bmTips[gameTipIndex],screen,NULL);
}
}
//Set the gameTip to 0.
gameTipIndex=0;
//Check if the player is dead, meaning we draw
if(player.dead){
//The player is dead, check if there's a state that can be loaded.
SDL_Surface* bm=NULL;
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[2]=TTF_RenderText_Shaded(fontText,
"Press R to restart current level or press F3 to load the game.",
fg,bg);
SDL_SetAlpha(bmTips[2],SDL_SRCALPHA,160);
}
bm=bmTips[2];
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[1]=TTF_RenderText_Shaded(fontText,
"Press R to restart current level.",
fg,bg);
SDL_SetAlpha(bmTips[1],SDL_SRCALPHA,160);
}
bm=bmTips[1];
}
//Draw the tip.
if(bm!=NULL)
applySurface(0,0,bm,screen,NULL);
}
//show time and records used in level editor.
if(stateID==STATE_LEVEL_EDITOR && time>0){
char c[32];
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
SDL_Surface *bm;
int y=SCREEN_HEIGHT;
sprintf(c,"%d recordings",recordings);
bm=TTF_RenderText_Shaded(fontText,c,fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
sprintf(c,"%-.2fs",time/40.0f);
bm=TTF_RenderText_Shaded(fontText,c,fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//Draw the current action in the upper right corner.
if(player.record){
applySurface(750,0,action,screen,NULL);
}else if(shadow.state!=0){
SDL_Rect r={50,0,50,50};
applySurface(750,0,action,screen,&r);
}
//if the game is play from record then draw something indicates it
if((time & 0x10)==0x10 && player.isPlayFromRecord()){
SDL_Rect r={50,0,50,50};
applySurface(0,0,action,screen,&r);
applySurface(0,SCREEN_HEIGHT-50,action,screen,&r);
applySurface(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,action,screen,&r);
}
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(player.canSaveState() && shadow.canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
//Save other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->saveState();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
//Load other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->loadState();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save){
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
//Reset the stats.
time=0;
recordings=0;
//There is no last checkpoint so set it to NULL.
if(save)
objLastCheckPoint=NULL;
//Reset other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels.getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
diff --git a/src/Player.cpp b/src/Player.cpp
index 7943929..7cb550e 100644
--- a/src/Player.cpp
+++ b/src/Player.cpp
@@ -1,1093 +1,1192 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "Player.h"
#include "Game.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "InputManager.h"
#include "MD5.h"
#include <iostream>
#include <fstream>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
using namespace std;
+#ifdef RECORD_FILE_DEBUG
+string recordKeyPressLog,recordKeyPressLog_saved;
+vector<SDL_Rect> recordPlayerPosition,recordPlayerPosition_saved;
+#endif
+
Player::Player(Game* objParent):xVelBase(0),yVelBase(0),objParent(objParent){
//Set the dimensions of the player.
//The size of the player is 21x40.
box.x=0;
box.y=0;
box.w=21;
box.h=40;
//Set his velocity to zero.
xVel=0;
yVel=0;
//Set the start position.
fx=0;
fy=0;
//Check if sound is enabled.
if(getSettings()->getBoolValue("sound")==true){
//It is so load the sounds.
jumpSound=Mix_LoadWAV((getDataPath()+"sfx/jump.wav").c_str());
hitSound=Mix_LoadWAV((getDataPath()+"sfx/hit.wav").c_str());
saveSound=Mix_LoadWAV((getDataPath()+"sfx/checkpoint.wav").c_str());
swapSound=Mix_LoadWAV((getDataPath()+"sfx/swap.wav").c_str());
toggleSound=Mix_LoadWAV((getDataPath()+"sfx/toggle.wav").c_str());
errorSound=Mix_LoadWAV((getDataPath()+"sfx/error.wav").c_str());
}
//Set some default values.
inAir=true;
isJump=false;
onGround=true;
shadowCall=false;
shadow=false;
canMove=true;
holdingOther=false;
dead=false;
record=false;
downKeyPressed=false;
spaceKeyPressed=false;
recordIndex=-1;
+#ifdef RECORD_FILE_DEBUG
+ recordKeyPressLog.clear();
+ recordKeyPressLog_saved.clear();
+ recordPlayerPosition.clear();
+ recordPlayerPosition_saved.clear();
+#endif
//Some default values for animation variables.
direction=0;
jumpTime=0;
state=0;
//xVelSaved is used to store if there's a state saved or not.
xVelSaved=0x80000000;
}
Player::~Player(){
//We only need to clean up the sounds if they were loaded.
if(getSettings()->getBoolValue("sound")==true){
Mix_FreeChunk(jumpSound);
Mix_FreeChunk(hitSound);
Mix_FreeChunk(saveSound);
Mix_FreeChunk(swapSound);
Mix_FreeChunk(toggleSound);
}
}
bool Player::isPlayFromRecord(){
return recordIndex>=0; // && recordIndex<(int)recordButton.size();
}
//get the game record object.
std::vector<int>* Player::getRecord(){
return &recordButton;
}
+
+#ifdef RECORD_FILE_DEBUG
+string& Player::keyPressLog(){
+ return recordKeyPressLog;
+}
+vector<SDL_Rect>& Player::playerPosition(){
+ return recordPlayerPosition;
+}
+#endif
//play the record.
void Player::playRecord(){
//TODO:
recordIndex=0;
}
void Player::spaceKeyDown(class Shadow* shadow){
//Start recording or stop, depending on the recording state.
if(record==false){
//We start recording.
if(shadow->called==true){
//The shadow is still busy so first stop him before we can start recording.
shadowCall=false;
shadow->called=false;
shadow->playerButton.clear();
}else if(!dead){
//The shadow isn't moving and we aren't dead so start recording.
record=true;
//We start a recording meaning we need to increase recordings by one.
//if(stateID!=STATE_LEVEL_EDITOR)
objParent->recordings++;
}
}else{
//The player is recording so stop recording and call the shadow.
record=false;
shadowCall=true;
}
}
void Player::handleInput(class Shadow* shadow){
//Check if we should read the input from record file.
//Actually, we read input from record file in
//another function shadowSetState.
bool readFromRecord=false;
if(recordIndex>=0 && recordIndex<(int)recordButton.size()) readFromRecord=true;
if(!readFromRecord){
//Reset horizontal velocity.
xVel=0;
if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
//Walking to the right.
xVel+=7;
}
if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
//Walking to the left.
xVel-=7;
}
//Check if a key has been released.
- if(event.type==SDL_KEYUP || !inputMgr.isKeyDown(INPUTMGR_ACTION)){
+ if(/*event.type==SDL_KEYUP || */!inputMgr.isKeyDown(INPUTMGR_ACTION)){
//It has so downKeyPressed can't be true.
downKeyPressed=false;
}
/*
//Don't reset spaceKeyPressed or when you press the space key
//and release another key then the bug occurs. (ticket #44)
if(event.type==SDL_KEYUP || !inputMgr.isKeyDown(INPUTMGR_SPACE)){
spaceKeyPressed=false;
}*/
}
//Check if a key is pressed (down).
if(inputMgr.isKeyDownEvent(INPUTMGR_JUMP) && !readFromRecord){
//The up key, if we aren't in the air we start jumping.
- if(inAir==false){
+ //Fixed a potential bug
+ if(!inAir && !isJump){
+#ifdef RECORD_FILE_DEBUG
+ char c[64];
+ sprintf(c,"[%05d] Jump key pressed\n",objParent->time);
+ cout<<c;
+ recordKeyPressLog+=c;
+#endif
isJump=true;
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) && !readFromRecord){
- spaceKeyDown(shadow);
- spaceKeyPressed=true;
+ //Fixed a potential bug
+ if(!spaceKeyPressed){
+#ifdef RECORD_FILE_DEBUG
+ char c[64];
+ sprintf(c,"[%05d] Space key pressed\n",objParent->time);
+ cout<<c;
+ recordKeyPressLog+=c;
+#endif
+ spaceKeyDown(shadow);
+ spaceKeyPressed=true;
+ }
}else if(inputMgr.isKeyDownEvent(INPUTMGR_ACTION)){
//Downkey is pressed.
- downKeyPressed=true;
+ //Fixed a potential bug
+ if(!downKeyPressed){
+#ifdef RECORD_FILE_DEBUG
+ char c[64];
+ sprintf(c,"[%05d] Action key pressed\n",objParent->time);
+ cout<<c;
+ recordKeyPressLog+=c;
+#endif
+ downKeyPressed=true;
+ }
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SAVE)){
//F2 only works in the level editor.
if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
//Save the state.
if(objParent)
objParent->saveState();
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_LOAD)){
//F3 is used to load the last state.
if(objParent)
objParent->loadState();
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SWAP)){
//F4 will swap the player and the shadow, but only in the level editor.
if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
swapState(shadow);
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_TELEPORT)){
//F5 will revive and teleoprt the player to the cursor. Only works in the level editor.
//Shift+F5 teleports the shadow.
if(stateID==STATE_LEVEL_EDITOR){
//get the position of the cursor.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
if(inputMgr.isKeyDown(INPUTMGR_SHIFT)){
//teleports the shadow.
shadow->dead=false;
shadow->box.x=x;
shadow->box.y=y;
}else{
//teleports the player.
dead=false;
box.x=x;
box.y=y;
}
//play sound?
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,swapSound,0);
}
}
}else if(inputMgr.isKeyDownEvent(INPUTMGR_SUICIDE)){
//F12 is suicide and only works in the leveleditor.
if(stateID==STATE_LEVEL_EDITOR){
die();
shadow->die();
}
}
}
void Player::setPosition(int x,int y){
box.x=x;
box.y=y;
}
void Player::move(vector<GameObject*> &levelObjects){
//Pointer to a checkpoint.
GameObject* objCheckPoint=NULL;
//Pointer to a swap.
GameObject* objSwap=NULL;
//Set the objShadowBlock to NULL.
//Only for swapping to prevent the shadow from swapping in a shadow block.
objShadowBlock=NULL;
//Boolean if the player can teleport.
bool canTeleport=true;
//Set the object the player is currently standing to NULL.
objCurrentStand=NULL;
//Check if the player is still alive.
if(dead==false){
//Add gravity
if(inAir==true){
yVel+=1;
//Cap fall speed to 13.
if(yVel>13){
yVel=13;
}
}
//Check if the player can move.
if(canMove==true){
//Check if the player is moving or not.
if(xVel>0){
direction=0;
onGround=false;
if(appearance.currentStateName!="walkright"){
appearance.changeState("walkright");
}
}else if(xVel<0){
direction=1;
onGround=false;
if(appearance.currentStateName!="walkleft"){
appearance.changeState("walkleft");
}
}else if(xVel==0){
onGround=true;
if(direction==1){
appearance.changeState("standleft");
}else{
appearance.changeState("standright");
}
}
//Move the player.
box.x+=xVel;
//Loop through the levelobjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check if the player can walk on the object.
if(levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this)){
//Get the collision box of the levelobject.
SDL_Rect r=levelObjects[o]->getBox();
//Check collision with the player.
if(checkCollision(box,r)){
//We have collision, get the velocity of the box.
SDL_Rect v=levelObjects[o]->getBox(BoxType_Delta);
//Check on which side of the box the player is.
if(box.x + box.w/2 <= r.x + r.w/2){
if(xVel+xVelBase>v.x){
if(box.x>r.x-box.w)
box.x=r.x-box.w;
}
}else{
if(xVel+xVelBase<v.x){
if(box.x<r.x+r.w)
box.x=r.x+r.w;
}
}
}
}
}
}
//Now apply the yVel. (gravity, jumping, etc..)
box.y+=yVel;
//Pointer to the object the player standed on.
GameObject* lastStand=NULL;
//???
inAir=true;
canMove=true;
//Loop through all the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check if the object is solid.
if(levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this)){
SDL_Rect r=levelObjects[o]->getBox();
if(checkCollision(r,box)==true){ //TODO:fix some bug
SDL_Rect v=levelObjects[o]->getBox(BoxType_Delta);
if(box.y+box.h/2<=r.y+r.h/2){
if(yVel>=v.y || yVel>=0){
inAir=false;
box.y=r.y-box.h;
yVel=1; //???
lastStand=levelObjects[o];
lastStand->onEvent(GameObjectEvent_PlayerIsOn);
}
}else{
if(yVel<=v.y+1){
yVel=v.y>0?v.y:0;
if(box.y<r.y+r.h)
box.y=r.y+r.h;
}
}
}
}
//Check if the object is a checkpoint.
if(levelObjects[o]->type==TYPE_CHECKPOINT && checkCollision(box,levelObjects[o]->getBox())){
//If we're not the shadow set the gameTip to Checkpoint.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_CHECKPOINT;
//And let objCheckPoint point to this object.
objCheckPoint=levelObjects[o];
}
//Check if the object is a swap.
if(levelObjects[o]->type==TYPE_SWAP && checkCollision(box,levelObjects[o]->getBox())){
//If we're not the shadow set the gameTip to swap.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_SWAP;
//And let objSwap point to this object.
objSwap=levelObjects[o];
}
//Check if the object is an exit.
//This doesn't work if the state is Level editor.
if(levelObjects[o]->type==TYPE_EXIT && stateID!=STATE_LEVEL_EDITOR && checkCollision(box,levelObjects[o]->getBox())){
//Check if it's playing game record (?)
if(recordIndex>=0){
msgBox("Game replay is done.",MsgBoxOKOnly,"Game Replay");
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
return;
}
//TEST ONLY: save the current game record to file.
//objParent->saveRecord("test.mnmsrec");
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get thest path.
bool filePathError=false;
//Set the current level won.
levels.getLevel()->won=true;
if(levels.getLevel()->time==-1 || levels.getLevel()->time>objParent->time){
levels.getLevel()->time=objParent->time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
objParent->getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
objParent->saveRecord(bestTimeFilePath.c_str());
}
}
if(levels.getLevel()->recordings==-1 || levels.getLevel()->recordings>objParent->recordings){
levels.getLevel()->recordings=objParent->recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
objParent->getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
objParent->saveRecord(bestRecordingFilePath.c_str());
}
}
//Goto the next level.
levels.nextLevel();
//Check if the level exists.
if(levels.getCurrentLevel()<levels.getLevelCount()){
//It does so unlock the levels.
levels.setLocked(levels.getCurrentLevel());
//And enter the GameState to start the new level.
setNextState(STATE_GAME);
//Don't forget the music.
getMusicManager()->pickMusic();
}else{
//Show the congratulations messagebox.
if(!levels.congratulationText.empty()){
msgBox(levels.congratulationText,MsgBoxOKOnly,"Congratulations");
}else{
msgBox("You have finished the levelpack!",MsgBoxOKOnly,"Congratulations");
}
//Save the progress.
levels.saveLevelProgress();
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
}
//Check if the object is a portal.
if(levelObjects[o]->type==TYPE_PORTAL && checkCollision(box,levelObjects[o]->getBox())){
//Check if the teleport id isn't empty.
if((dynamic_cast<Block*>(levelObjects[o]))->id.empty()){
cerr<<"Warning: Invalid teleport id!"<<endl;
canTeleport=false;
}
//If we're not the shadow set the gameTip to portal.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_PORTAL;
//Check if we can teleport and should (downkey -or- auto).
if(canTeleport && (downKeyPressed || (levelObjects[o]->queryProperties(GameObjectProperty_Flags,this)&1))){
canTeleport=false;
if(downKeyPressed || levelObjects[o]!=objLastTeleport){
downKeyPressed=false;
//Loop the levelobjects again to find the destination.
for(unsigned int oo=o+1;;){
//We started at our index+1.
//Meaning that if we reach the end of the vector then we need to start at the beginning.
if(oo>=levelObjects.size())
oo-=(int)levelObjects.size();
//It also means that if we reach the same index we need to stop.
//If the for loop breaks this way then we have no succes.
if(oo==o){
//Couldn't teleport so play the error sound.
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,errorSound,0);
}
break;
}
//Check if the second (oo) object is a portal.
if(levelObjects[oo]->type==TYPE_PORTAL){
//Check the id against the destination of the first portal.
if((dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Call the event.
levelObjects[o]->onEvent(GameObjectEvent_OnToggle);
objLastTeleport=levelObjects[oo];
//Get the destination location and teleport the player.
SDL_Rect r=levelObjects[oo]->getBox();
box.x=r.x+5;
box.y=r.y+2;
//Check if music/sound is enabled.
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,swapSound,0);
}
break;
}
}
//Increase oo.
oo++;
}
}
}
}
//Check if the object is a switch.
if(levelObjects[o]->type==TYPE_SWITCH && checkCollision(box,levelObjects[o]->getBox())){
//If we're not the shadow set the gameTip to switch.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_SWITCH;
//If the down key is pressed then invoke an event.
if(downKeyPressed){
//Play the animation.
levelObjects[o]->playAnimation(1);
//Check if sound is enabled, if so play the toggle sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,toggleSound,0);
}
if(objParent!=NULL){
//Make sure that the id isn't emtpy.
if(!(dynamic_cast<Block*>(levelObjects[o]))->id.empty()){
objParent->broadcastObjectEvent(0x10000 | (levelObjects[o]->queryProperties(GameObjectProperty_Flags,this)&3),
-1,(dynamic_cast<Block*>(levelObjects[o]))->id.c_str());
}else{
cerr<<"Warning: invalid switch id!"<<endl;
}
}
}
}
//Check if the object is a notification block.
if(levelObjects[o]->type==TYPE_NOTIFICATION_BLOCK && checkCollision(box,levelObjects[o]->getBox())){
//If we're not the shadow set the gameTip to notification block.
if(!shadow && objParent!=NULL)
objParent->gameTipIndex=TYPE_NOTIFICATION_BLOCK;
//If the down key is pressed then invoke an event.
if(downKeyPressed==true)
(dynamic_cast<Block*>(levelObjects[o]))->onEvent(GameObjectEvent_OnSwitchOn);
}
//Check if the object is a shadow block, only if we are the playre.
if((levelObjects[o]->type==TYPE_SHADOW_BLOCK || levelObjects[o]->type==TYPE_MOVING_SHADOW_BLOCK) && checkCollision(box,levelObjects[o]->getBox()) && !shadow){
objShadowBlock=levelObjects[o];
}
//Check if the object is deadly.
if(levelObjects[o]->queryProperties(GameObjectProperty_IsSpikes,this)){
//It is so get the collision box.
SDL_Rect r=levelObjects[o]->getBox();
//TODO: pixel-accuracy hit test.
//For now we shrink the box.
r.x+=2;
r.y+=2;
r.w-=4;
r.h-=4;
//Check collision, if the player collides then let him die.
if(checkCollision(box,r))
die();
}
}
//Check if the player fell of the level.
if(box.y>LEVEL_HEIGHT)
die();
//Check if the player changed blocks, meaning stepped onto a block.
objCurrentStand=lastStand;
if(lastStand!=objLastStand){
objLastStand=lastStand;
if(lastStand){
//Call the walk on event of the laststand.
lastStand->onEvent(GameObjectEvent_PlayerWalkOn);
//Bugfix for Fragile blocks.
if(lastStand->type==TYPE_FRAGILE && !lastStand->queryProperties(GameObjectProperty_PlayerCanWalkOn,this)){
inAir=true;
onGround=false;
isJump=false;
}
}
}
//Check if the player can teleport.
if(canTeleport)
objLastTeleport=NULL;
//Check the checkpoint pointer only if the downkey is pressed.
if(objParent!=NULL && downKeyPressed && objCheckPoint!=NULL){
//Checkpoint thus save the state.
if(objParent->saveState())
objParent->objLastCheckPoint=objCheckPoint;
}
//Check the swap pointer only if the down key is pressed.
if(objSwap!=NULL && downKeyPressed && objParent!=NULL){
//Now check if the shadow we're the shadow or not.
if(shadow){
if(!(dead || objParent->player.dead)){
//Check if the player isn't in front of a shadow block.
if(!objParent->player.objShadowBlock){
objParent->player.swapState(this);
objSwap->playAnimation(1);
}else{
//We can't swap so play the error sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,errorSound,0);
}
}
}
}else{
if(!(dead || objParent->shadow.dead)){
//Check if the player isn't in front of a shadow block.
if(!objShadowBlock){
swapState(&objParent->shadow);
objSwap->playAnimation(1);
}else{
//We can't swap so play the error sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,errorSound,0);
}
}
}
}
}
//Check for jump appearance (inAir).
if(inAir && !dead){
if(direction==1){
if(yVel>0){
if(appearance.currentStateName!="fallleft")
appearance.changeState("fallleft");
}else{
if(appearance.currentStateName!="jumpleft")
appearance.changeState("jumpleft");
}
}else{
if(yVel>0){
if(appearance.currentStateName!="fallright")
appearance.changeState("fallright");
}else{
if(appearance.currentStateName!="jumpright")
appearance.changeState("jumpright");
}
}
}
}
//Finally we reset some stuff.
downKeyPressed=false;
xVelBase=0;
yVelBase=0;
}
void Player::jump(){
//Check if the player is dead or not.
if(dead==true){
//The player can't jump if he's dead.
isJump=false;
}
//Check if the player can jump.
if(isJump==true && inAir==false){
//Set the jump velocity.
yVel=-13;
inAir=true;
isJump=false;
jumpTime++;
//Check if sound is enabled, if so play the jump sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,jumpSound,0);
}
}
}
void Player::show(){
//Check if we should render the recorded line.
//Only do this when we're recording and we're not the shadow.
if(shadow==false && record==true){
//FIXME: Adding an entry not in update but in render?
line.push_back(SDL_Rect());
line[line.size()-1].x=box.x+11;
line[line.size()-1].y=box.y+20;
//Loop through the line dots and draw them.
for(int l=0; l<(signed)line.size(); l++){
appearance.drawState("line",screen,line[l].x-camera.x,line[l].y-camera.y,NULL);
}
}
//NOTE: We do logic here, because it's only needed by the appearance.
appearance.updateAnimation();
appearance.draw(screen, box.x-camera.x, box.y-camera.y, NULL);
}
void Player::shadowSetState(){
int currentKey=0;
//Check if we should read the input from record file.
if(recordIndex>=0){ // && recordIndex<(int)recordButton.size()){
//read the input from record file
if(recordIndex<(int)recordButton.size()){
currentKey=recordButton[recordIndex];
recordIndex++;
}
//Reset horizontal velocity.
xVel=0;
if(currentKey&PlayerButtonRight){
//Walking to the right.
xVel+=7;
}
if(currentKey&PlayerButtonLeft){
//Walking to the left.
xVel-=7;
}
if(currentKey&PlayerButtonJump){
//The up key, if we aren't in the air we start jumping.
if(inAir==false){
isJump=true;
}else{
//Shouldn't go here
- cout<<"BUG"<<endl;
+ cout<<"Replay BUG"<<endl;
}
}
//check the down key
downKeyPressed=(currentKey&PlayerButtonDown)!=0;
//check the space key
if(currentKey&PlayerButtonSpace){
spaceKeyDown(&objParent->shadow);
}
}else{
//read the input from keyboard.
recordIndex=-1;
//Check for xvelocity.
if(xVel>0)
currentKey|=PlayerButtonRight;
if(xVel<0)
currentKey|=PlayerButtonLeft;
//Check for jumping.
- if(isJump)
+ if(isJump){
+#ifdef RECORD_FILE_DEBUG
+ char c[64];
+ sprintf(c,"[%05d] Jump key recorded\n",objParent->time-1);
+ cout<<c;
+ recordKeyPressLog+=c;
+#endif
currentKey|=PlayerButtonJump;
+ }
//Check if the downbutton is pressed.
- if(downKeyPressed)
+ if(downKeyPressed){
+#ifdef RECORD_FILE_DEBUG
+ char c[64];
+ sprintf(c,"[%05d] Action key recorded\n",objParent->time-1);
+ cout<<c;
+ recordKeyPressLog+=c;
+#endif
currentKey|=PlayerButtonDown;
+ }
+
+ if(spaceKeyPressed){
+#ifdef RECORD_FILE_DEBUG
+ char c[64];
+ sprintf(c,"[%05d] Space key recorded\n",objParent->time-1);
+ cout<<c;
+ recordKeyPressLog+=c;
+#endif
+ currentKey|=PlayerButtonSpace;
+ }
//record it
- recordButton.push_back(currentKey|(spaceKeyPressed?PlayerButtonSpace:0));
+ recordButton.push_back(currentKey);
+ }
+
+#ifdef RECORD_FILE_DEBUG
+ if(recordIndex>=0){
+ if(recordIndex>0 && recordIndex<=int(recordPlayerPosition.size())/2){
+ SDL_Rect &r1=recordPlayerPosition[recordIndex*2-2];
+ SDL_Rect &r2=recordPlayerPosition[recordIndex*2-1];
+ if(r1.x!=box.x || r1.y!=box.y || r2.x!=objParent->shadow.box.x || r2.y!=objParent->shadow.box.y){
+ char c[192];
+ sprintf(c,"Replay ERROR [%05d] %d %d %d %d Expected: %d %d %d %d\n",
+ objParent->time-1,box.x,box.y,objParent->shadow.box.x,objParent->shadow.box.y,r1.x,r1.y,r2.x,r2.y);
+ cout<<c;
+ }
+ }
+ }else{
+ recordPlayerPosition.push_back(box);
+ recordPlayerPosition.push_back(objParent->shadow.box);
}
+#endif
//reset spaceKeyPressed.
spaceKeyPressed=false;
//Only add an entry if the player is recording.
if(record){
//Add the action.
playerButton.push_back(currentKey);
//Change the state.
state++;
}
}
void Player::shadowGiveState(Shadow* shadow){
//Check if the player calls the shadow.
if(shadowCall==true){
//Clear any recording still with the shadow.
shadow->playerButton.clear();
//Loop the recorded moves and add them to the one of the shadow.
for(unsigned int s=0;s<playerButton.size();s++){
shadow->playerButton.push_back(playerButton[s]);
}
//Reset the state of both the player and the shadow.
stateReset();
shadow->stateReset();
//Clear the recording at the player's side.
playerButton.clear();
line.clear();
//Set shadowCall false
shadowCall=false;
//And let the shadow know that the player called him.
shadow->meCall();
}
}
void Player::stateReset(){
//Reset the state by setting it to 0.
state=0;
}
void Player::otherCheck(class Player* other){
//Check if the player is standing on the shadow, or vice versa.
//First make sure the player isn't dead.
if(!dead){
if(objCurrentStand!=NULL){
//Now get the velocity of the object the player is standing.
SDL_Rect v=objCurrentStand->getBox(BoxType_Velocity);
//Set the base velocity to the velocity of the object.
xVelBase=v.x;
yVelBase=v.y;
//Already move the player box.
box.x+=v.x;
box.y+=v.y;
}
//Make sure the other isn't dead.
if(!other->dead){
//Get the box of the shadow.
SDL_Rect boxShadow=other->getBox();
//Check collision between the shadow and the player.
if(checkCollision(box,boxShadow)==true){
//We have collision now check if the other is standing on top of you.
if(box.y+box.h<=boxShadow.y+13 && !other->inAir){
int yVelocity=yVel-1;
if(yVelocity>0){
box.y-=yVel;
box.y+=boxShadow.y-(box.y+box.h);
inAir=false;
canMove=false;
onGround=true;
other->holdingOther=true;
other->appearance.changeState("holding");
//Change our own appearance to standing.
if(direction==1){
appearance.changeState("standleft");
}else{
appearance.changeState("standright");
}
}
}
}else{
other->holdingOther=false;
}
}
}
objCurrentStand=NULL;
}
SDL_Rect Player::getBox(){
return box;
}
void Player::setMyCamera(){
//Only change the camera when the player isn't dead.
if(dead)
return;
//Check if the player is halfway pass the halfright of the screen.
if(box.x>camera.x+450){
//It is so ease the camera to the right.
camera.x+=(box.x-camera.x-400)>>4;
//Check if the camera isn't going too far.
if(box.x<camera.x+450){
camera.x=box.x-450;
}
}
//Check if the player is halfway pass the halfleft of the screen.
if(box.x<camera.x+350){
//It is so ease the camera to the left.
camera.x+=(box.x-camera.x-400)>>4;
//Check if the camera isn't going too far.
if(box.x>camera.x+350){
camera.x=box.x-350;
}
}
//If the camera is too far to the left we set it to 0.
if(camera.x<0){
camera.x=0;
}
//If the camera is too far to the right we set it to the max right.
if(camera.x+camera.w>LEVEL_WIDTH){
camera.x=LEVEL_WIDTH-camera.w;
}
//Check if the player is halfway pass the lower half of the screen.
if(box.y>camera.y+350){
//Ir is so ease the camera down.
camera.y+=(box.y-camera.y-300)>>4;
//Check if the camera isn't going too far.
if(box.y<camera.y+350){
camera.y=box.y-350;
}
}
//Check if the player is halfway pass the upper half of the screen.
if(box.y<camera.y+250){
//It is so ease the camera up.
camera.y+=(box.y-camera.y-300)>>4;
//Check if the camera isn't going too far.
if(box.y>camera.y+250){
camera.y=box.y-250;
}
}
//If the camera is too far up we set it to 0.
if(camera.y<0){
camera.y=0;
}
//If the camera is too far down we set it to the max down.
if(camera.y+camera.h>LEVEL_HEIGHT){
camera.y=LEVEL_HEIGHT-camera.h;
}
}
void Player::reset(bool save){
//Set the location of the player to it's initial state.
box.x=fx;
box.y=fy;
//Reset back to default value.
inAir=true;
isJump=false;
onGround=true;
shadowCall=false;
canMove=true;
holdingOther=false;
dead=false;
record=false;
downKeyPressed=false;
spaceKeyPressed=false;
//Some animation variables.
appearance.resetAnimation(save);
appearance.changeState("standright");
direction=0;
state=0;
yVel=0;
objCurrentStand=NULL;
//Clear the recording.
line.clear();
playerButton.clear();
recordButton.clear();
recordIndex=-1;
+#ifdef RECORD_FILE_DEBUG
+ recordKeyPressLog.clear();
+ recordPlayerPosition.clear();
+#endif
//xVelSaved is used to indicate if there's a state saved or not.
if(save){
xVelSaved=0x80000000;
}
}
void Player::saveState(){
//We can only save the state when the player isn't dead.
if(!dead){
boxSaved.x=box.x;
boxSaved.y=box.y;
xVelSaved=xVel;
yVelSaved=yVel;
inAirSaved=inAir;
isJumpSaved=isJump;
onGroundSaved=onGround;
canMoveSaved=canMove;
holdingOtherSaved=holdingOther;
//Let the appearance save.
appearance.saveAnimation();
//Save the record
savedRecordButton=recordButton;
+#ifdef RECORD_FILE_DEBUG
+ recordKeyPressLog_saved=recordKeyPressLog;
+ recordPlayerPosition_saved=recordPlayerPosition;
+#endif
//Only play the sound when it's enabled.
if(getSettings()->getBoolValue("sound")==true){
//To prevent playing the sound twice, only the player can cause the sound.
if(!shadow)
Mix_PlayChannel(-1,saveSound,0);
}
}
}
void Player::loadState(){
//Check with xVelSaved if there's a saved state.
if(xVelSaved==int(0x80000000)){
//There isn't so reset the game to load the first initial state.
//NOTE: There's no need in removing the saved state since there is none.
reset(false);
return;
}
//Restore the saved values.
box.x=boxSaved.x;
box.y=boxSaved.y;
//xVel is set to 0 since it's saved counterpart is used to indicate a saved state.
xVel=0;
yVel=yVelSaved;
//Restore teh saved values.
inAir=inAirSaved;
isJump=isJumpSaved;
onGround=onGroundSaved;
canMove=canMoveSaved;
holdingOther=holdingOtherSaved;
dead=false;
record=false;
shadowCall=false;
stateReset();
//Restore the appearance.
appearance.loadAnimation();
//Clear any recorded stuff.
line.clear();
playerButton.clear();
//Load the previously saved record
recordButton=savedRecordButton;
+#ifdef RECORD_FILE_DEBUG
+ recordKeyPressLog=recordKeyPressLog_saved;
+ recordPlayerPosition=recordPlayerPosition_saved;
+#endif
}
void Player::swapState(Player* other){
//We need to swap the values of the player with the ones of the given player.
swap(box.x,other->box.x);
swap(box.y,other->box.y);
//NOTE: xVel isn't there since it's used for something else.
swap(yVel,other->yVel);
swap(inAir,other->inAir);
swap(isJump,other->isJump);
swap(onGround,other->onGround);
swap(canMove,other->canMove);
swap(holdingOther,other->holdingOther);
swap(dead,other->dead);
//Also reset the state of the other.
other->stateReset();
//Play the swap sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,swapSound,0);
}
}
bool Player::canSaveState(){
//We can only save the state if the player isn't dead.
return !dead;
}
bool Player::canLoadState(){
//We use xVelSaved to indicate if a state is saved or not.
return xVelSaved != int(0x80000000);
}
void Player::die(){
//Make sure the player isn't already dead.
if(!dead){
dead=true;
//If sound is enabled run the hit sound.
if(getSettings()->getBoolValue("sound")==true){
Mix_PlayChannel(-1,hitSound,0);
}
//Change the apearance to die.
appearance.changeState("die");
}
}
diff --git a/src/Player.h b/src/Player.h
index 7cd2f37..fb27433 100644
--- a/src/Player.h
+++ b/src/Player.h
@@ -1,242 +1,251 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#ifndef PLAYER_H
#define PLAYER_H
#include "ThemeManager.h"
#include <vector>
+#include <string>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
+
+//debug the game record file
+#define RECORD_FILE_DEBUG
class GameObject;
class Game;
//The different player buttons.
//The right arrow.
const int PlayerButtonRight=0x01;
//The left arrow.
const int PlayerButtonLeft=0x02;
//The up arrow for jumping.
const int PlayerButtonJump=0x04;
//The down arrow for actions.
const int PlayerButtonDown=0x08;
//space bar for recording. (Only in recordButton)
const int PlayerButtonSpace=0x10;
class Player{
protected:
//Vector used to store the player actions in when recording.
//These can be given to the shadow so he can execute them.
std::vector<int> playerButton;
private:
//Vector used to record the whole game play.
//And saved record in checkpoint.
std::vector<int> recordButton,savedRecordButton;
//record index. -1 means read input from keyboard,
//otherwise read input from recordings (recordButton[recordIndex]).
int recordIndex;
//Vector containing squares along the path the player takes when recording.
//It will be drawn as a trail of squares.
std::vector<SDL_Rect> line;
//Boolean if the player called the shadow to copy his moves.
bool shadowCall;
//Boolean if the player is recording his moves.
bool record;
//The following variables are to store a state.
//Rectangle containing the players location.
SDL_Rect boxSaved;
//Boolean if the player is in the air.
bool inAirSaved;
//Boolean if the player is (going to) jump(ing).
bool isJumpSaved;
//Boolean if the player is (still) on the ground.
bool onGroundSaved;
//Boolean if the player can move.
bool canMoveSaved;
//Boolean if the player is holding the other (shadow).
bool holdingOtherSaved;
//The x velocity.
//NOTE: The x velocity is used to indicate that there's a state saved.
int xVelSaved;
//The y velocity.
int yVelSaved;
protected:
//Rectangle containing the player's location.
SDL_Rect box;
//The x and y velocity.
int xVel, yVel;
//The base x and y velocity, used for standing on moving blocks.
int xVelBase, yVelBase;
//Sound played when the player jumps.
Mix_Chunk* jumpSound;
//Sound played when the player dies.
Mix_Chunk* hitSound;
//Sound played when the saves a state.
Mix_Chunk* saveSound;
//Sound played when the player swaps.
Mix_Chunk* swapSound;
//Sound played when the player toggles a switch.
Mix_Chunk* toggleSound;
//The sound played when the player tries something that doesn't work.
//For example a broken portal or swapping the shadow into a shadow block.
Mix_Chunk* errorSound;
//Boolean if the player is in the air.
bool inAir;
//Boolean if the player is (going to) jump(ing).
bool isJump;
//Boolean if the player is (still) on the ground.
bool onGround;
//Boolean if the player can move.
bool canMove;
//Boolean if the player is alive/
bool dead;
//The direction the player is walking, 0=right, 1=left.
int direction;
//Integer containing the state of the player.
int state;
//The time the player is in the air (jumping).
int jumpTime;
//Boolean if the player is in fact the shadow.
bool shadow;
//Pointer to the Game state.
friend class Game;
Game* objParent;
//Boolean if the downkey is pressed.
bool downKeyPressed;
//Boolean if the space keu is pressed.
bool spaceKeyPressed;
//Pointer to the object that is currently been stand on by the player.
//This is always a valid pointer.
GameObject* objCurrentStand;
//Pointer to the object the player stood last on.
//NOTE: This is a weak reference only.
GameObject* objLastStand;
//Pointer to the teleporter the player last took.
//NOTE: This is a weak reference only.
GameObject* objLastTeleport;
//Pointer to the shadow block the player is in front of.
//This is always a valid pointer.
GameObject* objShadowBlock;
public:
//X and y location where the player starts and gets when reseted.
int fx, fy;
//The appearance of the player.
ThemeCharacterInstance appearance;
//Boolean if the player is holding the other.
bool holdingOther;
//Constructor.
//objParent: Pointer to the Game state.
Player(Game* objParent);
//Destructor.
~Player();
//Method used to set the position of the player.
//x: The new x location of the player.
//y: The new y location of the player.
void setPosition(int x,int y);
//Method used to handle (key) input.
//shadow: Pointer to the shadow used for recording/calling.
void handleInput(class Shadow* shadow);
//Method used to do the movement of the player.
//levelObjects: Array containing the levelObjects, used to check collision.
void move(std::vector<GameObject*> &levelObjects);
//Method used to check if the player can jump and executes the jump.
void jump();
//This method will render the player to the screen.
void show();
//Method that stores the actions if the player is recording.
void shadowSetState();
//Method that will reset the state to 0.
virtual void stateReset();
//This method checks the player against the other to see if they stand on eachother.
//other: The shadow or the player.
void otherCheck(class Player* other);
//Method that will ease the camera so that the player is in the center.
void setMyCamera();
//This method will reset the player to it's initial position.
//save: Boolean if the saved state should also be deleted.
void reset(bool save);
//Method used to retrieve the current location of the player.
//Returns: SDL_Rect containing the player's location.
SDL_Rect getBox();
//This method will
void shadowGiveState(class Shadow* shadow);
//Method that will save the current state.
//NOTE: The special <name>Saved variables will be used.
virtual void saveState();
//Method that will retrieve the last saved state.
//If there is none it will reset the player.
virtual void loadState();
//Method that checks if the player can save the state.
//Returns: True if the player can save his state.
virtual bool canSaveState();
//Method that checks if the player can load a state.
//Returns: True if the player can load a state.
virtual bool canLoadState();
//Method that will swap the state of the player with the other.
//other: The player or the shadow.
void swapState(Player* other);
//Check if this player is in fact the shadow.
//Returns: True if this is the shadow.
inline bool isShadow(){
return shadow;
}
//Let the player die when he falls of or hits spikes.
void die();
//Check if currently it's play from record file.
bool isPlayFromRecord();
//get the game record object.
std::vector<int>* getRecord();
+#ifdef RECORD_FILE_DEBUG
+ std::string& keyPressLog();
+ std::vector<SDL_Rect>& playerPosition();
+#endif
+
//play the record.
void playRecord();
private:
//The space key is down. call this function from handleInput and another function.
void spaceKeyDown(class Shadow* shadow);
};
#endif
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Jun 17, 1:33 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
72998
Default Alt Text
(63 KB)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline