File indexing completed on 2024-05-05 08:05:37
0001 /* 0002 SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kgrrunner.h" 0008 #include "kgrlevelgrid.h" 0009 #include "kgrrulebook.h" 0010 #include "kgrlevelplayer.h" 0011 #include "kgoldrunner_debug.h" 0012 #include "kgrdebug.h" 0013 0014 KGrRunner::KGrRunner (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid, 0015 int i, int j, const int pSpriteId, 0016 KGrRuleBook * pRules, const int startDelay) 0017 : 0018 QObject (pLevelPlayer), // Destroy runner when level is destroyed. 0019 levelPlayer (pLevelPlayer), 0020 grid (pGrid), 0021 rules (pRules), 0022 spriteId (pSpriteId), 0023 gridI (i), 0024 gridJ (j), 0025 deltaX (0), 0026 deltaY (0), 0027 pointCtr (0), 0028 falling (false), 0029 0030 currDirection (STAND), 0031 currAnimation (FALL_L), 0032 0033 // The start delay is zero for the hero and 50 msec for the enemies. This 0034 // gives the hero about one grid-point advantage. Without this lead, some 0035 // levels become impossible, notably Challenge, 4, "Quick Off The Mark" and 0036 // Curse of the Mummy, 20, "The Parting of the Red Sea". 0037 timeLeft (TickTime + startDelay), 0038 0039 leftRightSearch (true) 0040 { 0041 getRules(); 0042 0043 gridX = i * pointsPerCell; 0044 gridY = j * pointsPerCell; 0045 0046 // As soon as the initial timeLeft has expired (i.e. at the first tick in 0047 // the hero's case and after a short delay in the enemies' case), the 0048 // pointCtr will reach its maximum, the EndCell situation will occur and 0049 // each runner will look for a new direction and head that way if he can. 0050 pointCtr = pointsPerCell - 1; 0051 0052 dbgLevel = 0; 0053 } 0054 0055 KGrRunner::~KGrRunner() 0056 { 0057 } 0058 0059 void KGrRunner::getRules() 0060 { 0061 pointsPerCell = rules->pointsPerCell(); 0062 turnAnywhere = rules->turnAnywhere(); 0063 //if (spriteId < 1) { 0064 // qCDebug(KGOLDRUNNER_LOG) << "pointsPerCell" << pointsPerCell 0065 // << "turnAnywhere" << turnAnywhere; 0066 //} 0067 } 0068 0069 Situation KGrRunner::situation (const int scaledTime) 0070 { 0071 timeLeft -= scaledTime; 0072 if (timeLeft >= scaledTime) { 0073 dbe3 "%d sprite %02d scaled %02d timeLeft %03d - Not Time Yet\n", 0074 pointCtr, spriteId, scaledTime, timeLeft); 0075 return NotTimeYet; 0076 } 0077 0078 if (grid->cellType (gridI, gridJ) == BRICK) { 0079 dbe2 "%d sprite %02d scaled %02d timeLeft %03d - Caught in brick\n", 0080 pointCtr, spriteId, scaledTime, timeLeft); 0081 return CaughtInBrick; 0082 } 0083 0084 gridX += deltaX; 0085 gridY += deltaY; 0086 pointCtr++; 0087 0088 if (pointCtr < pointsPerCell) { 0089 timeLeft += interval; 0090 dbe2 "%d sprite %02d scaled %02d timeLeft %03d - Mid Cell\n", 0091 pointCtr, spriteId, scaledTime, timeLeft); 0092 return MidCell; 0093 } 0094 0095 dbe2 "%d sprite %02d scaled %02d timeLeft %03d - END Cell\n", 0096 pointCtr, spriteId, scaledTime, timeLeft); 0097 return EndCell; 0098 } 0099 0100 char KGrRunner::nextCell() 0101 { 0102 pointCtr = 0; 0103 gridI = gridX / pointsPerCell; 0104 gridJ = gridY / pointsPerCell; 0105 return grid->cellType (gridI, gridJ); 0106 } 0107 0108 bool KGrRunner::setNextMovement (const char spriteType, const char cellType, 0109 Direction & dir, 0110 AnimationType & anim, int & interval) 0111 { 0112 bool fallingState = false; 0113 0114 Flags OK = 0; 0115 if (spriteType == HERO) { 0116 dir = levelPlayer->getDirection (gridI, gridJ); 0117 OK = grid->heroMoves (gridI, gridJ); 0118 } 0119 else { 0120 dir = levelPlayer->getEnemyDirection (gridI, gridJ, leftRightSearch); 0121 OK = grid->enemyMoves (gridI, gridJ); 0122 } 0123 if ((dir >= nDirections) || (dir < 0)) { 0124 dir = STAND; // Make sure indices stay within range. 0125 } 0126 0127 if (dir == STAND) { 0128 anim = currAnimation; 0129 } 0130 else { 0131 anim = aType [dir]; 0132 } 0133 if (((anim == RUN_R) || (anim == RUN_L)) && (cellType == BAR)) { 0134 anim = (dir == RIGHT) ? CLIMB_R : CLIMB_L; 0135 } 0136 0137 interval = runTime; 0138 0139 // if (spriteType == HERO) { 0140 // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() for" << gridX << gridY; 0141 // } 0142 onEnemy = levelPlayer->standOnEnemy (spriteId, gridX, gridY); 0143 bool canStand = (OK & dFlag [STAND]) || (OK == 0) || onEnemy; 0144 if ((dir == DOWN) && (cellType == BAR)) { 0145 canStand = false; 0146 } 0147 bool cannotMoveAsRequired = (! (OK & dFlag [dir])); 0148 0149 // TODO - Check that the trap time is the same as in KGr 3.0. 0150 // TODO - We seem to be early getting into/out of the hole, but 0151 // the captive time is now OK. Total t down by ~100 in 4400. 0152 if ((spriteType == ENEMY) && (cellType == USEDHOLE)) { 0153 // The enemy is in a hole. 0154 if (currDirection == DOWN) { 0155 // The enemy is at the bottom of the hole: start the captive-timer. 0156 dir = STAND; 0157 anim = currAnimation; 0158 interval = trapTime; 0159 dbe1 "T %05lld id %02d Captive at [%02d,%02d]\n", 0160 t.elapsed(), spriteId, gridI, gridJ); 0161 } 0162 else { 0163 // The enemy can start climbing out after a cycle of captive-times. 0164 dir = UP; 0165 anim = CLIMB_U; 0166 dbe1 "T %05lld id %02d Start climb out at [%02d,%02d]\n", 0167 t.elapsed(), spriteId, gridI, gridJ); 0168 } 0169 } 0170 else if ((! canStand) || 0171 (onEnemy && (onEnemy->isFalling()) && cannotMoveAsRequired)) { 0172 // Must fall: cannot even walk left or right off an enemy's head here. 0173 fallingState = true; 0174 interval = onEnemy ? enemyFallTime : fallTime; 0175 dir = DOWN; 0176 anim = (falling) ? currAnimation : 0177 ((currDirection == RIGHT) ? FALL_R : FALL_L); 0178 } 0179 else if (cannotMoveAsRequired) { 0180 // Sprite cannot move, but the animation shows the desired direction. 0181 dir = STAND; 0182 } 0183 0184 return fallingState; 0185 } 0186 0187 0188 KGrHero::KGrHero (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid, 0189 int i, int j, int pSpriteId, KGrRuleBook * pRules) 0190 : 0191 KGrRunner (pLevelPlayer, pGrid, i, j, pSpriteId, pRules, 0), 0192 0193 // KGrLevelPlayer object will call setDigWhileFalling() and setNuggets(). 0194 digWhileFalling (true), 0195 nuggets (0) 0196 { 0197 //qCDebug(KGOLDRUNNER_LOG) << "THE HERO IS BORN at" << i << j << "sprite ID" << pSpriteId; 0198 rules->getHeroTimes (runTime, fallTime, enemyFallTime, trapTime); 0199 //qCDebug(KGOLDRUNNER_LOG) << "Hero run time" << runTime << "fall time" << fallTime; 0200 interval = runTime; 0201 } 0202 0203 KGrHero::~KGrHero() 0204 { 0205 } 0206 0207 HeroStatus KGrHero::run (const int scaledTime) 0208 { 0209 Situation s = situation (scaledTime); 0210 if (s == NotTimeYet) { 0211 return NORMAL; 0212 } 0213 0214 // Die if a brick has closed over us. 0215 if (s == CaughtInBrick) { 0216 return DEAD; 0217 } 0218 0219 // If standing on top row and all nuggets gone, go up a level. 0220 if ((gridJ == 1) && (nuggets <= 0) && 0221 (grid->heroMoves (gridI, gridJ) & dFlag [STAND])) { 0222 return WON_LEVEL; 0223 } 0224 0225 // Check if we have fallen onto an enemy. If so, continue at enemy-speed. 0226 if (falling && (interval != enemyFallTime)) { 0227 // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() for" << gridX << gridY; 0228 onEnemy = levelPlayer->standOnEnemy (spriteId, gridX, gridY); 0229 if (onEnemy != nullptr) { 0230 interval = enemyFallTime; 0231 // If MidCell, hero-speed animation overshoots, but looks OK. 0232 } 0233 } 0234 0235 // We need to check collision with enemies on every grid-point. 0236 if (levelPlayer->heroCaught (gridX, gridY)) { 0237 return DEAD; 0238 } 0239 0240 // Emit StepSound once per cell or ClimbSound twice per cell. 0241 if (((s == EndCell) || (pointCtr == (pointsPerCell/2))) && 0242 (currDirection != STAND) && (! falling)) { 0243 int step = ((currAnimation == RUN_R) || (currAnimation == RUN_L)) ? 0244 StepSound : ClimbSound; 0245 if ((s == EndCell) || (step == ClimbSound)) { 0246 Q_EMIT soundSignal (step); 0247 } 0248 } 0249 0250 if (s == MidCell) { 0251 return NORMAL; 0252 } 0253 0254 // Continue to the next cell. 0255 char cellType = nextCell(); 0256 0257 if (cellType == NUGGET) { 0258 nuggets = levelPlayer->runnerGotGold (spriteId, gridI, gridJ, true); 0259 Q_EMIT incScore (250); // Add to the human player's score. 0260 if (nuggets > 0) { 0261 Q_EMIT soundSignal (GoldSound); 0262 } 0263 else { 0264 Q_EMIT soundSignal (LadderSound); 0265 } 0266 } 0267 0268 Direction nextDirection; 0269 AnimationType nextAnimation; 0270 bool newFallingState = setNextMovement (HERO, cellType, nextDirection, 0271 nextAnimation, interval); 0272 if (newFallingState != falling) { 0273 Q_EMIT soundSignal (FallSound, newFallingState); // Start/stop falling. 0274 falling = newFallingState; 0275 } 0276 timeLeft += interval; 0277 dbe2 "%d sprite %02d [%02d,%02d] timeLeft %03d currDir %d nextDir %d " 0278 "currAnim %d nextAnim %d\n", 0279 pointCtr, spriteId, gridI, gridJ, timeLeft, currDirection, nextDirection, 0280 currAnimation, nextAnimation); 0281 0282 if ((nextDirection == currDirection) && (nextAnimation == currAnimation)) { 0283 if (nextDirection == STAND) { 0284 return NORMAL; 0285 } 0286 } 0287 0288 deltaX = movement [nextDirection][X]; 0289 deltaY = movement [nextDirection][Y]; 0290 0291 // Start the running animation (repeating). 0292 Q_EMIT startAnimation (spriteId, true, gridI, gridJ, 0293 (interval * pointsPerCell * TickTime) / scaledTime, 0294 nextDirection, nextAnimation); 0295 currAnimation = nextAnimation; 0296 currDirection = nextDirection; 0297 return NORMAL; 0298 } 0299 0300 bool KGrHero::dig (const Direction diggingDirection, int & i, int & j) 0301 { 0302 QString text = (diggingDirection == DIG_LEFT) ? QStringLiteral("LEFT") : QStringLiteral("RIGHT"); 0303 // qCDebug(KGOLDRUNNER_LOG) << "Start digging" << text; 0304 0305 Flags moves = grid->heroMoves (gridI, gridJ); 0306 bool result = false; 0307 0308 // If currDirection is UP, DOWN or STAND, dig next cell left or right. 0309 int relativeI = (diggingDirection == DIG_LEFT) ? -1 : +1; 0310 0311 if ((currDirection == LEFT) && (moves & dFlag [LEFT])) { 0312 // Running LEFT, so stop at -1: dig LEFT at -2 or dig RIGHT right here. 0313 relativeI = (diggingDirection == DIG_LEFT) ? -2 : 0; 0314 } 0315 else if ((currDirection == RIGHT) && (moves & dFlag [RIGHT])) { 0316 // Running RIGHT, so stop at +1: dig LEFT right here or dig RIGHT at -2. 0317 relativeI = (diggingDirection == DIG_LEFT) ? 0 : +2; 0318 } 0319 0320 // The place to dig must be clear and there must be a brick under it. 0321 char aboveBrick = grid->cellType (gridI + relativeI, gridJ); 0322 // qCDebug(KGOLDRUNNER_LOG) << "aboveBrick =" << aboveBrick; 0323 if ((grid->cellType (gridI + relativeI, gridJ + 1) == BRICK) && 0324 ((aboveBrick == FREE) || (aboveBrick == HOLE))) { 0325 0326 // You can dig under an enemy, empty space or hidden ladder, but not a 0327 // trapped enemy, ladder, gold, bar, brick, concrete or false brick. 0328 i = gridI + relativeI; 0329 j = gridJ + 1; 0330 result = true; 0331 0332 // If dig-while-falling is not allowed, prevent attempts to use it. 0333 // The boolean defaults to true but can be read from a setting for 0334 // the game, the specific level or a recording. So it can be false. 0335 if (! digWhileFalling) { 0336 // Work out where the hero WILL be standing when he digs. In the 0337 // second case, he will dig the brick that is now right under him. 0338 int nextGridI = (relativeI != 0) ? (gridI + relativeI/2) : 0339 ((currDirection == LEFT) ? (gridI - 1) : (gridI + 1)); 0340 Flags OK = grid->heroMoves (nextGridI, gridJ); 0341 bool canStand = (OK & dFlag [STAND]) || (OK == 0); 0342 bool enemyUnder = (onEnemy != nullptr); 0343 // Must be on solid ground or on an enemy (standing or riding down). 0344 if ((! canStand) && (nextGridI != gridI)) { 0345 // If cannot move to next cell and stand, is an enemy under it? 0346 // qCDebug(KGOLDRUNNER_LOG) << "Calling standOnEnemy() at gridX" << gridX 0347 // << "for" << (nextGridI * pointsPerCell) << gridY; 0348 enemyUnder = (levelPlayer->standOnEnemy (spriteId, 0349 nextGridI * pointsPerCell, gridY) != nullptr); 0350 } 0351 if ((! canStand) && (! enemyUnder)) { 0352 qCDebug(KGOLDRUNNER_LOG) << "INVALID DIG: hero at" << gridI << gridJ 0353 << "nextGridI" << nextGridI << "relI" << relativeI 0354 << "dirn" << currDirection << "brick at" << i << j 0355 << "heroMoves" << ((int) OK) << "canStand" << canStand 0356 << "enemyUnder" << enemyUnder; 0357 Q_EMIT invalidDig(); // Issue warning re dig while falling. 0358 result = false; 0359 } 0360 } 0361 } 0362 if (result) { 0363 Q_EMIT soundSignal (DigSound); 0364 } 0365 0366 return result; // Tell the levelPlayer whether & where to open a hole. 0367 } 0368 0369 void KGrHero::showState() 0370 { 0371 fprintf (stderr, "(%02d,%02d) %02d Hero ", gridI, gridJ, spriteId); 0372 fprintf (stderr, " gold %02d dir %d ctr %d", 0373 nuggets, currDirection, pointCtr); 0374 fprintf (stderr, " X %3d Y %3d anim %d dt %03d\n", 0375 gridX, gridY, currAnimation, interval); 0376 } 0377 0378 0379 KGrEnemy::KGrEnemy (KGrLevelPlayer * pLevelPlayer, KGrLevelGrid * pGrid, 0380 int i, int j, int pSpriteId, KGrRuleBook * pRules) 0381 : 0382 KGrRunner (pLevelPlayer, pGrid, i, j, pSpriteId, pRules, 50), 0383 nuggets (0), 0384 birthI (i), 0385 birthJ (j), 0386 prevInCell (-1) 0387 { 0388 rulesType = rules->getEnemyTimes (runTime, fallTime, trapTime); 0389 enemyFallTime = fallTime; 0390 interval = runTime; 0391 //qCDebug(KGOLDRUNNER_LOG) << "ENEMY" << pSpriteId << "IS BORN at" << i << j; 0392 //if (pSpriteId < 2) { 0393 // qCDebug(KGOLDRUNNER_LOG) << "Enemy run time " << runTime << "fall time" << fallTime; 0394 // qCDebug(KGOLDRUNNER_LOG) << "Enemy trap time" << trapTime << "Rules type" << rulesType; 0395 //} 0396 t.start(); // IDW 0397 } 0398 0399 KGrEnemy::~KGrEnemy() 0400 { 0401 } 0402 0403 void KGrEnemy::run (const int scaledTime) 0404 { 0405 Situation s = situation (scaledTime); 0406 if (s == NotTimeYet) { 0407 return; 0408 } 0409 0410 // Die if a brick has closed over us. 0411 if (s == CaughtInBrick) { 0412 releaseCell (gridI + deltaX, gridJ + deltaY); 0413 Q_EMIT incScore (75); // Killed: add to the player's score. 0414 dbe1 "T %05lld id %02d Died in brick at [%02d,%02d]\n", 0415 t.elapsed(), spriteId, gridI, gridJ); 0416 dieAndReappear(); // Move to a new (gridI, gridJ). 0417 reserveCell (gridI, gridJ); 0418 // Go to next cell, with s = CaughtInBrick, thus forcing re-animation. 0419 } 0420 0421 else if ((pointCtr == 1) && (currDirection == DOWN) && 0422 (grid->cellType (gridI, gridJ + 1) == HOLE)) { 0423 // Enemy is starting to fall into a hole. 0424 dbe1 "T %05lld id %02d Mark hole [%02d,%02d] as used\n", 0425 t.elapsed(), spriteId, gridI, gridJ+1); 0426 grid->changeCellAt (gridI, gridJ + 1, USEDHOLE); 0427 dropGold(); 0428 Q_EMIT incScore (75); // Trapped: add to the player's score. 0429 return; 0430 } 0431 0432 // Wait till end of cell. 0433 else if (s == MidCell) { 0434 if (grid->cellType (gridI, gridJ) == USEDHOLE) { 0435 dbe1 "T %05lld id %02d Stay captive at [%02d,%02d] count %d\n", 0436 t.elapsed(), spriteId, gridI, gridJ, pointCtr); 0437 } 0438 return; 0439 } 0440 0441 // Continue to the next cell. 0442 char cellType = nextCell(); 0443 0444 // Try to pick up or drop gold in the new cell. 0445 if (currDirection != STAND) { 0446 checkForGold(); 0447 } 0448 0449 // Find the next move that could lead to the hero. 0450 Direction nextDirection; 0451 AnimationType nextAnimation; 0452 bool fallingState = setNextMovement (ENEMY, cellType, nextDirection, 0453 nextAnimation, interval); 0454 dbe3 "\n"); 0455 0456 // If the enemy just left a hole, change it to empty. Must execute this 0457 // code AFTER finding the next direction and valid moves, otherwise the 0458 // enemy will just fall back into the hole again. 0459 if ((currDirection == UP) && 0460 (grid->cellType (gridI, gridJ + 1) == USEDHOLE)) { 0461 dbk3 << spriteId << "Hole emptied at" << gridI << (gridJ + 1); 0462 // Empty the hole, provided it had not somehow caught two enemies. 0463 if (grid->enemyOccupied (gridI, gridJ + 1) < 0) { 0464 grid->changeCellAt (gridI, gridJ + 1, HOLE); 0465 } 0466 } 0467 0468 dbe2 "%d sprite %02d [%02d,%02d] timeLeft %03d currDir %d nextDir %d " 0469 "currAnim %d nextAnim %d\n", 0470 pointCtr, spriteId, gridI, gridJ, timeLeft, 0471 currDirection, nextDirection, currAnimation, nextAnimation); 0472 0473 if (fallingState != falling) { 0474 falling = fallingState; 0475 if (falling) { 0476 t.restart(); 0477 dbe1 "T %05lld id %02d Start falling\n", t.elapsed(), spriteId); 0478 } 0479 } 0480 0481 // Check for a possible collision with another enemy. 0482 if (levelPlayer->bumpingFriend (spriteId, nextDirection, gridI, gridJ)) { 0483 nextDirection = STAND; // Wait for one timer interval. 0484 pointCtr = pointsPerCell - 1; // Try again after the interval. 0485 } 0486 0487 if ((rulesType == KGoldrunnerRules) && (nextDirection == STAND) && 0488 (cellType != USEDHOLE)) { 0489 // In KGoldrunner rules, if unable to move, switch the search direction. 0490 leftRightSearch = (leftRightSearch) ? false : true; 0491 pointCtr = pointsPerCell - 1; 0492 } 0493 0494 timeLeft += interval; 0495 deltaX = movement [nextDirection][X]; 0496 deltaY = movement [nextDirection][Y]; 0497 0498 // If moving, occupy the next cell in the enemy's path and release this one. 0499 if (nextDirection != STAND) { 0500 // If multiple enemies move into a cell, they stack. Each enemy uses 0501 // nextInCell to remember the ID of the enemy beneath it (or -1). The 0502 // top enemy is remembered by grid->setEnemyOccupied(). 0503 // 0504 // NOTE: In Scavenger rules, multiple enemies can fall or climb into a 0505 // a cell, but should keep to one per cell if moving horizontally. In 0506 // Traditional or KGoldrunner rules, it should not be possible for 0507 // multiple enemies to enter a cell, but anomalies can happen, so a 0508 // stack is used to record them temporarily and split them up again. 0509 0510 releaseCell (gridI, gridJ); 0511 0512 int nextI = gridI + deltaX; 0513 int nextJ = gridJ + deltaY; 0514 reserveCell (nextI, nextJ); 0515 } 0516 0517 if ((nextDirection == currDirection) && (nextAnimation == currAnimation)) { 0518 if ((nextDirection == STAND) && (s != CaughtInBrick)) { 0519 // In the CaughtInBrick situation, enemy sprites must not be shown 0520 // standing in the bricks where they died: we must re-animate them. 0521 return; 0522 } 0523 } 0524 0525 // Start the running animation (repeating). 0526 Q_EMIT startAnimation (spriteId, true, gridI, gridJ, 0527 (interval * pointsPerCell * TickTime) / scaledTime, 0528 nextDirection, nextAnimation); 0529 currAnimation = nextAnimation; 0530 currDirection = nextDirection; 0531 } 0532 0533 void KGrEnemy::dropGold() 0534 { 0535 if (nuggets > 0) { 0536 // If enemy is trapped when carrying gold, he must give it up. 0537 nuggets = 0; 0538 // Can drop in an empty cell, otherwise it is lost (no score for hero). 0539 bool lost = (grid->cellType (gridI, gridJ) != FREE); 0540 levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false, lost); 0541 } 0542 } 0543 0544 void KGrEnemy::checkForGold() 0545 { 0546 char cell = grid->cellType (gridI, gridJ); 0547 uchar random; 0548 if ((nuggets == 0) && (cell == NUGGET)) { 0549 bool collect = rules->alwaysCollectNugget(); 0550 if (! collect) { 0551 random = levelPlayer->randomByte ((uchar) 100); 0552 dbk3 << "Random" << random << "at NUGGET" << gridI << gridJ; 0553 collect = (random >= 80); 0554 } 0555 if (collect) { 0556 levelPlayer->runnerGotGold (spriteId, gridI, gridJ, true); 0557 dbk1 << "Enemy" << spriteId << "at" << gridI << gridJ 0558 << "COLLECTS gold"; 0559 nuggets = 1; 0560 } 0561 } 0562 else if ((nuggets > 0) && (cell == FREE)) { 0563 // Dropping gold is a random choice, but do not drop in thin air. 0564 char below = grid->cellType (gridI, gridJ + 1); 0565 if ((below != FREE) && (below != NUGGET) && (below != BAR)) { 0566 random = levelPlayer->randomByte ((uchar) 100); 0567 dbk3 << "Random" << random << "for DROP " << gridI << gridJ; 0568 if (random >= 93) { 0569 levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false); 0570 dbk1 << "Enemy" << spriteId << "at" << gridI << gridJ 0571 <<"DROPS gold"; 0572 nuggets = 0; 0573 } 0574 } 0575 } 0576 } 0577 0578 void KGrEnemy::dieAndReappear() 0579 { 0580 if (nuggets > 0) { 0581 // Enemy died and could not drop nugget. Gold is LOST - no score. 0582 nuggets = 0; // Set lost-flag in runnerGotGold(). 0583 levelPlayer->runnerGotGold (spriteId, gridI, gridJ, false, true); 0584 } 0585 0586 if (rules->reappearAtTop()) { 0587 // Traditional or Scavenger rules. 0588 dbk3 << spriteId << "REAPPEAR AT TOP"; 0589 levelPlayer->enemyReappear (gridI, gridJ); 0590 } 0591 else { 0592 // KGoldrunner rules. 0593 dbk3 << spriteId << "REAPPEAR AT BIRTHPLACE"; 0594 gridI = birthI; 0595 gridJ = birthJ; 0596 } 0597 0598 // Set up the enemy's state so as to be ready for moving to the next cell. 0599 0600 // There is no time-delay and no special animation here, though there was 0601 // in the Apple II game and there is in Scavenger. KGoldrunner has never 0602 // had a time-delay here, which makes KGoldrunner more difficult sometimes. 0603 gridX = gridI * pointsPerCell; 0604 gridY = gridJ * pointsPerCell; 0605 deltaX = 0; 0606 deltaY = 0; 0607 pointCtr = pointsPerCell; 0608 falling = false; 0609 interval = runTime; 0610 timeLeft = TickTime; 0611 currDirection = STAND; 0612 currAnimation = FALL_L; 0613 } 0614 0615 void KGrEnemy::reserveCell (const int i, const int j) 0616 { 0617 // Push down a previous enemy or -1 if the cell was empty. 0618 prevInCell = grid->enemyOccupied (i, j); 0619 grid->setEnemyOccupied (i, j, spriteId); 0620 dbe3 "%02d Entering [%02d,%02d] pushes %02d\n", spriteId, i, j, prevInCell); 0621 } 0622 0623 void KGrEnemy::releaseCell (const int i, const int j) 0624 { 0625 // Pop up a previous enemy or -1 if the cell was empty. 0626 if (spriteId == grid->enemyOccupied (i, j)) { 0627 grid->setEnemyOccupied (i, j, prevInCell); 0628 } 0629 else { 0630 levelPlayer->unstackEnemy (spriteId, i, j, prevInCell); 0631 } 0632 dbe3 "%02d Leaves [%02d,%02d] to %02d\n", spriteId, i, j, prevInCell); 0633 } 0634 0635 void KGrEnemy::showState() 0636 { 0637 fprintf (stderr, "(%02d,%02d) %02d Enemy", gridI, gridJ, spriteId); 0638 fprintf (stderr, " gold %02d dir %d ctr %d", 0639 nuggets, currDirection, pointCtr); 0640 fprintf (stderr, " X %3d Y %3d anim %d dt %03d prev %d\n", 0641 gridX, gridY, currAnimation, interval, prevInCell); 0642 } 0643 0644 #include "moc_kgrrunner.cpp"