1 /*
2  * Copyright (C) 2011-2013 Me and My Shadow
3  *
4  * This file is part of Me and My Shadow.
5  *
6  * Me and My Shadow is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Me and My Shadow is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Me and My Shadow.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "Block.h"
21 #include "Player.h"
22 #include "Game.h"
23 #include "Functions.h"
24 #include "FileManager.h"
25 #include "Globals.h"
26 #include "InputManager.h"
27 #include "SoundManager.h"
28 #include "StatisticsManager.h"
29 #include "MD5.h"
30 #include <iostream>
31 #include <SDL.h>
32 using namespace std;
33 
34 #ifdef RECORD_FILE_DEBUG
35 string recordKeyPressLog,recordKeyPressLog_saved;
36 vector<SDL_Rect> recordPlayerPosition,recordPlayerPosition_saved;
37 #endif
38 
39 //static internal array to store time of recent deaths for achievements
40 static Uint32 recentDeaths[10]={0};
41 static int loadAndDieTimes=0;
42 
43 //static internal function to add recent deaths and update achievements
addRecentDeaths(Uint32 recentLoad)44 static inline void addRecentDeaths(Uint32 recentLoad){
45 	//Get current time in ms.
46 	//We added it by 5 seconds to avoid bug if you choose a level to play
47 	//and die in 5 seconds after the game has startup.
48 	Uint32 t=SDL_GetTicks()+5000;
49 
50 	for(int i=9;i>0;i--){
51 		recentDeaths[i]=recentDeaths[i-1];
52 	}
53 	recentDeaths[0]=t;
54 
55 	//Update achievements
56 	if(recentDeaths[4]+5000>t){
57 		statsMgr.newAchievement("die5in5");
58 	}
59 	if(recentDeaths[9]+5000>t){
60 		statsMgr.newAchievement("die10in5");
61 	}
62 	if(recentLoad+1000>t){
63 		statsMgr.newAchievement("loadAndDie");
64 	}
65 }
66 
Player(Game * objParent)67 Player::Player(Game* objParent):xVelBase(0),yVelBase(0),objParent(objParent),recordSaved(false),
68 inAirSaved(false),isJumpSaved(false),canMoveSaved(false),holdingOtherSaved(false){
69 	//Set the dimensions of the player.
70 	//The size of the player is 21x40.
71 	box.x=0;
72 	box.y=0;
73 	box.w=23;
74 	box.h=40;
75 
76 	//Set his velocity to zero.
77 	xVel=0;
78 	yVel=0;
79 
80 	//Set the start position.
81 	fx=0;
82 	fy=0;
83 
84 	//Set some default values.
85 	inAir=true;
86 	isJump=false;
87 	shadowCall=false;
88 	shadow=false;
89 	canMove=true;
90 	holdingOther=false;
91 	dead=false;
92 	record=false;
93 	downKeyPressed=false;
94 	spaceKeyPressed=false;
95 
96 	recordIndex=-1;
97 #ifdef RECORD_FILE_DEBUG
98 	recordKeyPressLog.clear();
99 	recordKeyPressLog_saved.clear();
100 	recordPlayerPosition.clear();
101 	recordPlayerPosition_saved.clear();
102 #endif
103 	objNotificationBlock=objCurrentStandSave=objLastStandSave=NULL;
104 
105 	//Some default values for animation variables.
106 	direction=0;
107 	jumpTime=0;
108 
109 	state=stateSaved=0;
110 
111 	//xVelSaved is used to store if there's a state saved or not.
112 	xVelSaved=yVelSaved=0x80000000;
113 
114 	objCurrentStand=objLastStand=objLastTeleport=objShadowBlock=NULL;
115 }
116 
~Player()117 Player::~Player(){
118 	//Do nothing here
119 }
120 
isPlayFromRecord()121 bool Player::isPlayFromRecord(){
122 	return recordIndex>=0; // && recordIndex<(int)recordButton.size();
123 }
124 
125 //get the game record object.
getRecord()126 std::vector<int>* Player::getRecord(){
127 	return &recordButton;
128 }
129 
130 #ifdef RECORD_FILE_DEBUG
keyPressLog()131 string& Player::keyPressLog(){
132 	return recordKeyPressLog;
133 }
playerPosition()134 vector<SDL_Rect>& Player::playerPosition(){
135 	return recordPlayerPosition;
136 }
137 #endif
138 
139 //play the record.
playRecord()140 void Player::playRecord(){
141 	recordIndex=0;
142 }
143 
spaceKeyDown(class Shadow * shadow)144 void Player::spaceKeyDown(class Shadow* shadow){
145 	//Start recording or stop, depending on the recording state.
146 	if(record==false){
147 		//We start recording.
148 		if(shadow->called==true){
149 			//The shadow is still busy so first stop him before we can start recording.
150 			shadowCall=false;
151 			shadow->called=false;
152 			shadow->playerButton.clear();
153 		}else if(!dead){
154 			//Check if shadow is dead.
155 			if(shadow->dead){
156 				//Show tooltip.
157 				//Just reset the countdown (the shadow's jumptime).
158 				shadow->jumpTime=80;
159 
160 				//Play the error sound.
161 				getSoundManager()->playSound("error");
162 			}else{
163 				//The shadow isn't moving and both player and shadow aren't dead so start recording.
164 				record=true;
165 
166 				//We start a recording meaning we need to increase recordings by one.
167 				objParent->recordings++;
168 
169 				//Update statistics.
170 				if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
171 					statsMgr.recordTimes++;
172 
173 					if(statsMgr.recordTimes==100) statsMgr.newAchievement("record100");
174 					if(statsMgr.recordTimes==1000) statsMgr.newAchievement("record1k");
175 				}
176 			}
177 		}
178 	}else{
179 		//The player is recording so stop recording and call the shadow.
180 		record=false;
181 		shadowCall=true;
182 	}
183 }
184 
handleInput(class Shadow * shadow)185 void Player::handleInput(class Shadow* shadow){
186 	//Check if we should read the input from record file.
187 	//Actually, we read input from record file in
188 	//another function shadowSetState.
189 	bool readFromRecord=false;
190 	if(recordIndex>=0 && recordIndex<(int)recordButton.size()) readFromRecord=true;
191 
192 	if(!readFromRecord){
193 		//Reset horizontal velocity.
194 		xVel=0;
195 		if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
196 			//Walking to the right.
197 			xVel+=7;
198 		}
199 		if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
200 			//Walking to the left.
201 			if(xVel!=0 && !dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
202 				//Horizontal confusion achievement :)
203 				statsMgr.newAchievement("horizontal");
204 			}
205 			xVel-=7;
206 		}
207 
208 		//Check if the action key has been released.
209 		if(!inputMgr.isKeyDown(INPUTMGR_ACTION)){
210 			//It has so downKeyPressed can't be true.
211 			downKeyPressed=false;
212 		}
213 		/*
214 		//Don't reset spaceKeyPressed or when you press the space key
215 		//and release another key then the bug occurs. (ticket #44)
216 		if(event.type==SDL_KEYUP || !inputMgr.isKeyDown(INPUTMGR_SPACE)){
217 			spaceKeyPressed=false;
218 		}*/
219 	}
220 
221 	//Check if a key is pressed (down).
222 	if(inputMgr.isKeyDownEvent(INPUTMGR_JUMP) && !readFromRecord){
223 		//The up key, if we aren't in the air we start jumping.
224 		//Fixed a potential bug
225 		if(!inAir && !isJump){
226 #ifdef RECORD_FILE_DEBUG
227 			char c[64];
228 			sprintf(c,"[%05d] Jump key pressed\n",objParent->time);
229 			cout<<c;
230 			recordKeyPressLog+=c;
231 #endif
232 			isJump=true;
233 		}
234 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) && !readFromRecord){
235 		//Fixed a potential bug
236 		if(!spaceKeyPressed){
237 #ifdef RECORD_FILE_DEBUG
238 			char c[64];
239 			sprintf(c,"[%05d] Space key pressed\n",objParent->time);
240 			cout<<c;
241 			recordKeyPressLog+=c;
242 #endif
243 			spaceKeyDown(shadow);
244 			spaceKeyPressed=true;
245 		}
246 	}else if(inputMgr.isKeyUpEvent(INPUTMGR_SPACE) && !readFromRecord){
247 		if(record && getSettings()->getBoolValue("quickrecord")){
248 			spaceKeyDown(shadow);
249 			spaceKeyPressed=true;
250 		}
251 	}else if(record && !readFromRecord && inputMgr.isKeyDownEvent(INPUTMGR_CANCELRECORDING)){
252 		//Cancel current recording
253 
254 		//Search the recorded button and clear the last space key press
255 		int i=recordButton.size()-1;
256 		for(;i>=0;i--){
257 			if(recordButton[i] & PlayerButtonSpace){
258 				recordButton[i] &= ~PlayerButtonSpace;
259 				break;
260 			}
261 		}
262 
263 		if(i>=0){
264 			//Clear the recording at the player's side.
265 			playerButton.clear();
266 			line.clear();
267 
268 			//reset the record flag
269 			record=false;
270 
271 			//decrese the record count
272 			objParent->recordings--;
273 		}else{
274 			cout<<"Failed to find last recording"<<endl;
275 		}
276 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_ACTION)){
277 		//Downkey is pressed.
278 		//Fixed a potential bug
279 		if(!downKeyPressed){
280 #ifdef RECORD_FILE_DEBUG
281 			char c[64];
282 			sprintf(c,"[%05d] Action key pressed\n",objParent->time);
283 			cout<<c;
284 			recordKeyPressLog+=c;
285 #endif
286 			downKeyPressed=true;
287 		}
288 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_SAVE)){
289 		//F2 only works in the level editor.
290 		if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
291 			//Save the state. (delayed)
292 			if (objParent && !objParent->player.isPlayFromRecord() && !objParent->interlevel)
293 				objParent->saveStateNextTime=true;
294 		}
295 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_LOAD) && (!readFromRecord || objParent->interlevel)){
296 		//F3 is used to load the last state.
297 		if (objParent && canLoadState()) {
298 			recordIndex = -1;
299 			objParent->loadStateNextTime = true;
300 
301 			//Also delete any gui (most likely the interlevel gui). Only in game mode.
302 			if (GUIObjectRoot && stateID != STATE_LEVEL_EDITOR){
303 				delete GUIObjectRoot;
304 				GUIObjectRoot = NULL;
305 			}
306 
307 			//And set interlevel to false.
308 			objParent->interlevel = false;
309 		}
310 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_SWAP)){
311 		//F4 will swap the player and the shadow, but only in the level editor.
312 		if(!(dead || shadow->dead) && stateID==STATE_LEVEL_EDITOR){
313 			swapState(shadow);
314 		}
315 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_TELEPORT)){
316 		//F5 will revive and teleoprt the player to the cursor. Only works in the level editor.
317 		//Shift+F5 teleports the shadow.
318 		if(stateID==STATE_LEVEL_EDITOR){
319 			//get the position of the cursor.
320 			int x,y;
321 			SDL_GetMouseState(&x,&y);
322 			x+=camera.x;
323 			y+=camera.y;
324 
325 			if(inputMgr.isKeyDown(INPUTMGR_SHIFT)){
326 				//teleports the shadow.
327 				shadow->dead=false;
328 				shadow->box.x=x;
329 				shadow->box.y=y;
330 			}else{
331 				//teleports the player.
332 				dead=false;
333 				box.x=x;
334 				box.y=y;
335 			}
336 
337 			//play sound?
338 			getSoundManager()->playSound("swap");
339 		}
340 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_SUICIDE)){
341 		//F12 is suicide and only works in the leveleditor.
342 		if(stateID==STATE_LEVEL_EDITOR){
343 			die();
344 			shadow->die();
345 		}
346 	}
347 }
348 
setLocation(int x,int y)349 void Player::setLocation(int x,int y){
350 	box.x=x;
351 	box.y=y;
352 }
353 
move(vector<Block * > & levelObjects,int lastX,int lastY)354 void Player::move(vector<Block*> &levelObjects,int lastX,int lastY){
355 	//Only move when the player isn't dead.
356 	//Fixed the bug that player/shadow can teleport or pull the switch even if died.
357 	//FIXME: Don't know if there will be any side-effects.
358 	if(dead) return;
359 
360 	//Pointer to a checkpoint.
361 	Block* objCheckPoint=NULL;
362 	//Pointer to a swap.
363 	Block* objSwap=NULL;
364 
365 	//Set the objShadowBlock to NULL.
366 	//Only for swapping to prevent the shadow from swapping in a shadow block.
367 	objShadowBlock=NULL;
368 
369 	//Set the objNotificationBlock to NULL.
370 	objNotificationBlock=NULL;
371 
372 	//NOTE: to fix bugs regarding player/shadow swap, we should first process collision of player/shadow
373 	//then move them. The code is moved to Game::logic().
374 
375 	/*//Store the location.
376 	int lastX=box.x;
377 	int lastY=box.y;
378 
379 	collision(levelObjects);*/
380 
381 	bool canTeleport=true;
382 	bool isTraveling=true;
383 
384 	// for checking the achievenemt that player and shadow come to exit simultaneously.
385 	bool weWon = false;
386 
387 	//Now check the functional blocks.
388 	for(unsigned int o=0;o<levelObjects.size();o++){
389 		//Skip block which is not visible.
390 		if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000) continue;
391 
392 		//Check for collision.
393 		if(checkCollision(box,levelObjects[o]->getBox())){
394 			//Now switch the type.
395 			switch(levelObjects[o]->type){
396 				case TYPE_CHECKPOINT:
397 				{
398 					//If we're not the shadow set the gameTip to Checkpoint.
399 					if(!shadow && objParent!=NULL)
400 						objParent->gameTipIndex=TYPE_CHECKPOINT;
401 
402 					//And let objCheckPoint point to this object.
403 					objCheckPoint=levelObjects[o];
404 					break;
405 				}
406 				case TYPE_SWAP:
407 				{
408 					//If we're not the shadow set the gameTip to swap.
409 					if(!shadow && objParent!=NULL)
410 						objParent->gameTipIndex=TYPE_SWAP;
411 
412 					//And let objSwap point to this object.
413 					objSwap=levelObjects[o];
414 					break;
415 				}
416 				case TYPE_EXIT:
417 				{
418 					//Make sure we're not in the leveleditor.
419 					if(stateID==STATE_LEVEL_EDITOR)
420 						break;
421 
422 					//Check to see if we have enough keys to finish the level
423 					if(objParent->currentCollectables>=objParent->totalCollectables){
424 						//Update achievements
425 						if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
426 							if(objParent->player.dead || objParent->shadow.dead){
427 								//Finish the level with player or shadow died.
428 								statsMgr.newAchievement("forget");
429 							}
430 							if(objParent->won && !weWon){ // This checks if somebody already hit the exit but we haven't hit the exit yet.
431 								//Player and shadow come to exit simultaneously.
432 								statsMgr.newAchievement("jit");
433 							}
434 						}
435 
436 						//We can't just handle the winning here (in the middle of the update cycle)/
437 						//So set won in Game true.
438 						objParent->won=true;
439 
440 						//We hit the exit.
441 						weWon = true;
442 					}
443 					break;
444 				}
445 				case TYPE_PORTAL:
446 				{
447 					//Check if the teleport id isn't empty.
448 					if(levelObjects[o]->id.empty()){
449                         std::cerr<<"WARNING: Invalid teleport id!"<<std::endl;
450 						canTeleport=false;
451 					}
452 
453 					//If we're not the shadow set the gameTip to portal.
454 					if(!shadow && objParent!=NULL)
455 						objParent->gameTipIndex=TYPE_PORTAL;
456 
457 					//Check if we can teleport and should (downkey -or- auto).
458 					if(canTeleport && (downKeyPressed || (levelObjects[o]->queryProperties(GameObjectProperty_Flags,this)&1))){
459 						canTeleport=false;
460 						if(downKeyPressed || levelObjects[o]!=objLastTeleport){
461 							//Loop the levelobjects again to find the destination.
462 							for(unsigned int oo=o+1;;){
463 								//We started at our index+1.
464 								//Meaning that if we reach the end of the vector then we need to start at the beginning.
465 								if(oo>=levelObjects.size())
466 									oo-=(int)levelObjects.size();
467 								//It also means that if we reach the same index we need to stop.
468 								//If the for loop breaks this way then we have no succes.
469 								if(oo==o){
470 									//Couldn't teleport. We play the error sound only when the down key pressed.
471 									if (downKeyPressed) {
472 										getSoundManager()->playSound("error");
473 									}
474 									break;
475 								}
476 
477 								//Check if the second (oo) object is a portal and is visible.
478 								if (levelObjects[oo]->type == TYPE_PORTAL && (levelObjects[oo]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000) == 0){
479 									//Check the id against the destination of the first portal.
480 									if(levelObjects[o]->destination==levelObjects[oo]->id){
481 										//Get the destination location.
482 										SDL_Rect r = levelObjects[oo]->getBox();
483 										r.x += 5;
484 										r.y += 2;
485 										r.w = box.w;
486 										r.h = box.h;
487 
488 										//Check if the destination location is blocked.
489 										bool blocked = false;
490 										for (auto ooo : levelObjects){
491 											//Make sure to only check visible blocks.
492 											if (ooo->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
493 												continue;
494 											//Make sure the object is solid for the player.
495 											if (!ooo->queryProperties(GameObjectProperty_PlayerCanWalkOn, this))
496 												continue;
497 
498 											//Check for collision.
499 											if (checkCollision(r, ooo->getBox())) {
500 												blocked = true;
501 												break;
502 											}
503 										}
504 
505 										//Teleport only if the destination is not blocked.
506 										if (!blocked) {
507 											//Call the event.
508 											objParent->broadcastObjectEvent(GameObjectEvent_OnToggle, -1, NULL, levelObjects[o]);
509 											objLastTeleport = levelObjects[oo];
510 
511 											//Teleport the player.
512 											box.x = r.x;
513 											box.y = r.y;
514 
515 											//We don't count it to traveling distance.
516 											isTraveling = false;
517 
518 											//Play the swap sound.
519 											getSoundManager()->playSound("swap");
520 											break;
521 										}
522 									}
523 								}
524 
525 								//Increase oo.
526 								oo++;
527 							}
528 
529 							//Reset the down key pressed.
530 							downKeyPressed = false;
531 						}
532 					}
533 					break;
534 				}
535 				case TYPE_SWITCH:
536 				{
537 					//If we're not the shadow set the gameTip to switch.
538 					if(!shadow && objParent!=NULL)
539 						objParent->gameTipIndex=TYPE_SWITCH;
540 
541 					//If the down key is pressed then invoke an event.
542 					if(downKeyPressed){
543 						//Play the animation.
544 						levelObjects[o]->playAnimation();
545 
546 						//Play the toggle sound.
547 						getSoundManager()->playSound("toggle");
548 
549 						//Update statistics.
550 						if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
551 							statsMgr.switchTimes++;
552 
553 							//Update achievements
554 							switch(statsMgr.switchTimes){
555 							case 100:
556 								statsMgr.newAchievement("switch100");
557 								break;
558 							case 1000:
559 								statsMgr.newAchievement("switch1k");
560 								break;
561 							}
562 						}
563 
564 						levelObjects[o]->onEvent(GameObjectEvent_OnPlayerInteraction);
565 					}
566 					break;
567 				}
568 				case TYPE_SHADOW_BLOCK:
569 				case TYPE_MOVING_SHADOW_BLOCK:
570 				{
571 					//This only applies to the player.
572 					if(!shadow)
573 						objShadowBlock=levelObjects[o];
574 					break;
575 				}
576 				case TYPE_NOTIFICATION_BLOCK:
577 				{
578 					//This only applies to the player.
579 					if(!shadow)
580 						objNotificationBlock=levelObjects[o];
581 					break;
582 				}
583 				case TYPE_COLLECTABLE:
584 				{
585 					//Check if collectable is active (if it's not it's equal to 1(inactive))
586 					if((levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x1) == 0) {
587 						//Toggle an event
588 						objParent->broadcastObjectEvent(GameObjectEvent_OnToggle,-1,NULL,levelObjects[o]);
589 						//Increase the current number of collectables
590 						objParent->currentCollectables++;
591 						getSoundManager()->playSound("collect");
592 						//Open exit(s)
593 						if(objParent->currentCollectables>=objParent->totalCollectables){
594 							for(unsigned int i=0;i<levelObjects.size();i++){
595 								if(levelObjects[i]->type==TYPE_EXIT){
596 									objParent->broadcastObjectEvent(GameObjectEvent_OnSwitchOn,-1,NULL,levelObjects[i]);
597 								}
598 							}
599 						}
600 					}
601 					break;
602 				}
603 			}
604 
605 			//Now check for the spike property.
606 			if(levelObjects[o]->queryProperties(GameObjectProperty_IsSpikes,this)){
607 				//It is so get the collision box.
608 				SDL_Rect r=levelObjects[o]->getBox();
609 
610 				//TODO: pixel-accuracy hit test.
611 				//For now we shrink the box.
612 				r.x+=2;
613 				r.y+=2;
614 				r.w-=4;
615 				r.h-=4;
616 
617 				//Check collision, if the player collides then let him die.
618 				if(checkCollision(box,r)){
619 						die();
620 				}
621 			}
622 		}
623 	}
624 
625 	//Check if the player can teleport.
626 	if(canTeleport)
627 		objLastTeleport=NULL;
628 
629 	//Check the checkpoint pointer only if the downkey is pressed.
630 	//new: don't save the game if playing game record
631 	if (objParent != NULL && downKeyPressed && objCheckPoint != NULL && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
632 		//Checkpoint thus save the state.
633 		if(objParent->canSaveState()){
634 			objParent->saveStateNextTime=true;
635 			objParent->objLastCheckPoint=objCheckPoint;
636 		}
637 	}
638 	//Check the swap pointer only if the down key is pressed.
639 	if(objSwap!=NULL && downKeyPressed && objParent!=NULL){
640 		//Now check if the shadow we're the shadow or not.
641 		if(shadow){
642 			if(!(dead || objParent->player.dead)){
643 				//Check if the player isn't in front of a shadow block.
644 				if(!objParent->player.objShadowBlock){
645 					objParent->player.swapState(this);
646 					objSwap->playAnimation();
647 					//We don't count it to traveling distance.
648 					isTraveling=false;
649 					//NOTE: Statistics updated in swapState() function.
650 				}else{
651 					//We can't swap so play the error sound.
652 					getSoundManager()->playSound("error");
653 				}
654 			}
655 		}else{
656 			if(!(dead || objParent->shadow.dead)){
657 				//Check if the player isn't in front of a shadow block.
658 				if(!objShadowBlock){
659 					swapState(&objParent->shadow);
660 					objSwap->playAnimation();
661 					//We don't count it to traveling distance.
662 					isTraveling=false;
663 					//NOTE: Statistics updated in swapState() function.
664 				}else{
665 					//We can't swap so play the error sound.
666 					getSoundManager()->playSound("error");
667 				}
668 			}
669 		}
670 	}
671 
672 	//Determine the correct theme state.
673 	if(!dead){
674 		//Set the direction depending on the velocity.
675 		if(xVel>0)
676 			direction=0;
677 		else if(xVel<0)
678 			direction=1;
679 
680 		//Check if the player is in the air.
681 		if(!inAir){
682 			//On the ground so check the direction and movement.
683 			if(xVel>0){
684 				if(appearance.currentStateName!="walkright"){
685 					appearance.changeState("walkright");
686 				}
687 			}else if(xVel<0){
688 				if(appearance.currentStateName!="walkleft"){
689 					appearance.changeState("walkleft");
690 				}
691 			}else if(xVel==0){
692 				if(direction==1){
693 					appearance.changeState("standleft");
694 				}else{
695 					appearance.changeState("standright");
696 				}
697 			}
698 		}else{
699 			//Check for jump appearance (inAir).
700 			if(direction==1){
701 				if(yVel>0){
702 					if(appearance.currentStateName!="fallleft")
703 						appearance.changeState("fallleft");
704 				}else{
705 					if(appearance.currentStateName!="jumpleft")
706 						appearance.changeState("jumpleft");
707 				}
708 			}else{
709 				if(yVel>0){
710 					if(appearance.currentStateName!="fallright")
711 						appearance.changeState("fallright");
712 				}else{
713 					if(appearance.currentStateName!="jumpright")
714 						appearance.changeState("jumpright");
715 				}
716 			}
717 		}
718 	}
719 
720 
721 	//Update traveling distance statistics.
722 	if(isTraveling && (lastX!=box.x || lastY!=box.y) && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
723 		float dx=float(lastX-box.x),dy=float(lastY-box.y);
724 		float d0=statsMgr.playerTravelingDistance+statsMgr.shadowTravelingDistance,
725 			d=sqrtf(dx*dx+dy*dy)/50.0f;
726 		if(shadow) statsMgr.shadowTravelingDistance+=d;
727 		else statsMgr.playerTravelingDistance+=d;
728 
729 		//Update achievement
730 		d+=d0;
731 		if(d0<=100.0f && d>=100.0f) statsMgr.newAchievement("travel100");
732 		if(d0<=1000.0f && d>=1000.0f) statsMgr.newAchievement("travel1k");
733 		if(d0<=10000.0f && d>=10000.0f) statsMgr.newAchievement("travel10k");
734 		if(d0<=42195.0f && d>=42195.0f) statsMgr.newAchievement("travel42k");
735 	}
736 
737 	//Reset the downKeyPressed flag.
738 	downKeyPressed=false;
739 }
740 
collision(vector<Block * > & levelObjects,Player * other)741 void Player::collision(vector<Block*> &levelObjects, Player* other){
742 	//Only move when the player isn't dead.
743 	if(dead)
744 		return;
745 
746 	//First sort out the velocity.
747 
748 	//NOTE: This is the temporary xVel which takes canMove into consideration.
749 	//This shadows Player::xVel.
750 	const int xVel = canMove ? this->xVel : 0;
751 
752 	//Add gravity acceleration to the vertical velocity.
753 	if(isJump)
754 		jump();
755 	if(inAir==true){
756 		yVel+=1;
757 
758 		//Cap fall speed to 13.
759 		if(yVel>13)
760 			yVel=13;
761 	}
762 
763 	Block* baseBlock=NULL;
764 	if(objCurrentStand != NULL) {
765 		baseBlock=objCurrentStand;
766 	} else if(other && other->holdingOther) {
767 		//NOTE: this actually CAN happen, e.g. when player is holding shadow and the player is going to jump
768 		//assert(other->objCurrentStand != NULL);
769 		baseBlock=other->objCurrentStand;
770 	}
771 	if(baseBlock!=NULL){
772 		//Now get the velocity and delta of the object the player is standing on.
773 		SDL_Rect v=baseBlock->getBox(BoxType_Velocity);
774 		SDL_Rect delta=baseBlock->getBox(BoxType_Delta);
775 
776 		switch(baseBlock->type){
777 		//For conveyor belts the velocity is transfered.
778 		case TYPE_CONVEYOR_BELT:
779 		case TYPE_SHADOW_CONVEYOR_BELT:
780 			xVelBase=v.x;
781 			break;
782 		//In other cases, such as player on shadow, player on crate. the change in x position must be considered.
783 		default:
784 			{
785 				if(delta.x != 0)
786 					xVelBase+=delta.x;
787 			}
788 			break;
789 		}
790 		//NOTE: Only copy the velocity of the block when moving down.
791 		//Upwards is automatically resolved before the player is moved.
792 		if(delta.y>0){
793 			//Fixes the jitters when the player is on a pushable block on a downward moving box.
794 			//NEW FIX: the squash bug. The following line of code is commented and change 'v' to 'delta'.
795 			//box.y+=delta.y;
796 			yVelBase=delta.y;
797 		}
798 		else
799 			yVelBase=0;
800 	}
801 
802 	//Set the object the player is currently standing to NULL.
803 	objCurrentStand=NULL;
804 
805 	//Store the location of the player.
806 	int lastX=box.x;
807 	int lastY=box.y;
808 
809 	//An array that will hold all the GameObjects that are involved in the collision/movement.
810 	vector<Block*> objects;
811 	//All the blocks have moved so if there's collision with the player, the block moved into him.
812 	for(unsigned int o=0;o<levelObjects.size();o++){
813 		//Make sure to only check visible blocks.
814 		if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
815 			continue;
816 		//Make sure the object is solid for the player.
817 		if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
818 			continue;
819 
820 		//Check for collision.
821 		if(checkCollision(box,levelObjects[o]->getBox()))
822 			objects.push_back(levelObjects[o]);
823 	}
824 	//There was collision so try to resolve it.
825 	if(!objects.empty()){
826 		//FIXME: When multiple moving blocks are overlapping the player can be "bounced" off depending on the block order.
827 		for(unsigned int o=0;o<objects.size();o++){
828 			SDL_Rect r=objects[o]->getBox();
829 			SDL_Rect delta=objects[o]->getBox(BoxType_Delta);
830 
831 			//Check on which side of the box the player is.
832 			if(delta.x!=0){
833 				if(delta.x>0){
834 					//Move the player right if necessary.
835 					if((r.x+r.w)-box.x<=delta.x && box.x<r.x+r.w)
836 						box.x=r.x+r.w;
837 				}else{
838 					//Move the player left if necessary.
839 					if((box.x+box.w)-r.x<=-delta.x && box.x>r.x-box.w)
840 						box.x=r.x-box.w;
841 				}
842 			}
843 			if(delta.y!=0){
844 				if(delta.y>0){
845 					//Move the player down if necessary.
846 					if((r.y+r.h)-box.y<=delta.y && box.y<r.y+r.h)
847 						box.y=r.y+r.h;
848 				}else{
849 					//Move the player up if necessary.
850 					if((box.y+box.h)-r.y<=-delta.y && box.y>r.y-box.h)
851 						box.y=r.y-box.h;
852 				}
853 			}
854 		}
855 
856 		//Check if the player is squashed.
857 		for(unsigned int o=0;o<levelObjects.size();o++){
858 			//Make sure the object is visible.
859 			if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
860 				continue;
861 			//Make sure the object is solid for the player.
862 			if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
863 				continue;
864 
865 			if(checkCollision(box,levelObjects[o]->getBox())){
866 				//The player is squashed so first move him back.
867 				box.x=lastX;
868 				box.y=lastY;
869 
870 				//Update statistics.
871 				if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
872 					if(shadow) statsMgr.shadowSquashed++;
873 					else statsMgr.playerSquashed++;
874 
875 					switch(statsMgr.playerSquashed+statsMgr.shadowSquashed){
876 						case 1:
877 							statsMgr.newAchievement("squash1");
878 							break;
879 						case 50:
880 							statsMgr.newAchievement("squash50");
881 							break;
882 						}
883 				}
884 
885 				//Now call the die method.
886 				die();
887 				return;
888 			}
889 		}
890 	}
891 
892 	//Reuse the objects array, this time for blocks the player walks into.
893 	objects.clear();
894 	//Determine the collision frame.
895 	SDL_Rect frame={box.x,box.y,box.w,box.h};
896 	//Keep the horizontal movement of the player in mind.
897 	if(xVel+xVelBase>=0){
898 		frame.w+=(xVel+xVelBase);
899 	}else{
900 		frame.x+=(xVel+xVelBase);
901 		frame.w-=(xVel+xVelBase);
902 	}
903 	//And the vertical movement.
904 	if(yVel+yVelBase>=0){
905 		frame.h+=(yVel+yVelBase);
906 	}else{
907 		frame.y+=(yVel+yVelBase);
908 		frame.h-=(yVel+yVelBase);
909 	}
910 	//Loop through the game objects.
911 	for(unsigned int o=0; o<levelObjects.size(); o++){
912 		//Make sure the block is visible.
913 		if (levelObjects[o]->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
914 			continue;
915 		//Check if the player can collide with this game object.
916 		if(!levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,this))
917 			continue;
918 
919 		//Check if the block is inside the frame.
920 		if(checkCollision(frame,levelObjects[o]->getBox()))
921 			objects.push_back(levelObjects[o]);
922 	}
923 	//Horizontal pass.
924 	if(xVel+xVelBase!=0){
925 		box.x+=xVel+xVelBase;
926 		for(unsigned int o=0;o<objects.size();o++){
927 			SDL_Rect r=objects[o]->getBox();
928 			if(!checkCollision(box,r))
929 				continue;
930 
931 			//In case of a pushable block we give it velocity.
932 			if(objects[o]->type==TYPE_PUSHABLE){
933 				objects[o]->xVel+=(xVel+xVelBase)/2;
934 			}
935 
936 			if(xVel+xVelBase>0){
937 				//We came from the left so the right edge of the player must be less or equal than xVel+xVelBase.
938 				if((box.x+box.w)-r.x<=xVel+xVelBase)
939 					box.x=r.x-box.w;
940 			}else{
941 				//We came from the right so the left edge of the player must be greater or equal than xVel+xVelBase.
942 				if(box.x-(r.x+r.w)>=xVel+xVelBase)
943 					box.x=r.x+r.w;
944 			}
945 		}
946 	}
947 	//Some variables that are used in vertical movement.
948 	Block* lastStand=NULL;
949 	inAir=true;
950 
951 	//Vertical pass.
952 	if(yVel+yVelBase!=0){
953 		box.y+=yVel+yVelBase;
954 
955 		//Value containing the previous 'depth' of the collision.
956 		int prevDepth=0;
957 
958 		for(unsigned int o=0;o<objects.size();o++){
959 			SDL_Rect r=objects[o]->getBox();
960 			if(!checkCollision(box,r))
961 				continue;
962 
963 			//Now check how we entered the block (vertically or horizontally).
964 			if(yVel+yVelBase>0){
965 				//Calculate the number of pixels the player is in the block (vertically).
966 				int depth=(box.y+box.h)-r.y;
967 
968 				//We came from the top so the bottom edge of the player must be less or equal than yVel+yVelBase.
969 				if(depth<=yVel+yVelBase){
970 					//NOTE: lastStand is handled later since the player can stand on only one block at the time.
971 
972 					//Check if there's already a lastStand.
973 					if(lastStand){
974 						//Since the player fell he will stand on the highest block, meaning the highest 'depth'.
975 						if(depth>prevDepth){
976 							lastStand=objects[o];
977 							prevDepth=depth;
978 						}else if(depth==prevDepth){
979 							//Both blocks are at the same height so determine the block by the amount the player is standing on them.
980 							SDL_Rect r=objects[o]->getBox();
981 							int w=0;
982 							if(box.x+box.w>r.x+r.w)
983 								w=(r.x+r.w)-box.x;
984 							else
985 								w=(box.x+box.w)-r.x;
986 
987 							//Do the same for the other box.
988 							r=lastStand->getBox();
989 							int w2=0;
990 							if(box.x+box.w>r.x+r.w)
991 								w2=(r.x+r.w)-box.x;
992 							else
993 								w2=(box.x+box.w)-r.x;
994 
995 							//NOTE: It doesn't matter which block the player is on if they are both stationary.
996 							SDL_Rect v=objects[o]->getBox(BoxType_Velocity);
997 							SDL_Rect v2=lastStand->getBox(BoxType_Velocity);
998 
999 							//Either the have the same (vertical) velocity so most pixel standing on is the lastStand...
1000 							// ... OR one is moving slower down/faster up and that's the one the player is standing on.
1001 							if((v.y==v2.y && w>w2) || v.y<v2.y){
1002 								lastStand=objects[o];
1003 								prevDepth=depth;
1004 							}
1005 						}
1006 					}else{
1007 						//There isn't one so assume the current block for now.
1008 						lastStand=objects[o];
1009 						prevDepth=depth;
1010 					}
1011 				}
1012 			}else{
1013 				//We came from the bottom so the upper edge of the player must be greater or equal than yVel+yVelBase.
1014 				if(box.y-(r.y+r.h)>=yVel+yVelBase){
1015 					box.y=r.y+r.h;
1016 					yVel=0;
1017 				}
1018 			}
1019 		}
1020 	}
1021 	if(lastStand){
1022 		inAir=false;
1023 		yVel=1;
1024 		SDL_Rect r=lastStand->getBox();
1025 		box.y=r.y-box.h;
1026 	}
1027 
1028 	//Check if the player fell of the level, if so let him die but without animation.
1029 	if(box.y>LEVEL_HEIGHT)
1030 		die(false);
1031 
1032 	//Check if the player changed blocks, meaning stepped onto a block.
1033 	objCurrentStand=lastStand;
1034 	if(lastStand!=objLastStand){
1035 		//The player has changed block so call the playerleave event.
1036 		if(objLastStand)
1037 			objParent->broadcastObjectEvent(GameObjectEvent_PlayerLeave,-1,NULL,objLastStand);
1038 
1039 		//Set the new lastStand.
1040 		objLastStand=lastStand;
1041 		if(lastStand){
1042 			//NOTE: We partially revert this piece of code to that in commit 0072762,
1043 			//i.e. change the event GameObjectEvent_PlayerWalkOn from asynchronous back to synchronous,
1044 			//to fix the fragile block hit test bug when it is breaking.
1045 			//Hopefully it will not introduce bugs (e.g. bugs regarding dynamic add/delete of objects).
1046 
1047 			if (lastStand->type == TYPE_FRAGILE) {
1048 				//Call the walk on event of the laststand in a synchronous way.
1049 				lastStand->onEvent(GameObjectEvent_PlayerWalkOn);
1050 
1051 				//Bugfix for Fragile blocks.
1052 				if (!lastStand->queryProperties(GameObjectProperty_PlayerCanWalkOn, this)) {
1053 					inAir = true;
1054 					isJump = false;
1055 				}
1056 			} else {
1057 				//Call the walk on event of the laststand in an asynchronous way.
1058 				objParent->broadcastObjectEvent(GameObjectEvent_PlayerWalkOn, -1, NULL, lastStand);
1059 			}
1060 		}
1061 	}
1062 	//NOTE: The PlayerIsOn event must be handled here so that the script can change the location of a block without interfering with the collision detection.
1063 	//Handlingin it here also guarantees that this event will only be called once for one block per update.
1064 	if(objCurrentStand)
1065 		objParent->broadcastObjectEvent(GameObjectEvent_PlayerIsOn,-1,NULL,objCurrentStand);
1066 
1067 	//Reset the base velocity.
1068 	xVelBase=yVelBase=0;
1069 	canMove=true;
1070 }
1071 
jump(int strength)1072 void Player::jump(int strength){
1073 	//Check if the player can jump.
1074 	if(inAir==false){
1075 		//Set the jump velocity.
1076 		yVel=-strength;
1077 		inAir=true;
1078 		isJump=false;
1079 		jumpTime++;
1080 
1081 		//Update statistics
1082 		if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
1083 			if(shadow) statsMgr.shadowJumps++;
1084 			else statsMgr.playerJumps++;
1085 
1086 			if(statsMgr.playerJumps+statsMgr.shadowJumps==1000) statsMgr.newAchievement("frog");
1087 		}
1088 
1089 		//Check if sound is enabled, if so play the jump sound.
1090 		getSoundManager()->playSound("jump");
1091 	}
1092 }
1093 
show(SDL_Renderer & renderer)1094 void Player::show(SDL_Renderer& renderer){
1095 	//Check if we should render the recorded line.
1096 	//Only do this when we're recording and we're not the shadow.
1097 	if(shadow==false && record==true){
1098 		//FIXME: Adding an entry not in update but in render?
1099 		line.push_back(SDL_Rect());
1100 		line[line.size()-1].x=box.x+11;
1101 		line[line.size()-1].y=box.y+20;
1102 
1103 		//Loop through the line dots and draw them.
1104 		for(int l=0; l<(signed)line.size(); l++){
1105             appearance.drawState("line",renderer,line[l].x-camera.x,line[l].y-camera.y);
1106 		}
1107 	}
1108 
1109 	//NOTE: We do logic here, because it's only needed by the appearance.
1110 	appearance.updateAnimation();
1111     appearance.draw(renderer, box.x-camera.x, box.y-camera.y);
1112 }
1113 
shadowSetState()1114 void Player::shadowSetState(){
1115 	int currentKey=0;
1116 
1117 	/*//debug
1118 	extern int block_test_count;
1119 	extern bool block_test_only;
1120 	if(SDL_GetKeyState(NULL)[SDLK_p]){
1121 		block_test_count=recordButton.size();
1122 	}
1123 	if(block_test_count==(int)recordButton.size()){
1124 		block_test_only=true;
1125 	}*/
1126 
1127 	//Check if we should read the input from record file.
1128 	if(recordIndex>=0){ // && recordIndex<(int)recordButton.size()){
1129 		//read the input from record file
1130 		if(recordIndex<(int)recordButton.size()){
1131 			currentKey=recordButton[recordIndex];
1132 			recordIndex++;
1133 		}
1134 
1135 		//Reset horizontal velocity.
1136 		xVel=0;
1137 		if(currentKey&PlayerButtonRight){
1138 			//Walking to the right.
1139 			xVel+=7;
1140 		}
1141 		if(currentKey&PlayerButtonLeft){
1142 			//Walking to the left.
1143 			xVel-=7;
1144 		}
1145 
1146 		if(currentKey&PlayerButtonJump){
1147 			//The up key, if we aren't in the air we start jumping.
1148 			if(inAir==false){
1149 				isJump=true;
1150 			}else{
1151 				//Shouldn't go here
1152 				cout<<"Replay BUG"<<endl;
1153 			}
1154 		}
1155 
1156 		//check the down key
1157 		downKeyPressed=(currentKey&PlayerButtonDown)!=0;
1158 
1159 		//check the space key
1160 		if(currentKey&PlayerButtonSpace){
1161 			spaceKeyDown(&objParent->shadow);
1162 		}
1163 	}else{
1164 		//read the input from keyboard.
1165 		recordIndex=-1;
1166 
1167 		//Check for xvelocity.
1168 		if(xVel>0)
1169 			currentKey|=PlayerButtonRight;
1170 		if(xVel<0)
1171 			currentKey|=PlayerButtonLeft;
1172 
1173 		//Check for jumping.
1174 		if(isJump){
1175 #ifdef RECORD_FILE_DEBUG
1176 			char c[64];
1177 			sprintf(c,"[%05d] Jump key recorded\n",objParent->time-1);
1178 			cout<<c;
1179 			recordKeyPressLog+=c;
1180 #endif
1181 			currentKey|=PlayerButtonJump;
1182 		}
1183 
1184 		//Check if the downbutton is pressed.
1185 		if(downKeyPressed){
1186 #ifdef RECORD_FILE_DEBUG
1187 			char c[64];
1188 			sprintf(c,"[%05d] Action key recorded\n",objParent->time-1);
1189 			cout<<c;
1190 			recordKeyPressLog+=c;
1191 #endif
1192 			currentKey|=PlayerButtonDown;
1193 		}
1194 
1195 		if(spaceKeyPressed){
1196 #ifdef RECORD_FILE_DEBUG
1197 			char c[64];
1198 			sprintf(c,"[%05d] Space key recorded\n",objParent->time-1);
1199 			cout<<c;
1200 			recordKeyPressLog+=c;
1201 #endif
1202 			currentKey|=PlayerButtonSpace;
1203 		}
1204 
1205 		//Record it.
1206 		recordButton.push_back(currentKey);
1207 	}
1208 
1209 #ifdef RECORD_FILE_DEBUG
1210 	if(recordIndex>=0){
1211 		if(recordIndex>0 && recordIndex<=int(recordPlayerPosition.size())/2){
1212 			SDL_Rect &r1=recordPlayerPosition[recordIndex*2-2];
1213 			SDL_Rect &r2=recordPlayerPosition[recordIndex*2-1];
1214 			if(r1.x!=box.x || r1.y!=box.y || r2.x!=objParent->shadow.box.x || r2.y!=objParent->shadow.box.y){
1215 				char c[192];
1216 				sprintf(c,"Replay ERROR [%05d] %d %d %d %d Expected: %d %d %d %d\n",
1217 					objParent->time-1,box.x,box.y,objParent->shadow.box.x,objParent->shadow.box.y,r1.x,r1.y,r2.x,r2.y);
1218 				cout<<c;
1219 			}
1220 		}
1221 	}else{
1222 		recordPlayerPosition.push_back(box);
1223 		recordPlayerPosition.push_back(objParent->shadow.box);
1224 	}
1225 #endif
1226 
1227 	//reset spaceKeyPressed.
1228 	spaceKeyPressed=false;
1229 
1230 	//Only add an entry if the player is recording.
1231 	if(record){
1232 		//Add the action.
1233 		if(!dead && !objParent->shadow.dead){
1234 			playerButton.push_back(currentKey);
1235 
1236 			//Change the state.
1237 			state++;
1238 		}else{
1239 			//Either player or shadow is dead, stop recording.
1240 			playerButton.clear();
1241 			state=0;
1242 			record=false;
1243 		}
1244 	}
1245 }
1246 
shadowGiveState(Shadow * shadow)1247 void Player::shadowGiveState(Shadow* shadow){
1248 	//Check if the player calls the shadow.
1249 	if(shadowCall==true){
1250 		//Clear any recording still with the shadow.
1251 		shadow->playerButton.clear();
1252 
1253 		//Loop the recorded moves and add them to the one of the shadow.
1254 		for(unsigned int s=0;s<playerButton.size();s++){
1255 			shadow->playerButton.push_back(playerButton[s]);
1256 		}
1257 
1258 		//Reset the state of both the player and the shadow.
1259 		stateReset();
1260 		shadow->stateReset();
1261 
1262 		//Clear the recording at the player's side.
1263 		playerButton.clear();
1264 		line.clear();
1265 
1266 		//Set shadowCall false
1267 		shadowCall=false;
1268 		//And let the shadow know that the player called him.
1269 		shadow->meCall();
1270 	}
1271 }
1272 
stateReset()1273 void Player::stateReset(){
1274 	//Reset the state by setting it to 0.
1275 	state=0;
1276 }
1277 
otherCheck(class Player * other)1278 void Player::otherCheck(class Player* other){
1279 	//Now check if the player is on the shadow.
1280 	//First make sure they are both alive.
1281 	if(!dead && !other->dead){
1282 		//Get the box of the shadow.
1283 		SDL_Rect boxShadow=other->getBox();
1284 
1285 		//Check if the player is on top of the shadow.
1286 		if(checkCollision(box,boxShadow)==true){
1287 			//We have collision now check if the other is standing on top of you.
1288 			if(box.y+box.h<=boxShadow.y+13 && !other->inAir){
1289 				//Player is on shadow.
1290 				int yVelocity=yVel-1;
1291 				if(yVelocity>0){
1292 					//If the player is going to stand on the shadow for the first time, check if there are enough spaces for it.
1293 					if (!other->holdingOther) {
1294 						const SDL_Rect r = { box.x, boxShadow.y - box.h, box.w, box.h };
1295 						for (auto ooo : objParent->levelObjects){
1296 							//Make sure to only check visible blocks.
1297 							if (ooo->queryProperties(GameObjectProperty_Flags, this) & 0x80000000)
1298 								continue;
1299 							//Make sure the object is solid for the player.
1300 							if (!ooo->queryProperties(GameObjectProperty_PlayerCanWalkOn, this))
1301 								continue;
1302 
1303 							//Check for collision.
1304 							if (checkCollision(r, ooo->getBox())) {
1305 								//We are blocked hence we can't stand on it.
1306 								return;
1307 							}
1308 						}
1309 					}
1310 
1311 					box.y=boxShadow.y-box.h;
1312 					inAir=false;
1313 					canMove=false;
1314 					//Reset the vertical velocity.
1315 					yVel=2;
1316 					other->holdingOther=true;
1317 					other->appearance.changeState("holding");
1318 
1319 					//Change our own appearance to standing.
1320 					if(direction==1){
1321 						appearance.changeState("standleft");
1322 					}else{
1323 						appearance.changeState("standright");
1324 					}
1325 
1326 					//Set the velocity things.
1327 					objCurrentStand=NULL;
1328 				}
1329 			}else if(boxShadow.y+boxShadow.h<=box.y+13 && !inAir){
1330 				//Shadow is on player.
1331 				int yVelocity=other->yVel-1;
1332 				if(yVelocity>0){
1333 					//If the shadow is going to stand on the player for the first time, check if there are enough spaces for it.
1334 					if (!holdingOther) {
1335 						const SDL_Rect r = { boxShadow.x, box.y - boxShadow.h, boxShadow.w, boxShadow.h };
1336 						for (auto ooo : objParent->levelObjects){
1337 							//Make sure to only check visible blocks.
1338 							if (ooo->queryProperties(GameObjectProperty_Flags, other) & 0x80000000)
1339 								continue;
1340 							//Make sure the object is solid for the shadow.
1341 							if (!ooo->queryProperties(GameObjectProperty_PlayerCanWalkOn, other))
1342 								continue;
1343 
1344 							//Check for collision.
1345 							if (checkCollision(r, ooo->getBox())) {
1346 								//We are blocked hence we can't stand on it.
1347 								return;
1348 							}
1349 						}
1350 					}
1351 
1352 					other->box.y=box.y-boxShadow.h;
1353 					other->inAir=false;
1354 					other->canMove=false;
1355 					//Reset the vertical velocity of the other.
1356 					other->yVel=2;
1357 					holdingOther=true;
1358 					appearance.changeState("holding");
1359 
1360 					//Change our own appearance to standing.
1361 					if(other->direction==1){
1362 						other->appearance.changeState("standleft");
1363 					}else{
1364 						other->appearance.changeState("standright");
1365 					}
1366 
1367 					//Set the velocity things.
1368 					other->objCurrentStand=NULL;
1369 				}
1370 			}
1371 		}else{
1372 			holdingOther=false;
1373 			other->holdingOther=false;
1374 		}
1375 	}
1376 }
1377 
getBox()1378 SDL_Rect Player::getBox(){
1379 	return box;
1380 }
1381 
setMyCamera()1382 void Player::setMyCamera(){
1383 	//Only change the camera when the player isn't dead.
1384 	if(dead)
1385 		return;
1386 
1387 	//Check if the level fit's horizontally inside the camera.
1388 	if(camera.w>LEVEL_WIDTH){
1389 		camera.x=-(camera.w-LEVEL_WIDTH)/2;
1390 	}else{
1391 		//Check if the player is halfway pass the halfright of the screen.
1392 		if(box.x>camera.x+(SCREEN_WIDTH/2+50)){
1393 			//It is so ease the camera to the right.
1394 			camera.x+=(box.x-camera.x-(SCREEN_WIDTH/2))>>4;
1395 
1396 			//Check if the camera isn't going too far.
1397 			if(box.x<camera.x+(SCREEN_WIDTH/2+50)){
1398 				camera.x=box.x-(SCREEN_WIDTH/2+50);
1399 			}
1400 		}
1401 
1402 		//Check if the player is halfway pass the halfleft of the screen.
1403 		if(box.x<camera.x+(SCREEN_WIDTH/2-50)){
1404 			//It is so ease the camera to the left.
1405 			camera.x+=(box.x-camera.x-(SCREEN_WIDTH/2))>>4;
1406 
1407 			//Check if the camera isn't going too far.
1408 			if(box.x>camera.x+(SCREEN_WIDTH/2-50)){
1409 				camera.x=box.x-(SCREEN_WIDTH/2-50);
1410 			}
1411 		}
1412 
1413 		//If the camera is too far to the left we set it to 0.
1414 		if(camera.x<0){
1415 			camera.x=0;
1416 		}
1417 		//If the camera is too far to the right we set it to the max right.
1418 		if(camera.x+camera.w>LEVEL_WIDTH){
1419 			camera.x=LEVEL_WIDTH-camera.w;
1420 		}
1421 	}
1422 
1423 	//Check if the level fit's vertically inside the camera.
1424 	if(camera.h>LEVEL_HEIGHT){
1425 		//We don't centre vertical because the bottom line of the level (deadly) will be mid air.
1426 		camera.y=-(camera.h-LEVEL_HEIGHT);
1427 	}else{
1428 		//Check if the player is halfway pass the lower half of the screen.
1429 		if(box.y>camera.y+(SCREEN_HEIGHT/2+50)){
1430 			//If is so ease the camera down.
1431 			camera.y+=(box.y-camera.y-(SCREEN_HEIGHT/2))>>4;
1432 
1433 			//Check if the camera isn't going too far.
1434 			if(box.y<camera.y+(SCREEN_HEIGHT/2+50)){
1435 				camera.y=box.y-(SCREEN_HEIGHT/2+50);
1436 			}
1437 		}
1438 
1439 		//Check if the player is halfway pass the upper half of the screen.
1440 		if(box.y<camera.y+(SCREEN_HEIGHT/2-50)){
1441 			//It is so ease the camera up.
1442 			camera.y+=(box.y-camera.y-(SCREEN_HEIGHT/2))>>4;
1443 
1444 			//Check if the camera isn't going too far.
1445 			if(box.y>camera.y+(SCREEN_HEIGHT/2-50)){
1446 				camera.y=box.y-(SCREEN_HEIGHT/2-50);
1447 			}
1448 		}
1449 
1450 		//If the camera is too far up we set it to 0.
1451 		if(camera.y<0){
1452 			camera.y=0;
1453 		}
1454 		//If the camera is too far down we set it to the max down.
1455 		if(camera.y+camera.h>LEVEL_HEIGHT){
1456 			camera.y=LEVEL_HEIGHT-camera.h;
1457 		}
1458 	}
1459 }
1460 
reset(bool save)1461 void Player::reset(bool save){
1462 	//Set the location of the player to it's initial state.
1463 	box.x=fx;
1464 	box.y=fy;
1465 
1466 	//Reset back to default value.
1467 	inAir=true;
1468 	isJump=false;
1469 	shadowCall=false;
1470 	canMove=true;
1471 	holdingOther=false;
1472 	dead=false;
1473 	record=false;
1474 	downKeyPressed=false;
1475 	spaceKeyPressed=false;
1476 
1477 	//Some animation variables.
1478 	appearance.resetAnimation(save);
1479 	appearance.changeState("standright");
1480 	direction=0;
1481 
1482 	state=0;
1483 	xVel=0; //??? fixed a strange bug in game replay
1484 	yVel=0;
1485 
1486 	//Reset the gameObject pointers.
1487 	objCurrentStand=objLastStand=objLastTeleport=objNotificationBlock=objShadowBlock=NULL;
1488 	if(save)
1489 		objCurrentStandSave=objLastStandSave=NULL;
1490 
1491 	//Clear the recording.
1492 	line.clear();
1493 	playerButton.clear();
1494 	recordButton.clear();
1495 	recordIndex=-1;
1496 #ifdef RECORD_FILE_DEBUG
1497 	recordKeyPressLog.clear();
1498 	recordPlayerPosition.clear();
1499 #endif
1500 
1501 	if(save){
1502 		//xVelSaved is used to indicate if there's a state saved or not.
1503 		xVelSaved=0x80000000;
1504 
1505 		loadAndDieTimes=0;
1506 	}
1507 }
1508 
saveState()1509 void Player::saveState(){
1510 	//We can only save the state when the player isn't dead.
1511 	if(!dead){
1512 		boxSaved.x=box.x;
1513 		boxSaved.y=box.y;
1514 		xVelSaved=xVel;
1515 		yVelSaved=yVel;
1516 		inAirSaved=inAir;
1517 		isJumpSaved=isJump;
1518 		canMoveSaved=canMove;
1519 		holdingOtherSaved=holdingOther;
1520 		stateSaved=state;
1521 
1522 		//Let the appearance save.
1523 		appearance.saveAnimation();
1524 
1525 		//Save the lastStand and currentStand pointers.
1526 		objCurrentStandSave=objCurrentStand;
1527 		objLastStandSave=objLastStand;
1528 
1529 		//Save any recording stuff.
1530 		recordSaved=record;
1531 		playerButtonSaved=playerButton;
1532 		lineSaved=line;
1533 
1534 		//Save the record
1535 		savedRecordButton=recordButton;
1536 #ifdef RECORD_FILE_DEBUG
1537 		recordKeyPressLog_saved=recordKeyPressLog;
1538 		recordPlayerPosition_saved=recordPlayerPosition;
1539 #endif
1540 
1541 		//To prevent playing the sound twice, only the player can cause the sound.
1542 		if(!shadow)
1543 			getSoundManager()->playSound("checkpoint");
1544 
1545 		//We saved a new state so reset the counter
1546 		loadAndDieTimes=0;
1547 	}
1548 }
1549 
loadState()1550 void Player::loadState(){
1551 	//Check with xVelSaved if there's a saved state.
1552 	if(xVelSaved==int(0x80000000)){
1553 		//There isn't so reset the game to load the first initial state.
1554 		//NOTE: There's no need in removing the saved state since there is none.
1555 		reset(false);
1556 		return;
1557 	}
1558 
1559 	//Restore the saved values.
1560 	box.x=boxSaved.x;
1561 	box.y=boxSaved.y;
1562 	//xVel is set to 0 since it's saved counterpart is used to indicate a saved state.
1563 	xVel=0;
1564 	yVel=yVelSaved;
1565 
1566 	//Restore the saved values.
1567 	inAir=inAirSaved;
1568 	isJump=isJumpSaved;
1569 	canMove=canMoveSaved;
1570 	holdingOther=holdingOtherSaved;
1571 	dead=false;
1572 	record=false;
1573 	shadowCall=false;
1574 	state=stateSaved;
1575 
1576 	objCurrentStand=objCurrentStandSave;
1577 	objLastStand=objLastStandSave;
1578 
1579 	//Restore the appearance.
1580 	appearance.loadAnimation();
1581 
1582 	//Restore any recorded stuff.
1583 	record=recordSaved;
1584 	playerButton=playerButtonSaved;
1585 	line=lineSaved;
1586 
1587 	//Load the previously saved record
1588 	recordButton=savedRecordButton;
1589 #ifdef RECORD_FILE_DEBUG
1590 	recordKeyPressLog=recordKeyPressLog_saved;
1591 	recordPlayerPosition=recordPlayerPosition_saved;
1592 #endif
1593 }
1594 
swapState(Player * other)1595 void Player::swapState(Player* other){
1596 	//We need to swap the values of the player with the ones of the given player.
1597 	swap(box.x,other->box.x);
1598 	swap(box.y,other->box.y);
1599 	swap(xVelBase, other->yVelBase);
1600 	swap(yVelBase, other->yVelBase);
1601 	swap(objCurrentStand, other->objCurrentStand);
1602 	//NOTE: xVel isn't there since it's used for something else.
1603 	swap(yVel,other->yVel);
1604 	swap(inAir,other->inAir);
1605 	swap(isJump,other->isJump);
1606 	swap(canMove,other->canMove);
1607 	swap(holdingOther,other->holdingOther);
1608 	swap(dead, other->dead);
1609 
1610 	//Also reset the state of the other.
1611 	other->stateReset();
1612 
1613 	//Play the swap sound.
1614 	getSoundManager()->playSound("swap");
1615 
1616 	//Update statistics.
1617 	if(!dead && !objParent->player.isPlayFromRecord() && !objParent->interlevel){
1618 		if(objParent->time < objParent->recentSwap + FPS){
1619 			//Swap player and shadow twice in 1 senond.
1620 			statsMgr.newAchievement("quickswap");
1621 		}
1622 		objParent->recentSwap=objParent->time;
1623 
1624 		statsMgr.swapTimes++;
1625 
1626 		//Update achievements
1627 		switch(statsMgr.swapTimes){
1628 		case 100:
1629 			statsMgr.newAchievement("swap100");
1630 			break;
1631 		case 1000:
1632 			statsMgr.newAchievement("swap1k");
1633 			break;
1634 		}
1635 	}
1636 }
1637 
canSaveState()1638 bool Player::canSaveState(){
1639 	//We can only save the state if the player isn't dead.
1640 	return !dead;
1641 }
1642 
canLoadState()1643 bool Player::canLoadState(){
1644 	//We use xVelSaved to indicate if a state is saved or not.
1645 	return xVelSaved != int(0x80000000);
1646 }
1647 
die(bool animation)1648 void Player::die(bool animation){
1649 	//Make sure the player isn't already dead.
1650 	if(!dead){
1651 		dead=true;
1652 
1653 		//If sound is enabled run the hit sound.
1654 		getSoundManager()->playSound("hit");
1655 
1656 		//Change the apearance to die (if animation is true).
1657 		if(animation){
1658 			if(direction==1){
1659 				appearance.changeState("dieleft");
1660 			}else{
1661 				appearance.changeState("dieright");
1662 			}
1663 		}
1664 
1665 		//Update statistics
1666 		if(!objParent->player.isPlayFromRecord() && !objParent->interlevel){
1667 			addRecentDeaths(objParent->recentLoad);
1668 
1669 			if(shadow) statsMgr.shadowDies++;
1670 			else statsMgr.playerDies++;
1671 
1672 			switch(statsMgr.playerDies+statsMgr.shadowDies){
1673 			case 1:
1674 				statsMgr.newAchievement("die1");
1675 				break;
1676 			case 50:
1677 				statsMgr.newAchievement("die50");
1678 				break;
1679 			case 1000:
1680 				statsMgr.newAchievement("die1000");
1681 				break;
1682 			}
1683 
1684 			if(canLoadState() && (++loadAndDieTimes)==100){
1685 				statsMgr.newAchievement("loadAndDie100");
1686 			}
1687 
1688 			if(objParent->player.dead && objParent->shadow.dead) statsMgr.newAchievement("doubleKill");
1689 		}
1690 	}
1691 
1692 	//We set the jumpTime to 80 when this is the shadow.
1693 	//That's the countdown for the "Your shadow has died." message.
1694 	if(shadow){
1695 		jumpTime=80;
1696 	}
1697 }
1698