paulo@0: /* Input handling for LOCKJAW Tetromino Game paulo@0: paulo@0: Copyright (C) 2006 Damian Yerrick paulo@0: paulo@0: This work is free software; you can redistribute it and/or modify paulo@0: it under the terms of the GNU General Public License as published by paulo@0: the Free Software Foundation; either version 2 of the License, or paulo@0: (at your option) any later version. paulo@0: paulo@0: This program is distributed in the hope that it will be useful, paulo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the paulo@0: GNU General Public License for more details. paulo@0: paulo@0: You should have received a copy of the GNU General Public License paulo@0: along with this program; if not, write to the Free Software paulo@0: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA paulo@0: paulo@0: Original game concept and design by Alexey Pajitnov. paulo@0: The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, paulo@0: or The Tetris Company LLC. paulo@0: paulo@0: */ paulo@0: #include "lj.h" paulo@0: #include "ljcontrol.h" paulo@0: #include "ljreplay.h" paulo@0: #include "pcjoy.h" paulo@0: paulo@0: // first is rotation (+1 = 90 deg clockwise) paulo@0: // second is movement (+1 = right 1 block) paulo@0: // third is gravity (+1 = down 1/8 block) paulo@0: // fourth is extra actions (hold, lock) paulo@0: LJInput macros[8] = { paulo@0: { -1, 0, 0, 0 }, // default: alt. rotate left paulo@0: { -2, 0, 0, 0 }, // default: rotate left twice paulo@0: { 0, -9, 0, 0 }, // default: far left paulo@0: { 0, 9, 0, 0 }, // default: far right paulo@0: { 0, 0, 20*8, 0 }, // default: firm drop paulo@0: { 0, 0, 0, LJI_HOLD }, // default: alternate hold paulo@0: { 0, 0, 0, 0 }, paulo@0: { 0, 0, 0, 0 } paulo@0: }; paulo@0: paulo@0: void addMacrosToInput(LJInput *dst, LJBits keys) { paulo@0: int rotation = dst->rotation; paulo@0: int movement = dst->movement; paulo@0: int gravity = dst->gravity; paulo@0: int other = dst->other; paulo@0: int macro; paulo@0: paulo@0: keys >>= 8; paulo@0: for (macro = 0; paulo@0: macro < 8; paulo@0: keys >>= 1, ++macro) { paulo@0: if (keys & 1) { paulo@0: rotation += macros[macro].rotation; paulo@0: movement += macros[macro].movement; paulo@0: gravity += macros[macro].gravity; paulo@0: other |= macros[macro].other; paulo@0: } paulo@0: } paulo@0: paulo@0: // Clip rotation to [-3, +3] paulo@0: rotation -= rotation / 4 * 4; paulo@0: paulo@0: // Clip movement to playfield width paulo@0: if (movement < (int)-LJ_PF_WID) { paulo@0: movement = -LJ_PF_WID; paulo@0: } else if (movement > (int)LJ_PF_WID) { paulo@0: movement = LJ_PF_WID; paulo@0: } paulo@0: paulo@0: // Clip gravity to playfield height paulo@0: if (gravity > LJ_PF_HT * 8) { paulo@0: gravity = LJ_PF_HT * 8; paulo@0: } paulo@0: paulo@0: dst->rotation = rotation; paulo@0: dst->movement = movement; paulo@0: dst->gravity = gravity; paulo@0: dst->other = other; paulo@0: } paulo@0: paulo@0: static const LJFixed softDropSpeeds[3] = { paulo@0: LJITOFIX(1), paulo@0: LJITOFIX(1)/2, paulo@0: LJITOFIX(1)/3 paulo@0: }; paulo@0: paulo@0: void addKeysToInput(LJInput *dst, LJBits keys, const LJField *p, LJControl *c) { paulo@0: int actualKeys = keys; paulo@0: paulo@0: if (c->replaySrc) { paulo@0: keys = getReplayFrame(c->replaySrc, dst); paulo@0: if (keys == LJREPLAY_EOF) { paulo@0: keys = actualKeys; paulo@0: replayClose(c->replaySrc); paulo@0: c->replaySrc = NULL; paulo@0: } paulo@0: } paulo@0: paulo@0: int lastFrameKeys = c->lastKeys; paulo@0: paulo@0: // If diagonal presses are disabled, ignore any changes paulo@0: if (!c->allowDiagonals paulo@0: && (keys & (VKEY_UP | VKEY_DOWN)) paulo@0: && (keys & (VKEY_LEFT | VKEY_RIGHT))) { paulo@0: keys &= ~(VKEY_UP | VKEY_DOWN | VKEY_LEFT | VKEY_RIGHT); paulo@0: keys |= lastFrameKeys paulo@0: & (VKEY_UP | VKEY_DOWN | VKEY_LEFT | VKEY_RIGHT); paulo@0: } paulo@0: paulo@0: LJBits newKeys = keys & ~lastFrameKeys; paulo@0: c->lastKeys = keys; paulo@0: paulo@0: // Count presses for Baboo!, excluding console buttons paulo@0: c->presses += countOnes(newKeys & 0x0000FFFF); paulo@0: paulo@0: // Only once the side effect of counting presses for Baboo! paulo@0: // is complete can we break out of a replay. paulo@0: if (c->replaySrc) { paulo@0: return; paulo@0: } paulo@0: paulo@0: LJBits releasedKeys = ~keys & lastFrameKeys; paulo@0: paulo@0: // At this point, c->lastKeys holds the keys actually held paulo@0: // by the player this frame, and lastFrameKeys holds the keys paulo@0: // actually held by the player last frame. paulo@0: paulo@0: // Handle keys that must be re-pressed paulo@0: releasedKeys &= ~c->repressKeys; paulo@0: c->repressKeys &= keys; paulo@0: keys &= ~c->repressKeys; paulo@0: paulo@0: // If locking in a mode without ARE, require paulo@0: // down to be re-pressed before next piece paulo@0: if (p->sounds & LJSND_LOCK paulo@0: && p->speed.entryDelay <= c->dasDelay) { paulo@0: c->repressKeys |= VKEY_DOWN; paulo@0: paulo@0: // Treat up the same way when hard drop lock is set to lock on release. paulo@0: if (c->hardDropLock != LJZANGI_SLIDE paulo@0: || p->lockReset == LJLOCK_NOW paulo@0: || p->speed.lockDelay <= c->dasDelay) { paulo@0: c->repressKeys |= VKEY_UP | VKEY_MACRO(4); paulo@0: } paulo@0: } paulo@0: paulo@0: // Initial Rotation System (IRS): paulo@0: // When a piece spawns from next or hold, and a rotation or macro paulo@0: // key is held, treat the key as if they had just been pressed. paulo@0: // Treat hard drop the same way when ARE is turned on. paulo@0: if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD)) paulo@0: && c->initialRotate) { paulo@0: newKeys |= c->lastKeys paulo@0: & (VKEY_ROTL | VKEY_ROTR | VKEY_MACROS); paulo@0: if (p->speed.entryDelay > 0) { paulo@0: newKeys |= c->lastKeys paulo@0: & VKEY_UP; paulo@0: } paulo@0: } paulo@0: paulo@0: // if we're pretending that keys are not pressed, paulo@0: // pretend consistently paulo@0: newKeys &= keys; paulo@0: paulo@0: // TGM does not perform sideways movement on paulo@0: // the first frame after a piece is spawned. paulo@0: if (c->initialDAS == 0 && paulo@0: (p->sounds & (LJSND_SPAWN | LJSND_HOLD))) { paulo@0: paulo@0: } else if (keys & VKEY_LEFT) { paulo@0: if (c->dasCounter > -(int)c->dasDelay) { paulo@0: if (c->dasCounter >= 0) { paulo@0: c->dasCounter = -1; paulo@0: dst->movement = -1; paulo@0: } else { paulo@0: c->dasCounter -= 1; paulo@0: } paulo@0: } else { paulo@0: int dasSpeed = c->dasSpeed; paulo@0: if (dasSpeed) { paulo@0: dst->movement = -1; paulo@0: c->dasCounter += dasSpeed - 1; paulo@0: } else { paulo@0: dst->movement = -(int)LJ_PF_WID; paulo@0: } paulo@0: } paulo@0: } else if (keys & VKEY_RIGHT) { paulo@0: if (c->dasCounter < c->dasDelay) { paulo@0: if (c->dasCounter <= 0) { paulo@0: c->dasCounter = 1; paulo@0: dst->movement = 1; paulo@0: } else { paulo@0: c->dasCounter += 1; paulo@0: } paulo@0: } else { paulo@0: int dasSpeed = c->dasSpeed; paulo@0: if (dasSpeed) { paulo@0: dst->movement = 1; paulo@0: c->dasCounter -= dasSpeed - 1; paulo@0: } else { paulo@0: dst->movement = (int)LJ_PF_WID; paulo@0: } paulo@0: } paulo@0: } else { paulo@0: c->dasCounter = 0; paulo@0: } paulo@0: paulo@0: if(keys & VKEY_DOWN) { paulo@0: int g = softDropSpeeds[c->softDropSpeed]; paulo@0: paulo@0: // dither speed to 1/8G units paulo@0: g += ljitofix(p->gameTime % 3) / 24; paulo@0: dst->gravity += g >> 13; paulo@0: paulo@0: if ((newKeys & VKEY_DOWN) paulo@0: || c->softDropLock == LJZANGI_LOCK) { paulo@0: dst->other |= LJI_LOCK; paulo@0: } paulo@0: } paulo@0: paulo@0: if (newKeys & VKEY_ROTL) { paulo@0: dst->rotation -= 1; paulo@0: } paulo@0: if (newKeys & VKEY_ROTR) { paulo@0: dst->rotation += 1; paulo@0: } paulo@0: if (newKeys & VKEY_HOLD) { paulo@0: dst->other |= LJI_HOLD; paulo@0: } paulo@0: if (newKeys & VKEY_UP) { paulo@0: dst->gravity = LJ_PF_HT << 3; paulo@0: if (p->state == LJS_LANDED paulo@0: || c->hardDropLock == LJZANGI_LOCK) { paulo@0: dst->other |= LJI_LOCK; paulo@0: } paulo@0: } paulo@0: paulo@0: if (c->hardDropLock == LJZANGI_LOCK_RELEASE) { paulo@0: if (releasedKeys & VKEY_UP) { paulo@0: dst->other |= LJI_LOCK; paulo@0: } paulo@0: } paulo@0: if (c->softDropLock == LJZANGI_LOCK_RELEASE) { paulo@0: if (releasedKeys & VKEY_DOWN) { paulo@0: dst->other |= LJI_LOCK; paulo@0: } paulo@0: } paulo@0: paulo@0: addMacrosToInput(dst, newKeys); paulo@0: paulo@0: // Baboo! ends with a hard drop paulo@0: if (p->gimmick == LJGM_BABY && c->presses >= 300) { paulo@0: dst->gravity = LJ_PF_HT << 3; paulo@0: dst->other |= LJI_LOCK; paulo@0: } paulo@0: paulo@0: if (c->replayDst) { paulo@0: replayRecord(c->replayDst, actualKeys, dst); paulo@0: } paulo@0: }