diff --git a/src/platform/gba/OpenLara.vcxproj b/src/platform/gba/OpenLara.vcxproj index e8ada82..dabd363 100644 --- a/src/platform/gba/OpenLara.vcxproj +++ b/src/platform/gba/OpenLara.vcxproj @@ -30,6 +30,7 @@ + @@ -146,6 +147,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + MultiThreaded Console @@ -166,6 +168,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + MultiThreaded Console diff --git a/src/platform/gba/camera.h b/src/platform/gba/camera.h index 4dcce9f..8f0d74e 100644 --- a/src/platform/gba/camera.h +++ b/src/platform/gba/camera.h @@ -3,18 +3,22 @@ #include "common.h" -#define CAM_SPEED (1 << 3) -#define CAM_ROT_SPEED (1 << 9) -#define CAM_ROT_X_MAX int16(85 * 0x8000 / 180) -#define CAM_DIST_FOLLOW (1024 + 512) +#define CAM_SPEED (1 << 3) +#define CAM_ROT_SPEED (1 << 9) +#define CAM_DIST_FOLLOW (1024 + 512) +#define CAMERA_ANGLE_FOLLOW -10 * DEG2SHORT +#define CAMERA_ANGLE_MAX 85 * DEG2SHORT +#define CAMERA_TRACE_SHIFT 3 +#define CAMERA_TRACE_STEPS (1 << CAMERA_TRACE_SHIFT) enum CameraMode { - CAMERA_MODE_FREE = 0, - CAMERA_MODE_FOLLOW = 1, - CAMERA_MODE_COMBAT = 2, - CAMERA_MODE_FIXED = 3, - CAMERA_MODE_OBJECT = 4, + CAMERA_MODE_FREE, + CAMERA_MODE_FOLLOW, + CAMERA_MODE_COMBAT, + CAMERA_MODE_LOOK, + CAMERA_MODE_FIXED, + CAMERA_MODE_OBJECT, }; struct Camera @@ -27,37 +31,63 @@ struct Camera Location view; Location target; + int32 targetDist; int16 targetAngleX; int16 targetAngleY; - int16 targetDist; int16 angleX; int16 angleY; AABB frustumBase; - Item* item; + Item* laraItem; + Item* lastItem; + Item* lookAtItem; + + int32 speed; + int32 timer; + int32 index; + int32 lastIndex; CameraMode mode; - bool modeSwitch; - void init() + bool modeSwitch; + bool lastFixed; + bool center; + + void init(Item* lara) { - item = NULL; - mode = CAMERA_MODE_FOLLOW; - modeSwitch = false; + target.pos = lara->pos; + target.pos.y -= 1024; + target.room = lara->room; + + view = target; + view.pos.z -= 100; angleX = 0; angleY = 0; - view.pos = vec3i(0); - target.pos = view.pos; - targetDist = CAM_DIST_FOLLOW; targetAngleX = 0; targetAngleY = 0; + targetDist = CAM_DIST_FOLLOW; + + laraItem = lara; + lastItem = NULL; + lookAtItem = NULL; + + speed = 1; + timer = 0; + index = -1; + lastIndex = -1; + + mode = CAMERA_MODE_FOLLOW; + + modeSwitch = false; + lastFixed = false; + center = false; } - void freeControl() + void updateFree() { matrixSetView(view.pos, angleX, angleY); @@ -68,7 +98,7 @@ struct Camera if (keys & IK_LEFT) angleY -= CAM_ROT_SPEED; if (keys & IK_RIGHT) angleY += CAM_ROT_SPEED; - angleX = X_CLAMP(angleX, -CAM_ROT_X_MAX, CAM_ROT_X_MAX); + angleX = X_CLAMP(angleX, -CAMERA_ANGLE_MAX, CAMERA_ANGLE_MAX); if (keys & IK_A) { @@ -133,6 +163,166 @@ struct Camera return true; } + bool trace(const Location &from, Location &to, int32 radius) + { + vec3i d = to.pos - from.pos; + d.x >>= CAMERA_TRACE_SHIFT; + d.y >>= CAMERA_TRACE_SHIFT; + d.z >>= CAMERA_TRACE_SHIFT; + + Room* room = from.room; + vec3i pos = from.pos; + int32 i; + + for (i = 0; i < CAMERA_TRACE_STEPS; i++) + { + if (radius) + { + to.pos = pos; + to.room = room; + } + + pos += d; + room = room->getRoom(pos.x, pos.y, pos.z); + + const Sector* sector = room->getSector(pos.x, pos.z); + int32 floor = sector->getFloor(pos.x, pos.y, pos.z); + int32 ceiling = sector->getCeiling(pos.x, pos.y, pos.z); + + if (floor == WALL || ceiling == WALL || ceiling >= floor) + { + return false; + } + + int32 h = pos.y - floor; + if (h > 0) + { + if (h >= radius) { + return false; + } + pos.y = floor; + } + + h = ceiling - pos.y; + if (h > 0) + { + if (h >= radius) { + return false; + } + pos.y = ceiling; + } + } + + to.pos = pos; + to.room = room; + + return true; + } + + Location getLocationForAngle(int32 angle, int32 distH, int32 distV) + { + Location res; + res.pos.x = target.pos.x - (distH * phd_sin(angle) >> FIXED_SHIFT); + res.pos.y = target.pos.y + (distV); + res.pos.z = target.pos.z - (distH * phd_cos(angle) >> FIXED_SHIFT); + res.room = target.room; + return res; + } + + Location getBestLocation(Item* item) + { + int32 distH = targetDist * phd_cos(targetAngleX) >> FIXED_SHIFT; + int32 distV = targetDist * phd_sin(targetAngleX) >> FIXED_SHIFT; + + Location best = getLocationForAngle(targetAngleY + item->angle.y, distH, distV); + + if (trace(target, best, 200)) + return best; + + int32 distQ = X_SQR(target.pos.x - best.pos.x) + X_SQR(target.pos.z - best.pos.z); + + if (distQ > X_SQR(768)) + return best; + + int32 minDistQ = INT_MAX; + + for (int32 i = 0; i < 4; i++) + { + Location tmp = getLocationForAngle(i * ANGLE_90, distH, distV); + + if (!trace(target, tmp, 200) || !trace(tmp, view, 0)) { + continue; + } + + distQ = X_SQR(view.pos.x - tmp.pos.x) + X_SQR(view.pos.z - tmp.pos.z); + + if (distQ < minDistQ) + { + minDistQ = distQ; + best = tmp; + } + } + + return best; + } + + void move(const Location &to, int32 speed) + { + vec3i d = to.pos - view.pos; + if (speed > 1) + { + d.x /= speed; + d.y /= speed; + d.z /= speed; + } + + view.pos += d; + view.room = to.room->getRoom(view.pos.x, view.pos.y, view.pos.z); + } + + void updateFollow(Item* item) + { + if (targetAngleX == 0) { + targetAngleX = CAMERA_ANGLE_FOLLOW; + } + + targetAngleX = X_CLAMP(targetAngleX + item->angle.x, -CAMERA_ANGLE_MAX, CAMERA_ANGLE_MAX); + + Location best = getBestLocation(item); + + move(best, lastFixed ? speed : 12); + } + + void updateCombat() + { + // + } + + void updateLook() + { + // + } + + void updateFixed() + { + const FixedCamera* cam = cameras + index; + + Location best; + best.pos = cam->pos; + best.room = rooms + cam->roomIndex; + + lastFixed = true; + move(best, 1); + + if (timer != 0) + { + timer--; + if (timer == 0) { + timer = -1; + } + } + } + void update() { if (keys & IK_START) @@ -152,51 +342,98 @@ struct Camera if (mode == CAMERA_MODE_FREE) { - freeControl(); - } - - if (mode == CAMERA_MODE_FOLLOW && item) - { - int32 tx = item->pos.x; - int32 ty = item->pos.y; - int32 tz = item->pos.z; - - const Bounds &box = item->getBoundingBox(); - ty += box.maxY + ((box.minY - box.maxY) * 3 >> 2); - - target.pos.x = tx; - target.pos.y += (ty - target.pos.y) >> 2; - target.pos.z = tz; - - int16 angle = item->angle.y + targetAngleY; - - int32 dy = targetDist * phd_sin(targetAngleX) >> FIXED_SHIFT; - int32 dz = targetDist * phd_cos(targetAngleX) >> FIXED_SHIFT; - - int32 cx = target.pos.x - (phd_sin(angle) * dz >> FIXED_SHIFT); - int32 cy = target.pos.y - 256 + dy; - int32 cz = target.pos.z - (phd_cos(angle) * dz >> FIXED_SHIFT); - - view.pos.x += (cx - view.pos.x) >> 2; - view.pos.y += (cy - view.pos.y) >> 2; - view.pos.z += (cz - view.pos.z) >> 2; - + updateFree(); + prepareFrustum(); + matrixSetView(view.pos, angleX, angleY); updateRoom(); - - vec3i dir = target.pos - view.pos; - anglesFromVector(dir.x, dir.y, dir.z, angleX, angleY); + return; } + bool isFixed = false; + Item* item = laraItem; + + if (lookAtItem && (mode == CAMERA_MODE_FIXED || mode == CAMERA_MODE_OBJECT)) + { + isFixed = true; + item = lookAtItem; + } + + ASSERT(item); + + target.room = item->room; + + const Bounds &box = item->getBoundingBox(); + + int32 y = item->pos.y; + if (isFixed) { + y += (box.minY + box.maxY) >> 1; + } else { + y += box.maxY + ((box.minY - box.maxY) * 3 >> 2); + } + + if (mode == CAMERA_MODE_LOOK || mode == CAMERA_MODE_COMBAT) + { + y -= 256; + + if (lastFixed) { + target.pos.y = 0; + speed = 1; + } else { + target.pos.y += (y - target.pos.y) >> 2; + speed = (mode == CAMERA_MODE_LOOK) ? 4 : 8; + } + } else { + target.pos.x = item->pos.x; + target.pos.z = item->pos.z; + + if (center) + { + int32 offset = (box.minZ + box.maxZ) >> 1; + target.pos.x += (phd_sin(item->angle.y) * offset) >> FIXED_SHIFT; + target.pos.z += (phd_cos(item->angle.y) * offset) >> FIXED_SHIFT; + } + + lastFixed ^= isFixed; + + if (lastFixed) { + target.pos.y = y; + speed = 1; + } else { + target.pos.y += (y - target.pos.y) >> 2; + } + } + + switch (mode) + { + case CAMERA_MODE_FOLLOW : updateFollow(item); break; + case CAMERA_MODE_COMBAT : updateCombat(); break; + case CAMERA_MODE_LOOK : updateLook(); break; + default : updateFixed(); + } + + lastFixed = isFixed; + lastIndex = index; + + if (mode != CAMERA_MODE_OBJECT || timer == -1) + { + mode = CAMERA_MODE_FOLLOW; + index = -1; + lastItem = lookAtItem; + lookAtItem = NULL; + targetAngleX = 0; + targetAngleY = 0; + targetDist = CAM_DIST_FOLLOW; + center = false; + } + + vec3i dir = target.pos - view.pos; + anglesFromVector(dir.x, dir.y, dir.z, angleX, angleY); + prepareFrustum(); matrixSetView(view.pos, angleX, angleY); updateRoom(); - - // reset additional angles, Lara states can override it during the update proc - targetAngleX = 0; - targetAngleY = 0; - targetDist = CAM_DIST_FOLLOW; } void prepareFrustum() diff --git a/src/platform/gba/collision.h b/src/platform/gba/collision.h index 0d717d5..d04b85f 100644 --- a/src/platform/gba/collision.h +++ b/src/platform/gba/collision.h @@ -47,7 +47,7 @@ struct CollisionInfo vec3i pos; int16 angle; - int16 quadrant; + uint16 quadrant; CollisionType type; @@ -132,8 +132,8 @@ bool collideStatic(Room* room, CollisionInfo &cinfo, const vec3i &p, int32 heigh { const RoomMesh* mesh = room->data.meshes + i; - #ifdef NO_STATIC_MESHES - if (mesh->id != STATIC_MESH_GATE) continue; + #ifdef NO_STATIC_MESH_PLANTS + if (mesh->id < 10) continue; #endif const StaticMesh* staticMesh = staticMeshes + mesh->id; @@ -191,8 +191,8 @@ bool collideStatic(Room* room, CollisionInfo &cinfo, const vec3i &p, int32 heigh void collideRoom(Item* item, int32 height, int32 yOffset = 0) { - cinfo.type = CT_NONE; - cinfo.offset = vec3i(0, 0, 0); + cinfo.type = CT_NONE; + cinfo.offset = vec3i(0, 0, 0); vec3i p = item->pos; p.y += yOffset; @@ -203,8 +203,10 @@ void collideRoom(Item* item, int32 height, int32 yOffset = 0) int32 floor, ceiling; + Room* room = item->room; + #define CHECK_HEIGHT(v) {\ - const Room* room = item->room->getRoom(v.x, cy, v.z);\ + room = room->getRoom(v.x, cy, v.z);\ const Sector* sector = room->getSector(v.x, v.z);\ floor = sector->getFloor(v.x, cy, v.z);\ if (floor != WALL) floor -= p.y;\ diff --git a/src/platform/gba/common.cpp b/src/platform/gba/common.cpp index a858e39..bd170ba 100644 --- a/src/platform/gba/common.cpp +++ b/src/platform/gba/common.cpp @@ -12,6 +12,31 @@ EWRAM_DATA SaveGame gSaveGame; const FloorData* gLastFloorData; FloorData gLastFloorSlant; +int32 rand_seed_ctrl; +int32 rand_seed_draw; + +void set_seed_ctrl(int32 seed) +{ + rand_seed_ctrl = seed; +} + +void set_seed_draw(int32 seed) +{ + rand_seed_draw = seed; +} + +#define X_RAND(seed) (((seed = 0x3039 + seed * 0x41C64E6D) >> 10) & 0x7FFF); + +int16 rand_ctrl() +{ + return X_RAND(rand_seed_ctrl); +} + +int16 rand_draw() +{ + return X_RAND(rand_seed_draw); +} + const uint16 divTable[DIV_TABLE_SIZE] = { // ROM, not a big difference with IWRAM 0xFFFF, 0xFFFF, 0x8000, 0x5555, 0x4000, 0x3333, 0x2AAA, 0x2492, 0x2000, 0x1C71, 0x1999, 0x1745, 0x1555, 0x13B1, 0x1249, 0x1111, @@ -805,7 +830,9 @@ void matrixFrame(const vec3i &pos, uint16* angles) matrixRotateYXZ(angleX, angleY, angleZ); } -#define LERP_FAST(a, b, mul, div) a = (a + b) >> 1 +#define LERP_1(a, b, mul, div) a = (b + a) >> 1 +#define LERP_2(a, b, mul, div) a = a + ((b - a) >> 2) +#define LERP_3(a, b, mul, div) a = b - ((b - a) >> 2) #define LERP_SLOW(a, b, mul, div) a = a + (b - a) * mul / div #define LERP_ROW(lerp_func, a, b, mul, div) \ @@ -818,15 +845,23 @@ void matrixLerp(const Matrix &n, int32 multiplier, int32 divider) { Matrix &m = matrixGet(); - if (divider == 2) { - LERP_ROW(LERP_FAST, m[0], n[0], multiplier, divider); - LERP_ROW(LERP_FAST, m[1], n[1], multiplier, divider); - LERP_ROW(LERP_FAST, m[2], n[2], multiplier, divider); + if ((divider == 2) || ((divider == 4) && (multiplier == 2))) { + LERP_ROW(LERP_1, m[0], n[0], multiplier, divider); + LERP_ROW(LERP_1, m[1], n[1], multiplier, divider); + LERP_ROW(LERP_1, m[2], n[2], multiplier, divider); + } else if (multiplier == 1) { + LERP_ROW(LERP_2, m[0], n[0], multiplier, divider); + LERP_ROW(LERP_2, m[1], n[1], multiplier, divider); + LERP_ROW(LERP_2, m[2], n[2], multiplier, divider); } else { - LERP_ROW(LERP_SLOW, m[0], n[0], multiplier, divider); - LERP_ROW(LERP_SLOW, m[1], n[1], multiplier, divider); - LERP_ROW(LERP_SLOW, m[2], n[2], multiplier, divider); + LERP_ROW(LERP_3, m[0], n[0], multiplier, divider); + LERP_ROW(LERP_3, m[1], n[1], multiplier, divider); + LERP_ROW(LERP_3, m[2], n[2], multiplier, divider); } + + //LERP_ROW(LERP_SLOW, m[0], n[0], multiplier, divider); + //LERP_ROW(LERP_SLOW, m[1], n[1], multiplier, divider); + //LERP_ROW(LERP_SLOW, m[2], n[2], multiplier, divider); } void matrixSetIdentity() diff --git a/src/platform/gba/common.h b/src/platform/gba/common.h index b48cb66..e97724b 100644 --- a/src/platform/gba/common.h +++ b/src/platform/gba/common.h @@ -29,7 +29,7 @@ //#define DEBUG_OVERDRAW #endif -#define NO_STATIC_MESHES +#define NO_STATIC_MESH_PLANTS #if defined(_WIN32) #define _CRT_SECURE_NO_WARNINGS @@ -98,8 +98,6 @@ #define ALIGN16 __attribute__((aligned(16))) #endif -#define UNUSED(x) (void)(x) - typedef signed char int8; typedef signed short int16; typedef signed int int32; @@ -149,6 +147,9 @@ typedef int16 Index; extern uint16 fb[VRAM_WIDTH * FRAME_HEIGHT]; #endif +// system +int32 osGetSystemTimeMS(); + #ifdef PROFILE #if defined(_WIN32) @@ -228,6 +229,13 @@ struct vec3s { vec3s() {} X_INLINE vec3s(int16 x, int16 y, int16 z) : x(x), y(y), z(z) {} + + X_INLINE vec3s operator + (const vec3s &v) const { return vec3s(x + v.x, y + v.y, z + v.z); } + X_INLINE vec3s operator - (const vec3s &v) const { return vec3s(x - v.x, y - v.y, z - v.z); } + X_INLINE bool operator == (const vec3s &v) { return x == v.x && y == v.y && z == v.z; } + X_INLINE bool operator != (const vec3s &v) { return x != v.x || y != v.y || z != v.z; } + X_INLINE vec3s& operator += (const vec3s &v) { x += v.x; y += v.y; z += v.z; return *this; } + X_INLINE vec3s& operator -= (const vec3s &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } }; struct vec3i { @@ -239,6 +247,10 @@ struct vec3i { X_INLINE vec3i(vec3s &v) : x(v.x), y(v.y), z(v.z) {} X_INLINE vec3i operator + (const vec3i &v) const { return vec3i(x + v.x, y + v.y, z + v.z); } X_INLINE vec3i operator - (const vec3i &v) const { return vec3i(x - v.x, y - v.y, z - v.z); } + X_INLINE vec3i operator * (int32 s) const { return vec3i(x * s, y * s, z * s); } + X_INLINE vec3i operator / (int32 s) const { return vec3i(x / s, y / s, z / s); } + X_INLINE bool operator == (const vec3i &v) const { return x == v.x && y == v.y && z == v.z; } + X_INLINE bool operator != (const vec3i &v) const { return x != v.x || y != v.y || z != v.z; } X_INLINE vec3i& operator += (const vec3i &v) { x += v.x; y += v.y; z += v.z; return *this; } X_INLINE vec3i& operator -= (const vec3i &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } }; @@ -308,6 +320,10 @@ struct Bounds { X_INLINE Bounds() {} X_INLINE Bounds(int16 minX, int16 maxX, int16 minY, int16 maxY, int16 minZ, int16 maxZ) : minX(minX), maxX(maxX), minY(minY), maxY(maxY), minZ(minZ), maxZ(maxZ) {} + + X_INLINE vec3i getCenter() const { + return vec3i((maxX + minX) >> 1, (maxY + minY) >> 1, (maxZ + minZ) >> 1); + } }; struct AABB { @@ -327,10 +343,9 @@ struct RoomVertex { }; struct RoomSprite { - int16 x, y, z; + vec3s pos; uint8 g; - uint8 reserved; - uint16 texture; + uint8 index; }; struct Portal { @@ -405,7 +420,12 @@ struct RoomInfo uint8 xSectors; uint8 zSectors; uint8 alternateRoom; - uint8 flags; + union { + uint8 value; + struct { + uint8 water:1, :7; + }; + } flags; RoomData data; }; @@ -427,7 +447,8 @@ struct Room { void add(Item* item); void remove(Item* item); - const Sector* getSector(int32 posX, int32 posZ) const; + const Sector* getSector(int32 x, int32 z) const; + const Sector* getWaterSector(int32 x, int32 z) const; Room* getRoom(int32 x, int32 y, int32 z); bool checkPortal(const Portal* portal); Room** addVisibleRoom(Room** list); @@ -435,19 +456,16 @@ struct Room { Room** getNearRooms(const vec3i &pos, int32 radius, int32 height); Room** getAdjRooms(); Room** getVisibleRooms(); - - int32 getWaterLevel(); - int32 getWaterDepth(); }; struct Node { uint32 flags; - vec3i pos; + vec3i pos; }; struct Model { - uint8 type; - int8 count; + uint8 type; + int8 count; uint16 start; uint16 nodeIndex; uint16 animIndex; @@ -475,69 +493,73 @@ struct SoundInfo }; struct Anim { - uint32 frameOffset; - - uint8 frameRate; - uint8 frameSize; - uint16 state; - - int32 speed; - - int32 accel; - - uint16 frameBegin; - uint16 frameEnd; - - uint16 nextAnimIndex; - uint16 nextFrameIndex; - - uint16 statesCount; - uint16 statesStart; - - uint16 commandsCount; - uint16 commandsStart; + uint32 frameOffset; + uint8 frameRate; + uint8 frameSize; + uint16 state; + int32 speed; + int32 accel; + uint16 frameBegin; + uint16 frameEnd; + uint16 nextAnimIndex; + uint16 nextFrameIndex; + uint16 statesCount; + uint16 statesStart; + uint16 commandsCount; + uint16 commandsStart; }; struct AnimState { - uint8 state; - uint8 rangesCount; + uint8 state; + uint8 rangesCount; uint16 rangesStart; }; struct AnimRange { - uint16 frameBegin; - uint16 frameEnd; - uint16 nextAnimIndex; - uint16 nextFrameIndex; + uint16 frameBegin; + uint16 frameEnd; + uint16 nextAnimIndex; + uint16 nextFrameIndex; }; struct AnimFrame { - Bounds box; - vec3s pos; + Bounds box; + vec3s pos; uint16 angles[1]; }; struct Texture { - uint16 attribute; - uint16 tile:14, :2; - uint32 uv0; - uint32 uv1; - uint32 uv2; - uint32 uv3; + uint16 attribute; + uint16 tile:14, :2; + uint32 uv0; + uint32 uv1; + uint32 uv2; + uint32 uv3; }; struct Sprite { - uint16 tile; - uint8 u, v; - uint16 w, h; - int16 l, t, r, b; + uint16 tile; + uint8 u, v; + uint8 w, h; + int16 l, t, r, b; }; struct SpriteSeq { - uint16 type; - uint16 unused; - int16 count; - int16 start; + uint16 type; + uint16 unused; + int16 count; + int16 start; +}; + +struct FixedCamera { + vec3i pos; + int16 roomIndex; + union { + uint16 value; + struct { + uint16 timer:8, once:1, speed:5, :2; + }; + } flags; }; struct ItemInfo @@ -546,7 +568,9 @@ struct ItemInfo uint8 roomIndex; vec3s pos; uint16 intensity; - uint16 flags; + struct { + uint16 value:14, angle:2; + } flags; }; #define ITEM_FLAGS_MASK_ALL 0x1F @@ -631,8 +655,8 @@ struct Lara; E( TRAP_DOOR_2 ) \ E( UNUSED_4 ) \ E( BRIDGE_FLAT ) \ - E( BRIDGE_TILT1 ) \ - E( BRIDGE_TILT2 ) \ + E( BRIDGE_TILT_1 ) \ + E( BRIDGE_TILT_2 ) \ E( INV_PASSPORT ) \ E( INV_COMPASS ) \ E( INV_HOME ) \ @@ -680,14 +704,14 @@ struct Lara; E( INV_PUZZLE_2 ) \ E( INV_PUZZLE_3 ) \ E( INV_PUZZLE_4 ) \ - E( PUZZLE_HOLE_1 ) \ - E( PUZZLE_HOLE_2 ) \ - E( PUZZLE_HOLE_3 ) \ - E( PUZZLE_HOLE_4 ) \ - E( PUZZLE_DONE_1 ) \ - E( PUZZLE_DONE_2 ) \ - E( PUZZLE_DONE_3 ) \ - E( PUZZLE_DONE_4 ) \ + E( PUZZLEHOLE_1 ) \ + E( PUZZLEHOLE_2 ) \ + E( PUZZLEHOLE_3 ) \ + E( PUZZLEHOLE_4 ) \ + E( PUZZLEHOLE_DONE_1 ) \ + E( PUZZLEHOLE_DONE_2 ) \ + E( PUZZLEHOLE_DONE_3 ) \ + E( PUZZLEHOLE_DONE_4 ) \ E( LEADBAR ) \ E( INV_LEADBAR ) \ E( MIDAS_HAND ) \ @@ -699,10 +723,10 @@ struct Lara; E( INV_KEY_ITEM_2 ) \ E( INV_KEY_ITEM_3 ) \ E( INV_KEY_ITEM_4 ) \ - E( KEY_HOLE_1 ) \ - E( KEY_HOLE_2 ) \ - E( KEY_HOLE_3 ) \ - E( KEY_HOLE_4 ) \ + E( KEYHOLE_1 ) \ + E( KEYHOLE_2 ) \ + E( KEYHOLE_3 ) \ + E( KEYHOLE_4 ) \ E( UNUSED_5 ) \ E( UNUSED_6 ) \ E( SCION_PICKUP_QUALOPEC ) \ @@ -739,9 +763,9 @@ struct Lara; E( UNUSED_16 ) \ E( UNUSED_17 ) \ E( LAVA_PARTICLE ) \ - E( TRAP_LAVA_EMITTER ) \ + E( LAVA_EMITTER ) \ E( FLAME ) \ - E( TRAP_FLAME_EMITTER ) \ + E( FLAME_EMITTER ) \ E( TRAP_LAVA ) \ E( MUTANT_EGG_BIG ) \ E( BOAT ) \ @@ -772,7 +796,7 @@ struct Item { union { struct { - uint16 save:1, gravity:1, active:1, status:2, collision:1, custom:2, once:1, mask:5, reverse:1, shadow:1; // TODO + uint16 save:1, gravity:1, active:1, status:2, collision:1, dozy:1, custom:1, once:1, mask:5, reverse:1, shadow:1; // TODO }; uint16 value; } flags; @@ -795,15 +819,18 @@ struct Item { }; int16 moveAngle; - int16 floor; - int16 turnSpeed; - int16 vSpeedHack; uint16 input; uint8 type; uint8 intensity; + int16 roomFloor; + int16 vSpeedHack; + int16 swimTimer; + uint8 weaponState; + uint8 _reserved; + Item* nextItem; Item* nextActive; @@ -821,19 +848,42 @@ struct Item { const Anim* animSet(int32 newAnimIndex, bool resetState, int32 frameOffset = 0); const Anim* animChange(const Anim* anim); void animCmd(bool fx, const Anim* anim); + void animSkip(int32 stateBefore, int32 stateAfter, bool advance = false); + void animProcess(bool movement = true); + bool animIsEnd(int32 offset) const; + bool moveTo(const vec3i &point, Item* item, bool lerp); - void skipAnim(); - void updateAnim(bool movement = true); void updateRoom(int32 offset = 0); - int32 calcLighting(const Bounds& box) const; + + vec3i getRelative(const vec3i &point) const; + + int32 getWaterLevel(); + int32 getWaterDepth(); Item* init(Room* room); X_INLINE Item() {} Item(Room* room); + virtual void collide(Lara* lara, CollisionInfo* cinfo); virtual void update(); virtual void draw(); - virtual void collide(Lara* lara, CollisionInfo* cinfo); +}; + +enum WeaponState { + WEAPON_STATE_FREE, + WEAPON_STATE_BUSY, + WEAPON_STATE_DRAW, + WEAPON_STATE_HOLSTER, + WEAPON_STATE_READY +}; + +enum WeaponType { + WEAPON_TYPE_NONE, + WEAPON_TYPE_PISTOLS, + WEAPON_TYPE_MAGNUMS, + WEAPON_TYPE_UZIS, + WEAPON_TYPE_SHOTGUN, + WEAPON_TYPE_MAX }; struct SaveGame @@ -842,7 +892,8 @@ struct SaveGame uint8 once:1, mask:5, :2; }; - uint8 secrets; + uint8 secrets; + uint8 pickups; TrackFlags tracks[64]; }; @@ -927,9 +978,9 @@ enum SlantType { enum WaterState { WATER_STATE_ABOVE, + WATER_STATE_WADE, WATER_STATE_SURFACE, WATER_STATE_UNDER, - WATER_STATE_WADE, }; enum AnimCommand { @@ -1076,8 +1127,10 @@ struct Level { uint16 spriteSequencesCount; uint16 soundSourcesCount; uint16 boxesCount; + uint16 texturesCount; uint16 animTexDataSize; uint16 itemsCount; + uint16 camerasCount; uint16 cameraFramesCount; const uint16* palette; @@ -1098,12 +1151,12 @@ struct Level { const Texture* textures; const Sprite* sprites; const SpriteSeq* spriteSequences; - uint32 cameras; + const FixedCamera* cameras; uint32 soundSources; const Box* boxes; const uint16* overlaps; const uint16* zones[2][3]; - uint32 animTexData; + const int16* animTexData; const ItemInfo* itemsInfo; uint32 cameraFrames; const uint16* soundMap; @@ -1177,9 +1230,12 @@ extern int32 fps; #define MAX_MODELS ITEM_MAX #define MAX_ROOMS 139 // LEVEL7A #define MAX_STATIC_MESHES 50 -#define MAX_VERTICES 3072 +#define MAX_CAMERAS 16 +#define MAX_VERTICES (4*1024) +#define MAX_TEXTURES 1536 #define MAX_FACES 1024 #define MAX_ROOM_LIST 16 +#define MAX_CAUSTICS 32 #define FOV_SHIFT 3 #define FOG_SHIFT 1 @@ -1196,7 +1252,8 @@ extern int32 fps; #define FACE_COLORED 0x4000 #define FACE_CLIPPED 0x2000 #define FACE_FLAT 0x1000 -#define FACE_SHADOW 0x0800 +#define FACE_SPRITE 0x0800 +#define FACE_SHADOW (FACE_COLORED | FACE_FLAT | FACE_SPRITE) #define FACE_TEXTURE 0x07FF #define NO_ROOM 0xFF @@ -1216,6 +1273,7 @@ extern int32 fps; #define X_MIN(a,b) ((a) < (b) ? (a) : (b)) #define X_MAX(a,b) ((a) > (b) ? (a) : (b)) #define X_SQR(x) ((x) * (x)) +#define X_COUNT(x) int32(sizeof(x) / sizeof(x[0])) #define DP43c(a,bx,by,bz) ((a).x * bx + (a).y * by + (a).z * bz + (a).w) #define DP33c(a,bx,by,bz) ((a).x * bx + (a).y * by + (a).z * bz) @@ -1298,12 +1356,10 @@ X_INLINE void swap(T &a, T &b) { b = tmp; } -X_INLINE int32 classify(const Vertex &v, const Rect &clip) { - return (v.x < clip.x0 ? 1 : 0) | - (v.x > clip.x1 ? 2 : 0) | - (v.y < clip.y0 ? 4 : 0) | - (v.y > clip.y1 ? 8 : 0); -} +void set_seed_ctrl(int32 seed); +void set_seed_draw(int32 seed); +int16 rand_ctrl(); +int16 rand_draw(); int32 phd_sin(int32 x); int32 phd_cos(int32 x); @@ -1363,10 +1419,11 @@ int32 rectIsVisible(const Rect* rect); int32 boxIsVisible(const Bounds* box); void transform(int32 vx, int32 vy, int32 vz, int32 vg); bool transformBoxRect(const Bounds* box, Rect* rect); -void transformRoom(const RoomVertex* vertices, int32 vCount); -void transformMesh(const vec3s* vertices, int32 vCount, uint16 intensity); +void transformRoom(const RoomVertex* vertices, int32 vCount, bool applyCaustics); +void transformMesh(const vec3s* vertices, int32 vCount, const uint16* vIntensity, const vec3s* vNormal); void faceAddQuad(uint32 flags, const Index* indices, int32 startVertex); void faceAddTriangle(uint32 flags, const Index* indices, int32 startVertex); +void faceAddSprite(int32 vx, int32 vy, int32 vz, int32 vg, int32 index); void faceAddRoom(const Quad* quads, int32 qCount, const Triangle* triangles, int32 tCount, int32 startVertex); void faceAddMesh(const Quad* rFaces, const Quad* crFaces, const Triangle* tFaces, const Triangle* ctFaces, int32 rCount, int32 crCount, int32 tCount, int32 ctCount, int32 startVertex); @@ -1377,7 +1434,7 @@ void readLevel(const uint8 *data); Lara* getLara(const vec3i &pos); bool useSwitch(Item* item, int32 timer); -bool useKey(Item* item); +bool useKey(Item* item, Item* lara); bool usePickup(Item* item); void musicPlay(int32 track); @@ -1395,13 +1452,4 @@ X_INLINE void dmaFill(void *dst, uint8 value, uint32 count) #endif } -X_INLINE int16 xRand() -{ -#ifdef __GBA__ - return qran(); -#else - return rand(); -#endif -} - #endif diff --git a/src/platform/gba/draw.h b/src/platform/gba/draw.h index 253b577..a886f20 100644 --- a/src/platform/gba/draw.h +++ b/src/platform/gba/draw.h @@ -4,7 +4,85 @@ #include "common.h" #include "item.h" -extern AABB fastClipAABB; +int32 lightAmbient; + +int32 caustics[MAX_CAUSTICS]; +int32 causticsRand[MAX_CAUSTICS]; +int32 causticsFrame; + +void drawInit() +{ + for (int32 i = 0; i < MAX_CAUSTICS; i++) + { + int16 rot = i * (ANGLE_90 * 4) / MAX_CAUSTICS; + caustics[i] = phd_sin(rot) * 768 >> FIXED_SHIFT; + causticsRand[i] = (rand_draw() >> 5) - 511; + } +} + +void drawFree() +{ + // +} + +void calcLightingDynamic(const Room* room, const vec3i &point) +{ + const RoomInfo* info = room->info; + + lightAmbient = (info->ambient << 5); + + if (!info->lightsCount) + return; + + lightAmbient = 8191 - lightAmbient; + int32 maxLum = 0; + + for (int i = 0; i < info->lightsCount; i++) + { + const Light* light = room->data.lights + i; + + vec3i pos; + pos.x = light->pos.x + (info->x << 8); + pos.y = light->pos.y; + pos.z = light->pos.z + (info->z << 8); + + int32 radius = light->radius << 8; + int32 intensity = light->intensity << 5; + + vec3i d = point - pos; + int32 dist = dot(d, d) >> 12; + int32 att = X_SQR(radius) >> 12; + + int32 lum = (intensity * att) / (dist + att) + lightAmbient; + + if (lum > maxLum) + { + maxLum = lum; + } + } + + lightAmbient = 8191 - ((maxLum + lightAmbient) >> 1); + + Matrix &m = matrixGet(); + + int32 fogZ = m[2].w >> FIXED_SHIFT; + if (fogZ > FOG_MIN) { + lightAmbient += (fogZ - FOG_MIN) << FOG_SHIFT; + lightAmbient = X_MIN(lightAmbient, 8191); + } +} + +void calcLightingStatic(int32 intensity) +{ + lightAmbient = intensity - 4096; + + Matrix &m = matrixGet(); + + int32 fogZ = m[2].w >> FIXED_SHIFT; + if (fogZ > FOG_MIN) { + lightAmbient += (fogZ - FOG_MIN) << FOG_SHIFT; + } +} void drawNumber(int32 number, int32 x, int32 y) { @@ -22,7 +100,7 @@ void drawNumber(int32 number, int32 x, int32 y) } } -void drawMesh(int16 meshIndex, uint16 intensity) +void drawMesh(int16 meshIndex) { int32 offset = level.meshOffsets[meshIndex]; const uint8* ptr = level.meshData + offset; @@ -33,12 +111,17 @@ void drawMesh(int16 meshIndex, uint16 intensity) const vec3s* vertices = (vec3s*)ptr; ptr += vCount * 3 * sizeof(int16); + const uint16* vIntensity = NULL; + const vec3s* vNormal = NULL; + int16 nCount = *(int16*)ptr; ptr += 2; //const int16* normals = (int16*)ptr; if (nCount > 0) { // normals + vNormal = (vec3s*)ptr; ptr += nCount * 3 * sizeof(int16); } else { // intensity - ptr += vCount * sizeof(int16); + vIntensity = (uint16*)ptr; + ptr += vCount * sizeof(uint16); } int16 rCount = *(int16*)ptr; ptr += 2; @@ -56,7 +139,7 @@ void drawMesh(int16 meshIndex, uint16 intensity) int32 startVertex = gVerticesCount; PROFILE_START(); - transformMesh(vertices, vCount, intensity); + transformMesh(vertices, vCount, vIntensity, vNormal); PROFILE_STOP(dbg_transform); PROFILE_START(); @@ -106,16 +189,17 @@ void drawShadow(const Item* item, int32 size) 6, 3, 4, 5 }; - faceAddQuad(FACE_COLORED | FACE_FLAT | FACE_SHADOW, indices + 0, startVertex); - faceAddQuad(FACE_COLORED | FACE_FLAT | FACE_SHADOW, indices + 4, startVertex); - faceAddQuad(FACE_COLORED | FACE_FLAT | FACE_SHADOW, indices + 8, startVertex); + faceAddQuad(FACE_SHADOW, indices + 0, startVertex); + faceAddQuad(FACE_SHADOW, indices + 4, startVertex); + faceAddQuad(FACE_SHADOW, indices + 8, startVertex); matrixPop(); } void drawSprite(const Item* item) { - // TODO + vec3i d = item->pos - cameraViewPos; + faceAddSprite(d.x, d.y, d.z, item->intensity << 5, models[item->type].start); } void drawModel(const Item* item, uint16* meshOverrides) @@ -134,7 +218,10 @@ void drawModel(const Item* item, uint16* meshOverrides) int32 intensity = item->intensity << 5; if (intensity == 0) { - intensity = item->calcLighting(frame->box); + vec3i point = item->getRelative(frame->box.getCenter()); + calcLightingDynamic(item->room, point); + } else { + calcLightingStatic(intensity); } int32 vis = boxIsVisible(&frame->box); @@ -156,7 +243,7 @@ void drawModel(const Item* item, uint16* meshOverrides) matrixFrame(frame->pos, frameAngles); - drawMesh(meshOverrides ? meshOverrides[0] : model->start, intensity); + drawMesh(meshOverrides ? meshOverrides[0] : model->start); for (int32 i = 1; i < model->count; i++) { @@ -166,7 +253,7 @@ void drawModel(const Item* item, uint16* meshOverrides) frameAngles += 2; matrixFrame(node->pos, frameAngles); - drawMesh(meshOverrides ? meshOverrides[i] : (model->start + i), intensity); + drawMesh(meshOverrides ? meshOverrides[i] : (model->start + i)); node++; } @@ -209,21 +296,27 @@ void drawRoom(const Room* room) enableClipping = true; PROFILE_START(); - transformRoom(data.vertices, info->verticesCount); + transformRoom(data.vertices, info->verticesCount, info->flags.water); PROFILE_STOP(dbg_transform); - matrixPop(); - PROFILE_START(); faceAddRoom(data.quads, info->quadsCount, data.triangles, info->trianglesCount, startVertex); PROFILE_STOP(dbg_poly); + for (int32 i = 0; i < info->spritesCount; i++) + { + const RoomSprite* sprite = data.sprites + i; + faceAddSprite(sprite->pos.x, sprite->pos.y, sprite->pos.z, sprite->g << 5, sprite->index); + } + + matrixPop(); + for (int32 i = 0; i < info->meshesCount; i++) { const RoomMesh* mesh = data.meshes + i; - #ifdef NO_STATIC_MESHES - if (mesh->id != STATIC_MESH_GATE) continue; + #ifdef NO_STATIC_MESH_PLANTS + if (mesh->id < 10) continue; #endif const StaticMesh* staticMesh = staticMeshes + mesh->id; @@ -243,8 +336,10 @@ void drawRoom(const Room* room) int32 vis = boxIsVisible(&staticMesh->vbox); if (vis != 0) { - enableClipping = vis < 0; - drawMesh(staticMesh->meshIndex, mesh->intensity << 5); + enableClipping = true;//vis < 0; // TODO wrong visibility BBox? + + calcLightingStatic(mesh->intensity << 5); + drawMesh(staticMesh->meshIndex); } matrixPop(); @@ -277,103 +372,4 @@ void drawRooms() flush(); } -#ifdef TEST -void faceAddQuad(uint32 flags, const Index* indices, int32 startVertex); - -extern Vertex gVertices[MAX_VERTICES]; - -Rect testClip = { 0, 0, FRAME_WIDTH, FRAME_HEIGHT }; -int32 testTile = 10; // 707 // 712 - -void drawTest() { -#ifdef _WIN32 - Sleep(16); -#endif - - int dx = 0; - int dy = 0; - - if (keys & IK_LEFT) dy++; - if (keys & IK_RIGHT) dy--; - if (keys & IK_UP) dx--; - if (keys & IK_DOWN) dx++; - - if (keys & IK_L) { - testClip.x0 += dx; - testClip.y0 += dy; - } - - if (keys & IK_R) { - testClip.x1 += dx; - testClip.y1 += dy; - } - - if (keys & IK_A) { - testTile++; - //Sleep(100); - } - - if (keys & IK_B) { - testTile--; - //Sleep(100); - } - - //testTile = (testTile + texturesCount) % texturesCount; - - clip = testClip; - - static int vidx = 0; - - if (keys & IK_SELECT) { - vidx++; - //Sleep(100); - } - - gVertices[(vidx + 0) % 4].x = -25; - gVertices[(vidx + 0) % 4].y = -25; - - gVertices[(vidx + 1) % 4].x = 25; - gVertices[(vidx + 1) % 4].y = -25; - - gVertices[(vidx + 2) % 4].x = 50; - gVertices[(vidx + 2) % 4].y = 25; - - gVertices[(vidx + 3) % 4].x = -50; - gVertices[(vidx + 3) % 4].y = 25; - - for (int i = 0; i < 4; i++) - { - gVertices[i].x += FRAME_WIDTH/2; - gVertices[i].y += FRAME_HEIGHT/2; - gVertices[i].z = 100; - gVertices[i].g = 16; - gVertices[i].clip = classify(gVertices[i], clip); - } - gVerticesCount = 4; - - Index indices[] = { 0, 1, 2, 3, 0, 2, 3 }; - - faceAddQuad(testTile, indices, 0); - -#ifdef _WIN32 - for (int y = 0; y < FRAME_HEIGHT; y++) - { - for (int x = 0; x < FRAME_WIDTH; x++) - { - if (x == clip.x0 || x == clip.x1 - 1 || y == clip.y0 || y == clip.y1 - 1) - { - #ifdef MODE4 - ((uint8*)fb)[y * FRAME_WIDTH + x] = 255; - #else - ((uint16*)fb)[y * FRAME_WIDTH + x] = 255; - #endif - } - } - } -#endif - - flush(); -} -#endif - #endif diff --git a/src/platform/gba/enemy.h b/src/platform/gba/enemy.h index 49d7da5..7b13a1f 100644 --- a/src/platform/gba/enemy.h +++ b/src/platform/gba/enemy.h @@ -14,13 +14,11 @@ struct Enemy : Item virtual void collide(Lara* lara, CollisionInfo* cinfo) { // - UNUSED(lara); - UNUSED(cinfo); } virtual void update() { - updateAnim(); + animProcess(); } }; diff --git a/src/platform/gba/game.h b/src/platform/gba/game.h index 8a83bce..60eba65 100644 --- a/src/platform/gba/game.h +++ b/src/platform/gba/game.h @@ -9,25 +9,23 @@ #include "item.h" #include "draw.h" -Lara* players[2]; - -Lara* getLara(const vec3i &pos) -{ - UNUSED(pos); // TODO two players - return players[0]; // TODO find nearest player -} - struct Game { + int32 animTexFrame; + void init() { - loadLevel(LEVEL1_PKD); + set_seed_ctrl(osGetSystemTimeMS() * 3); + set_seed_draw(osGetSystemTimeMS() * 7); + + animTexFrame = 0; + + loadLevel(levelData); } void loadLevel(const void* data) { - camera.init(); - + drawFree(); readLevel((uint8*)data); // prepare rooms @@ -39,34 +37,47 @@ struct Game // prepare items for (int32 i = 0; i < level.itemsCount; i++) { - Item* item = items + i; const ItemInfo* info = level.itemsInfo + i; + Item* item = items + i; item->type = info->type; - item->intensity = info->intensity << 5; + item->intensity = info->intensity; item->pos.x = info->pos.x + (rooms[info->roomIndex].info->x << 8); item->pos.y = info->pos.y; item->pos.z = info->pos.z + (rooms[info->roomIndex].info->z << 8); - item->angle.y = ((info->flags >> 14) - 2) * ANGLE_90; - item->flags.value = info->flags & 0x3FFF; + item->angle.y = (info->flags.angle - 2) * ANGLE_90; + item->flags.value = info->flags.value; item->init(rooms + info->roomIndex); if (item->type == ITEM_LARA) { - camera.item = item; + camera.init(item); + camera.laraItem = item; //#ifdef PROFILE - // debug - //resetItem(item, 0, vec3i(74588, 3072, 19673), 0); // level 1 (first darts) - //resetItem(item, 9, vec3i(49669, 7680, 57891), 0); // level 1 (first door) - //resetItem(item, 10, vec3i(43063, 7168, 61198), 0); // level 1 (transp) - //resetItem(item, 14, vec3i(20215, 6656, 52942), ANGLE_90 + ANGLE_45); // level 1 (bridge) - //resetItem(item, 17, vec3i(16475, 6656, 59845), ANGLE_90); // level 1 (bear) - //resetItem(item, 26, vec3i(24475, 6912, 83505), ANGLE_90); // level 1 (switch timer) - //resetItem(item, 35, vec3i(35149, 2048, 74189), ANGLE_90); // level 1 (switch timer) + // gym + //resetItem(item, 13, vec3i(38953, 3328, 63961), ANGLE_90 + ANGLE_45); // pool + // level 1 + //resetItem(item, 0, vec3i(74588, 3072, 19673), ANGLE_0); // first darts + //resetItem(item, 9, vec3i(49669, 7680, 57891), ANGLE_0); // first door + //resetItem(item, 10, vec3i(43063, 7168, 61198), ANGLE_0); // transp + //resetItem(item, 14, vec3i(20215, 6656, 52942), ANGLE_90 + ANGLE_45); // bridge + //resetItem(item, 17, vec3i(16475, 6656, 59845), ANGLE_90); // bear + //resetItem(item, 26, vec3i(24475, 6912, 83505), ANGLE_90); // switch timer 1 + //resetItem(item, 35, vec3i(35149, 2048, 74189), ANGLE_90); // switch timer 2 + // level 2 + //resetItem(item, 15, vec3i(66179, 0, 25920), -ANGLE_90 - ANGLE_45); // sprites + //resetItem(item, 19, vec3i(61018, 1024, 31214), ANGLE_180); // block + //resetItem(item, 14, vec3i(64026, 512, 20806), ANGLE_0); // key and puzzle + //resetItem(item, 5, vec3i(55644, 0, 29155), -ANGLE_90); // keyhole + //resetItem(item, 71, vec3i(12705, -768, 30195), -ANGLE_90); // puzzle + //resetItem(item, 63, vec3i(31055, -2048, 33406), ANGLE_0); // right room + //resetItem(item, 44, vec3i(27868, -1024, 29191), -ANGLE_90); // swing blades + // level 3a + //resetItem(item, 44, vec3i(73798, 2304, 9819), ANGLE_90); // uw gears //#endif camera.view.pos = camera.target.pos = item->pos; @@ -75,6 +86,8 @@ struct Game players[0] = (Lara*)item; } } + + drawInit(); } void resetItem(Item* item, int32 roomIndex, const vec3i &pos, int32 angleY) @@ -99,11 +112,20 @@ struct Game } } + void nextFrame(int32 frames) + { + causticsFrame += frames; + + animTexFrame += frames; + while (animTexFrame > 5) + { + animTexturesShift(); + animTexFrame -= 5; + } + } + void update(int32 frames) { - #ifdef TEST - return; - #endif if (frames > MAX_UPDATE_FRAMES) { frames = MAX_UPDATE_FRAMES; } @@ -114,6 +136,8 @@ struct Game camera.update(); } + nextFrame(frames); + if (keys & IK_SELECT) { mixer.playMusic(TRACK_13_WAV); } @@ -129,36 +153,22 @@ struct Game clear(); - #ifdef TEST - #ifdef __GBA__ - VBlankIntrWait(); - #endif + #ifdef PROFILE + dbg_transform = 0; + dbg_poly = 0; + dbg_flush = 0; + dbg_vert_count = 0; + dbg_poly_count = 0; + #endif - int32 cycles = 0; - PROFILE_START(); - drawTest(); - PROFILE_STOP(cycles); - - drawNumber(cycles, TEXT_POSX, 32); - #else - #ifdef PROFILE - dbg_transform = 0; - dbg_poly = 0; - dbg_flush = 0; - dbg_vert_count = 0; - dbg_poly_count = 0; - #endif - - drawRooms(); - - #ifdef PROFILE - drawNumber(dbg_transform, TEXT_POSX, 32); - drawNumber(dbg_poly, TEXT_POSX, 48); - drawNumber(dbg_flush, TEXT_POSX, 64); - drawNumber(dbg_vert_count, TEXT_POSX, 84); - drawNumber(dbg_poly_count, TEXT_POSX, 100); - #endif + drawRooms(); + #ifdef PROFILE + drawNumber(dbg_transform, TEXT_POSX, 32); + drawNumber(dbg_poly, TEXT_POSX, 48); + drawNumber(dbg_flush, TEXT_POSX, 64); + drawNumber(dbg_vert_count, TEXT_POSX, 84); + drawNumber(dbg_poly_count, TEXT_POSX, 100); #endif drawNumber(fps, TEXT_POSX, 16); diff --git a/src/platform/gba/inventory.h b/src/platform/gba/inventory.h new file mode 100644 index 0000000..3e7504f --- /dev/null +++ b/src/platform/gba/inventory.h @@ -0,0 +1,319 @@ +#ifndef H_INVENTORY +#define H_INVENTORY + +#include "common.h" + +enum StringID { + STR_PASSPORT + , STR_COMPASS + , STR_HOME + , STR_MAP + , STR_DETAIL + , STR_SOUND + , STR_CONTROLS + , STR_GAMMA + , STR_PISTOLS + , STR_SHOTGUN + , STR_MAGNUMS + , STR_UZIS + , STR_AMMO_PISTOLS + , STR_AMMO_SHOTGUN + , STR_AMMO_MAGNUMS + , STR_AMMO_UZIS + , STR_EXPLOSIVE + , STR_MEDIKIT_SMALL + , STR_MEDIKIT_BIG + , STR_PUZZLE + , STR_PUZZLE_GOLD_IDOL + , STR_PUZZLE_GOLD_BAR + , STR_PUZZLE_COG + , STR_PUZZLE_FUSE + , STR_PUZZLE_ANKH + , STR_PUZZLE_HORUS + , STR_PUZZLE_ANUBIS + , STR_PUZZLE_SCARAB + , STR_PUZZLE_PYRAMID + , STR_LEADBAR + , STR_KEY + , STR_KEY_SILVER + , STR_KEY_RUSTY + , STR_KEY_GOLD + , STR_KEY_SAPPHIRE + , STR_KEY_NEPTUNE + , STR_KEY_ATLAS + , STR_KEY_DAMOCLES + , STR_KEY_THOR + , STR_KEY_ORNATE + , STR_SCION + , STR_MAX +}; + +const char* InvNames[STR_MAX] = { + "Game" + , "Compass" + , "Lara's Home" + , "Map" + , "Detail Levels" + , "Sound" + , "Controls" + , "Gamma" + , "Pistols" + , "Shotgun" + , "Magnums" + , "Uzis" + , "Pistol Clips" + , "Shotgun Shells" + , "Magnum Clips" + , "Uzi Clips" + , "Explosive" + , "Small Medi Pack" + , "Large Medi Pack" + , "Puzzle" + , "Gold Idol" + , "Gold Bar" + , "Machine Cog" + , "Fuse" + , "Ankh" + , "Eye of Horus" + , "Seal of Anubis" + , "Scarab" + , "Pyramid Key" + , "Lead Bar" + , "Key" + , "Silver Key" + , "Rusty Key" + , "Gold Key" + , "Sapphire Key" + , "Neptune Key" + , "Atlas Key" + , "Damocles Key" + , "Thor Key" + , "Ornate Key" + , "Scion" +}; + +enum InvSlot { +// Items + SLOT_LEADBAR + , SLOT_KEY_ITEM_1 + , SLOT_KEY_ITEM_2 + , SLOT_KEY_ITEM_3 + , SLOT_KEY_ITEM_4 + , SLOT_PUZZLE_4 + , SLOT_PUZZLE_3 + , SLOT_PUZZLE_2 + , SLOT_PUZZLE_1 + , SLOT_SCION +// Inventory + , SLOT_COMPASS + , SLOT_MAP + , SLOT_PISTOLS + , SLOT_AMMO_PISTOLS + , SLOT_SHOTGUN + , SLOT_AMMO_SHOTGUN + , SLOT_MAGNUMS + , SLOT_AMMO_MAGNUMS + , SLOT_UZIS + , SLOT_AMMO_UZIS + , SLOT_EXPLOSIVE + , SLOT_MEDIKIT_BIG + , SLOT_MEDIKIT_SMALL +// Options + , SLOT_PASSPORT + , SLOT_DETAIL + , SLOT_SOUND + , SLOT_CONTROLS + , SLOT_GAMMA + , SLOT_HOME + , SLOT_MAX +}; + + +struct InvItem +{ + uint8 type; + uint8 sid; + // TODO params +}; + +const InvItem INV_SLOTS[SLOT_MAX] = { + { ITEM_INV_LEADBAR , STR_LEADBAR } + , { ITEM_INV_KEY_ITEM_1 , STR_KEY } + , { ITEM_INV_KEY_ITEM_2 , STR_KEY } + , { ITEM_INV_KEY_ITEM_3 , STR_KEY } + , { ITEM_INV_KEY_ITEM_4 , STR_KEY } + , { ITEM_INV_PUZZLE_4 , STR_PUZZLE } + , { ITEM_INV_PUZZLE_3 , STR_PUZZLE } + , { ITEM_INV_PUZZLE_2 , STR_PUZZLE } + , { ITEM_INV_PUZZLE_1 , STR_PUZZLE } + , { ITEM_INV_SCION , STR_SCION } + , { ITEM_INV_COMPASS , STR_COMPASS } + , { ITEM_INV_MAP , STR_MAP } + , { ITEM_INV_PISTOLS , STR_PISTOLS } + , { ITEM_INV_AMMO_PISTOLS , STR_AMMO_PISTOLS } + , { ITEM_INV_SHOTGUN , STR_SHOTGUN } + , { ITEM_INV_AMMO_SHOTGUN , STR_AMMO_SHOTGUN } + , { ITEM_INV_MAGNUMS , STR_MAGNUMS } + , { ITEM_INV_AMMO_MAGNUMS , STR_AMMO_MAGNUMS } + , { ITEM_INV_UZIS , STR_UZIS } + , { ITEM_INV_AMMO_UZIS , STR_AMMO_UZIS } + , { ITEM_INV_EXPLOSIVE , STR_EXPLOSIVE } + , { ITEM_INV_MEDIKIT_BIG , STR_MEDIKIT_BIG } + , { ITEM_INV_MEDIKIT_SMALL , STR_MEDIKIT_SMALL } + , { ITEM_INV_PASSPORT , STR_PASSPORT } + , { ITEM_INV_DETAIL , STR_DETAIL } + , { ITEM_INV_SOUND , STR_SOUND } + , { ITEM_INV_CONTROLS , STR_CONTROLS } + , { ITEM_INV_GAMMA , STR_GAMMA } + , { ITEM_INV_HOME , STR_HOME } +}; + + +struct Inventory +{ + InvSlot useSlot; + + int32 numKeys; + + int32 counts[X_COUNT(INV_SLOTS)]; + + Inventory() + { + memset(counts, 0, sizeof(counts)); + + useSlot = SLOT_MAX; + numKeys = 0; + + add(ITEM_INV_PASSPORT); + add(ITEM_INV_DETAIL); + add(ITEM_INV_SOUND); + add(ITEM_INV_CONTROLS); + //add(ITEM_INV_GAMMA); + //add(ITEM_INV_HOME); + + add(ITEM_INV_COMPASS); + add(ITEM_INV_PISTOLS); + + //add(ITEM_INV_KEY_ITEM_1); + //add(ITEM_INV_PUZZLE_1); + } + + ItemType remapToInv(ItemType type) + { + switch (type) + { + case ITEM_PISTOLS : return ITEM_INV_PISTOLS; + case ITEM_SHOTGUN : return ITEM_INV_SHOTGUN; + case ITEM_MAGNUMS : return ITEM_INV_MAGNUMS; + case ITEM_UZIS : return ITEM_INV_UZIS; + case ITEM_AMMO_PISTOLS : return ITEM_INV_AMMO_PISTOLS; + case ITEM_AMMO_SHOTGUN : return ITEM_INV_AMMO_SHOTGUN; + case ITEM_AMMO_MAGNUMS : return ITEM_INV_AMMO_MAGNUMS; + case ITEM_AMMO_UZIS : return ITEM_INV_AMMO_UZIS; + case ITEM_MEDIKIT_SMALL : return ITEM_INV_MEDIKIT_SMALL; + case ITEM_MEDIKIT_BIG : return ITEM_INV_MEDIKIT_BIG; + case ITEM_PUZZLE_1 : return ITEM_INV_PUZZLE_1; + case ITEM_PUZZLE_2 : return ITEM_INV_PUZZLE_2; + case ITEM_PUZZLE_3 : return ITEM_INV_PUZZLE_3; + case ITEM_PUZZLE_4 : return ITEM_INV_PUZZLE_4; + case ITEM_LEADBAR : return ITEM_INV_LEADBAR; + case ITEM_KEY_ITEM_1 : return ITEM_INV_KEY_ITEM_1; + case ITEM_KEY_ITEM_2 : return ITEM_INV_KEY_ITEM_2; + case ITEM_KEY_ITEM_3 : return ITEM_INV_KEY_ITEM_3; + case ITEM_KEY_ITEM_4 : return ITEM_INV_KEY_ITEM_4; + case ITEM_SCION_PICKUP_QUALOPEC : + case ITEM_SCION_PICKUP_DROP : + case ITEM_SCION_PICKUP_HOLDER : return ITEM_INV_SCION; + default : return type; + } + } + + InvSlot remapToSlot(ItemType type) + { + type = remapToInv(type); + + for (int32 i = 0; i < X_COUNT(INV_SLOTS); i++) + { + if (INV_SLOTS[i].type == type) + return (InvSlot)i; + } + + ASSERT(false); + return SLOT_COMPASS; + } + + InvSlot remapHoleToSlot(ItemType type) + { + switch (type) + { + case ITEM_PUZZLEHOLE_1 : return SLOT_PUZZLE_1; + case ITEM_PUZZLEHOLE_2 : return SLOT_PUZZLE_2; + case ITEM_PUZZLEHOLE_3 : return SLOT_PUZZLE_3; + case ITEM_PUZZLEHOLE_4 : return SLOT_PUZZLE_4; + case ITEM_KEYHOLE_1 : return SLOT_KEY_ITEM_1; + case ITEM_KEYHOLE_2 : return SLOT_KEY_ITEM_2; + case ITEM_KEYHOLE_3 : return SLOT_KEY_ITEM_3; + case ITEM_KEYHOLE_4 : return SLOT_KEY_ITEM_4; + default : return SLOT_MAX; + } + } + + void add(ItemType type, int32 count = 1) + { + InvSlot slot = remapToSlot(type); + counts[slot] += count; + // TODO check max + + if (slot < SLOT_COMPASS) { + numKeys += count; + } + } + + void remove(InvSlot slot, int32 count) + { + counts[slot] -= count; + + if (slot < SLOT_COMPASS) { + numKeys -= count; + } + } + + void show(Item* lara, Item* hole) + { + if (hole) { + useSlot = remapHoleToSlot((ItemType)hole->type); + } + } + + bool applyItem(Item* hole) + { + #define CHECK_CASE(A, B) case A: { if (useSlot != B) return false; break; } + + switch (hole->type) + { + CHECK_CASE(ITEM_PUZZLEHOLE_1, SLOT_PUZZLE_1); + CHECK_CASE(ITEM_PUZZLEHOLE_2, SLOT_PUZZLE_2); + CHECK_CASE(ITEM_PUZZLEHOLE_3, SLOT_PUZZLE_3); + CHECK_CASE(ITEM_PUZZLEHOLE_4, SLOT_PUZZLE_4); + CHECK_CASE(ITEM_KEYHOLE_1, SLOT_KEY_ITEM_1); + CHECK_CASE(ITEM_KEYHOLE_2, SLOT_KEY_ITEM_2); + CHECK_CASE(ITEM_KEYHOLE_3, SLOT_KEY_ITEM_3); + CHECK_CASE(ITEM_KEYHOLE_4, SLOT_KEY_ITEM_4); + default: return false; + } + + remove(useSlot, 1); + useSlot = SLOT_MAX; + + return true; + } + + void draw() + { + // + } +}; + +Inventory inventory; + +#endif diff --git a/src/platform/gba/item.h b/src/platform/gba/item.h index f3ae6b2..0f7bc86 100644 --- a/src/platform/gba/item.h +++ b/src/platform/gba/item.h @@ -5,61 +5,59 @@ #include "sound.h" #include "camera.h" #include "draw.h" +#include "room.h" int32 curItemIndex; #define GRAVITY 6 -int16 angleDec(int16 angle, int32 value) { - if (angle < -value) { - return angle + value; - } else if (angle > value) { - return angle - value; - } - return 0; +X_INLINE int16 angleLerp(int16 a, int16 b, int32 w) +{ + int16 d = b - a; + if (d > +w) return a + w; + if (d < -w) return a - w; + return b; } +#define angleDec(angle, value) angleLerp(angle, 0, value) + Mixer::Sample* soundPlay(int16 id, const vec3i &pos) { int16 a = level.soundMap[id]; - if (a == -1) { + if (a == -1) return NULL; - } const SoundInfo* b = level.soundsInfo + a; - if (b->chance && b->chance < xRand()) { + if (b->chance && b->chance < rand_draw()) return NULL; - } vec3i d = pos - camera.target.pos; - if (abs(d.x) >= SND_MAX_DIST || abs(d.y) >= SND_MAX_DIST || abs(d.z) >= SND_MAX_DIST) { + if (abs(d.x) >= SND_MAX_DIST || abs(d.y) >= SND_MAX_DIST || abs(d.z) >= SND_MAX_DIST) return NULL; - } - + int32 volume = b->volume - (phd_sqrt(dot(d, d)) << 2); if (b->flags.gain) { - volume -= xRand() >> 2; + volume -= rand_draw() >> 2; } volume = X_MIN(volume, 0x7FFF) >> 9; - if (volume <= 0) { + if (volume <= 0) return NULL; - } int32 pitch = 128; if (b->flags.pitch) { - pitch += ((xRand() * 13) >> 14) - 13; + pitch += ((rand_draw() * 13) >> 14) - 13; } int32 index = b->index; if (b->flags.count > 1) { - index += (xRand() * b->flags.count) >> 15; + index += (rand_draw() * b->flags.count) >> 15; } const uint8 *data = level.soundData + level.soundOffsets[index]; @@ -68,9 +66,36 @@ Mixer::Sample* soundPlay(int16 id, const vec3i &pos) memcpy(&size, data + 40, 4); // TODO preprocess and remove wave header data += 44; + if (id >= 148 + 25) { + pitch >>= 1; // GYM PC sample rate hack + } + return mixer.playSample(data, size, volume, pitch, b->flags.mode); } +void soundStop(int16 id) +{ + int16 a = level.soundMap[id]; + + if (a == -1) + return; + + const SoundInfo* b = level.soundsInfo + a; + + for (int32 i = 0; i < b->flags.count; i++) + { + int32 index = b->index + i; + + const uint8 *data = level.soundData + level.soundOffsets[index]; + + int32 size; + memcpy(&size, data + 40, 4); // TODO preprocess and remove wave header + data += 44; + + mixer.stopSample(data); + } +} + void musicPlay(int32 track) { if (track > 25 && track < 57) // gym tutorial @@ -189,15 +214,17 @@ void Item::animCmd(bool fx, const Anim* anim) { int32 cmd = *ptr++; - switch (cmd) { + switch (cmd) + { case ANIM_CMD_NONE: break; + case ANIM_CMD_OFFSET: { if (!fx) { - int32 s = phd_sin(moveAngle); - int32 c = phd_cos(moveAngle); + int32 s = phd_sin(angle.y); + int32 c = phd_cos(angle.y); int32 x = ptr[0]; int32 y = ptr[1]; int32 z = ptr[2]; @@ -208,7 +235,9 @@ void Item::animCmd(bool fx, const Anim* anim) ptr += 3; break; } + case ANIM_CMD_JUMP: + { if (!fx) { if (vSpeedHack) { @@ -222,23 +251,37 @@ void Item::animCmd(bool fx, const Anim* anim) } ptr += 2; break; + } + case ANIM_CMD_EMPTY: + { + if (!fx) { + weaponState = WEAPON_STATE_FREE; + } break; + } + case ANIM_CMD_KILL: - if (!fx) - { + { + if (!fx) { flags.status = ITEM_FLAGS_STATUS_INACTIVE; } break; + } + case ANIM_CMD_SOUND: - if (fx && frameIndex == ptr[0]) - { + { + if (fx && frameIndex == ptr[0]) { soundPlay(ptr[1] & 0x03FFF, pos); } ptr += 2; break; + } + case ANIM_CMD_EFFECT: - if (fx && frameIndex == ptr[0]) { + { + if (fx && frameIndex == ptr[0]) + { switch (ptr[1]) { case FX_ROTATE_180 : angle.y += ANGLE_180; break; /* @@ -262,26 +305,42 @@ void Item::animCmd(bool fx, const Anim* anim) } ptr += 2; break; + } } } } -void Item::skipAnim() +void Item::animSkip(int32 stateBefore, int32 stateAfter, bool advance) { + goalState = stateBefore; + vec3i p = pos; while (state != goalState) { - updateAnim(false); + animProcess(false); + } + + if (advance) { + animProcess(); } pos = p; vSpeed = 0; hSpeed = 0; + + goalState = stateAfter; } -void Item::updateAnim(bool movement) +#define ANIM_MOVE_LERP_POS (16) +#define ANIM_MOVE_LERP_ROT (2 * DEG2SHORT) + +void Item::animProcess(bool movement) { + if (models[type].count <= 0) { + return; // TODO sprite animation + } + ASSERT(models[type].count > 0); const Anim* anim = level.anims + animIndex; @@ -311,6 +370,41 @@ void Item::updateAnim(bool movement) #endif } +bool Item::animIsEnd(int32 offset) const +{ + return frameIndex == level.anims[animIndex].frameEnd - offset; +} + +bool Item::moveTo(const vec3i &point, Item* item, bool lerp) +{ + // lerp position + vec3i p = item->getRelative(point); + + if (!lerp) + { + pos = p; + angle = item->angle; + return true; + } + + vec3i posDelta = p - pos; + + int32 dist = phd_sqrt(X_SQR(posDelta.x) + X_SQR(posDelta.y) + X_SQR(posDelta.z)); + + if (dist > ANIM_MOVE_LERP_POS) { + pos += (posDelta * ANIM_MOVE_LERP_POS) / dist; + } else { + pos = p; + } + + // lerp rotation + angle.x = angleLerp(angle.x, item->angle.x, ANIM_MOVE_LERP_ROT); + angle.y = angleLerp(angle.y, item->angle.y, ANIM_MOVE_LERP_ROT); + angle.z = angleLerp(angle.z, item->angle.z, ANIM_MOVE_LERP_ROT); + + return (pos == p && angle == item->angle); +} + Item* Item::add(ItemType type, Room* room, const vec3i &pos, int32 angleY) { if (!Item::sFirstFree) { @@ -324,7 +418,7 @@ Item* Item::add(ItemType type, Room* room, const vec3i &pos, int32 angleY) item->type = type; item->pos = pos; item->angle.y = angleY; - item->intensity = 0; + item->intensity = 128; item->init(room); @@ -342,7 +436,7 @@ void Item::remove() void Item::activate() { - ASSERT(!flags.active) + //ASSERT(!flags.active) flags.active = true; @@ -389,62 +483,47 @@ void Item::updateRoom(int32 offset) } const Sector* sector = room->getSector(pos.x, pos.z); - floor = sector->getFloor(pos.x, pos.y, pos.z); + roomFloor = sector->getFloor(pos.x, pos.y, pos.z); } -int32 Item::calcLighting(const Bounds& box) const +vec3i Item::getRelative(const vec3i &point) const { matrixPush(); + Matrix &m = matrixGet(); - m[0][3] = m[1][3] = m[2][3] = 0; + + matrixSetIdentity(); matrixRotateYXZ(angle.x, angle.y, angle.z); - vec3i p((box.maxX + box.minX) >> 1, - (box.maxY + box.minY) >> 1, - (box.maxZ + box.minZ) >> 1); + vec3i p; + p.x = pos.x + (DP33(m[0], point) >> FIXED_SHIFT); + p.y = pos.y + (DP33(m[1], point) >> FIXED_SHIFT); + p.z = pos.z + (DP33(m[2], point) >> FIXED_SHIFT); - matrixTranslate(p); - - p = vec3i(m[0][3] >> FIXED_SHIFT, - m[1][3] >> FIXED_SHIFT, - m[2][3] >> FIXED_SHIFT) + pos; matrixPop(); - const RoomInfo* info = room->info; + return p; +} - if (!info->lightsCount) { - return info->ambient << 5; +int32 Item::getWaterLevel() +{ + const Sector* sector = room->getWaterSector(pos.x, pos.z); + if (sector) { + return sector->ceiling * 256; } - int32 ambient = 8191 - (info->ambient << 5); - int32 maxLum = 0; + return WALL; +} - for (int i = 0; i < info->lightsCount; i++) - { - const Light* light = room->data.lights + i; +int32 Item::getWaterDepth() +{ + const Sector* sector = room->getWaterSector(pos.x, pos.z); - // TODO preprocess align - vec3i pos; - pos.x = light->pos.x + (info->x << 8); - pos.y = light->pos.y; - pos.z = light->pos.z + (info->z << 8); - - int32 radius = light->radius << 8; - int32 intensity = light->intensity << 5; - - vec3i d = p - pos; - int32 dist = dot(d, d) >> 12; - int32 att = X_SQR(radius) >> 12; - - int32 lum = (intensity * att) / (dist + att) + ambient; - - if (lum > maxLum) - { - maxLum = lum; - } + if (sector) { + return sector->getFloor(pos.x, pos.y, pos.z) - (sector->ceiling * 256); } - return 8191 - ((maxLum + ambient) >> 1); + return WALL; } #include "lara.h" @@ -506,8 +585,7 @@ void Item::draw() void Item::collide(Lara* lara, CollisionInfo* cinfo) { - UNUSED(lara); - UNUSED(cinfo); + // empty } Item* Item::init(Room* room) @@ -547,7 +625,7 @@ Item* Item::init(Room* room) INIT_ITEM( NATLA , Natla ); INIT_ITEM( ADAM , Adam ); INIT_ITEM( TRAP_FLOOR , TrapFloor ); - // INIT_ITEM( TRAP_SWING_BLADE , ??? ); + INIT_ITEM( TRAP_SWING_BLADE , TrapSwingBlade ); // INIT_ITEM( TRAP_SPIKES , ??? ); // INIT_ITEM( TRAP_BOULDER , ??? ); INIT_ITEM( DART , Dart ); @@ -559,10 +637,10 @@ Item* Item::init(Room* room) // INIT_ITEM( HAMMER_BLOCK , ??? ); // INIT_ITEM( LIGHTNING , ??? ); // INIT_ITEM( MOVING_OBJECT , ??? ); - // INIT_ITEM( BLOCK_1 , ??? ); - // INIT_ITEM( BLOCK_2 , ??? ); - // INIT_ITEM( BLOCK_3 , ??? ); - // INIT_ITEM( BLOCK_4 , ??? ); + INIT_ITEM( BLOCK_1 , Block ); + INIT_ITEM( BLOCK_2 , Block ); + INIT_ITEM( BLOCK_3 , Block ); + INIT_ITEM( BLOCK_4 , Block ); // INIT_ITEM( MOVING_BLOCK , ??? ); // INIT_ITEM( TRAP_CEILING_1 , ??? ); // INIT_ITEM( TRAP_CEILING_2 , ??? ); @@ -576,12 +654,12 @@ Item* Item::init(Room* room) INIT_ITEM( DOOR_6 , Door ); INIT_ITEM( DOOR_7 , Door ); INIT_ITEM( DOOR_8 , Door ); - // INIT_ITEM( TRAP_DOOR_1 , ??? ); - // INIT_ITEM( TRAP_DOOR_2 , ??? ); + INIT_ITEM( TRAP_DOOR_1 , TrapDoor ); + INIT_ITEM( TRAP_DOOR_2 , TrapDoor ); // INIT_ITEM( UNUSED_3 , ??? ); // INIT_ITEM( BRIDGE_FLAT , ??? ); - // INIT_ITEM( BRIDGE_TILT1 , ??? ); - // INIT_ITEM( BRIDGE_TILT2 , ??? ); + // INIT_ITEM( BRIDGE_TILT_1 , ??? ); + // INIT_ITEM( BRIDGE_TILT_2 , ??? ); // INIT_ITEM( INV_PASSPORT , ??? ); // INIT_ITEM( INV_COMPASS , ??? ); // INIT_ITEM( INV_HOME , ??? ); @@ -629,10 +707,10 @@ Item* Item::init(Room* room) // INIT_ITEM( INV_PUZZLE_2 , ??? ); // INIT_ITEM( INV_PUZZLE_3 , ??? ); // INIT_ITEM( INV_PUZZLE_4 , ??? ); - // INIT_ITEM( PUZZLE_HOLE_1 , ??? ); - // INIT_ITEM( PUZZLE_HOLE_2 , ??? ); - // INIT_ITEM( PUZZLE_HOLE_3 , ??? ); - // INIT_ITEM( PUZZLE_HOLE_4 , ??? ); + INIT_ITEM( PUZZLEHOLE_1 , PuzzleHole ); + INIT_ITEM( PUZZLEHOLE_2 , PuzzleHole ); + INIT_ITEM( PUZZLEHOLE_3 , PuzzleHole ); + INIT_ITEM( PUZZLEHOLE_4 , PuzzleHole ); // INIT_ITEM( PUZZLE_DONE_1 , ??? ); // INIT_ITEM( PUZZLE_DONE_2 , ??? ); // INIT_ITEM( PUZZLE_DONE_3 , ??? ); @@ -648,10 +726,10 @@ Item* Item::init(Room* room) // INIT_ITEM( INV_KEY_ITEM_2 , ??? ); // INIT_ITEM( INV_KEY_ITEM_3 , ??? ); // INIT_ITEM( INV_KEY_ITEM_4 , ??? ); - // INIT_ITEM( KEY_HOLE_1 , ??? ); - // INIT_ITEM( KEY_HOLE_2 , ??? ); - // INIT_ITEM( KEY_HOLE_3 , ??? ); - // INIT_ITEM( KEY_HOLE_4 , ??? ); + INIT_ITEM( KEYHOLE_1 , KeyHole ); + INIT_ITEM( KEYHOLE_2 , KeyHole ); + INIT_ITEM( KEYHOLE_3 , KeyHole ); + INIT_ITEM( KEYHOLE_4 , KeyHole ); // INIT_ITEM( UNUSED_4 , ??? ); // INIT_ITEM( UNUSED_5 , ??? ); // INIT_ITEM( SCION_PICKUP_QUALOPEC , ??? ); @@ -681,16 +759,16 @@ Item* Item::init(Room* room) // INIT_ITEM( UNUSED_13 , ??? ); // INIT_ITEM( UNUSED_14 , ??? ); INIT_ITEM( VIEW_TARGET , ViewTarget ); - // INIT_ITEM( WATERFALL , ??? ); + INIT_ITEM( WATERFALL , Waterfall ); // INIT_ITEM( NATLA_BULLET , ??? ); // INIT_ITEM( MUTANT_BULLET , ??? ); // INIT_ITEM( CENTAUR_BULLET , ??? ); // INIT_ITEM( UNUSED_15 , ??? ); // INIT_ITEM( UNUSED_16 , ??? ); // INIT_ITEM( LAVA_PARTICLE , ??? ); - // INIT_ITEM( TRAP_LAVA_EMITTER , ??? ); + INIT_ITEM( LAVA_EMITTER , LavaEmitter ); // INIT_ITEM( FLAME , ??? ); - // INIT_ITEM( TRAP_FLAME_EMITTER , ??? ); + // INIT_ITEM( FLAME_EMITTER , ??? ); // INIT_ITEM( TRAP_LAVA , ??? ); // INIT_ITEM( MUTANT_EGG_BIG , ??? ); // INIT_ITEM( BOAT , ??? ); diff --git a/src/platform/gba/lara.h b/src/platform/gba/lara.h index b84a5e3..6f0a9ee 100644 --- a/src/platform/gba/lara.h +++ b/src/platform/gba/lara.h @@ -6,63 +6,98 @@ #include "collision.h" #include "camera.h" +// ------------- +// TODO list - GBA demo (GYM, LEVEL1, LEVEL2) +// ------------- +// fix portals flickering +// darts damage +// swing blade damage +// multi-pickups +// sprite effects (splash, smoke, ricochet) +// animation lerp for Lara (enemies?) +// inventory +// camera look +// lookat +// weapons +// enemies (Bat, Wolf, Bear) +// main menu +// save game +// ADPCM sounds and tracks +// gameflow +// ------------- + #define LARA_STATES(E) \ - E( WALK ) \ - E( RUN ) \ - E( STOP ) \ - E( FORWARD_JUMP ) \ - E( POSE ) \ - E( FAST_BACK ) \ - E( TURN_RIGHT ) \ - E( TURN_LEFT ) \ - E( DEATH ) \ - E( FALL ) \ - E( HANG ) \ - E( REACH ) \ - E( SPLAT ) \ - E( TREAD ) \ - E( LAND ) \ - E( COMPRESS ) \ - E( BACK ) \ - E( SWIM ) \ - E( GLIDE ) \ - E( HANG_UP ) \ - E( FAST_TURN ) \ - E( STEP_RIGHT ) \ - E( STEP_LEFT ) \ - E( ROLL_END ) \ - E( SLIDE ) \ - E( BACK_JUMP ) \ - E( RIGHT_JUMP ) \ - E( LEFT_JUMP ) \ - E( UP_JUMP ) \ - E( FALL_BACK ) \ - E( HANG_LEFT ) \ - E( HANG_RIGHT ) \ - E( SLIDE_BACK ) \ - E( SURF_TREAD ) \ - E( SURF_SWIM ) \ - E( DIVE ) \ - E( PUSH_BLOCK ) \ - E( PULL_BLOCK ) \ - E( PUSH_PULL_READY ) \ - E( PICK_UP ) \ - E( SWITCH_DOWN ) \ - E( SWITCH_UP ) \ - E( USE_KEY ) \ - E( USE_PUZZLE ) \ - E( UW_DEATH ) \ - E( ROLL_START ) \ - E( SPECIAL ) \ - E( SURF_BACK ) \ - E( SURF_LEFT ) \ - E( SURF_RIGHT ) \ - E( MIDAS_USE ) \ - E( MIDAS_DEATH ) \ - E( SWAN_DIVE ) \ - E( FAST_DIVE ) \ - E( HANDSTAND ) \ - E( WATER_OUT ) + E( STATE_WALK ) \ + E( STATE_RUN ) \ + E( STATE_STOP ) \ + E( STATE_JUMP ) \ + E( STATE_POSE ) \ + E( STATE_BACK_FAST ) \ + E( STATE_TURN_RIGHT ) \ + E( STATE_TURN_LEFT ) \ + E( STATE_DEATH ) \ + E( STATE_FALL ) \ + E( STATE_HANG ) \ + E( STATE_REACH ) \ + E( STATE_SPLAT ) \ + E( STATE_UW_TREAD ) \ + E( STATE_LAND ) \ + E( STATE_COMPRESS ) \ + E( STATE_BACK ) \ + E( STATE_UW_SWIM ) \ + E( STATE_UW_GLIDE ) \ + E( STATE_HANG_UP ) \ + E( STATE_TURN_FAST ) \ + E( STATE_STEP_RIGHT ) \ + E( STATE_STEP_LEFT ) \ + E( STATE_ROLL_END ) \ + E( STATE_SLIDE ) \ + E( STATE_JUMP_BACK ) \ + E( STATE_JUMP_RIGHT ) \ + E( STATE_JUMP_LEFT ) \ + E( STATE_JUMP_UP ) \ + E( STATE_FALL_BACK ) \ + E( STATE_HANG_LEFT ) \ + E( STATE_HANG_RIGHT ) \ + E( STATE_SLIDE_BACK ) \ + E( STATE_SURF_TREAD ) \ + E( STATE_SURF_SWIM ) \ + E( STATE_UW_DIVE ) \ + E( STATE_BLOCK_PUSH ) \ + E( STATE_BLOCK_PULL ) \ + E( STATE_BLOCK_READY ) \ + E( STATE_PICKUP ) \ + E( STATE_SWITCH_DOWN ) \ + E( STATE_SWITCH_UP ) \ + E( STATE_USE_KEY ) \ + E( STATE_USE_PUZZLE ) \ + E( STATE_DEATH_UW ) \ + E( STATE_ROLL_START ) \ + E( STATE_SPECIAL ) \ + E( STATE_SURF_BACK ) \ + E( STATE_SURF_LEFT ) \ + E( STATE_SURF_RIGHT ) \ + E( STATE_USE_MIDAS ) \ + E( STATE_DEATH_MIDAS ) \ + E( STATE_SWAN_DIVE ) \ + E( STATE_FAST_DIVE ) \ + E( STATE_HANDSTAND ) \ + E( STATE_WATER_OUT ) \ + E( STATE_CLIMB_START ) \ + E( STATE_CLIMB_UP ) \ + E( STATE_CLIMB_LEFT ) \ + E( STATE_CLIMB_END ) \ + E( STATE_CLIMB_RIGHT ) \ + E( STATE_CLIMB_DOWN ) \ + E( STATE_UNUSED_1 ) \ + E( STATE_UNUSED_2 ) \ + E( STATE_UNUSED_3 ) \ + E( STATE_WADE ) \ + E( STATE_ROLL_UW ) \ + E( STATE_PICKUP_FLARE ) \ + E( STATE_ROLL_AIR ) \ + E( STATE_UNUSED_4 ) \ + E( STATE_ZIPLINE ) #define DECL_ENUM(v) v, #define DECL_S_HANDLER(v) &Lara::s_##v, @@ -80,7 +115,7 @@ #define LARA_RADIUS_WATER 300 #define LARA_RADIUS_CLIMB 220 #define LARA_MAX_HEALTH 1000 -#define LARA_MAX_OXYGEN 1800 +#define LARA_MAX_OXYGEN 1800 // TODO +30 sec for TR5 #define LARA_HEIGHT 762 #define LARA_TURN_ACCEL (2 * DEG2SHORT + DEG2SHORT / 4) #define LARA_TURN_JUMP (3 * DEG2SHORT) @@ -102,6 +137,9 @@ #define LARA_SURF_ACCEL 8 #define LARA_SURF_SPEED_MAX 60 #define LARA_DIVE_SPEED 80 +#define LARA_WADE_MIN_DEPTH 384 +#define LARA_WADE_MAX_DEPTH 730 +#define LARA_SWIM_MIN_DEPTH 512 enum { JOINT_HIPS = 0, @@ -147,6 +185,8 @@ enum { JOINT_MASK_BRAID = JOINT_MASK_HEAD | JOINT_MASK_CHEST | JOINT_MASK_ARM_L1 | JOINT_MASK_ARM_L2 | JOINT_MASK_ARM_R1 | JOINT_MASK_ARM_R2, }; +int32 swimTimer; + struct Lara : Item { enum State { @@ -291,7 +331,7 @@ struct Lara : Item void stopScreaming() { - // TODO + soundStop(SND_SCREAM); } // common @@ -310,17 +350,17 @@ struct Lara : Item return (angle & (ANGLE_90 - 1)) > 0; } - void alignWall() + void alignWall(int32 radius) { - int x = (~1023) & pos.x; - int z = (~1023) & pos.z; + int x = pos.x & ~1023; + int z = pos.z & ~1023; switch (angle.y) { - case ANGLE_0 : pos.z = z + 1024 + LARA_RADIUS; break; - case ANGLE_90 : pos.x = x + 1024 + LARA_RADIUS; break; - case -ANGLE_90 : pos.x = x - LARA_RADIUS; break; - case ANGLE_180 : pos.z = z - LARA_RADIUS; break; + case ANGLE_0 : pos.z = z + 1024 - radius; break; + case ANGLE_90 : pos.x = x + 1024 - radius; break; + case -ANGLE_90 : pos.x = x + radius; break; + case ANGLE_180 : pos.z = z + radius; break; default : ASSERT(false); } } @@ -335,7 +375,7 @@ struct Lara : Item Room* roomFront = room->getRoom(x, y, z); const Sector* sector = roomFront->getSector(x, z); - int16 floor = sector->getFloor(x, y, z); + int32 floor = sector->getFloor(x, y, z); if (floor != WALL) { floor -= pos.y; @@ -353,16 +393,41 @@ struct Lara : Item return frame->box; } - bool checkDeath() + bool checkDeath(State deathState) { - return health <= 0; + if (health <= 0) { + goalState = deathState; + return true; + } + return false; } // state control - bool s_checkFront(int16 angle) + bool s_checkFront(int16 angleDelta) { - UNUSED(angle); // TODO - return true; // TODO + CollisionInfo tmpInfo = cinfo; + int16 tmpAngle = moveAngle; + + c_angle(angleDelta); + cinfo.radius = LARA_RADIUS + 4; + + cinfo.gapPos = -WALL; + cinfo.gapNeg = -LARA_STEP_HEIGHT; + cinfo.stopOnSlant = true; + cinfo.gapCeiling = 0; + + if ((angleDelta == ANGLE_180) && ((input & IN_WALK) || (waterState == WATER_STATE_WADE))) { + cinfo.gapPos = LARA_STEP_HEIGHT; + } + + collideRoom(this, LARA_HEIGHT, 0); + + bool collide = (cinfo.type == CT_FRONT) || (cinfo.type == CT_FRONT_CEILING); + + cinfo = tmpInfo; + moveAngle = tmpAngle; + + return !collide; } void s_ignoreEnemy() @@ -388,10 +453,10 @@ struct Lara : Item { if (vSpeed > 131) { - if (state == SWAN_DIVE) { - goalState = FAST_DIVE; + if (state == STATE_SWAN_DIVE) { + goalState = STATE_FAST_DIVE; } else { - goalState = FALL; + goalState = STATE_FALL; } return true; } @@ -402,9 +467,9 @@ struct Lara : Item { if ((input & IN_UP) && s_checkFront(ANGLE_0)) { if (input & IN_WALK) { - goalState = WALK; + goalState = STATE_WALK; } else { - goalState = RUN; + goalState = STATE_RUN; } } else { goalState = stopState; @@ -421,10 +486,10 @@ struct Lara : Item if ((waterState == WATER_STATE_ABOVE) && roll) { - if ((state == RUN) || (state == STOP)) + if ((state == STATE_RUN) || (state == STATE_STOP)) { animSet(ANIM_STAND_ROLL_BEGIN, true, 2); - goalState = STOP; + goalState = STATE_STOP; return true; } } @@ -432,114 +497,131 @@ struct Lara : Item return false; } - S_HANDLER( WALK ) + void s_turnUW() { - if (checkDeath()) { - goalState = STOP; - return; + if (input & IN_UP) { + angle.x -= 2 * DEG2SHORT; + } else if (input & IN_DOWN) { + angle.x += 2 * DEG2SHORT; } + if (input & IN_LEFT) { + turnSpeed = max(turnSpeed - LARA_TURN_ACCEL, -LARA_TURN_MED); + angle.z -= LARA_TILT_ACCEL * 2; + } else if (input & IN_RIGHT) { + turnSpeed = min(turnSpeed + LARA_TURN_ACCEL, LARA_TURN_MED); + angle.z += LARA_TILT_ACCEL * 2; + } + } + + void s_dive() + { + animSet(ANIM_SURF_DIVE, true); + angle.x = -45 * DEG2SHORT; + vSpeed = LARA_DIVE_SPEED; + waterState = WATER_STATE_UNDER; + } + + S_HANDLER( STATE_WALK ) + { + if (checkDeath(STATE_STOP)) + return; + s_rotate(LARA_TURN_SLOW, 0); - s_checkWalk(STOP); + s_checkWalk(STATE_STOP); } - S_HANDLER( RUN ) + S_HANDLER( STATE_RUN ) { - if (checkDeath()) { - goalState = DEATH; + if (checkDeath(STATE_DEATH)) return; - } - if (s_checkRoll()) { + if (s_checkRoll()) return; - } s_rotate(LARA_TURN_FAST, 1); if ((input & IN_JUMP) && !flags.gravity) { - goalState = FORWARD_JUMP; + goalState = STATE_JUMP; } else { - s_checkWalk(STOP); + s_checkWalk(STATE_STOP); } } - S_HANDLER( STOP ) + S_HANDLER( STATE_STOP ) { - if (checkDeath()) + if (checkDeath(STATE_DEATH)) + return; + + if (s_checkRoll()) + return; + + goalState = STATE_STOP; + + if ((input & (IN_UP | IN_ACTION)) == (IN_UP | IN_ACTION)) { - nextState = DEATH; - return; - } - - if (s_checkRoll()) { - return; - } - - goalState = STOP; - /* - if ((input & (IN_UP | IN_ACTION)) == (IN_UP | IN_ACTION)) { c_angle(ANGLE_0); cinfo.radius = LARA_RADIUS + 4; c_default(); cinfo.radius = LARA_RADIUS; - if (c_checkClimbUp()) { + + if (c_checkClimbUp()) return; - } } - */ + if (input & IN_WALK) { if ((input & IN_LEFT) && s_checkFront(-ANGLE_90)) { - goalState = STEP_LEFT; + goalState = STATE_STEP_LEFT; } else if ((input & IN_RIGHT) && s_checkFront(ANGLE_90)) { - goalState = STEP_RIGHT; + goalState = STATE_STEP_RIGHT; } } else { if (input & IN_LEFT) { - goalState = TURN_LEFT; + goalState = STATE_TURN_LEFT; } else if (input & IN_RIGHT) { - goalState = TURN_RIGHT; + goalState = STATE_TURN_RIGHT; } } if (input & IN_JUMP) { - goalState = COMPRESS; + goalState = STATE_COMPRESS; } else if ((input & IN_UP) && s_checkFront(ANGLE_0)) { if (input & IN_WALK) { - s_WALK(); + s_STATE_WALK(); } else { - s_RUN(); + s_STATE_RUN(); } } else if ((input & IN_DOWN) && s_checkFront(ANGLE_180)) { if (input & IN_WALK) { - s_BACK(); + s_STATE_BACK(); } else { - goalState = FAST_BACK; + goalState = STATE_BACK_FAST; } } } - S_HANDLER( FORWARD_JUMP ) + S_HANDLER( STATE_JUMP ) { - if (goalState == SWAN_DIVE || - goalState == REACH) + if (goalState == STATE_SWAN_DIVE || + goalState == STATE_REACH) { - goalState = FORWARD_JUMP; + goalState = STATE_JUMP; } - if (goalState != DEATH && - goalState != STOP && - goalState != RUN) + if (goalState != STATE_DEATH && + goalState != STATE_STOP && + goalState != STATE_RUN) { if (input & IN_ACTION) { - goalState = REACH; + goalState = STATE_REACH; } if (input & IN_WALK) { - goalState = SWAN_DIVE; + goalState = STATE_SWAN_DIVE; } s_checkRoll(); @@ -549,22 +631,25 @@ struct Lara : Item s_rotate(LARA_TURN_JUMP, 0); } - S_HANDLER( POSE ) + S_HANDLER( STATE_POSE ) { // empty } - S_HANDLER( FAST_BACK ) + S_HANDLER( STATE_BACK_FAST ) { s_rotate(LARA_TURN_MED, 0); - goalState = STOP; + goalState = STATE_STOP; } - S_HANDLER( TURN_RIGHT ) + S_HANDLER( STATE_TURN_RIGHT ) { - if (checkDeath() || (input & IN_LOOK)) + if (checkDeath(STATE_STOP)) + return; + + if (input & IN_LOOK) { - goalState = STOP; + goalState = STATE_STOP; return; } @@ -572,17 +657,20 @@ struct Lara : Item if (turnSpeed > LARA_TURN_SLOW) { - goalState = FAST_TURN; + goalState = STATE_TURN_FAST; } - s_checkWalk((input & IN_RIGHT) ? goalState : STOP); + s_checkWalk((input & IN_RIGHT) ? goalState : STATE_STOP); } - S_HANDLER( TURN_LEFT ) + S_HANDLER( STATE_TURN_LEFT ) { - if (checkDeath() || (input & IN_LOOK)) + if (checkDeath(STATE_STOP)) + return; + + if (input & IN_LOOK) { - goalState = STOP; + goalState = STATE_STOP; return; } @@ -590,184 +678,242 @@ struct Lara : Item if (turnSpeed < -LARA_TURN_SLOW) { - goalState = FAST_TURN; + goalState = STATE_TURN_FAST; } - s_checkWalk((input & IN_LEFT) ? goalState : STOP); + s_checkWalk((input & IN_LEFT) ? goalState : STATE_STOP); } - S_HANDLER( DEATH ) + S_HANDLER( STATE_DEATH ) { s_ignoreEnemy(); } - S_HANDLER( FALL ) + S_HANDLER( STATE_FALL ) { hSpeed = (hSpeed * 95) / 100; - if (vSpeed >= 154) - { + + if (vSpeed >= 154) { startScreaming(); } } - S_HANDLER( HANG ) + S_HANDLER( STATE_HANG ) { camera.targetAngleX = -60 * DEG2SHORT; s_ignoreEnemy(); if (input & IN_LEFT) { - goalState = HANG_LEFT; + goalState = STATE_HANG_LEFT; } else if (input & IN_RIGHT) { - goalState = HANG_RIGHT; + goalState = STATE_HANG_RIGHT; } } - S_HANDLER( REACH ) + S_HANDLER( STATE_REACH ) { camera.targetAngleY = 85 * DEG2SHORT; s_checkFall(); } - S_HANDLER( SPLAT ) + S_HANDLER( STATE_SPLAT ) { // empty } - S_HANDLER( TREAD ) {} // TODO + S_HANDLER( STATE_UW_TREAD ) + { + if (checkDeath(STATE_DEATH_UW)) + return; - S_HANDLER( LAND ) + if (s_checkRoll()) + return; + + s_turnUW(); + + if (input & IN_JUMP) { + goalState = STATE_UW_SWIM; + } + + vSpeed = max(vSpeed - LARA_SWIM_FRICTION, 0); + } + + S_HANDLER( STATE_LAND ) { // empty } - S_HANDLER( COMPRESS ) + S_HANDLER( STATE_COMPRESS ) { if ((input & IN_UP) && getFront(ANGLE_0) >= -LARA_STEP_HEIGHT) { - goalState = FORWARD_JUMP; + goalState = STATE_JUMP; } else if ((input & IN_LEFT) && getFront(-ANGLE_90) >= -LARA_STEP_HEIGHT) { - goalState = LEFT_JUMP; + goalState = STATE_JUMP_LEFT; } else if ((input & IN_RIGHT) && getFront(ANGLE_90) >= -LARA_STEP_HEIGHT) { - goalState = RIGHT_JUMP; + goalState = STATE_JUMP_RIGHT; } else if ((input & IN_DOWN) && getFront(ANGLE_180) >= -LARA_STEP_HEIGHT) { - goalState = BACK_JUMP; + goalState = STATE_JUMP_BACK; } s_checkFall(); } - S_HANDLER( BACK ) + S_HANDLER( STATE_BACK ) { - if (checkDeath()) { - goalState = STOP; + if (checkDeath(STATE_STOP)) return; - } if ((input & (IN_WALK | IN_DOWN)) != (IN_WALK | IN_DOWN)) { - goalState = STOP; + goalState = STATE_STOP; } else { - goalState = BACK; + goalState = STATE_BACK; } s_rotate(LARA_TURN_SLOW, 0); } - S_HANDLER( SWIM ) {} // TODO - S_HANDLER( GLIDE ) {} // TODO + S_HANDLER( STATE_UW_SWIM ) + { + if (checkDeath(STATE_DEATH_UW)) + return; - S_HANDLER( HANG_UP ) + if (s_checkRoll()) + return; + + s_turnUW(); + + vSpeed = min(vSpeed + LARA_SWIM_ACCEL, LARA_SWIM_SPEED_MAX); + + if (!(input & IN_JUMP)) { + goalState = STATE_UW_GLIDE; + } + } + + S_HANDLER( STATE_UW_GLIDE ) + { + if (checkDeath(STATE_DEATH_UW)) + return; + + if (s_checkRoll()) + return; + + s_turnUW(); + + if (input & IN_JUMP) { + goalState = STATE_UW_SWIM; + } + + vSpeed = max(vSpeed - LARA_SWIM_FRICTION, 0); + + if (vSpeed <= LARA_SWIM_SPEED_MIN) { + goalState = STATE_UW_TREAD; + } + } + + S_HANDLER( STATE_HANG_UP ) { s_ignoreEnemy(); } - S_HANDLER( FAST_TURN ) + S_HANDLER( STATE_TURN_FAST ) { - if (checkDeath() || (input & IN_LOOK)) + if (checkDeath(STATE_STOP)) + return; + + if (input & IN_LOOK) { - goalState = STOP; + goalState = STATE_STOP; return; } if (turnSpeed < 0) { turnSpeed = -LARA_TURN_FAST; if (!(input & IN_LEFT)) { - goalState = STOP; + goalState = STATE_STOP; } } else { turnSpeed = LARA_TURN_FAST; if (!(input & IN_RIGHT)) { - goalState = STOP; + goalState = STATE_STOP; } } } - S_HANDLER( STEP_RIGHT ) + S_HANDLER( STATE_STEP_RIGHT ) { - if (checkDeath() || (input & (IN_WALK | IN_RIGHT)) != (IN_WALK | IN_RIGHT)) + if (checkDeath(STATE_STOP)) + return; + + if ((input & (IN_WALK | IN_RIGHT)) != (IN_WALK | IN_RIGHT)) { - goalState = STOP; + goalState = STATE_STOP; } } - S_HANDLER( STEP_LEFT ) + S_HANDLER( STATE_STEP_LEFT ) { - if (checkDeath() || (input & (IN_WALK | IN_LEFT)) != (IN_WALK | IN_LEFT)) + if (checkDeath(STATE_STOP)) + return; + + if ((input & (IN_WALK | IN_LEFT)) != (IN_WALK | IN_LEFT)) { - goalState = STOP; + goalState = STATE_STOP; } } - S_HANDLER( ROLL_END ) + S_HANDLER( STATE_ROLL_END ) { // empty } - S_HANDLER( SLIDE ) + S_HANDLER( STATE_SLIDE ) { camera.targetAngleX = -45 * DEG2SHORT; if (input & IN_JUMP) { - goalState = FORWARD_JUMP; + goalState = STATE_JUMP; } } - S_HANDLER( BACK_JUMP ) + S_HANDLER( STATE_JUMP_BACK ) { camera.targetAngleY = 135 * DEG2SHORT; - if (s_checkFall()) return; + if (s_checkFall()) + return; - if (goalState == RUN) { - goalState = STOP; + if (goalState == STATE_RUN) { + goalState = STATE_STOP; } } - S_HANDLER( RIGHT_JUMP ) + S_HANDLER( STATE_JUMP_RIGHT ) { s_checkFall(); } - S_HANDLER( LEFT_JUMP ) + S_HANDLER( STATE_JUMP_LEFT ) { s_checkFall(); } - S_HANDLER( UP_JUMP ) + S_HANDLER( STATE_JUMP_UP ) { s_checkFall(); } - S_HANDLER( FALL_BACK ) + S_HANDLER( STATE_FALL_BACK ) { s_checkFall(); if (input & IN_ACTION) { - goalState = REACH; + goalState = STATE_REACH; } } - S_HANDLER( HANG_LEFT ) + S_HANDLER( STATE_HANG_LEFT ) { camera.targetAngleX = -60 * DEG2SHORT; @@ -775,11 +921,11 @@ struct Lara : Item if (!(input & IN_LEFT)) { - goalState = HANG; + goalState = STATE_HANG; } } - S_HANDLER( HANG_RIGHT ) + S_HANDLER( STATE_HANG_RIGHT ) { camera.targetAngleX = -60 * DEG2SHORT; @@ -787,39 +933,94 @@ struct Lara : Item if (!(input & IN_RIGHT)) { - goalState = HANG; + goalState = STATE_HANG; } } - S_HANDLER( SLIDE_BACK ) + S_HANDLER( STATE_SLIDE_BACK ) { if (input & IN_JUMP) { - goalState = BACK_JUMP; + goalState = STATE_JUMP_BACK; } } - S_HANDLER( SURF_TREAD ) {} // TODO - S_HANDLER( SURF_SWIM ) {} // TODO - S_HANDLER( DIVE ) {} // TODO + S_HANDLER( STATE_SURF_TREAD ) + { + vSpeed = max(vSpeed - LARA_SURF_FRICTION, 0); - S_HANDLER( PUSH_BLOCK ) + if (input & IN_LEFT) { + angle.y -= LARA_TURN_SLOW; + } else if (input & IN_RIGHT) { + angle.y += LARA_TURN_SLOW; + } + + if (input & IN_UP) { + goalState = STATE_SURF_SWIM; + } else if (input & IN_DOWN) { + goalState = STATE_SURF_BACK; + } else if ((input & (IN_WALK | IN_LEFT)) == (IN_WALK | IN_LEFT)) { + goalState = STATE_SURF_LEFT; + } else if ((input & (IN_WALK | IN_RIGHT)) == (IN_WALK | IN_RIGHT)) { + goalState = STATE_SURF_RIGHT; + } + + if (input & IN_JUMP) { + swimTimer++; + if (swimTimer == LARA_SWIM_TIMER) { + s_dive(); + } + } else { + swimTimer = 0; + } + } + + S_HANDLER( STATE_SURF_SWIM ) + { + if (checkDeath(STATE_DEATH_UW)) + return; + + swimTimer = 0; + + if (input & IN_LEFT) { + angle.y -= LARA_TURN_SLOW; + } else if (input & IN_RIGHT) { + angle.y += LARA_TURN_SLOW; + } + + if (!(input & IN_UP) || (input & IN_JUMP)) { + goalState = STATE_SURF_TREAD; + } + + vSpeed = min(vSpeed + LARA_SURF_ACCEL, LARA_SURF_SPEED_MAX); + } + + S_HANDLER( STATE_UW_DIVE ) + { + if (input & IN_UP) { + angle.x -= ANGLE_1; + } + } + + S_HANDLER( STATE_BLOCK_PUSH ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = 35 * DEG2SHORT; + camera.center = true; s_ignoreEnemy(); } - S_HANDLER( PULL_BLOCK ) + S_HANDLER( STATE_BLOCK_PULL ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = 35 * DEG2SHORT; + camera.center = true; s_ignoreEnemy(); } - S_HANDLER( PUSH_PULL_READY ) + S_HANDLER( STATE_BLOCK_READY ) { camera.targetAngleY = 75 * DEG2SHORT; @@ -827,11 +1028,11 @@ struct Lara : Item if (!(input & IN_ACTION)) { - goalState = STOP; + goalState = STATE_STOP; } } - S_HANDLER( PICK_UP ) + S_HANDLER( STATE_PICKUP ) { camera.targetAngleX = -15 * DEG2SHORT; camera.targetAngleY = -130 * DEG2SHORT; @@ -840,7 +1041,7 @@ struct Lara : Item s_ignoreEnemy(); } - S_HANDLER( SWITCH_DOWN ) + S_HANDLER( STATE_SWITCH_DOWN ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = 80 * DEG2SHORT; @@ -849,7 +1050,7 @@ struct Lara : Item s_ignoreEnemy(); } - S_HANDLER( SWITCH_UP ) + S_HANDLER( STATE_SWITCH_UP ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = 80 * DEG2SHORT; @@ -858,7 +1059,7 @@ struct Lara : Item s_ignoreEnemy(); } - S_HANDLER( USE_KEY ) + S_HANDLER( STATE_USE_KEY ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = -80 * DEG2SHORT; @@ -867,7 +1068,7 @@ struct Lara : Item s_ignoreEnemy(); } - S_HANDLER( USE_PUZZLE ) + S_HANDLER( STATE_USE_PUZZLE ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = -80 * DEG2SHORT; @@ -876,65 +1077,145 @@ struct Lara : Item s_ignoreEnemy(); } - S_HANDLER( UW_DEATH ) + S_HANDLER( STATE_DEATH_UW ) { vSpeed = X_MAX(vSpeed - LARA_SWIM_ACCEL, 0); angle.x = angleDec(angle.x, 2 * DEG2SHORT); } - S_HANDLER( ROLL_START ) + S_HANDLER( STATE_ROLL_START ) { // empty } - S_HANDLER( SPECIAL ) + S_HANDLER( STATE_SPECIAL ) { camera.targetAngleX = -25 * DEG2SHORT; camera.targetAngleY = 170 * DEG2SHORT; + camera.center = true; } - S_HANDLER( SURF_BACK ) {} // TODO - S_HANDLER( SURF_LEFT ) {} // TODO - S_HANDLER( SURF_RIGHT ) {} // TODO + S_HANDLER( STATE_SURF_BACK ) + { + if (checkDeath(STATE_DEATH_UW)) + return; - S_HANDLER( MIDAS_USE ) + swimTimer = 0; + + if (input & IN_LEFT) { + angle.y -= LARA_TURN_VERY_SLOW; + } else if (input & IN_RIGHT) { + angle.y += LARA_TURN_VERY_SLOW; + } + + if (!(input & IN_DOWN)) { + goalState = STATE_SURF_TREAD; + } + + vSpeed = min(vSpeed + LARA_SURF_ACCEL, LARA_SURF_SPEED_MAX); + } + + S_HANDLER( STATE_SURF_LEFT ) + { + if (checkDeath(STATE_DEATH_UW)) + return; + + swimTimer = 0; + + if ((input & (IN_WALK | IN_LEFT)) != (IN_WALK | IN_LEFT)) { + goalState = STATE_SURF_TREAD; + } + + vSpeed = min(vSpeed + LARA_SURF_ACCEL, LARA_SURF_SPEED_MAX); + } + + S_HANDLER( STATE_SURF_RIGHT ) + { + if (checkDeath(STATE_DEATH_UW)) + return; + + swimTimer = 0; + + if ((input & (IN_WALK | IN_RIGHT)) != (IN_WALK | IN_RIGHT)) { + goalState = STATE_SURF_TREAD; + } + + vSpeed = min(vSpeed + LARA_SURF_ACCEL, LARA_SURF_SPEED_MAX); + } + + S_HANDLER( STATE_USE_MIDAS ) { s_ignoreEnemy(); } - S_HANDLER( MIDAS_DEATH ) + S_HANDLER( STATE_DEATH_MIDAS ) { s_ignoreEnemy(); flags.gravity = false; } - S_HANDLER( SWAN_DIVE ) {} // TODO - S_HANDLER( FAST_DIVE ) {} // TODO + S_HANDLER( STATE_SWAN_DIVE ) + { + cinfo.enemyPush = true; + cinfo.enemyHit = false; - S_HANDLER( HANDSTAND ) + s_checkFall(); + } + + S_HANDLER( STATE_FAST_DIVE ) + { + cinfo.enemyPush = true; + cinfo.enemyHit = false; + hSpeed = (hSpeed * 95) / 100; + + s_checkRoll(); + } + + S_HANDLER( STATE_HANDSTAND ) { s_ignoreEnemy(); } - S_HANDLER( WATER_OUT ) + S_HANDLER( STATE_WATER_OUT ) { s_ignoreEnemy(); + camera.center = true; } + S_HANDLER( STATE_CLIMB_START ) {} + S_HANDLER( STATE_CLIMB_UP ) {} + S_HANDLER( STATE_CLIMB_LEFT ) {} + S_HANDLER( STATE_CLIMB_END ) {} + S_HANDLER( STATE_CLIMB_RIGHT ) {} + S_HANDLER( STATE_CLIMB_DOWN ) {} + S_HANDLER( STATE_UNUSED_1 ) {} + S_HANDLER( STATE_UNUSED_2 ) {} + S_HANDLER( STATE_UNUSED_3 ) {} + S_HANDLER( STATE_WADE ) {} + S_HANDLER( STATE_ROLL_UW ) {} + S_HANDLER( STATE_PICKUP_FLARE ) {} + S_HANDLER( STATE_ROLL_AIR ) {} + S_HANDLER( STATE_UNUSED_4 ) {} + S_HANDLER( STATE_ZIPLINE ) {} + + // collision control - void c_applyOffset() { + void c_applyOffset() + { pos += cinfo.offset; cinfo.offset = vec3i(0, 0, 0); } - void c_angle(int32 angleDelta) { + void c_angle(int32 angleDelta) + { moveAngle = angle.y + angleDelta; cinfo.angle = moveAngle; cinfo.quadrant = uint16(cinfo.angle + ANGLE_45) / ANGLE_90; } - bool c_checkCeiling() { + bool c_checkCeiling() + { if (cinfo.type != CT_CEILING && cinfo.type != CT_FLOOR_CEILING) { return false; } @@ -949,10 +1230,12 @@ struct Lara : Item return true; } - bool c_checkWall() { - if (cinfo.type == CT_FRONT || cinfo.type == CT_FRONT_CEILING) { + bool c_checkWall() + { + if (cinfo.type == CT_FRONT || cinfo.type == CT_FRONT_CEILING) + { c_applyOffset(); - goalState = STOP; + goalState = STATE_STOP; hSpeed = 0; flags.gravity = false; return true; @@ -971,7 +1254,8 @@ struct Lara : Item return false; } - bool c_checkWallUW() { + bool c_checkWallUW() + { if (cinfo.type == CT_FRONT) { if (angle.x > 35 * DEG2SHORT) { angle.x += 2 * DEG2SHORT; @@ -1001,7 +1285,7 @@ struct Lara : Item angle.x += 2 * DEG2SHORT; } - int32 waterDepth = room->getWaterDepth(); + int32 waterDepth = getWaterDepth(); if (waterDepth == WALL) { vSpeed = 0; @@ -1010,7 +1294,7 @@ struct Lara : Item waterState = WATER_STATE_WADE; animSet(ANIM_SWIM_STAND, true); - goalState = STOP; + goalState = STATE_STOP; angle.x = 0; angle.z = 0; @@ -1022,7 +1306,8 @@ struct Lara : Item return false; } - bool c_checkWallSurf() { + bool c_checkWallSurf() + { if ((cinfo.m.floor < 0 && cinfo.m.slantType == SLANT_HIGH) || (cinfo.type & (CT_FRONT | CT_CEILING | CT_FRONT_CEILING | CT_FLOOR_CEILING))) { pos = cinfo.pos; vSpeed = 0; @@ -1035,7 +1320,8 @@ struct Lara : Item return true; } - bool c_checkSlide() { + bool c_checkSlide() + { if (waterState == WATER_STATE_WADE) { return false; } @@ -1059,13 +1345,13 @@ struct Lara : Item } if (abs(realAngle - angle.y) <= ANGLE_90) { - if (state != SLIDE) { + if (state != STATE_SLIDE) { animSet(ANIM_SLIDE_FORTH, true); } moveAngle = realAngle; angle.y = realAngle; } else { - if (state != SLIDE_BACK) { + if (state != STATE_SLIDE_BACK) { animSet(ANIM_SLIDE_BACK, true); } moveAngle = realAngle; @@ -1075,7 +1361,8 @@ struct Lara : Item return true; } - bool c_checkFall(int32 height, int32 fallAnimIndex = ANIM_FALL_FORTH) { + bool c_checkFall(int32 height, int32 fallAnimIndex = ANIM_FALL_FORTH) + { if (waterState == WATER_STATE_WADE) { return false; } @@ -1092,24 +1379,24 @@ struct Lara : Item return true; } - bool c_checkLanding() { - if ((state == FAST_DIVE /*|| state == AIR_ROLL*/) && vSpeed > 133) { + bool c_checkLanding() + { + if ((state == STATE_FAST_DIVE || state == STATE_ROLL_AIR) && vSpeed > 133) + { health = 0; return true; } int32 y = pos.y; pos.y += cinfo.m.floor; - /* TODO - v2floor = pos.y; + roomFloor = pos.y; checkTrigger(cinfo.trigger, this); - */ + pos.y = y; - if (vSpeed <= 140) { + if (vSpeed <= 140) return false; - } if (vSpeed > 154) { health = 0; @@ -1117,10 +1404,11 @@ struct Lara : Item health -= (X_SQR(vSpeed - 140) * LARA_MAX_HEALTH) / 196; } - return checkDeath(); + return checkDeath((State)state); } - bool c_checkSwing() { + bool c_checkSwing() + { int32 x = pos.x; int32 y = pos.y; int32 z = pos.z; @@ -1150,21 +1438,28 @@ struct Lara : Item return false; } - bool c_checkGrab() { - return !(input & IN_ACTION) || cinfo.type != CT_FRONT || abs(cinfo.r.floor - cinfo.l.floor) >= LARA_HANG_SLANT; + bool c_checkGrab() + { + return !(input & IN_ACTION) || (cinfo.type != CT_FRONT) || (abs(cinfo.r.floor - cinfo.l.floor) >= LARA_HANG_SLANT); } - bool c_checkSpace() { + bool c_checkSpace() + { return (cinfo.f.floor < cinfo.f.ceiling || cinfo.l.floor < cinfo.l.ceiling || cinfo.r.floor < cinfo.r.ceiling); } - bool c_checkClimbStart() { + bool c_checkClimbStart() + { return false; } - bool c_checkClimbUp() { + bool c_checkClimbUp() + { + if (cinfo.f.floor == WALL) + return false; + if (c_checkGrab()) { return false; } @@ -1178,34 +1473,32 @@ struct Lara : Item if (c_checkSpace()) return false; animSet(ANIM_CLIMB_2, true); - state = HANG_UP; + state = STATE_HANG_UP; pos.y += 512 + cinfo.f.floor; } else if (cinfo.f.floor >= -896 && cinfo.f.floor <= -640) { if (c_checkSpace()) return false; animSet(ANIM_CLIMB_3, true); - state = HANG_UP; + state = STATE_HANG_UP; pos.y += 768 + cinfo.f.floor; } else if (cinfo.f.floor >= -1920 && cinfo.f.floor <= -896) { animSet(ANIM_STAND, true); - goalState = UP_JUMP; + goalState = STATE_JUMP_UP; vSpeedHack = -int32(phd_sqrt(-2 * GRAVITY * (cinfo.f.floor + 800)) + 3); - - updateAnim(); - } /* TODO for main branch + animProcess(); + /*} TODO climb else if ((waterState != WATER_STATE_WADE) && (cinfo.f.floor <= -1920) && (cinfo.l.floor <= -1920) && (cinfo.r.floor <= -1920) && (cinfo.m.ceiling <= -1158)) { animSet(ANIM_STAND, true); - goalState = UP_JUMP; + goalState = STATE_JUMP_UP; vSpeedHack = -116; - animUpdate(this); - } - else if (((cinfo.f.floor < -1024) && (cinfo.f.ceiling >= 506)) || (cinfo.m.ceiling <= -518) && c_checkClimbStart()) { + animProcess(); + } else if (((cinfo.f.floor < -1024) && (cinfo.f.ceiling >= 506)) || ((cinfo.m.ceiling <= -518) && c_checkClimbStart())) { animSet(ANIM_STAND, true); - goalState = CLIMB_START; - processAnimation(); - }*/ else { + goalState = STATE_CLIMB_START; + animProcess();*/ + } else { return false; } @@ -1215,12 +1508,16 @@ struct Lara : Item return true; } - bool c_checkHang() { + bool c_checkHang() + { if (c_checkGrab()) { return false; } - if (cinfo.f.ceiling > 0 || cinfo.m.ceiling > -LARA_STEP_HEIGHT || (cinfo.m.floor < 200 && state == REACH)) { + if ((cinfo.f.ceiling > 0) || + (cinfo.m.ceiling > -LARA_STEP_HEIGHT) || + (cinfo.m.floor < 200 && state == STATE_REACH)) + { return false; } @@ -1235,7 +1532,7 @@ struct Lara : Item return false; } - if (state == REACH) + if (state == STATE_REACH) { if (c_checkSwing()) { animSet(ANIM_HANG_SWING, true); @@ -1257,10 +1554,12 @@ struct Lara : Item return true; } - bool c_checkDrop() { + bool c_checkDrop() + { // TODO getTrigger here - if ((health > 0) && (input & IN_ACTION)) { + if ((health > 0) && (input & IN_ACTION)) + { flags.gravity = false; vSpeed = 0; return false; @@ -1275,7 +1574,8 @@ struct Lara : Item return true; } - bool c_checkWaterOut() { + bool c_checkWaterOut() + { if (!(input & IN_ACTION) || (cinfo.type != CT_FRONT) || (cinfo.f.ceiling > 0) || @@ -1295,27 +1595,27 @@ struct Lara : Item return false; } - angle.y += h - 5; + pos.y += h - 5; updateRoom(-LARA_HEIGHT / 2); - alignWall(); + alignWall(-LARA_RADIUS); - /* TODO for main branch - if ((h < -128) || (level->version & TR::VER_TR1)) { - setAnimV2(ANIM_WATER_OUT, true); - specular = LARA_WET_SPECULAR; + if ((h < -128)) { // TODO || (level->version & TR::VER_TR1)) { + animSet(ANIM_WATER_OUT, true); + // specular = LARA_WET_SPECULAR; } else if (h < 128) { - setAnimV2(ANIM_SURF_OUT, true); + animSet(ANIM_SURF_OUT, true); } else { - setAnimV2(ANIM_SURF_STAND, true); + animSet(ANIM_SURF_STAND, true); } - game->waterDrop(pos, 128.0f, 0.2f); - */ + + //game->waterDrop(pos, 128.0f, 0.2f); + animSet(ANIM_WATER_OUT, true); waterState = WATER_STATE_ABOVE; - goalState = STOP; + goalState = STATE_STOP; angle.x = 0; angle.z = 0; hSpeed = 0; @@ -1325,7 +1625,8 @@ struct Lara : Item return true; } - void c_default() { + void c_default() + { cinfo.gapPos = LARA_STEP_HEIGHT; cinfo.gapNeg = -LARA_STEP_HEIGHT; cinfo.gapCeiling = 0; @@ -1334,7 +1635,8 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); } - void c_step() { + void c_step() + { cinfo.gapPos = (waterState == WATER_STATE_WADE) ? -WALL : 128; cinfo.gapNeg = -128; cinfo.gapCeiling = 0; @@ -1342,29 +1644,32 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; if (c_checkWall()) { animSet(ANIM_STAND, true); } - if (c_checkSlide()) return; + if (c_checkSlide()) + return; pos.y += cinfo.m.floor; } - void c_fall() { + void c_fall() + { if (vSpeed <= 0 || cinfo.m.floor > 0) return; if (c_checkLanding()) { - goalState = DEATH; - } else if (state == FORWARD_JUMP && (input & IN_UP) && !(input & IN_WALK)) { - goalState = RUN; - } else if (state == FALL) { + goalState = STATE_DEATH; + } else if (state == STATE_JUMP && (input & IN_UP) && !(input & IN_WALK)) { + goalState = STATE_RUN; + } else if (state == STATE_FALL) { animSet(ANIM_LANDING, true); } else { - goalState = STOP; + goalState = STATE_STOP; } stopScreaming(); @@ -1373,27 +1678,29 @@ struct Lara : Item vSpeed = 0; flags.gravity = false; - if (state == FORWARD_JUMP) { - updateAnim(); + if (state == STATE_JUMP) { + animProcess(); } } - void c_jump() { + void c_jump() + { cinfo.gapPos = -WALL; - cinfo.gapNeg = (state == REACH) ? 0 : -LARA_STEP_HEIGHT; + cinfo.gapNeg = (state == STATE_REACH) ? 0 : -LARA_STEP_HEIGHT; cinfo.gapCeiling = 192; - collideRoom(this, state == UP_JUMP ? LARA_HEIGHT_JUMP : LARA_HEIGHT, 0); + collideRoom(this, state == STATE_JUMP_UP ? LARA_HEIGHT_JUMP : LARA_HEIGHT, 0); - if ((state == REACH || state == UP_JUMP) && c_checkHang()) { + if ((state == STATE_REACH || state == STATE_JUMP_UP) && c_checkHang()) return; - } c_applyOffset(); - bool slide = (state == FALL) || (state == REACH) || (state == UP_JUMP); + // TODO long up jump + // TODO can't side jump near walls + bool slide = (state == STATE_FALL) || (state == STATE_REACH) || (state == STATE_JUMP_UP); - if ((cinfo.type == CT_CEILING) || ((cinfo.type == CT_FRONT_CEILING) && slide)) { + if ((cinfo.type == CT_CEILING) || (slide && (cinfo.type == CT_FRONT_CEILING))) { if (vSpeed <= 0) { vSpeed = 1; } @@ -1405,8 +1712,8 @@ struct Lara : Item vSpeed = 1; } } else if (cinfo.type == CT_FLOOR_CEILING) { - pos.x -= (LARA_RADIUS * phd_sin(cinfo.angle)) >> FIXED_SHIFT; - pos.z -= (LARA_RADIUS * phd_cos(cinfo.angle)) >> FIXED_SHIFT; + pos.x -= (phd_sin(cinfo.angle) * LARA_RADIUS) >> FIXED_SHIFT; + pos.z -= (phd_cos(cinfo.angle) * LARA_RADIUS) >> FIXED_SHIFT; cinfo.m.floor = 0; hSpeed = 0; if (vSpeed <= 0) { @@ -1421,7 +1728,8 @@ struct Lara : Item c_fall(); } - void c_slide() { + void c_slide() + { cinfo.gapPos = -WALL; cinfo.gapNeg = -512; cinfo.gapCeiling = 0; @@ -1429,22 +1737,25 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; c_checkWall(); - if (c_checkFall(200, state == SLIDE ? ANIM_FALL_FORTH : ANIM_FALL_BACK)) return; + if (c_checkFall(200, state == STATE_SLIDE ? ANIM_FALL_FORTH : ANIM_FALL_BACK)) + return; c_checkSlide(); pos.y += cinfo.m.floor; if (cinfo.m.slantType != SLANT_HIGH) { - goalState = STOP; + goalState = STATE_STOP; } } - void c_roll() { + void c_roll() + { vSpeed = 0; flags.gravity = false; @@ -1455,18 +1766,22 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; - if (c_checkSlide()) return; + if (c_checkSlide()) + return; - if (c_checkFall(200, state == ROLL_START ? ANIM_FALL_FORTH : ANIM_FALL_BACK)) return; + if (c_checkFall(200, state == STATE_ROLL_START ? ANIM_FALL_FORTH : ANIM_FALL_BACK)) + return; c_applyOffset(); pos.y += cinfo.m.floor; } - void c_hang(int32 angleDelta) { + void c_hang(int32 angleDelta) + { c_angle(angleDelta); cinfo.gapPos = -WALL; cinfo.gapNeg = WALL; @@ -1480,7 +1795,8 @@ struct Lara : Item cinfo.gapNeg = -LARA_STEP_HEIGHT; cinfo.gapCeiling = 0; - switch (cinfo.quadrant) { + switch (cinfo.quadrant) + { case 0 : pos.z += 2; break; case 1 : pos.x += 2; break; case 2 : pos.z -= 2; break; @@ -1491,7 +1807,8 @@ struct Lara : Item moveAngle = angle.y + angleDelta; - if (health <= 0 || !(input & IN_ACTION)) { + if (health <= 0 || !(input & IN_ACTION)) + { animSet(ANIM_FALL_HANG, true, 9); cinfo.offset.y = cinfo.f.floor - getBounds().minY + 2; @@ -1508,7 +1825,7 @@ struct Lara : Item if (noFloor || (cinfo.type != CT_FRONT) || (cinfo.m.ceiling >= 0) || abs(cinfo.r.floor - cinfo.l.floor) >= LARA_HANG_SLANT) { - if (state != HANG) { + if (state != STATE_HANG) { animSet(ANIM_HANG, true, 21); } pos = cinfo.pos; @@ -1527,7 +1844,158 @@ struct Lara : Item } } - C_HANDLER( WALK ) + enum ClimbState { + CLIMB_HANG, + CLIMB_COLLIDE, + CLIMB_OK, + }; + + ClimbState c_climbCollide(int32 width) + { + /* TODO + int32 dx = 0, dz = 0; + + int32 x = pos.x; + int32 y = pos.y - 512; + int32 z = pos.z; + + switch (cinfo.quadrant) { + case 0 : + x += width; + z += cinfo.radius; + dz = 4; + break; + case 1 : + x += cinfo.radius; + z -= width; + dx = 4; + break; + case 2 : + x -= width; + z -= cinfo.radius; + dz = -4; + break; + case 3 : + x -= cinfo.radius; + z += width; + dx = -4; + break; + } + + // TODO + cinfo.offset.y = 0; + */ + return CLIMB_OK; + } + + void c_climbSide(int32 width) + { + if (c_checkDrop()) + return; + + switch (c_climbCollide(width)) + { + case CLIMB_HANG: + { + pos.x = cinfo.pos.x; + pos.z = cinfo.pos.z; + goalState = STATE_HANG; + break; + } + + case CLIMB_COLLIDE: + { + pos.x = cinfo.pos.x; + pos.z = cinfo.pos.z; + animSet(ANIM_CLIMB_START, true); + break; + } + + case CLIMB_OK: + { + if (input & IN_LEFT) { + goalState = STATE_CLIMB_LEFT; + } else if (input & IN_RIGHT) { + goalState = STATE_CLIMB_RIGHT; + } else { + goalState = STATE_CLIMB_START; + } + pos.y += cinfo.offset.y; + break; + } + } + } + + void c_swim() { + c_angle(ANGLE_0); + + collideRoom(this, LARA_HEIGHT_UW, LARA_HEIGHT_UW / 2); + + c_applyOffset(); + + if (c_checkWallUW()) + return; + } + + void c_surf() + { + collideRoom(this, LARA_HEIGHT_SURF + 100, LARA_HEIGHT_SURF); + + c_applyOffset(); + + c_checkWallSurf(); + + if (state == STATE_SURF_TREAD) { + if (frameIndex == 0) { + //game->waterDrop(getJoint(jointHead).pos, 96.0f, 0.03f); + } + } else { + if (frameIndex % 4 == 0) { + //game->waterDrop(getJoint(jointHead).pos, 96.0f, 0.02f); + } + } + + int32 waterLevel = getWaterLevel(); + if (waterLevel - pos.y <= -100) { + s_dive(); + return; + } + + /* TODO + if (level->version & TR::VER_TR1) { + return; + } + + if ((cinfo.type == CT_FRONT) || (cinfo.m.slantType == TR::SLANT_HIGH) || (cinfo.m.floor >= 0)) { + return; + } + + if (cinfo.m.floor >= -128) { + if (targetState == STATE_SURF_LEFT) { + targetState = STATE_STEP_LEFT; + } else if (targetState == STATE_SURF_RIGHT) { + targetState = STATE_STEP_RIGHT; + } else { + setAnimV2(ANIM_WADE, true); + } + } else { + setAnimV2(ANIM_WADE_ASCEND, true); + targetState = STATE_STOP; + } + + v2pos.y += cinfo.f.floor + LARA_HEIGHT - 5; + updateRoomV2(-LARA_HEIGHT / 2); + + gravity = false; + v2rot.x = 0; + v2rot.z = 0; + hSpeed = 0; + vSpeed = 0; + waterState = WATER_STATE_WADE; + */ + } + + C_HANDLER( STATE_WALK ) { vSpeed = 0; flags.gravity = false; @@ -1536,11 +2004,14 @@ struct Lara : Item c_angle(ANGLE_0); c_default(); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; - if (c_checkClimbUp()) return; + if (c_checkClimbUp()) + return; - if (c_checkWall()) { + if (c_checkWall()) + { if (frameIndex >= 29 && frameIndex <= 47) { animSet(ANIM_STAND_RIGHT, false); } else if ((frameIndex >= 22 && frameIndex <= 28) || (frameIndex >= 48 && frameIndex <= 57)) { @@ -1550,10 +2021,12 @@ struct Lara : Item } } - if (c_checkFall(LARA_STEP_HEIGHT)) return; + if (c_checkFall(LARA_STEP_HEIGHT)) + return; // descend - if (cinfo.m.floor > 128) { + if (cinfo.m.floor > 128) + { if (frameIndex >= 28 && frameIndex <= 45) { animSet(ANIM_WALK_DESCEND_RIGHT, false); } else { @@ -1562,7 +2035,8 @@ struct Lara : Item } // ascend - if (cinfo.m.floor >= -LARA_STEP_HEIGHT && cinfo.m.floor < -128) { + if (cinfo.m.floor >= -LARA_STEP_HEIGHT && cinfo.m.floor < -128) + { if (frameIndex >= 27 && frameIndex <= 44) { animSet(ANIM_WALK_ASCEND_RIGHT, false); } else { @@ -1570,12 +2044,13 @@ struct Lara : Item } } - if (c_checkSlide()) return; + if (c_checkSlide()) + return; pos.y += cinfo.m.floor; } - C_HANDLER( RUN ) + C_HANDLER( STATE_RUN ) { c_angle(ANGLE_0); @@ -1586,26 +2061,31 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; - if (c_checkClimbUp()) return; + if (c_checkClimbUp()) + return; if (c_checkWall()) { angle.z = 0; - if (cinfo.f.slantType == SLANT_NONE && cinfo.f.floor < -LARA_SMASH_HEIGHT && frameIndex < 22) { + if (cinfo.f.slantType == SLANT_NONE && cinfo.f.floor < -LARA_SMASH_HEIGHT && frameIndex < 22) + { animSet(frameIndex < 10 ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT, false); - state = SPLAT; + state = STATE_SPLAT; return; } animSet(ANIM_STAND, true); } - if (c_checkFall(LARA_STEP_HEIGHT)) return; + if (c_checkFall(LARA_STEP_HEIGHT)) + return; // ascend - if (cinfo.m.floor >= -LARA_STEP_HEIGHT && cinfo.m.floor < -128) { + if (cinfo.m.floor >= -LARA_STEP_HEIGHT && cinfo.m.floor < -128) + { if (frameIndex >= 3 && frameIndex <= 14) { animSet(ANIM_RUN_ASCEND_RIGHT, false); } else { @@ -1613,9 +2093,11 @@ struct Lara : Item } } - if (c_checkSlide()) return; + if (c_checkSlide()) + return; - if (cinfo.m.floor >= 50) { + if (cinfo.m.floor >= 50) + { pos.y += 50; return; } @@ -1623,7 +2105,7 @@ struct Lara : Item pos.y += cinfo.m.floor; } - C_HANDLER( STOP ) + C_HANDLER( STATE_STOP ) { vSpeed = 0; flags.gravity = false; @@ -1631,29 +2113,32 @@ struct Lara : Item c_angle(ANGLE_0); c_default(); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; - if (c_checkFall(100)) return; + if (c_checkFall(100)) + return; - if (c_checkSlide()) return; + if (c_checkSlide()) + return; c_applyOffset(); pos.y += cinfo.m.floor; } - C_HANDLER( FORWARD_JUMP ) + C_HANDLER( STATE_JUMP ) { c_angle(ANGLE_0); c_jump(); } - C_HANDLER( POSE ) + C_HANDLER( STATE_POSE ) { - c_STOP(); + c_STATE_STOP(); } - C_HANDLER( FAST_BACK ) + C_HANDLER( STATE_BACK_FAST ) { vSpeed = 0; flags.gravity = false; @@ -1667,9 +2152,11 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; - if (c_checkFall(200, ANIM_FALL_BACK)) return; + if (c_checkFall(200, ANIM_FALL_BACK)) + return; if (c_checkWall()) { animSet(ANIM_STAND, false); @@ -1678,24 +2165,26 @@ struct Lara : Item pos.y += cinfo.m.floor; } - C_HANDLER( TURN_RIGHT ) + C_HANDLER( STATE_TURN_RIGHT ) { c_angle(ANGLE_0); c_default(); - if (c_checkFall(100)) return; + if (c_checkFall(100)) + return; - if (c_checkSlide()) return; + if (c_checkSlide()) + return; pos.y += cinfo.m.floor; } - C_HANDLER( TURN_LEFT ) + C_HANDLER( STATE_TURN_LEFT ) { - c_TURN_RIGHT(); + c_STATE_TURN_RIGHT(); } - C_HANDLER( DEATH ) + C_HANDLER( STATE_DEATH ) { cinfo.radius = LARA_RADIUS * 4; @@ -1706,52 +2195,57 @@ struct Lara : Item pos.y += cinfo.m.floor; } - C_HANDLER( FALL ) + C_HANDLER( STATE_FALL ) { flags.gravity = true; c_jump(); } - C_HANDLER( HANG ) + C_HANDLER( STATE_HANG ) { c_hang(0); - if ((input & IN_UP) && goalState == HANG) + if ((input & IN_UP) && goalState == STATE_HANG) { - if (cinfo.f.floor <= -850 || - cinfo.f.floor >= -650 || - c_checkSpace() || cinfo.staticHit) return; + if (abs(cinfo.f.floor) >= 850) + return; + + if (c_checkSpace() || cinfo.staticHit) + return; if (input & IN_WALK) { - goalState = HANDSTAND; + goalState = STATE_HANDSTAND; } else { - goalState = HANG_UP; + goalState = STATE_HANG_UP; } } } - C_HANDLER( REACH ) + C_HANDLER( STATE_REACH ) { flags.gravity = true; c_angle(ANGLE_0); c_jump(); } - C_HANDLER( SPLAT ) + C_HANDLER( STATE_SPLAT ) { c_angle(ANGLE_0); c_default(); c_applyOffset(); } - C_HANDLER( TREAD ) {} // TODO - - C_HANDLER( LAND ) + C_HANDLER( STATE_UW_TREAD ) { - c_STOP(); + c_swim(); } - C_HANDLER( COMPRESS ) + C_HANDLER( STATE_LAND ) + { + c_STATE_STOP(); + } + + C_HANDLER( STATE_COMPRESS ) { vSpeed = 0; flags.gravity = false; @@ -1762,7 +2256,8 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (cinfo.m.ceiling > -100) { + if (cinfo.m.ceiling > -100) + { animSet(ANIM_STAND, true); pos = cinfo.pos; hSpeed = 0; @@ -1771,7 +2266,7 @@ struct Lara : Item } } - C_HANDLER( BACK ) + C_HANDLER( STATE_BACK ) { vSpeed = 0; flags.gravity = false; @@ -1785,7 +2280,8 @@ struct Lara : Item collideRoom(this, LARA_HEIGHT, 0); - if (c_checkCeiling()) return; + if (c_checkCeiling()) + return; if (c_checkWall()) { @@ -1801,200 +2297,265 @@ struct Lara : Item } } - if (c_checkSlide()) return; + if (c_checkSlide()) + return; pos.y += cinfo.m.floor; } - C_HANDLER( SWIM ) {} // TODO - C_HANDLER( GLIDE ) {} // TODO + C_HANDLER( STATE_UW_SWIM ) + { + c_swim(); + } - C_HANDLER( HANG_UP ) + C_HANDLER( STATE_UW_GLIDE ) + { + c_swim(); + } + + C_HANDLER( STATE_HANG_UP ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( FAST_TURN ) + C_HANDLER( STATE_TURN_FAST ) { - c_STOP(); + c_STATE_STOP(); } - C_HANDLER( STEP_RIGHT ) + C_HANDLER( STATE_STEP_RIGHT ) { c_angle(+ANGLE_90); c_step(); } - C_HANDLER( STEP_LEFT ) + C_HANDLER( STATE_STEP_LEFT ) { c_angle(-ANGLE_90); c_step(); } - C_HANDLER( ROLL_END ) + C_HANDLER( STATE_ROLL_END ) { c_angle(ANGLE_180); c_roll(); } - C_HANDLER( SLIDE ) + C_HANDLER( STATE_SLIDE ) { c_angle(ANGLE_0); c_slide(); } - C_HANDLER( BACK_JUMP ) + C_HANDLER( STATE_JUMP_BACK ) { c_angle(ANGLE_180); c_jump(); } - C_HANDLER( RIGHT_JUMP ) + C_HANDLER( STATE_JUMP_RIGHT ) { c_angle(ANGLE_90); c_jump(); } - C_HANDLER( LEFT_JUMP ) + C_HANDLER( STATE_JUMP_LEFT ) { c_angle(-ANGLE_90); c_jump(); } - C_HANDLER( UP_JUMP ) + C_HANDLER( STATE_JUMP_UP ) { c_angle(ANGLE_0); c_jump(); } - C_HANDLER( FALL_BACK ) + C_HANDLER( STATE_FALL_BACK ) { c_angle(ANGLE_180); c_jump(); } - C_HANDLER( HANG_LEFT ) + C_HANDLER( STATE_HANG_LEFT ) { c_hang(-ANGLE_90); } - C_HANDLER( HANG_RIGHT ) + C_HANDLER( STATE_HANG_RIGHT ) { c_hang(ANGLE_90); } - C_HANDLER( SLIDE_BACK ) + C_HANDLER( STATE_SLIDE_BACK ) { c_angle(ANGLE_180); c_slide(); } - C_HANDLER( SURF_TREAD ) {} // TODO - C_HANDLER( SURF_SWIM ) {} // TODO - C_HANDLER( DIVE ) {} // TODO + C_HANDLER( STATE_SURF_TREAD ) + { + c_angle(ANGLE_0); + c_surf(); + } - C_HANDLER( PUSH_BLOCK ) + C_HANDLER( STATE_SURF_SWIM ) + { + cinfo.gapNeg = -LARA_STEP_HEIGHT; + + c_angle(ANGLE_0); + c_surf(); + c_checkWaterOut(); + } + + C_HANDLER( STATE_UW_DIVE ) + { + c_swim(); + } + + C_HANDLER( STATE_BLOCK_PUSH ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( PULL_BLOCK ) + C_HANDLER( STATE_BLOCK_PULL ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( PUSH_PULL_READY ) + C_HANDLER( STATE_BLOCK_READY ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( PICK_UP ) + C_HANDLER( STATE_PICKUP ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( SWITCH_DOWN ) + C_HANDLER( STATE_SWITCH_DOWN ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( SWITCH_UP ) + C_HANDLER( STATE_SWITCH_UP ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( USE_KEY ) + C_HANDLER( STATE_USE_KEY ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( USE_PUZZLE ) + C_HANDLER( STATE_USE_PUZZLE ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( UW_DEATH ) {} // TODO + C_HANDLER( STATE_DEATH_UW ) + { + health = 0; + oxygen = 0; - C_HANDLER( ROLL_START ) + int16 waterLevel = getWaterLevel(); + if (waterLevel != WALL && waterLevel < pos.y - LARA_RADIUS) { + pos.y -= LARA_FLOAT_UP_SPEED; + } + + c_swim(); + } + + C_HANDLER( STATE_ROLL_START ) { c_angle(ANGLE_0); c_roll(); } - C_HANDLER( SPECIAL ) + C_HANDLER( STATE_SPECIAL ) { // empty } - C_HANDLER( SURF_BACK ) {} // TODO - C_HANDLER( SURF_LEFT ) {} // TODO - C_HANDLER( SURF_RIGHT ) {} // TODO + C_HANDLER( STATE_SURF_BACK ) + { + c_angle(ANGLE_180); + c_surf(); + } - C_HANDLER( MIDAS_USE ) + C_HANDLER( STATE_SURF_LEFT ) + { + c_angle(-ANGLE_90); + c_surf(); + } + + C_HANDLER( STATE_SURF_RIGHT ) + { + c_angle(ANGLE_90); + c_surf(); + } + + C_HANDLER( STATE_USE_MIDAS ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( MIDAS_DEATH ) + C_HANDLER( STATE_DEATH_MIDAS ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( SWAN_DIVE ) + C_HANDLER( STATE_SWAN_DIVE ) { c_angle(ANGLE_0); c_jump(); } - C_HANDLER( FAST_DIVE ) + C_HANDLER( STATE_FAST_DIVE ) { c_angle(ANGLE_0); c_jump(); } - C_HANDLER( HANDSTAND ) + C_HANDLER( STATE_HANDSTAND ) { c_angle(ANGLE_0); c_default(); } - C_HANDLER( WATER_OUT ) + C_HANDLER( STATE_WATER_OUT ) { c_angle(ANGLE_0); c_default(); } + C_HANDLER( STATE_CLIMB_START ) {} + C_HANDLER( STATE_CLIMB_UP ) {} + C_HANDLER( STATE_CLIMB_LEFT ) {} + C_HANDLER( STATE_CLIMB_END ) {} + C_HANDLER( STATE_CLIMB_RIGHT ) {} + C_HANDLER( STATE_CLIMB_DOWN ) {} + C_HANDLER( STATE_UNUSED_1 ) {} + C_HANDLER( STATE_UNUSED_2 ) {} + C_HANDLER( STATE_UNUSED_3 ) {} + C_HANDLER( STATE_WADE ) {} + C_HANDLER( STATE_ROLL_UW ) {} + C_HANDLER( STATE_PICKUP_FLARE ) {} + C_HANDLER( STATE_ROLL_AIR ) {} + C_HANDLER( STATE_UNUSED_4 ) {} + C_HANDLER( STATE_ZIPLINE ) {} + Lara(Room* room) : Item(room) { health = LARA_MAX_HEALTH; @@ -2002,13 +2563,17 @@ struct Lara : Item flags.shadow = true; activate(); + + animSet(ANIM_STAND, true, 0); } // update control void updateInput() { input = 0; - if (camera.mode == CAMERA_MODE_FREE) return; + + if (camera.mode == CAMERA_MODE_FREE) + return; if (keys & IK_LEFT) input |= IN_LEFT; if (keys & IK_RIGHT) input |= IN_RIGHT; @@ -2022,10 +2587,159 @@ struct Lara : Item if ((keys & (IK_L | IK_R)) == (IK_L | IK_R)) input |= IN_LOOK; } - virtual void update() + void updateWaterState() { - updateInput(); + int32 waterLevel = getWaterLevel(); + int32 waterDist = WALL; + if (waterLevel != WALL) { + waterDist = pos.y - waterLevel; + } + + // change water state + switch (waterState) + { + case WATER_STATE_ABOVE: + { + if (waterDist == WALL || waterDist < LARA_WADE_MIN_DEPTH) { + break; + } + + int32 waterDepth = getWaterDepth(); + if (waterDepth > LARA_WADE_MAX_DEPTH - 256) + { + if (!room->info->flags.water) // go dive + break; + + waterState = WATER_STATE_UNDER; + flags.gravity = false; + oxygen = LARA_MAX_OXYGEN; + + pos.y += 100; + updateRoom(0); + stopScreaming(); + + if (state == STATE_SWAN_DIVE) { + angle.x = -45 * DEG2SHORT; + goalState = STATE_UW_DIVE; + animProcess(); + vSpeed *= 2; + //game->waterDrop(pos, 128.0f, 0.2f); + } else if (state == STATE_FAST_DIVE) { + angle.x = -85 * DEG2SHORT; + goalState = STATE_UW_DIVE; + animProcess(); + vSpeed *= 2; + //game->waterDrop(pos, 128.0f, 0.2f); + } else { + angle.x = -45 * DEG2SHORT; + animSet(ANIM_WATER_FALL, true); + state = STATE_UW_DIVE; // TODO check necessary + goalState = STATE_UW_SWIM; + vSpeed = vSpeed * 3 / 2; + //game->waterDrop(pos, 256.0f, 0.2f); + } + + //v2head.x = v2head.y = 0; + //v2torso.x = v2torso.y = 0; + //waterSplash(); + } else if (waterDist > LARA_WADE_MIN_DEPTH) { + waterState = WATER_STATE_WADE; + if (!flags.gravity) { + goalState = STATE_STOP; + } + } + break; + } + + case WATER_STATE_SURFACE: + { + if (room->info->flags.water) + break; + + if (waterDist > LARA_WADE_MIN_DEPTH) { + waterState = WATER_STATE_WADE; + animSet(ANIM_STAND_NORMAL, true); + goalState = STATE_WADE; + animProcess(); + } else { + waterState = WATER_STATE_ABOVE; + animSet(ANIM_FALL_FORTH, true); + hSpeed = vSpeed / 4; + flags.gravity = true; + } + + vSpeed = 0; + angle.x = angle.z = 0; + //v2head.x = v2head.y = 0; + //v2torso.x = v2torso.y = 0; + break; + } + + case WATER_STATE_UNDER: + { + if (room->info->flags.water || flags.dozy) + break; + + if ((getWaterDepth() != WALL) && abs(waterDist) < 256) { + waterState = WATER_STATE_SURFACE; + pos.y -= (waterDist - 1); + animSet(ANIM_SURF, true); + vSpeed = 0; + swimTimer = LARA_SWIM_TIMER + 1; // block dive before we press jump button again + updateRoom(-LARA_HEIGHT / 2); + //game->playSound(TR::SND_BREATH, pos, Sound::PAN | Sound::UNIQUE); + } else { + waterState = WATER_STATE_ABOVE; + animSet(ANIM_FALL_FORTH, true); + hSpeed = vSpeed / 4; + vSpeed = 0; + flags.gravity = true; + } + + angle.x = angle.z = 0; + //v2head.x = v2head.y = 0; + //v2torso.x = v2torso.y = 0; + break; + } + + case WATER_STATE_WADE: + { + if (waterDist < LARA_WADE_MIN_DEPTH) + { + waterState = WATER_STATE_ABOVE; + if (state == STATE_WADE) { + goalState = STATE_RUN; + } + } else if (waterDist > LARA_WADE_MAX_DEPTH) { + waterState = WATER_STATE_SURFACE; + pos.y -= (waterDist - 1); + + if (state == STATE_BACK) { + animSet(ANIM_SURF_BACK, true); + } else if (state == STATE_STEP_RIGHT) { + animSet(ANIM_SURF_RIGHT, true); + } else if (state == STATE_STEP_LEFT) { + animSet(ANIM_SURF_LEFT, true); + } else { + animSet(ANIM_SURF_SWIM, true); + } + + swimTimer = 0; + vSpeed = 0; + flags.gravity = false; + angle.x = angle.z = 0; + //v2head.x = v2head.y = 0; + //v2torso.x = v2torso.y = 0; + updateRoom(0); + } + break; + } + } + } + + void updateAbove() + { cinfo.trigger = NULL; cinfo.radius = LARA_RADIUS; cinfo.pos = pos; @@ -2039,14 +2753,109 @@ struct Lara : Item angle.z = angleDec(angle.z, 1 * DEG2SHORT); turnSpeed = angleDec(turnSpeed, 2 * DEG2SHORT); angle.y += turnSpeed; + } - updateAnim(); + void updateSurface() + { + cinfo.trigger = NULL; + cinfo.radius = LARA_RADIUS; + cinfo.gapPos = -WALL; + cinfo.gapNeg = -128; + cinfo.gapCeiling = 100; + cinfo.pos = pos; + cinfo.enemyPush = false; + cinfo.enemyHit = false; + cinfo.stopOnSlant = false; + cinfo.stopOnLava = false; + + updateState(); + + angle.z = angleDec(angle.z, 2 * DEG2SHORT); + + pos.x += (phd_sin(moveAngle) * vSpeed) >> 16; + pos.z += (phd_cos(moveAngle) * vSpeed) >> 16; + + camera.targetAngleX = -22 * DEG2SHORT; + } + + void updateUnder() + { + cinfo.trigger = NULL; + cinfo.radius = LARA_RADIUS_WATER; + cinfo.gapPos = -WALL; + cinfo.gapNeg = -LARA_HEIGHT_UW; + cinfo.gapCeiling = LARA_HEIGHT_UW; + cinfo.pos = pos; + cinfo.enemyPush = false; + cinfo.enemyHit = false; + cinfo.stopOnSlant = false; + cinfo.stopOnLava = false; + + updateState(); + + angle.z = angleDec(angle.z, 2 * DEG2SHORT); + turnSpeed = angleDec(turnSpeed, 2 * DEG2SHORT); + angle.y += turnSpeed; + + angle.x = X_CLAMP(angle.x, -85 * DEG2SHORT, 85 * DEG2SHORT); + angle.z = X_CLAMP(angle.z, -22 * DEG2SHORT, 22 * DEG2SHORT); + + int32 c = phd_cos(angle.x); + int32 s = phd_sin(angle.x); + + pos.y -= (s * vSpeed) >> 16; + pos.x += (c * ((phd_sin(angle.y) * vSpeed) >> 16)) >> FIXED_SHIFT; + pos.z += (c * ((phd_cos(angle.y) * vSpeed) >> 16)) >> FIXED_SHIFT; + } + + void updateWeapon() + { + // TODO + } + + virtual void update() + { + updateInput(); + + updateWaterState(); + + if (health > 0) + { + if (waterState == WATER_STATE_UNDER) + { + if (oxygen > 0) { + oxygen--; + } else { + health -= 5; + } + } else { + oxygen = X_MIN(oxygen + 10, LARA_MAX_OXYGEN); + } + } + + switch (waterState) + { + case WATER_STATE_ABOVE : + case WATER_STATE_WADE : updateAbove(); break; + case WATER_STATE_SURFACE : updateSurface(); break; + case WATER_STATE_UNDER : updateUnder(); break; + } + + animProcess(); updateCollision(); - updateRoom(-LARA_HEIGHT / 2); + int32 offset; + if (waterState == WATER_STATE_SURFACE) { + offset = LARA_RADIUS; + } else if (waterState == WATER_STATE_UNDER) { + offset = 0; + } else { + offset = -LARA_HEIGHT / 2; + } + updateRoom(offset); - //updateWeapon(); + updateWeapon(); checkTrigger(cinfo.trigger, this); } @@ -2090,18 +2899,18 @@ int32 doTutorial(Item* lara, int32 track) { switch (track) { - case 28 : if (gSaveGame.tracks[track].once && lara->state == Lara::UP_JUMP) return 29; + case 28 : if (gSaveGame.tracks[track].once && lara->state == Lara::STATE_JUMP_UP) return 29; case 37 : - case 41 : if (lara->state != Lara::HANG) return 0; - case 42 : if (gSaveGame.tracks[track].once && lara->state == Lara::HANG) return 43; - case 49 : if (lara->state != Lara::SURF_TREAD) return 0; + case 41 : if (lara->state != Lara::STATE_HANG) return 0; + case 42 : if (gSaveGame.tracks[track].once && lara->state == Lara::STATE_HANG) return 43; + case 49 : if (lara->state != Lara::STATE_SURF_TREAD) return 0; case 50 : // end of GYM if (gSaveGame.tracks[track].once) { //timer += Core::deltaTime; //if (timer > 3.0f) // game->loadNextLevel(); } else { - if (lara->state != Lara::WATER_OUT) + if (lara->state != Lara::STATE_WATER_OUT) return 0; //timer = 0.0f; } @@ -2111,4 +2920,11 @@ int32 doTutorial(Item* lara, int32 track) return track; } +Lara* players[2]; + +Lara* getLara(const vec3i &pos) +{ + return players[0]; // TODO find nearest player +} + #endif diff --git a/src/platform/gba/level.h b/src/platform/gba/level.h index 3586a48..42d40a2 100644 --- a/src/platform/gba/level.h +++ b/src/platform/gba/level.h @@ -5,7 +5,7 @@ Level level; -const Texture* textures; +const Sprite* sprites; const uint8* tiles; #ifndef MODE_PAL @@ -16,13 +16,14 @@ IWRAM_DATA uint8 lightmap[256 * 32]; // IWRAM 8k EWRAM_DATA Item items[MAX_ITEMS]; -#define MAX_DYN_SECTORS 1024 +#define MAX_DYN_SECTORS (1024*3) int32 dynSectorsCount; EWRAM_DATA Sector dynSectors[MAX_DYN_SECTORS]; // EWRAM 8k - +EWRAM_DATA Texture textures[MAX_TEXTURES]; EWRAM_DATA Room rooms[MAX_ROOMS]; EWRAM_DATA Model models[MAX_MODELS]; EWRAM_DATA StaticMesh staticMeshes[MAX_STATIC_MESHES]; +EWRAM_DATA FixedCamera cameras[MAX_CAMERAS]; Item* Item::sFirstActive; Item* Item::sFirstFree; @@ -93,7 +94,10 @@ void readLevel(const uint8* data) tiles = level.tiles; - textures = level.textures; + // prepare textures (required by anim tex logic) + memcpy(textures, level.textures, level.texturesCount * sizeof(Texture)); + + sprites = level.sprites; // prepare models // TODO prerocess memset(models, 0, sizeof(models)); @@ -126,6 +130,9 @@ void readLevel(const uint8* data) m->start = spriteSeq->start; } + // prepare fixed cameras + memcpy(cameras, level.cameras, level.camerasCount * sizeof(FixedCamera)); + // prepare free list for (int32 i = MAX_ITEMS - 1; i >= level.itemsCount; i--) { diff --git a/src/platform/gba/main.cpp b/src/platform/gba/main.cpp index e620de8..cb5a7c1 100644 --- a/src/platform/gba/main.cpp +++ b/src/platform/gba/main.cpp @@ -1,11 +1,13 @@ #if defined(_WIN32) || defined(__DOS__) - void* LEVEL1_PKD; - void* TRACK_13_WAV; + const void* TRACK_13_WAV; + const void* levelData; + #define LEVEL_NAME "LEVEL1.PKD" #elif defined(__GBA__) - #include "LEVEL1_PKD.h" #include "TRACK_13_WAV.h" + #include "LEVEL1_PKD.h" + const void* levelData = LEVEL1_PKD; #elif defined(__TNS__) - void* LEVEL1_PHD; + const void* levelData; #endif #include "game.h" @@ -41,11 +43,22 @@ int32 fpsCounter = 0; memcpy(MEM_PAL_BG, palette, 256 * 2); #endif } + + int32 osGetSystemTimeMS() + { + return GetTickCount(); + } + #elif defined(__GBA__) void paletteSet(const uint16* palette) { memcpy((uint16*)MEM_PAL_BG, palette, 256 * 2); } + + int32 osGetSystemTimeMS() + { + return 0; // TODO + } #elif defined(__TNS__) unsigned int osTime; volatile unsigned int *timerBUS; @@ -67,11 +80,16 @@ int32 fpsCounter = 0; osTime = *timerCLK; } - int GetTickCount() + int32 GetTickCount() { return (osTime - *timerCLK) / 33; } + int32 osGetSystemTimeMS() + { + return *timerCLK / 33; + } + void paletteSet(uint16* palette) { memcpy((uint16*)0xC0000200, palette, 256 * 2); @@ -419,7 +437,7 @@ int main(void) { { // level1 #if defined(_WIN32) || defined(__DOS__) - FILE *f = fopen("data/LEVEL1.PKD", "rb"); + FILE *f = fopen("data/" LEVEL_NAME, "rb"); #elif defined(__TNS__) FILE *f = fopen("/documents/OpenLara/LEVEL1.PHD.tns", "rb"); #else @@ -430,12 +448,16 @@ int main(void) { return 0; } - fseek(f, 0, SEEK_END); - int32 size = ftell(f); - fseek(f, 0, SEEK_SET); - LEVEL1_PKD = new uint8[size]; - fread(LEVEL1_PKD, 1, size, f); - fclose(f); + { + fseek(f, 0, SEEK_END); + int32 size = ftell(f); + fseek(f, 0, SEEK_SET); + uint8* data = new uint8[size]; + fread(data, 1, size, f); + fclose(f); + + levelData = data; + } // track 13 #if defined(_WIN32) || defined(__DOS__) @@ -448,16 +470,14 @@ int main(void) { fseek(f, 0, SEEK_END); int32 size = ftell(f); fseek(f, 0, SEEK_SET); - TRACK_13_WAV = new uint8[size]; - fread(TRACK_13_WAV, 1, size, f); + uint8* data = new uint8[size]; + fread(data, 1, size, f); fclose(f); + + TRACK_13_WAV = data; } #endif } -#elif defined(__GBA__) - // set low latency mode via WAITCNT register (thanks to GValiente) - REG_WSCNT = WS_ROM0_N2 | WS_ROM0_S1 | WS_PREFETCH; - //*(vu32*)(REG_BASE+0x0800) = 0x0E000020; // Undocumented - Internal Memory Control (R/W) #endif #if defined(_WIN32) @@ -507,6 +527,30 @@ int main(void) { //uint16 &GreenSwap = *(uint16*)0x4000002; //GreenSwap = 1; + // set low latency mode via WAITCNT register (thanks to GValiente) + REG_WSCNT = WS_ROM0_N2 | WS_ROM0_S1 | WS_PREFETCH; // fast ROM + + // Undocumented - Internal Memory Control (R/W) + #define MEM_CHECK_MAGIC 14021968 + vu32& fastAccessReg = *(vu32*)(REG_BASE+0x0800); + + fastAccessReg = 0x0E000020; // fast EWRAM + + vu32* fastAccessMem = (vu32*)soundBufferA; // check EWRAM access + // write + for (int32 i = 0; i < 16; i++) + { + fastAccessMem[i] = MEM_CHECK_MAGIC + i; + } + // read + for (int32 i = 0; i < 16; i++) + { + if (fastAccessMem[i] != vu32(MEM_CHECK_MAGIC + i)) + { + fastAccessReg = 0x0D000020; // device doesn't support this feature, revert reg value + } + } + soundInit(); game.init(); diff --git a/src/platform/gba/object.h b/src/platform/gba/object.h index d1ff9f4..998e479 100644 --- a/src/platform/gba/object.h +++ b/src/platform/gba/object.h @@ -3,6 +3,7 @@ #include "item.h" #include "lara.h" +#include "inventory.h" vec3i getBlockOffset(int16 angleY, int32 offset) { @@ -27,6 +28,31 @@ namespace Limits Bounds( -200, 200, 0, 0, 312, 512 ), vec3s( 10 * DEG2SHORT, 30 * DEG2SHORT, 10 * DEG2SHORT ) }; + + static const Limit SWITCH_UW = { + Bounds( -1024, 1024, -1024, 1024, -1024, 1024 ), + vec3s( 80 * DEG2SHORT, 80 * DEG2SHORT, 80 * DEG2SHORT ) + }; + + static const Limit BLOCK = { + Bounds( -300, 300, 0, 0, -692, -512 ), + vec3s( 10 * DEG2SHORT, 30 * DEG2SHORT, 10 * DEG2SHORT ) + }; + + static const Limit PICKUP = { + Bounds( -256, 256, -100, 100, -256, 100 ), + vec3s( 10 * DEG2SHORT, 0, 0 ) + }; + + static const Limit PICKUP_UW = { + Bounds( -512, 512, -512, 512, -512, 512 ), + vec3s( 45 * DEG2SHORT, 45 * DEG2SHORT, 45 * DEG2SHORT ) + }; + + static const Limit HOLE = { + Bounds( -200, 200, 0, 0, 312, 512 ), + vec3s( 10 * DEG2SHORT, 30 * DEG2SHORT, 10 * DEG2SHORT ) + }; }; @@ -36,7 +62,7 @@ struct Object : Item virtual void update() { - updateAnim(); + animProcess(); } bool isActive() @@ -88,6 +114,21 @@ struct ViewTarget : Object }; +struct Waterfall : Object +{ + Waterfall(Room* room) : Object(room) {} + + virtual void draw() {} +}; + + +struct LavaEmitter : Object +{ + LavaEmitter(Room* room) : Object(room) {} + + virtual void draw() {} +}; + struct Door : Object { enum { @@ -117,13 +158,12 @@ struct Door : Object } } - updateAnim(); + animProcess(); } virtual void collide(Lara* lara, CollisionInfo* cinfo) { - UNUSED(lara); - UNUSED(cinfo); + // TODO door collision } void action(bool close) @@ -160,6 +200,8 @@ struct Door : Object nextRoom = sector->getNextRoom(); } + // TODO modify level.boxes + if (!behind && nextRoom) { activate(close, true, nextRoom, pos.x, pos.z); // use sector from item pos } @@ -167,6 +209,32 @@ struct Door : Object }; +struct TrapDoor : Object +{ + enum { + STATE_CLOSE, + STATE_OPEN, + }; + + TrapDoor(Room* room) : Object(room) {} + + virtual void update() + { + if (isActive()) { + if (state == STATE_CLOSE) { + goalState = STATE_OPEN; + } + } else { + if (state == STATE_OPEN) { + goalState = STATE_CLOSE; + } + } + + animProcess(); + } +}; + + struct Switch : Object { enum { @@ -189,12 +257,13 @@ struct Switch : Object virtual void collide(Lara* lara, CollisionInfo* cinfo) { - UNUSED(cinfo); + if (lara->weaponState != WEAPON_STATE_FREE) + return; if (!(lara->input & IN_ACTION)) return; - if (lara->state != Lara::STOP) + if (lara->state != Lara::STATE_STOP) return; if (flags.status != ITEM_FLAGS_STATUS_NONE) @@ -207,22 +276,13 @@ struct Switch : Object ASSERT(state == STATE_DOWN || state == STATE_UP); - if (state == STATE_DOWN) { - lara->goalState = Lara::SWITCH_DOWN; - goalState = STATE_UP; - } else { - lara->goalState = Lara::SWITCH_UP; - goalState = STATE_DOWN; - } + bool isDown = (state == STATE_DOWN); - flags.status = ITEM_FLAGS_STATUS_ACTIVE; + goalState = isDown ? STATE_UP : STATE_DOWN; + lara->animSkip(isDown ? Lara::STATE_SWITCH_DOWN : Lara::STATE_SWITCH_UP, Lara::STATE_STOP, true); activate(); - - lara->skipAnim(); - - updateAnim(); - - lara->goalState = Lara::STOP; + flags.status = ITEM_FLAGS_STATUS_ACTIVE; + lara->weaponState = WEAPON_STATE_BUSY; } bool use(int32 t) @@ -254,9 +314,30 @@ struct SwitchWater : Switch virtual void collide(Lara* lara, CollisionInfo* cinfo) { - UNUSED(lara); - UNUSED(cinfo); - // TODO + if (lara->weaponState != WEAPON_STATE_FREE) + return; + + if (!(lara->input & IN_ACTION)) + return; + + if (lara->state != Lara::STATE_UW_TREAD) + return; + + if (!checkLimit(lara, Limits::SWITCH_UW)) + return; + + if (!lara->moveTo(vec3i(0, 0, 108), this, true)) + return; + + lara->vSpeed = 0; // underwater speed + goalState = (state == STATE_UP) ? STATE_DOWN : STATE_UP; + + lara->animSkip(Lara::STATE_SWITCH_DOWN, Lara::STATE_UW_TREAD, true); + activate(); + flags.status = ITEM_FLAGS_STATUS_ACTIVE; + + //TODO TR2+ + //lara->weaponState = WEAPON_STATE_BUSY; } }; @@ -265,9 +346,9 @@ struct Key : Object { Key(Room* room) : Object(room) {} - bool use() + bool use(Item* lara) { - if (flags.status == ITEM_FLAGS_STATUS_ACTIVE) // TODO check weapons + if (flags.status == ITEM_FLAGS_STATUS_ACTIVE && lara->weaponState == WEAPON_STATE_FREE) // TODO check weapons { flags.status = ITEM_FLAGS_STATUS_INACTIVE; return true; @@ -290,6 +371,81 @@ struct Pickup : Object } return false; } + + virtual void collide(Lara* lara, CollisionInfo* cinfo) + { + angle.y = lara->angle.y; + angle.z = 0; + + if (lara->waterState == WATER_STATE_ABOVE || lara->waterState == WATER_STATE_WADE) + { + angle.x = 0; + + if (!checkLimit(lara, Limits::PICKUP)) + return; + + if (lara->state == Lara::STATE_PICKUP) + { + if (!lara->animIsEnd(23)) + return; + + inventory.add((ItemType)type); + gSaveGame.pickups++; + room->remove(this); + flags.status = ITEM_FLAGS_STATUS_INVISIBLE; + } + else if (lara->state == Lara::STATE_STOP) + { + if (lara->weaponState != WEAPON_STATE_FREE) + return; + + if (!(lara->input & IN_ACTION)) + return; + + if (!lara->moveTo(vec3i(0, 0, -100), this, false)) + return; + + lara->animSkip(Lara::STATE_PICKUP, Lara::STATE_STOP); + lara->weaponState = WEAPON_STATE_BUSY; + } + } + + if (lara->waterState == WATER_STATE_UNDER) + { + angle.x = -25 * DEG2SHORT; + + if (!checkLimit(lara, Limits::PICKUP_UW)) + return; + + if (lara->state == Lara::STATE_PICKUP) + { + if (!lara->animIsEnd(14)) + return; + + inventory.add((ItemType)type); + gSaveGame.pickups++; + room->remove(this); + flags.status = ITEM_FLAGS_STATUS_INVISIBLE; + } + else if (lara->state == Lara::STATE_UW_TREAD) + { + // TODO TR2+ + //if (lara->weaponState != WEAPON_STATE_FREE) + // return; + + if (!(lara->input & IN_ACTION)) + return; + + if (!lara->moveTo(vec3i(0, -200, -350), this, true)) + return; + + lara->animSkip(Lara::STATE_PICKUP, Lara::STATE_UW_TREAD); + + // TODO TR2+ + //lara->weaponState = WEAPON_STATE_BUSY; // TODO check CMD_EMPTY event + } + } + } }; @@ -298,9 +454,9 @@ bool useSwitch(Item* item, int32 timer) return ((Switch*)item)->use(timer); } -bool useKey(Item* item) +bool useKey(Item* item, Item* lara) { - return ((Key*)item)->use(); + return ((Key*)item)->use(lara); } bool usePickup(Item* item) @@ -309,6 +465,98 @@ bool usePickup(Item* item) } +vec3i tmpPos; + +struct Hole : Object // parent class for KeyHole and PuzzleHole +{ + Hole(Room* room) : Object(room) {} + + void apply(int32 offset, Lara* lara, Lara::State stateUse) + { + if (lara->weaponState != WEAPON_STATE_FREE) + return; + + if (flags.status != ITEM_FLAGS_STATUS_NONE) + return; + + if (lara->state != Lara::STATE_STOP || lara->animIndex != Lara::ANIM_STAND_NORMAL) + return; + + if (!(lara->input & IN_ACTION) && (inventory.useSlot == SLOT_MAX)) + return; + + if (!checkLimit(lara, Limits::HOLE)) + return; + + if (inventory.useSlot == SLOT_MAX) + { + if (inventory.numKeys > 0) { + inventory.show(lara, this); + return; + } + } else { + if (inventory.applyItem(this)) + { + lara->moveTo(vec3i(0, 0, offset), this, false); + lara->animSkip(stateUse, Lara::STATE_STOP); + lara->weaponState = WEAPON_STATE_BUSY; + flags.status = ITEM_FLAGS_STATUS_ACTIVE; + return; + } + + tmpPos.x = ~lara->pos.x; + inventory.useSlot = SLOT_MAX; + } + + if (tmpPos != lara->pos) + { + tmpPos = lara->pos; + soundPlay(SND_NO, lara->pos); + } + } +}; + + +struct KeyHole : Hole +{ + KeyHole(Room* room) : Hole(room) {} + + virtual void collide(Lara* lara, CollisionInfo* cinfo) + { + apply(362, lara, Lara::STATE_USE_KEY); + } +}; + + +struct PuzzleHole : Hole +{ + PuzzleHole(Room* room) : Hole(room) {} + + virtual void collide(Lara* lara, CollisionInfo* cinfo) + { + if (lara->state == Lara::STATE_USE_PUZZLE) + { + if (!checkLimit(lara, Limits::HOLE)) + return; + + if (!lara->animIsEnd(28)) + return; + + switch (type) { + case ITEM_PUZZLEHOLE_1 : type = ITEM_PUZZLEHOLE_DONE_1; break; + case ITEM_PUZZLEHOLE_2 : type = ITEM_PUZZLEHOLE_DONE_2; break; + case ITEM_PUZZLEHOLE_3 : type = ITEM_PUZZLEHOLE_DONE_3; break; + case ITEM_PUZZLEHOLE_4 : type = ITEM_PUZZLEHOLE_DONE_4; break; + } + + return; + } + + apply(327, lara, Lara::STATE_USE_PUZZLE); + } +}; + + struct TrapFloor : Object { enum { @@ -344,7 +592,7 @@ struct TrapFloor : Object break; } - updateAnim(); + animProcess(); if (flags.status == ITEM_FLAGS_STATUS_INACTIVE) { @@ -354,9 +602,9 @@ struct TrapFloor : Object updateRoom(); - if (state == STATE_FALL && pos.y >= floor) + if (state == STATE_FALL && pos.y >= roomFloor) { - pos.y = floor; + pos.y = roomFloor; vSpeed = 0; flags.gravity = false; goalState = STATE_DOWN; @@ -365,6 +613,44 @@ struct TrapFloor : Object }; +struct TrapSwingBlade : Object +{ + enum { + STATE_STATIC, + STATE_BEGIN, + STATE_SWING, + STATE_END, + }; + + TrapSwingBlade(Room* room) : Object(room) + { + flags.shadow = true; + } + + virtual void collide(Lara* lara, CollisionInfo* cinfo) + { + // + } + + virtual void update() + { + if (isActive()) { + if (state == STATE_STATIC) { + goalState = STATE_SWING; + } + } else { + if (state == STATE_SWING) { + goalState = STATE_STATIC; + } + } + + // TODO damage + + animProcess(); + } +}; + + struct Dart : Object { Dart(Room* room) : Object(room) @@ -379,10 +665,10 @@ struct Dart : Object { // TODO collide with Lara - updateAnim(); + animProcess(); updateRoom(); - if (pos.y >= floor) + if (pos.y >= roomFloor) { // TODO create spark remove(); @@ -420,12 +706,206 @@ struct TrapDartEmitter : Object if (dart) { + dart->intensity = 0; dart->flags.status = ITEM_FLAGS_STATUS_ACTIVE; dart->activate(); } } - updateAnim(); + animProcess(); + } +}; + + +struct Block : Object +{ + enum { + STATE_NONE, + STATE_READY, + STATE_PUSH, + STATE_PULL, + }; + + Block(Room* room) : Object(room) + { + if (flags.status != ITEM_FLAGS_STATUS_INVISIBLE) { + updateFloor(-1024); + } + } + + void updateFloor(int32 offset) + { + room->modify(); + + Sector* sector = (Sector*)room->getSector(pos.x, pos.z); + + if (sector->floor == NO_FLOOR) { + sector->floor = sector->ceiling + (offset >> 8); + } else { + sector->floor += (offset >> 8); + if (sector->floor == sector->ceiling) { + sector->floor = NO_FLOOR; + } + } + + // TODO modify level.boxes + } + + bool checkBlocking() + { + const Sector* sector = room->getSector(pos.x, pos.z); + + return (sector->floor == NO_FLOOR) || ((sector->floor << 8) + 1024 == pos.y); + } + + bool checkObstacles(int32 x, int32 z, int32 height) + { + Room* nextRoom = room->getRoom(x, pos.y, z); + const Sector* sector = nextRoom->getSector(x, z); + + int32 floor = pos.y; + int32 ceiling = pos.y - height; + + if ((sector->floor << 8) != floor) + return false; + + nextRoom = nextRoom->getRoom(x, ceiling, z); + sector = nextRoom->getSector(x, z); + + if ((sector->ceiling << 8) > ceiling) + return false; + + return true; + } + + bool checkPush() + { + if (!checkBlocking()) + return false; + + vec3i offset = getBlockOffset(angle.y, -1024); + + return checkObstacles(pos.x + offset.x, pos.z + offset.z, 1024); + } + + bool checkPull(Item* lara) + { + if (!checkBlocking()) + return false; + + vec3i offset = getBlockOffset(angle.y, 1024); + + if (!checkObstacles(pos.x + offset.x, pos.z + offset.z, 1024)) + return false; + + return checkObstacles(lara->pos.x + offset.x, lara->pos.z + offset.z, LARA_HEIGHT); + } + + virtual void collide(Lara* lara, CollisionInfo* cinfo) + { + if (lara->weaponState != WEAPON_STATE_FREE) + return; + + if (!(lara->input & IN_ACTION)) + return; + + if (flags.status == ITEM_FLAGS_STATUS_ACTIVE) + return; + + if (lara->pos.y != pos.y) + return; + + uint16 quadrant = uint16(lara->angle.y + ANGLE_45) / ANGLE_90; + + if (lara->state == Lara::STATE_BLOCK_READY) + { + if (!lara->animIsEnd(0)) + return; + + if (!checkLimit(lara, Limits::BLOCK)) + return; + + if (lara->input & IN_UP) + { + if (!checkPush()) + return; + + lara->goalState = Lara::STATE_BLOCK_PUSH; + goalState = STATE_PUSH; + } + else if (lara->input & IN_DOWN) + { + if (!checkPull(lara)) + return; + + lara->goalState = Lara::STATE_BLOCK_PULL; + goalState = STATE_PULL; + } + else + { + return; + } + + updateFloor(1024); + + activate(); + flags.status = ITEM_FLAGS_STATUS_ACTIVE; + + animProcess(); + lara->animProcess(); + } + + if (lara->state == Lara::STATE_STOP) + { + if (lara->input & (IN_UP | IN_DOWN)) + return; + + angle.y = quadrant * ANGLE_90; + + if (!checkLimit(lara, Limits::BLOCK)) + return; + + lara->goalState = Lara::STATE_BLOCK_READY; + lara->angle.y = angle.y; + lara->alignWall(LARA_RADIUS); + lara->animProcess(); + } + } + + virtual void update() + { + if (flags.once) + { + updateFloor(1024); + remove(); + return; + } + + animProcess(); + + updateRoom(); // it'll get roomFloor and gLastFloorData + + if (pos.y > roomFloor) + { + flags.gravity = true; + } + else if (flags.gravity) + { + flags.gravity = false; + flags.status = ITEM_FLAGS_STATUS_INACTIVE; + pos.y = roomFloor; + // TODO EarthQuake + playSound 70 (Thor room) + } + + if (flags.status == ITEM_FLAGS_STATUS_INACTIVE) + { + deactivate(); + flags.status = ITEM_FLAGS_STATUS_NONE; + + updateFloor(-1024); + + checkTrigger(gLastFloorData, NULL); + } } }; diff --git a/src/platform/gba/rasterizeFTA_mode4.s b/src/platform/gba/rasterizeFTA_mode4.s index eeb73d6..87edab6 100644 --- a/src/platform/gba/rasterizeFTA_mode4.s +++ b/src/platform/gba/rasterizeFTA_mode4.s @@ -65,8 +65,10 @@ SP_RDT = 12 ldrb indexB, [TILE, indexB] add t, dtdx - // cheap non-accurate alpha test, skip pixels pair if both are transparent - orrs indexB, indexA, indexB, lsl #8 // indexB = indexA | (indexB << 8) + // cheap non-accurate alpha test, skip pixels pair if one or both are transparent + ands indexA, #255 + andnes indexB, #255 + orrne indexB, indexA, indexB, lsl #8 // indexB = indexA | (indexB << 8) ldrneb indexA, [LMAP, indexA] ldrneb indexB, [LMAP, indexB, lsr #8] orrne indexA, indexB, lsl #8 @@ -206,19 +208,19 @@ rasterizeFTA_mode4_asm: .align_left: tst tmp, #1 // if (tmp & 1) beq .align_right - ldrb indexB, [tmp, #-1]! // read pal index from VRAM (byte) and indexA, t, #0xFF00 orr indexA, t, lsr #24 // res = (t & 0xFF00) | (t >> 24) - add t, dtdx ldrb indexA, [TILE, indexA] - ldrb indexA, [LMAP, indexA] cmp indexA, #0 + ldrneb indexB, [tmp, #-1] // read pal index from VRAM (byte) ldrneb indexA, [LMAP, indexA] orrne indexB, indexA, lsl #8 strneh indexB, [tmp] - add tmp, #2 + + add tmp, #1 + add t, dtdx subs width, #1 // width-- beq .scanline_end // if (width == 0) @@ -226,7 +228,6 @@ rasterizeFTA_mode4_asm: .align_right: tst width, #1 beq .align_block_4px - ldrb indexB, [tmp, width] sub Rt, dtdx and indexA, Rt, #0xFF00 @@ -236,6 +237,7 @@ rasterizeFTA_mode4_asm: cmp indexA, #0 ldrneb indexA, [LMAP, indexA] + ldrneb indexB, [tmp, width] orrne indexB, indexA, indexB, lsl #8 strneh indexB, [tmp, width] diff --git a/src/platform/gba/rasterizeGTA_mode4.s b/src/platform/gba/rasterizeGTA_mode4.s index 6e15a2c..14fa5b5 100644 --- a/src/platform/gba/rasterizeGTA_mode4.s +++ b/src/platform/gba/rasterizeGTA_mode4.s @@ -91,8 +91,10 @@ SP_RDT = 20 ldrb indexB, [TILE, indexB] add t, dtdx - // cheap non-accurate alpha test, skip pixels pair if both are transparent - orrs indexB, indexA, indexB, lsl #8 // indexB = indexA | (indexB << 8) + // cheap non-accurate alpha test, skip pixels pair if one or both are transparent + ands indexA, #255 + andnes indexB, #255 + orrne indexB, indexA, indexB, lsl #8 // indexB = indexA | (indexB << 8) ldrneb indexA, [LMAP, indexA] ldrneb indexB, [LMAP, indexB, lsr #8] orrne indexA, indexB, lsl #8 @@ -256,19 +258,25 @@ rasterizeGTA_mode4_asm: .align_left: tst ptr, #1 // if (ptr & 1) beq .align_right - ldrb indexB, [ptr, #-1]! // read pal index from VRAM (byte) - - bic LMAP, g, #255 - add g, dgdx, asr #1 and indexA, t, #0xFF00 orr indexA, t, lsr #24 // res = (t & 0xFF00) | (t >> 24) ldrb indexA, [TILE, indexA] + + cmp indexA, #0 + beq .skip_left + + bic LMAP, g, #255 ldrb indexA, [LMAP, indexA] + ldrb indexB, [ptr, #-1] // read pal index from VRAM (byte) orr indexB, indexA, lsl #8 - strh indexB, [ptr], #2 + strh indexB, [ptr] + +.skip_left: + add ptr, #1 add t, dtdx + add g, dgdx, asr #1 subs width, #1 // width-- beq .scanline_end // if (width == 0) @@ -276,15 +284,19 @@ rasterizeGTA_mode4_asm: .align_right: tst width, #1 beq .align_block_4px + ldrb indexB, [ptr, width] - subs width, #1 // width-- + sub width, #1 // width-- mla Rti, width, dtdx, t // Rti = width * dtdx + t and indexA, Rti, #0xFF00 orr indexA, Rti, lsr #24 // res = (t & 0xFF00) | (t >> 24) ldrb indexA, [TILE, indexA] + cmp indexA, #0 + beq .skip_right + asr Rgi, dgdx, #1 mla Rgi, width, Rgi, g // Rgi = width * (dgdx / 2) + g bic LMAP, Rgi, #255 @@ -294,6 +306,8 @@ rasterizeGTA_mode4_asm: orr indexB, indexA, indexB, lsl #8 strh indexB, [ptr, width] +.skip_right: + cmp width, #0 // width-- beq .scanline_end // if (width == 0) .align_block_4px: diff --git a/src/platform/gba/rasterizer_mode4.h b/src/platform/gba/rasterizer_mode4.h index df2775b..b0e0525 100644 --- a/src/platform/gba/rasterizer_mode4.h +++ b/src/platform/gba/rasterizer_mode4.h @@ -5,6 +5,7 @@ extern uint8 lightmap[256 * 32]; extern const uint8* tile; +extern const Sprite* sprite; #if defined(__GBA__) #define USE_ASM @@ -29,6 +30,7 @@ extern const uint8* tile; #define rasterizeGT rasterizeGT_mode4_asm #define rasterizeFTA rasterizeFTA_mode4_asm #define rasterizeGTA rasterizeGTA_mode4_asm + #define rasterizeSprite rasterizeSprite_mode4_c #else #define rasterizeS rasterizeS_mode4_c #define rasterizeF rasterizeF_mode4_c @@ -37,6 +39,7 @@ extern const uint8* tile; #define rasterizeGT rasterizeGT_mode4_c #define rasterizeFTA rasterizeFTA_mode4_c #define rasterizeGTA rasterizeGTA_mode4_c + #define rasterizeSprite rasterizeSprite_mode4_c void rasterizeS_mode4_c(uint16* pixel, const VertexUV* L, const VertexUV* R) { @@ -517,7 +520,7 @@ void rasterizeGT_mode4_c(uint16* pixel, const VertexUV* L, const VertexUV* R) // 8-bit fractional part precision for Gouraud component // has some artifacts but allow to save one reg for inner loop - // for aligned by 64k address of lightmap array + // with aligned by 64k address of lightmap array while (1) { @@ -528,7 +531,7 @@ void rasterizeGT_mode4_c(uint16* pixel, const VertexUV* L, const VertexUV* R) if (N->v.y < L->v.y) return; Lh = N->v.y - L->v.y; - Lx = L->v.x; + Lx = L->v.x; Lg = L->v.g; Lt = L->t.uv; @@ -800,8 +803,295 @@ void rasterizeFTA_mode4_c(uint16* pixel, const VertexUV* L, const VertexUV* R) void rasterizeGTA_mode4_c(uint16* pixel, const VertexUV* L, const VertexUV* R) { - rasterizeGT(pixel, L, R); -} +#ifdef ALIGNED_LIGHTMAP + ASSERT((intptr_t(lightmap) & 0xFFFF) == 0); // lightmap should be 64k aligned #endif + int32 Lh = 0, Rh = 0; + int32 Lx, Rx, Lg, Rg, Ldx = 0, Rdx = 0, Ldg = 0, Rdg = 0; + uint32 Lt, Rt, Ldt, Rdt; + Ldt = 0; + Rdt = 0; + + // 8-bit fractional part precision for Gouraud component + // has some artifacts but allow to save one reg for inner loop + // with aligned by 64k address of lightmap array + + while (1) + { + while (!Lh) + { + const VertexUV* N = L->prev; + + if (N->v.y < L->v.y) return; + + Lh = N->v.y - L->v.y; + Lx = L->v.x; + Lg = L->v.g; + Lt = L->t.uv; + + if (Lh > 1) + { + int32 tmp = FixedInvU(Lh); + Ldx = tmp * (N->v.x - Lx); + Ldg = tmp * (N->v.g - Lg) >> 8; + + uint32 duv = N->t.uv - Lt; + uint32 du = tmp * int16(duv >> 16); + uint32 dv = tmp * int16(duv); + Ldt = (du & 0xFFFF0000) | (dv >> 16); + } + + Lx <<= 16; + Lg <<= 8; + L = N; + } + + while (!Rh) + { + const VertexUV* N = R->next; + + if (N->v.y < R->v.y) return; + + Rh = N->v.y - R->v.y; + Rx = R->v.x; + Rg = R->v.g; + Rt = R->t.uv; + + if (Rh > 1) + { + int32 tmp = FixedInvU(Rh); + Rdx = tmp * (N->v.x - Rx); + Rdg = tmp * (N->v.g - Rg) >> 8; + + uint32 duv = N->t.uv - Rt; + uint32 du = tmp * int16(duv >> 16); + uint32 dv = tmp * int16(duv); + Rdt = (du & 0xFFFF0000) | (dv >> 16); + } + + Rx <<= 16; + Rg <<= 8; + R = N; + } + + int32 h = X_MIN(Lh, Rh); + Lh -= h; + Rh -= h; + + while (h--) + { + int32 x1 = Lx >> 16; + int32 x2 = Rx >> 16; + + int32 width = x2 - x1; + + if (width > 0) + { + int32 tmp = FixedInvU(width); + + int32 dgdx = tmp * (Rg - Lg) >> 15; + + uint32 duv = Rt - Lt; + uint32 u = tmp * int16(duv >> 16); + uint32 v = tmp * int16(duv); + uint32 dtdx = (u & 0xFFFF0000) | (v >> 16); + + int32 g = Lg; + uint32 t = Lt; + + volatile uint8* ptr = (uint8*)pixel + x1; + + if (intptr_t(ptr) & 1) + { + ptr--; + + uint8 indexB = tile[(t & 0xFF00) | (t >> 24)]; + + if (indexB) { + *(uint16*)ptr = *ptr | (lightmap[(g >> 8 << 8) | indexB] << 8); + } + + ptr += 2; + t += dtdx; + g += dgdx >> 1; + width--; + } + + if (width & 1) + { + uint32 tmp = Rt - dtdx; + + uint8 indexA = tile[(tmp & 0xFF00) | (tmp >> 24)]; + + if (indexA) { + *(uint16*)(ptr + width - 1) = (ptr[width] << 8) | lightmap[(Rg >> 8 << 8) | indexA]; + } + } + + #ifdef ALIGNED_LIGHTMAP + g += intptr_t(lightmap); + #endif + + width >>= 1; + + while (width--) + { + #ifdef ALIGNED_LIGHTMAP + uint8 indexA = tile[(t & 0xFF00) | (t >> 24)]; + t += dtdx; + uint8 indexB = tile[(t & 0xFF00) | (t >> 24)]; + t += dtdx; + g += dgdx; + + + if (indexA && indexB) { + const uint8* LMAP = (uint8*)(g >> 8 << 8); + *(uint16*)ptr = LMAP[indexA] | (LMAP[indexB] << 8); + } + #else + uint8 indexA = tile[(t & 0xFF00) | (t >> 24)]; + t += dtdx; + uint8 indexB = tile[(t & 0xFF00) | (t >> 24)]; + t += dtdx; + g += dgdx; + + if (indexA && indexB) { + *(uint16*)ptr = lightmap[(g >> 8 << 8) | indexA] | (lightmap[(g >> 8 << 8) | indexB] << 8); + } + #endif + + ptr += 2; + } + } + + pixel += VRAM_WIDTH; + + Lx += Ldx; + Rx += Rdx; + Lg += Ldg; + Rg += Rdg; + Lt += Ldt; + Rt += Rdt; + } + } +} + +#endif + +// TODO ARM version +void rasterizeSprite_mode4_c(uint16* pixel, const VertexUV* L, const VertexUV* R) +{ + const uint8* ft_lightmap = &lightmap[L->v.g << 8]; + + int32 w = R->v.x - L->v.x; + if (w <= 0) return; + + int32 h = R->v.y - L->v.y; + if (h <= 0) return; + + int32 u = L->t.u << 8; + int32 v = L->t.v << 8; + + int32 du = (R->t.u << 8) / w; + int32 dv = (R->t.v << 8) / h; + + if (L->v.y < 0) + { + pixel -= L->v.y * VRAM_WIDTH; + v -= L->v.y * dv; + h += L->v.y; + } + + if (R->v.y > FRAME_HEIGHT) + { + h -= R->v.y - FRAME_HEIGHT; + } + + uint8* ptr = (uint8*)pixel; + + if (h <= 0) return; + + ptr += L->v.x; + + if (L->v.x < 0) + { + ptr -= L->v.x; + u -= L->v.x * du; + w += L->v.x; + } + + if (R->v.x > FRAME_WIDTH) + { + w -= R->v.x - FRAME_WIDTH; + } + + if (w <= 0) return; + + bool alignL = intptr_t(ptr) & 1; + if (alignL) + { + ptr--; + w--; + } + + bool alignR = w & 1; + if (alignR) + { + w--; + } + + w >>= 1; + + for (int32 y = 0; y < h; y++) + { + const uint8* xtile = tile + (v & 0xFF00); + + volatile uint8* xptr = ptr; + + int32 xu = u; + + if (alignL) + { + uint8 indexB = ft_lightmap[xtile[xu >> 8]]; + if (indexB) { + *(uint16*)xptr = *xptr | (indexB << 8); + } + + xptr += 2; + xu += du; + } + + for (int32 x = 0; x < w; x++) + { + uint8 indexA = ft_lightmap[xtile[xu >> 8]]; + xu += du; + uint8 indexB = ft_lightmap[xtile[xu >> 8]]; + xu += du; + + if (indexA | indexB) + { + if (indexA == 0) indexA = xptr[0]; + if (indexB == 0) indexB = xptr[1]; + *(uint16*)xptr = indexA | (indexB << 8); + } + + xptr += 2; + } + + if (alignR) + { + uint8 indexA = ft_lightmap[xtile[xu >> 8]]; + if (indexA) { + *(uint16*)xptr = indexA | (xptr[1] << 8); + } + } + + v += dv; + + ptr += FRAME_WIDTH; + } +} + + #endif diff --git a/src/platform/gba/render.cpp b/src/platform/gba/render.cpp index 8a36f2c..4fcd376 100644 --- a/src/platform/gba/render.cpp +++ b/src/platform/gba/render.cpp @@ -34,10 +34,16 @@ uint16 palette[256]; // IWRAM 0.5k #endif extern uint8 lightmap[256 * 32]; -extern const Texture* textures; +extern Texture textures[MAX_TEXTURES]; +extern const Sprite* sprites; extern const uint8* tiles; +extern int32 lightAmbient; +extern int32 caustics[MAX_CAUSTICS]; +extern int32 causticsRand[MAX_CAUSTICS]; +extern int32 causticsFrame; const uint8* tile; +const Sprite* sprite; uint32 gVerticesCount = 0; int32 gFacesCount = 0; // 1 is reserved as OT terminator @@ -72,10 +78,11 @@ bool transformBoxRect(const Bounds* box, Rect* rect) *rect = Rect( INT_MAX, INT_MAX, INT_MIN, INT_MIN ); - for (int32 i = 0; i < 8; i++) { + for (int32 i = 0; i < 8; i++) + { int32 z = DP43(m[2], v[i]); - if (z < VIEW_MIN_F || z >= VIEW_MAX_F) { // TODO znear clip + if (z < VIEW_MIN_F || z >= VIEW_MAX_F) { continue; } @@ -122,6 +129,13 @@ int32 boxIsVisible(const Bounds* box) return rectIsVisible(&rect); } +X_INLINE int32 classify(const Vertex &v, const Rect &clip) { + return (v.x < clip.x0 ? 1 : 0) | + (v.x > clip.x1 ? 2 : 0) | + (v.y < clip.y0 ? 4 : 0) | + (v.y > clip.y1 ? 8 : 0); +} + void transform(int32 vx, int32 vy, int32 vz, int32 vg) { ASSERT(gVerticesCount < MAX_VERTICES); @@ -132,26 +146,16 @@ void transform(int32 vx, int32 vy, int32 vz, int32 vg) int32 z = DP43c(m[2], vx, vy, vz); - if (z < VIEW_MIN_F || z >= VIEW_MAX_F) // TODO znear clip + if (z < VIEW_MIN_F || z >= VIEW_MAX_F) { - res.clip = 16; + res.clip = 32; return; } int32 x = DP43c(m[0], vx, vy, vz); int32 y = DP43c(m[1], vx, vy, vz); - int32 fogZ = z >> FIXED_SHIFT; - res.z = fogZ; - - if (fogZ > FOG_MIN) - { - vg += (fogZ - FOG_MIN) << FOG_SHIFT; - if (vg > 8191) { - vg = 8191; - } - } - + res.z = z >> FIXED_SHIFT; res.g = vg >> 8; PERSPECTIVE(x, y, z); @@ -162,10 +166,10 @@ void transform(int32 vx, int32 vy, int32 vz, int32 vg) res.x = x + (FRAME_WIDTH >> 1); res.y = y + (FRAME_HEIGHT >> 1); - res.clip = enableClipping ? classify(res, viewport) : 0; + res.clip = classify(res, viewport); // enableClipping ? classify(res, viewport) : 0; TODO fix clip boxes for static meshes } -void transformRoomVertex(const RoomVertex* v) +void transformRoomVertex(const RoomVertex* v, int32 caustics) { int32 vx = v->x << 10; int32 vz = v->z << 10; @@ -178,7 +182,7 @@ void transformRoomVertex(const RoomVertex* v) if (vz < frustumAABB.minZ || vz > frustumAABB.maxZ || vx < frustumAABB.minX || vx > frustumAABB.maxX) { - res.clip = 16; + res.clip = 32; return; } #endif @@ -189,10 +193,13 @@ void transformRoomVertex(const RoomVertex* v) int32 z = DP43c(m[2], vx, vy, vz); - if (z < VIEW_MIN_F || z >= VIEW_MAX_F) // TODO znear clip + if (z < VIEW_MIN_F || z >= VIEW_MAX_F) { + if (z < VIEW_MIN_F) z = VIEW_MIN_F; + if (z >= VIEW_MAX_F) z = VIEW_MAX_F; res.clip = 16; - return; + } else { + res.clip = 0; } int32 fogZ = z >> FIXED_SHIFT; @@ -200,6 +207,10 @@ void transformRoomVertex(const RoomVertex* v) int32 vg = v->g << 5; + if (caustics) { + vg = X_CLAMP(vg + caustics, 0, 8191); + } + if (fogZ > FOG_MIN) { vg += (fogZ - FOG_MIN) << FOG_SHIFT; @@ -221,23 +232,32 @@ void transformRoomVertex(const RoomVertex* v) res.x = x + (FRAME_WIDTH >> 1); res.y = y + (FRAME_HEIGHT >> 1); - res.clip = classify(res, viewport); + res.clip |= classify(res, viewport); } -void transformRoom(const RoomVertex* vertices, int32 vCount) +void transformRoom(const RoomVertex* vertices, int32 vCount, bool applyCaustics) { + int32 causticsValue = 0; + for (int32 i = 0; i < vCount; i++) { - transformRoomVertex(vertices); + if (applyCaustics) { + causticsValue = caustics[(causticsRand[i & (MAX_CAUSTICS - 1)] + causticsFrame) & (MAX_CAUSTICS - 1)]; + } + + transformRoomVertex(vertices, causticsValue); vertices++; } } -void transformMesh(const vec3s* vertices, int32 vCount, uint16 intensity) +void transformMesh(const vec3s* vertices, int32 vCount, const uint16* vIntensity, const vec3s* vNormal) { + // TODO calc lighting for vNormal for (int32 i = 0; i < vCount; i++) { - transform(vertices->x, vertices->y, vertices->z, intensity); + ASSERT(!vIntensity || (vIntensity[i] + lightAmbient >= 0)); // ohhh, use X_CLAMP... + + transform(vertices->x, vertices->y, vertices->z, vIntensity ? X_MIN(vIntensity[i] + lightAmbient, 8191) : lightAmbient); vertices++; } } @@ -313,7 +333,7 @@ void rasterize(const Face* face, const VertexUV *top) #else if (face->flags & FACE_COLORED) { if (face->flags & FACE_FLAT) { - if (face->flags & FACE_SHADOW) { + if (face->flags & FACE_SPRITE) { rasterizeS(pixel, top, top); } else { rasterizeF(pixel, top, top, face->flags & FACE_TEXTURE); @@ -322,7 +342,9 @@ void rasterize(const Face* face, const VertexUV *top) rasterizeG(pixel, top, top, face->flags & FACE_TEXTURE); } } else { - if (enableAlphaTest) { + if (face->flags & FACE_SPRITE) { + rasterizeSprite(pixel, top, top + 1); + } else if (enableAlphaTest) { if (face->flags & FACE_FLAT) { rasterizeFTA(pixel, top, top); } else { @@ -475,6 +497,14 @@ void drawPoly(Face* face, VertexUV* v) rasterize(face, top); } +void drawSprite(Face* face, VertexUV* v) +{ + sprite = sprites + face->indices[1]; + tile = tiles + (sprite->tile << 16); + + rasterize(face, v); +} + void drawGlyph(const Sprite *sprite, int32 x, int32 y) { int32 w = sprite->r - sprite->l; @@ -536,14 +566,17 @@ void drawGlyph(const Sprite *sprite, int32 x, int32 y) } } -X_INLINE void faceAddToOTable(Face* face, int32 depth) +X_INLINE Face* faceAdd(int32 depth) { ASSERT(depth < OT_SIZE); + Face* face = gFaces + gFacesCount++; face->next = otFaces[depth]; otFaces[depth] = face; if (depth < otMin) otMin = depth; if (depth > otMax) otMax = depth; + + return face; } void faceAddQuad(uint32 flags, const Index* indices, int32 startVertex) @@ -556,17 +589,15 @@ void faceAddQuad(uint32 flags, const Index* indices, int32 startVertex) const Vertex* v3 = v + indices[2]; const Vertex* v4 = v + indices[3]; - if (v1->clip == 16 || v2->clip == 16 || v3->clip == 16 || v4->clip == 16) + if (v1->clip & v2->clip & v3->clip & v4->clip) return; - if (enableClipping) - { - if (v1->clip & v2->clip & v3->clip & v4->clip) - return; + int32 clip = (v1->clip | v2->clip | v3->clip | v4->clip); + if (clip & 32) + return; - if (v1->clip | v2->clip | v3->clip | v4->clip) { - flags |= FACE_CLIPPED; - } + if (clip) { + flags |= FACE_CLIPPED; } if (v1->g == v2->g && v1->g == v3->g && v1->g == v4->g) { @@ -575,19 +606,12 @@ void faceAddQuad(uint32 flags, const Index* indices, int32 startVertex) int32 depth = X_MAX(v1->z, X_MAX(v2->z, X_MAX(v3->z, v4->z))) >> OT_SHIFT; - // z-bias hack for the shadow plane - if (flags & FACE_SHADOW) { - depth = X_MAX(0, depth - 8); - } - - Face *f = gFaces + gFacesCount++; - faceAddToOTable(f, depth); - - f->flags = uint16(flags); - f->indices[0] = startVertex + indices[0]; - f->indices[1] = startVertex + indices[1]; - f->indices[2] = startVertex + indices[2]; - f->indices[3] = startVertex + indices[3]; + Face* f = faceAdd(depth); + f->flags = uint16(flags); + f->indices[0] = v1 - gVertices; + f->indices[1] = v2 - gVertices; + f->indices[2] = v3 - gVertices; + f->indices[3] = v4 - gVertices; } void faceAddTriangle(uint32 flags, const Index* indices, int32 startVertex) @@ -599,17 +623,16 @@ void faceAddTriangle(uint32 flags, const Index* indices, int32 startVertex) const Vertex* v2 = v + indices[1]; const Vertex* v3 = v + indices[2]; - if (v1->clip == 16 || v2->clip == 16 || v3->clip == 16) + if (v1->clip & v2->clip & v3->clip) return; - if (enableClipping) - { - if (v1->clip & v2->clip & v3->clip) - return; + int32 clip = (v1->clip | v2->clip | v3->clip); - if (v1->clip | v2->clip | v3->clip) { - flags |= FACE_CLIPPED; - } + if (clip & 32) + return; + + if (clip) { + flags |= FACE_CLIPPED; } if (v1->g == v2->g && v1->g == v3->g) { @@ -618,13 +641,84 @@ void faceAddTriangle(uint32 flags, const Index* indices, int32 startVertex) int32 depth = X_MAX(v1->z, X_MAX(v2->z, v3->z)) >> OT_SHIFT; - Face *f = gFaces + gFacesCount++; - faceAddToOTable(f, depth); + Face* f = faceAdd(depth); + f->flags = uint16(flags | FACE_TRIANGLE); + f->indices[0] = v1 - gVertices; + f->indices[1] = v2 - gVertices; + f->indices[2] = v3 - gVertices; +} - f->flags = uint16(flags | FACE_TRIANGLE); - f->indices[0] = startVertex + indices[0]; - f->indices[1] = startVertex + indices[1]; - f->indices[2] = startVertex + indices[2]; +void faceAddSprite(int32 vx, int32 vy, int32 vz, int32 vg, int32 index) +{ + ASSERT(gVerticesCount + 1 < MAX_VERTICES); + + const Matrix &m = matrixGet(); + + int32 z = DP43c(m[2], vx, vy, vz); + + if (z < VIEW_MIN_F || z >= VIEW_MAX_F) + { + return; + } + + int32 x = DP43c(m[0], vx, vy, vz); + int32 y = DP43c(m[1], vx, vy, vz); + + const Sprite* sprite = sprites + index; + + int32 l = x + (sprite->l << FIXED_SHIFT); + int32 r = x + (sprite->r << FIXED_SHIFT); + int32 t = y + (sprite->t << FIXED_SHIFT); + int32 b = y + (sprite->b << FIXED_SHIFT); + + PERSPECTIVE(l, t, z); + + l += (FRAME_WIDTH >> 1); + if (l >= FRAME_WIDTH) return; + + t += (FRAME_HEIGHT >> 1); + if (t >= FRAME_HEIGHT) return; + + PERSPECTIVE(r, b, z); + + r += (FRAME_WIDTH >> 1); + if (r < 0) return; + + b += (FRAME_HEIGHT >> 1); + if (b < 0) return; + + if (l == r) return; + if (t == b) return; + + int32 fogZ = z >> FIXED_SHIFT; + if (fogZ > FOG_MIN) + { + vg += (fogZ - FOG_MIN) << FOG_SHIFT; + if (vg > 8191) { + vg = 8191; + } + } + vg >>= 8; + + Vertex &v1 = gVertices[gVerticesCount++]; + v1.x = l; + v1.y = t; + //v1.z = z; + v1.g = vg; + + Vertex &v2 = gVertices[gVerticesCount++]; + v2.x = r; + v2.y = b; + //v2.z = z; + //v2.g = g; + + ASSERT(v2.x >= v1.x); + ASSERT(v2.y >= v1.y); + + Face* f = faceAdd(fogZ >> OT_SHIFT); + f->flags = uint16(FACE_SPRITE); + f->indices[0] = gVerticesCount - 2; + f->indices[1] = index; } void faceAddRoom(const Quad* quads, int32 qCount, const Triangle* triangles, int32 tCount, int32 startVertex) @@ -666,6 +760,7 @@ void flush() if (gFacesCount) { PROFILE_START(); + for (int32 i = otMax; i >= otMin; i--) { if (!otFaces[i]) continue; @@ -678,34 +773,51 @@ void flush() uint32 flags = face->flags; - if (!(flags & FACE_COLORED)) - { - const Texture &tex = textures[face->flags & FACE_TEXTURE]; - tile = tiles + (tex.tile << 16); + if (flags == FACE_SPRITE) { + const Sprite &sprite = sprites[face->indices[1]]; - v[0].t.uv = tex.uv0; - v[1].t.uv = tex.uv1; - v[2].t.uv = tex.uv2; - v[3].t.uv = tex.uv3; - enableAlphaTest = (tex.attribute == 1); - } + v[0].v = gVertices[face->indices[0] + 0]; + v[0].t.u = sprite.u; + v[0].t.v = sprite.v; + v[1].v = gVertices[face->indices[0] + 1]; + v[1].t.u = sprite.w; + v[1].t.v = sprite.h; - v[0].v = gVertices[face->indices[0]]; - v[1].v = gVertices[face->indices[1]]; - v[2].v = gVertices[face->indices[2]]; - if (!(flags & FACE_TRIANGLE)) { - v[3].v = gVertices[face->indices[3]]; - } + ASSERT(v[0].v.x <= v[1].v.x); + ASSERT(v[0].v.y <= v[1].v.y); - if (flags & FACE_CLIPPED) { - drawPoly(face, v); + drawSprite(face, v); } else { - if (flags & FACE_TRIANGLE) { - drawTriangle(face, v); - } else { - drawQuad(face, v); + if (!(flags & FACE_COLORED)) + { + const Texture &tex = textures[flags & FACE_TEXTURE]; + tile = tiles + (tex.tile << 16); + + v[0].t.uv = tex.uv0; + v[1].t.uv = tex.uv1; + v[2].t.uv = tex.uv2; + v[3].t.uv = tex.uv3; + + enableAlphaTest = (tex.attribute == 1); } - }; + + v[0].v = gVertices[face->indices[0]]; + v[1].v = gVertices[face->indices[1]]; + v[2].v = gVertices[face->indices[2]]; + if (!(flags & FACE_TRIANGLE)) { + v[3].v = gVertices[face->indices[3]]; + } + + if (flags & FACE_CLIPPED) { + drawPoly(face, v); + } else { + if (flags & FACE_TRIANGLE) { + drawTriangle(face, v); + } else { + drawQuad(face, v); + } + }; + } face = face->next; } while (face); diff --git a/src/platform/gba/room.h b/src/platform/gba/room.h index 616d2ac..ccff3ca 100644 --- a/src/platform/gba/room.h +++ b/src/platform/gba/room.h @@ -2,6 +2,28 @@ #define H_ROOM #include "common.h" +#include "camera.h" + +void animTexturesShift() +{ + const int16* data = level.animTexData; + + int32 texRangesCount = *data++; + + for (int32 i = 0; i < texRangesCount; i++) + { + int32 count = *data++; + + Texture tmp = textures[*data]; + while (count > 0) + { + textures[data[0]] = textures[data[1]]; + data++; + count--; + } + textures[*data++] = tmp; + } +} int32 getBridgeFloor(const Item* item, int32 x, int32 z) { @@ -22,7 +44,7 @@ int32 getBridgeFloor(const Item* item, int32 x, int32 z) h &= 1023; - return item->pos.y + ((item->type == ITEM_BRIDGE_TILT1) ? (h >> 2) : (h >> 1)); + return item->pos.y + ((item->type == ITEM_BRIDGE_TILT_1) ? (h >> 2) : (h >> 1)); } int32 getTrapDoorFloor(const Item* item, int32 x, int32 z) @@ -79,8 +101,8 @@ void getItemFloorCeiling(const Item* item, int32 x, int32 y, int32 z, int32* flo break; } case ITEM_BRIDGE_FLAT: - case ITEM_BRIDGE_TILT1: - case ITEM_BRIDGE_TILT2: + case ITEM_BRIDGE_TILT_1: + case ITEM_BRIDGE_TILT_2: { h = getBridgeFloor(item, x, z); break; @@ -119,14 +141,14 @@ const Sector* Sector::getSectorBelow(int32 posX, int32 posZ) const { if (roomBelow == NO_ROOM) return this; - return rooms[roomBelow].getSector(posX, posZ); + return rooms[roomBelow].getSector(posX, posZ)->getSectorBelow(posX, posZ); } const Sector* Sector::getSectorAbove(int32 posX, int32 posZ) const { if (roomAbove == NO_ROOM) return this; - return rooms[roomAbove].getSector(posX, posZ); + return rooms[roomAbove].getSector(posX, posZ)->getSectorAbove(posX, posZ); } int32 Sector::getFloor(int32 x, int32 y, int32 z) const @@ -291,14 +313,49 @@ void Sector::getTriggerFloorCeiling(int32 x, int32 y, int32 z, int32* floor, int } -const Sector* Room::getSector(int32 posX, int32 posZ) const +const Sector* Room::getSector(int32 x, int32 z) const { - int32 sx = X_CLAMP((posX - (info->x << 8)) >> 10, 0, info->xSectors - 1); - int32 sz = X_CLAMP((posZ - (info->z << 8)) >> 10, 0, info->zSectors - 1); + int32 sx = X_CLAMP((x - (info->x << 8)) >> 10, 0, info->xSectors - 1); + int32 sz = X_CLAMP((z - (info->z << 8)) >> 10, 0, info->zSectors - 1); return sectors + sx * info->zSectors + sz; } +const Sector* Room::getWaterSector(int32 x, int32 z) const +{ + const Room* room = this; + const Sector* sector = room->getSector(x, z); + + // go up to the air + if (room->info->flags.water) + { + while (sector->roomAbove != NO_ROOM) + { + room = rooms + sector->roomAbove; + + if (!room->info->flags.water) { + return sector; + } + + sector = room->getSector(x, z); + } + return sector; + } + + // go down to the water + while (sector->roomBelow != NO_ROOM) + { + room = rooms + sector->roomBelow; + sector = room->getSector(x, z); + + if (room->info->flags.water) { + return sector; + } + } + + return NULL; +} + Room* Room::getRoom(int32 x, int32 y, int32 z) { const Sector* sector = getSector(x, z); @@ -329,16 +386,6 @@ Room* Room::getRoom(int32 x, int32 y, int32 z) return room; } -int32 Room::getWaterLevel() -{ - return WALL; // TODO -} - -int32 Room::getWaterDepth() -{ - return WALL; // TODO -} - bool Room::checkPortal(const Portal* portal) { vec3i d; @@ -598,10 +645,61 @@ void Room::remove(Item* item) } } +void checkCamera(const FloorData* fd) +{ + if (camera.mode == CAMERA_MODE_OBJECT) + return; + + while (1) + { + FloorData::TriggerCommand triggerCmd = (fd++)->triggerCmd; + + switch (triggerCmd.action) + { + case TRIGGER_ACTION_ACTIVATE_CAMERA: + { + triggerCmd.end = (fd++)->triggerCmd.end; + + if (triggerCmd.args != camera.lastIndex) + break; + + camera.index = triggerCmd.args; + + if (camera.timer < 0 || camera.mode == CAMERA_MODE_LOOK || camera.mode == CAMERA_MODE_COMBAT) + { + camera.timer = -1; + break; + } + + camera.mode = CAMERA_MODE_FIXED; + break; + } + + case TRIGGER_ACTION_CAMERA_TARGET: + { + if (camera.mode == CAMERA_MODE_LOOK || camera.mode == CAMERA_MODE_COMBAT) + break; + + ASSERT(triggerCmd.args < level.itemsCount); + camera.lookAtItem = items + triggerCmd.args; + break; + } + + case TRIGGER_ACTION_FLYBY: + { + triggerCmd.end = (fd++)->triggerCmd.end; + break; + } + } + + if (triggerCmd.end) break; + }; +} void checkTrigger(const FloorData* fd, Item* lara) { - if (!fd) return; + if (!fd) + return; if (fd->cmd.func == FLOOR_TYPE_LAVA) { @@ -609,6 +707,7 @@ void checkTrigger(const FloorData* fd, Item* lara) if (fd->cmd.end) return; + fd++; } @@ -616,8 +715,9 @@ void checkTrigger(const FloorData* fd, Item* lara) FloorData::TriggerInfo info = (fd++)->triggerInfo; Item* switchItem = NULL; - Item* keyItem = NULL; - Item* pickupItem = NULL; + Item* cameraItem = NULL; + + checkCamera(fd); if (!lara && cmd.type != TRIGGER_TYPE_OBJECT) return; @@ -631,37 +731,48 @@ void checkTrigger(const FloorData* fd, Item* lara) case TRIGGER_TYPE_PAD: case TRIGGER_TYPE_ANTIPAD: - if (lara->pos.y != lara->floor) + { + if (lara->pos.y != lara->roomFloor) return; break; + } case TRIGGER_TYPE_SWITCH: + { switchItem = items + fd->triggerCmd.args; if (!useSwitch(switchItem, info.timer)) return; fd++; break; + } case TRIGGER_TYPE_KEY: - keyItem = items + fd->triggerCmd.args; - if (!useKey(keyItem)) + { + Item* keyItem = items + fd->triggerCmd.args; + if (!useKey(keyItem, lara)) return; fd++; break; + } case TRIGGER_TYPE_PICKUP: - pickupItem = items + fd->triggerCmd.args; + { + Item* pickupItem = items + fd->triggerCmd.args; if (!usePickup(pickupItem)) return; fd++; break; + } case TRIGGER_TYPE_OBJECT: return; case TRIGGER_TYPE_COMBAT: - // TODO unused? + { + if (lara->weaponState != WEAPON_STATE_READY) + return; break; + } case TRIGGER_TYPE_DUMMY: return; @@ -674,15 +785,18 @@ void checkTrigger(const FloorData* fd, Item* lara) switch (triggerCmd.action) { - case TRIGGER_ACTION_ACTIVATE_OBJECT: { + case TRIGGER_ACTION_ACTIVATE_OBJECT: + { + ASSERT(triggerCmd.args < level.itemsCount); Item* item = items + triggerCmd.args; if (item->flags.once) break; item->timer = info.timer; - if (item->timer != 1) + if (item->timer != 1) { item->timer *= 30; + } if (cmd.type == TRIGGER_TYPE_SWITCH) { item->flags.mask ^= info.mask; @@ -707,8 +821,40 @@ void checkTrigger(const FloorData* fd, Item* lara) } case TRIGGER_ACTION_ACTIVATE_CAMERA: - // TODO fixed camera + { + FloorData::TriggerCommand cam = (fd++)->triggerCmd; + triggerCmd.end = cam.end; + + if (cameras[triggerCmd.args].flags.once) + break; + + camera.index = triggerCmd.args; + + if (camera.mode == CAMERA_MODE_LOOK || camera.mode == CAMERA_MODE_COMBAT) + break; + + if (cmd.type == TRIGGER_TYPE_COMBAT) + break; + + if (cmd.type == TRIGGER_TYPE_SWITCH && (switchItem->state == 1) && (info.timer != 0)) + break; + + if (cmd.type == TRIGGER_TYPE_SWITCH || camera.index != camera.lastIndex) + { + camera.timer = cam.timer; + if (camera.timer != 1) { + camera.timer *= 30; + } + + if (cam.once) { + cameras[camera.index].flags.once = true; + } + + camera.speed = (cam.speed << 3) + 1; + camera.mode = lara ? CAMERA_MODE_FIXED : CAMERA_MODE_OBJECT; + } break; + } case TRIGGER_ACTION_FLOW: // TODO flow @@ -727,14 +873,17 @@ void checkTrigger(const FloorData* fd, Item* lara) break; case TRIGGER_ACTION_CAMERA_TARGET: - // TODO change fixed camera target + { + cameraItem = items + triggerCmd.args; break; + } case TRIGGER_ACTION_END: // TODO go to the next level break; - case TRIGGER_ACTION_SOUNDTRACK: { + case TRIGGER_ACTION_SOUNDTRACK: + { int32 track = doTutorial(lara, triggerCmd.args); if (track == 0) break; @@ -764,7 +913,8 @@ void checkTrigger(const FloorData* fd, Item* lara) // TODO effect break; - case TRIGGER_ACTION_SECRET: { + case TRIGGER_ACTION_SECRET: + { if ((gSaveGame.secrets >> triggerCmd.args) & 1) break; @@ -777,6 +927,7 @@ void checkTrigger(const FloorData* fd, Item* lara) break; case TRIGGER_ACTION_FLYBY: + triggerCmd.end = (fd++)->triggerCmd.end; break; case TRIGGER_ACTION_CUTSCENE: @@ -785,6 +936,11 @@ void checkTrigger(const FloorData* fd, Item* lara) if (triggerCmd.end) break; }; + + if (cameraItem && (camera.mode == CAMERA_MODE_FIXED || camera.mode == CAMERA_MODE_OBJECT)) + { + camera.lookAtItem = cameraItem; + } } #endif diff --git a/src/platform/gba/sound.h b/src/platform/gba/sound.h index c7f5d89..276d9ff 100644 --- a/src/platform/gba/sound.h +++ b/src/platform/gba/sound.h @@ -105,8 +105,6 @@ struct Mixer void fill(uint8* bufferA, uint8* bufferB, int32 count) { - UNUSED(bufferB); - if ((channelsCount == 0) && !music.data) { dmaFill(bufferA, SND_ENCODE(0), count); @@ -178,9 +176,8 @@ struct Mixer } } - if (channelsCount >= SND_CHANNELS) { + if (channelsCount >= SND_CHANNELS) return NULL; - } #ifdef USE_9BIT_SOUND // expand 8 to 9-bit @@ -197,6 +194,19 @@ struct Mixer return sample; } + void stopSample(const uint8* data) + { + int32 i = channelsCount; + + while (--i >= 0) + { + if (channels[i].data == data) + { + channels[i] = channels[--channelsCount]; + } + } + } + void playMusic(const void* data) { music.data = (uint8*)data + 16;