diff --git a/src/controller.h b/src/controller.h index 7340003..9b3aa17 100644 --- a/src/controller.h +++ b/src/controller.h @@ -92,8 +92,8 @@ struct IGame { virtual bool invChooseKey(int playerIndex, TR::Entity::Type hole) { return false; } virtual Sound::Sample* playSound(int id, const vec3 &pos = vec3(0.0f), int flags = 0) const { return NULL; } - virtual void playTrack(uint8 track) {} - virtual void stopTrack() {} + virtual void playTrack(uint8 track, bool background = false) {} + virtual void stopTrack() {} }; struct Controller { diff --git a/src/enemy.h b/src/enemy.h index ab57bdb..1342bc4 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -348,6 +348,7 @@ struct Enemy : Character { return false; thinkTime -= 1.0f / 30.0f; + int zoneOld = zone; updateZone(); target = (Character*)game->getLara(pos); @@ -422,6 +423,9 @@ struct Enemy : Character { if (path && this->box != path->boxes[path->index - 1] && this->box != path->boxes[path->index]) targetBoxOld = -1; + if (zoneOld != zone) + targetBoxOld = -1; + if (targetBoxOld != targetBox) { if (findPath(stepHeight, dropHeight, getEntity().isBigEnemy())) nextWaypoint(); @@ -512,7 +516,7 @@ struct Enemy : Character { return false; } - void shot(TR::Entity::Type type, int joint, const vec3 &offset) { + void shot(TR::Entity::Type type, int joint, const vec3 &offset, float rx, float ry) { vec3 from = getJoint(joint) * offset; vec3 to = target->getBoundingBox().center(); @@ -520,11 +524,15 @@ struct Enemy : Character { if (bullet) { vec3 dir = to - from; vec3 ang = vec3(-atan2f(dir.y, sqrtf(dir.x * dir.x + dir.z * dir.z)), atan2f(dir.x, dir.z), 0.0f); - ang += vec3(randf() * 2.0f - 1.0f, randf() * 2.0f - 1.0f, 0.0f) * (1.5f * DEG2RAD); + ang += vec3(rx, ry, 0.0f); bullet->setAngle(ang); } } + void shot(TR::Entity::Type type, int joint, const vec3 &offset) { + shot(type, joint, offset, (randf() * 2.0f - 1.0f) * (1.5f * DEG2RAD), (randf() * 2.0f - 1.0f) * (1.5f * DEG2RAD)); + } + bool isVisible() { for (int i = 0; i < 2; i++) { ICamera *camera = game->getCamera(i); @@ -2777,7 +2785,7 @@ struct SkaterBoy : Human { fullChestRotation = state == STATE_STAND_FIRE || state == STATE_MOVE_FIRE; if (health < 120) - game->playTrack(56); + game->playTrack(56, true); switch (state) { case STATE_STOP : @@ -2982,10 +2990,196 @@ struct MrT : Human { } }; +#define NATLA_FIRE_ANGLE (30 * DEG2RAD) +#define NATLA_FAINT_TIME 16.0f +#define NATLA_HALF_HEALTH 200.0f +#define NATLA_FAINT_HEALTH -8192.0f +#define NATLA_LIFT_SPEED MUTANT_LIFT_SPEED struct Natla : Human { - Natla(IGame *game, int entity) : Human(game, entity, 400) {} + enum { + STATE_NONE, + STATE_STOP, + STATE_FLY, + STATE_RUN, + STATE_AIM, + STATE_FAINT, + STATE_FIRE, + STATE_FALL, + STATE_STAND, + STATE_DEATH, + }; + + enum { + FLAG_FLY = 1, + }; + + Natla(IGame *game, int entity) : Human(game, entity, 400) { + jointChest = 1; + jointHead = 2; + } + + int stage1() { + flying = (flags.unused & FLAG_FLY); + + stepHeight = 256; + dropHeight = -256; + + if (flying) { + stepHeight *= 80; + dropHeight *= 80; + } + + if (!think(false)) + return state; + + timer += Core::deltaTime; + bool canShot = target && target->health > 0.0f && targetIsVisible(HUMAN_DIST_SHOT); + + if (canShot && state == STATE_FLY && flying && rand() < 256) + flags.unused &= ~FLAG_FLY; + else if (!canShot) + flags.unused |= FLAG_FLY; + + flying = (flags.unused & FLAG_FLY); + + if (state == nextState) + nextState = STATE_NONE; + + switch (state) { + case STATE_STOP : + timer = 0.0f; + return flying ? STATE_FLY : STATE_AIM; + case STATE_FLY : { + if (timer >= 1.0f) { + { //if (canShot) { ??? + game->playSound(TR::SND_NATLA_SHOT, pos, Sound::PAN); + shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f)); + } + timer -= 1.0f; + } + + int16 roomIndex = getRoomIndex(); + TR::Room::Sector *sector = level->getSector(roomIndex, pos); + float floor = level->getFloor(sector, pos) - 128.0f; + + if (!flying && pos.y >= floor) + return STATE_STOP; + + break; + } + case STATE_AIM : + if (nextState != STATE_NONE) + return nextState; + return canShot ? STATE_FIRE : STATE_STOP; + case STATE_FIRE : + if (nextState != STATE_NONE) + return state; + + game->playSound(TR::SND_NATLA_SHOT, pos, Sound::PAN); + shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f)); + shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f), 0.0f, (randf() * 2.0f - 1.0f) * (25.0f * DEG2RAD)); + shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f), 0.0f, (randf() * 2.0f - 1.0f) * (25.0f * DEG2RAD)); + + nextState = STATE_STOP; + break; + } + + return state; + } + + int stage2() { + stepHeight = 256; + dropHeight = -256; + flying = false; + + if (!think(true)) + return state; + + timer += Core::deltaTime; + bool canShot = target && target->health > 0.0f && fabsf(targetAngle) < NATLA_FIRE_ANGLE && targetIsVisible(HUMAN_DIST_SHOT); + + switch (state) { + case STATE_RUN : + case STATE_STAND : + if (timer >= 20.0f / 30.0f) { + { // if (canShot) { ??? + game->playSound(TR::SND_NATLA_SHOT, pos, Sound::PAN); + shot(TR::Entity::MUTANT_BULLET, 4, vec3(5.0f, 220.0f, 7.0f)); + } + timer -= 20.0f / 30.0f; + } + return canShot ? STATE_STAND : STATE_RUN; + case STATE_FLY : + health = NATLA_FAINT_HEALTH; + return STATE_FALL; + case STATE_STOP : + case STATE_AIM : + case STATE_FIRE : + health = NATLA_FAINT_HEALTH; + return STATE_FAINT; + } + return state; + } + + virtual int getStateGround() { + if (health > NATLA_HALF_HEALTH) + return stage1(); + return stage2(); + } + + virtual int getStateDeath() { + switch (state) { + case STATE_FALL : { + int16 roomIndex = getRoomIndex(); + TR::Room::Sector *sector = level->getSector(roomIndex, pos); + float floor = level->getFloor(sector, pos); + if (pos.y >= floor) { + pos.y = floor; + timer = 0.0f; + return STATE_FAINT; + } + return state; + } + case STATE_FAINT : { + timer += Core::deltaTime; + if (timer >= NATLA_FAINT_TIME) { + health = NATLA_HALF_HEALTH; + timer = 0.0f; + flags.unused = 0; + game->playTrack(54, true); + return STATE_STAND; + } + return state; + } + } + return STATE_DEATH; + } + + virtual void updateVelocity() { + if (state == STATE_FLY) { + angle.y += targetAngle; + updateJoints(); + angle.y -= targetAngle; + } + Enemy::updateVelocity(); + } + + virtual void updatePosition() { + if (flying) { + stand = STAND_AIR; + lift(target->pos.y - pos.y, NATLA_LIFT_SPEED); + } + + turn(true, state == STATE_RUN ? HUMAN_TURN_FAST : HUMAN_TURN_SLOW); + + Enemy::updatePosition(); + setOverrides(state != STATE_FAINT, jointChest, jointHead); + lookAt(target); + + stand = STAND_GROUND; + } }; struct Dog : Enemy { diff --git a/src/format.h b/src/format.h index 2fc054e..d173c71 100644 --- a/src/format.h +++ b/src/format.h @@ -960,6 +960,8 @@ namespace TR { SND_STAIRS2SLOPE = 119, + SND_NATLA_SHOT = 123, + SND_HIT_SKATEBOY = 132, SND_HIT_MUTANT = 142, @@ -2250,7 +2252,7 @@ namespace TR { ByteFlags flipmaps[MAX_FLIPMAP_COUNT]; ByteFlags tracks[MAX_TRACKS_COUNT]; - uint16 fogColor; + uint16 fogColor; union { struct { uint16 track:8, flipped:1; }; uint16 value; diff --git a/src/lara.h b/src/lara.h index f379746..28a0d85 100644 --- a/src/lara.h +++ b/src/lara.h @@ -527,14 +527,16 @@ struct Lara : Character { //reset(57, vec3(71081, 5632, 73042), 0); // Level 10a (Skaterboy) //reset(68, vec3(52458, -9984, 93724), 270 * DEG2RAD); // Level 10a (MrT) //reset(44, vec3(75803, -11008, 21097), 90 * DEG2RAD); // Level 10a (boat) - //reset(47, vec3(50546, -13056, 53783), 270 * DEG2RAD); // Level 10b (trap door slope) - //reset(59, vec3(42907, -13056, 63012), 270 * DEG2RAD); // Level 10b (doppelganger) - //reset(53, vec3(39617, -18385, 48950), 180 * DEG2RAD); // Level 10b (centaur) - //reset(50, vec3(52122, -18688, 47313), 150 * DEG2RAD); // Level 10b (scion holder pickup) + //reset(47, vec3(50546, -13056, 53783), 270 * DEG2RAD); // Level 10b (trap door slope) + //reset(59, vec3(42907, -13056, 63012), 270 * DEG2RAD); // Level 10b (doppelganger) + //reset(53, vec3(39617, -18385, 48950), 180 * DEG2RAD); // Level 10b (centaur) + //reset(50, vec3(52122, -18688, 47313), 150 * DEG2RAD); // Level 10b (scion holder pickup) //reset(50, vec3(53703, -18688, 13769), PI); // Level 10c (scion holder) //reset(19, vec3(35364, -512, 40199), PI * 0.5f); // Level 10c (lava flow) //reset(9, vec3(69074, -14592, 25192), 0); // Level 10c (trap slam) //reset(21, vec3(47668, -10752, 32163), 0); // Level 10c (lava emitter) + //reset(29, vec3(61586, -439, 51734), PI * 0.5f); // Level 10c (precise falling) + //reset(33, vec3(64641, 9578, 61861), 0); // Level 10c (Natla) //reset(10, vec3(90443, 11264 - 256, 114614), PI, STAND_ONWATER); // villa mortal 2 //dbgBoxes = NULL; @@ -1720,7 +1722,7 @@ struct Lara : Character { } int doTutorial(int track) { - if (level->version == TR::VER_TR1_PC || level->version == TR::VER_TR1_PSX) + if (level->version & TR::VER_TR1) switch (track) { // GYM tutorial routine case 28 : if (level->state.tracks[track].once && state == STATE_UP_JUMP) track = 29; break; case 37 : @@ -2036,7 +2038,7 @@ struct Lara : Character { if (!(level->state.progress.secrets & (1 << cmd.args))) { level->state.progress.secrets |= 1 << cmd.args; if (!game->playSound(TR::SND_SECRET, pos)) - game->playTrack(TR::TRACK_TR1_SECRET); + game->playTrack(TR::TRACK_TR1_SECRET, true); } break; } diff --git a/src/level.h b/src/level.h index 553455d..93bf8aa 100644 --- a/src/level.h +++ b/src/level.h @@ -607,7 +607,7 @@ struct Level : IGame { void stopChannel(Sound::Sample *channel) { if (channel == sndTrack) { sndTrack = NULL; - if (level.state.flags.track == TR::LEVEL_INFO[level.id].track) // play ambient track + if (level.state.flags.track != TR::LEVEL_INFO[level.id].track) // play ambient track playTrack(0); } } @@ -636,7 +636,19 @@ struct Level : IGame { delete req; } - virtual void playTrack(uint8 track) { + static void playAsyncBG(Stream *stream, void *userData) { + TrackRequest *req = (TrackRequest*)userData; + if (stream) + Sound::play(stream, NULL, 1.0f, 1.0f, req->flags); + delete req; + } + + virtual void playTrack(uint8 track, bool background = false) { + if (background) { + TR::getGameTrack(level.version, track, playAsyncBG, new TrackRequest(this, Sound::MUSIC)); + return; + } + if (track == 0) track = TR::LEVEL_INFO[level.id].track; diff --git a/src/video.h b/src/video.h index b5e2ba4..0104b8c 100644 --- a/src/video.h +++ b/src/video.h @@ -857,7 +857,7 @@ struct Video { height = (frame.height + 15) / 16 * 16; fps = 150 / (frame.size / VIDEO_CHUNK_SIZE); fps = (fps < 20) ? 15 : 30; - //fps = int(fps * 44100 / 37800); + stream->setPos(pos); vFrameIndex = aFrameIndex = -1;