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