diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index a5bcd746..587b6f64 100644 Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ diff --git a/src/camera.h b/src/camera.h index 32ea5479..4d664a41 100644 --- a/src/camera.h +++ b/src/camera.h @@ -153,18 +153,17 @@ struct Frustum { struct Camera : Controller { - Controller *owner; - Frustum *frustum; + Lara *owner; + Frustum *frustum; - float fov, znear, zfar; - vec3 target, destPos, lastDest, angleAdv; + float fov, znear, zfar; + vec3 target, destPos, lastDest, angleAdv; + int room; - int room; - - Camera(TR::Level *level, Controller *owner) : Controller(level, owner->entity), owner(owner), frustum(new Frustum()) { + Camera(TR::Level *level, Lara *owner) : Controller(level, owner->entity), owner(owner), frustum(new Frustum()) { fov = 75.0f; - znear = 0.1f * 2048.0f; - zfar = 1000.0f * 2048.0f; + znear = 128; + zfar = 100.0f * 1024.0f; angleAdv = vec3(0.0f); room = owner->getEntity().room; @@ -212,18 +211,28 @@ struct Camera : Controller { angle = owner->angle + angleAdv; angle.z = 0.0f; //angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD); - + target = vec3(owner->pos.x, owner->pos.y - height, owner->pos.z); - vec3 dir = getDir(); + vec3 dir; + float lerpFactor = 2.0f; + if (owner->targetEntity > -1) { + TR::Entity &e = level->entities[owner->targetEntity]; + dir = (vec3(e.x, e.y, e.z) - target).normal(); + lerpFactor = 10.0f; + } else + dir = getDir(); if (owner->state != Lara::STATE_BACK_JUMP) { - destPos = target - dir * 1024.0f; + vec3 eye = target - dir * 1024.0f; + destPos = trace(owner->getRoomIndex(), target, eye); lastDest = destPos; - } else - destPos = lastDest + dir.cross(vec3(0, 1, 0)).normal() * 2048.0f - vec3(0.0f, 512.0f, 0.0f); + } else { + vec3 eye = lastDest + dir.cross(vec3(0, 1, 0)).normal() * 2048.0f - vec3(0.0f, 512.0f, 0.0f); + destPos = trace(owner->getRoomIndex(), target, eye); + } - pos = pos.lerp(destPos, min(1.0f, Core::deltaTime * 2.0f)); + pos = pos.lerp(destPos, min(1.0f, Core::deltaTime * lerpFactor)); TR::Level::FloorInfo info; level->getFloorInfo(room, (int)pos.x, (int)pos.z, info); @@ -248,6 +257,62 @@ struct Camera : Controller { } } + vec3 trace(int fromRoom, const vec3 &from, const vec3 &to) { // TODO: use Bresenham + int room = fromRoom; + + vec3 pos = from, dir = to - from; + int px = (int)pos.x, py = (int)pos.y, pz = (int)pos.z; + + float dist = dir.length(); + dir = dir * (1.0f / dist); + + int lx = -1, lz = -1; + TR::Level::FloorInfo info; + while (dist > 1.0f) { + int sx = px / 1024 * 1024 + 512, + sz = pz / 1024 * 1024 + 512; + + if (lx != sx || lz != sz) { + level->getFloorInfo(room, sx, sz, info); + lx = sx; + lz = sz; + if (info.roomNext != 0xFF) room = info.roomNext; + } + + if (py > info.floor && info.roomBelow != 0xFF) + room = info.roomBelow; + else if (py < info.ceiling && info.roomAbove != 0xFF) + room = info.roomAbove; + else if (py > info.floor || py < info.ceiling) { + int minX = px / 1024 * 1024; + int minZ = pz / 1024 * 1024; + int maxX = minX + 1024; + int maxZ = minZ + 1024; + + pos = vec3(clamp(px, minX, maxX), pos.y, clamp(pz, minZ, maxZ)) + boxNormal(px, pz) * 256.0f; + dir = (pos - from).normal(); + } + + float d = min(dist, 128.0f); // STEP = 128 + dist -= d; + pos = pos + dir * d; + + px = (int)pos.x, py = (int)pos.y, pz = (int)pos.z; + } + + return pos; + } + + vec3 boxNormal(int x, int z) { + x %= 1024; + z %= 1024; + + if (x > 1024 - z) + return x < z ? vec3(0, 0, 1) : vec3(1, 0, 0); + else + return x < z ? vec3(-1, 0, 0) : vec3(0, 0, -1); + } + void setup() { Core::mViewInv = mat4(pos, target, vec3(0, -1, 0)); Core::mView = Core::mViewInv.inverse(); diff --git a/src/controller.h b/src/controller.h index 1721c6b5..0c04bc07 100644 --- a/src/controller.h +++ b/src/controller.h @@ -52,9 +52,9 @@ struct Controller { Controller(TR::Level *level, int entity) : level(level), entity(entity), velocity(0.0f), animTime(0.0f), animPrevFrame(0), health(100), turnTime(0.0f), nextAction(TR::Action::NONE, 0, 0.0f) { TR::Entity &e = getEntity(); pos = vec3((float)e.x, (float)e.y, (float)e.z); - angle = vec3(0.0f, e.rotation / 16384.0f * PI * 0.5f, 0.0f); + angle = vec3(0.0f, e.rotation, 0.0f); stand = STAND_GROUND; - animIndex = getModel().animation; + animIndex = e.modelIndex > 0 ? level->models[e.modelIndex - 1].animation : 0; state = level->anims[animIndex].state; } @@ -63,7 +63,7 @@ struct Controller { e.x = int(pos.x); e.y = int(pos.y); e.z = int(pos.z); - e.rotation = int(angle.y / (PI * 0.5f) * 16384.0f); + e.rotation = angle.y; } bool insideRoom(const vec3 &pos, int room) const { @@ -80,15 +80,6 @@ struct Controller { return level->entities[entity]; } - TR::Model& getModel() const { - TR::Entity &entity = getEntity(); - for (int i = 0; i < level->modelsCount; i++) - if (entity.id == level->models[i].id) - return level->models[i]; - ASSERT(false); - return level->models[0]; - } - TR::Room& getRoom() const { int index = getRoomIndex(); ASSERT(index >= 0 && index < level->roomsCount); @@ -337,7 +328,7 @@ struct Controller { int idx = frame - anim->frameStart; if (idx > animPrevFrame && idx <= frameIndex) { - if (getEntity().id != ENTITY_ENEMY_BAT) // temporary mute the bat + if (getEntity().id != TR::Entity::ENEMY_BAT) // temporary mute the bat playSound(id); } break; @@ -375,4 +366,51 @@ struct Controller { } }; + +struct SpriteController : Controller { + + enum { + FRAME_ANIMATED = -1, + FRAME_RANDOM = -2, + }; + + int frame; + bool instant, animated; + + SpriteController(TR::Level *level, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(level, entity), instant(instant), animated(frame == FRAME_ANIMATED) { + if (frame >= 0) { // specific frame + this->frame = frame; + } else if (frame == FRAME_RANDOM) { // random frame + this->frame = rand() % getSequence().sCount; + } else if (frame == FRAME_ANIMATED) { // animated + this->frame = 0; + } + } + + TR::SpriteSequence& getSequence() { + return level->spriteSequences[-(getEntity().modelIndex + 1)]; + } + + void update() { + bool remove = false; + animTime += Core::deltaTime; + + if (animated) { + frame = int(animTime * 10.0f); + TR::SpriteSequence &seq = getSequence(); + if (instant && frame >= seq.sCount) + remove = true; + else + frame %= seq.sCount; + } else + if (instant && animTime >= 0.1f) + remove = true; + + if (remove) { + level->entityRemove(entity); + delete this; + } + } +}; + #endif \ No newline at end of file diff --git a/src/debug.h b/src/debug.h index 337a7586..2d1a6e42 100644 --- a/src/debug.h +++ b/src/debug.h @@ -195,19 +195,19 @@ namespace Debug { cmd = (*fd++).cmd; switch (cmd.func) { - case TR::FD_PORTAL : + case TR::FloorData::PORTAL : isPortal = true; fd++; break; // portal - case TR::FD_FLOOR : // floor & ceiling - case TR::FD_CEILING : { + case TR::FloorData::FLOOR : // floor & ceiling + case TR::FloorData::CEILING : { TR::FloorData::Slant slant = (*fd++).slant; int sx = 256 * (int)slant.x; int sz = 256 * (int)slant.z; auto &p = cmd.func == 0x02 ? vf : vc; - if (cmd.func == 0x02) { // floor + if (cmd.func == TR::FloorData::FLOOR) { // floor if (sx > 0) { p[0].y += sx; p[3].y += sx; @@ -242,28 +242,12 @@ namespace Debug { } break; } - case TR::FD_TRIGGER : { + case TR::FloorData::TRIGGER : { TR::FloorData::TriggerInfo info = (*fd++).triggerInfo; TR::FloorData::TriggerCommand trigCmd; glColor3f(1, 0, 1); do { trigCmd = (*fd++).triggerCmd; // trigger action - switch (trigCmd.func) { - case 0 : break; // activate item - case 1 : break; // switch to camera - case 2 : break; // camera delay - case 3 : break; // flip map - case 4 : break; // flip on - case 5 : break; // flip off - case 6 : break; // look at item - case 7 : break; // end level - case 8 : break; // play soundtrack - case 9 : break; // special hadrdcode trigger - case 10 : break; // secret found - case 11 : break; // clear bodies - case 12 : break; // flyby camera sequence - case 13 : break; // play cutscene - } } while (!trigCmd.end); break; } @@ -468,7 +452,7 @@ namespace Debug { matrix.rotateX(controller->angle.x); matrix.rotateZ(controller->angle.z); } else - matrix.rotateY(e.rotation / 16384.0f * PI * 0.5f); + matrix.rotateY(e.rotation); for (int j = 0; j < level.modelsCount; j++) { TR::Model &m = level.models[j]; diff --git a/src/format.h b/src/format.h index fc00b5ef..377f9d45 100644 --- a/src/format.h +++ b/src/format.h @@ -5,6 +5,8 @@ #define TR1_DEMO +#define MAX_RESERVED_ENTITIES 64 + namespace TR { enum : int32 { @@ -12,14 +14,6 @@ namespace TR { ROOM_FLAG_VISIBLE = 0x8000 }; - enum { - FD_PORTAL = 1, - FD_FLOOR = 2, - FD_CEILING = 3, - FD_TRIGGER = 4, - FD_KILL = 5, - }; - enum { ANIM_CMD_MOVE = 1, ANIM_CMD_SPEED = 2, @@ -56,7 +50,7 @@ namespace TR { NONE = -1, // no action ACTIVATE = 0, // activate item CAMERA_SWITCH = 1, // switch to camera - CAMERA_DELAY = 2, // camera delay + UNDERWATER = 2, // underwater flow FLIP_MAP = 3, // flip map FLIP_ON = 4, // flip on FLIP_OFF = 5, // flip off @@ -70,7 +64,6 @@ namespace TR { CUTSCENE = 13, // play cutscene }; - #define DATA_PORTAL 0x01 #define DATA_FLOOR 0x02 #define DATA_CEILING 0x03 @@ -79,67 +72,6 @@ namespace TR { #define ENTITY_FLAG_VISIBLE 0x0100 #define ENTITY_FLAG_ACTIVE 0x3E00 - - #define ENTITY_LARA 0 - #define ENTITY_LARA_CUT 77 - - #define ENTITY_ENEMY_TWIN 6 - #define ENTITY_ENEMY_WOLF 7 - #define ENTITY_ENEMY_BEAR 8 - #define ENTITY_ENEMY_BAT 9 - #define ENTITY_ENEMY_CROCODILE_LAND 10 - #define ENTITY_ENEMY_CROCODILE_WATER 11 - #define ENTITY_ENEMY_LION_MALE 12 - #define ENTITY_ENEMY_LION_FEMALE 13 - #define ENTITY_ENEMY_PUMA 14 - #define ENTITY_ENEMY_GORILLA 15 - #define ENTITY_ENEMY_RAT_LAND 16 - #define ENTITY_ENEMY_RAT_WATER 17 - #define ENTITY_ENEMY_REX 18 - #define ENTITY_ENEMY_RAPTOR 19 - #define ENTITY_ENEMY_MUTANT 20 - - #define ENTITY_ENEMY_CENTAUR 23 - #define ENTITY_ENEMY_MUMMY 24 - #define ENTITY_ENEMY_LARSON 27 - - #define ENTITY_TRAP_FLOOR 35 - #define ENTITY_TRAP_BLADE 36 - #define ENTITY_TRAP_SPIKES 37 - #define ENTITY_TRAP_STONE 38 - #define ENTITY_TRAP_DART 39 - #define ENTITY_TRAP_DARTGUN 40 - - #define ENTITY_CRYSTAL 83 - - #define ENTITY_MEDIKIT_SMALL 93 - #define ENTITY_MEDIKIT_BIG 94 - - - #define ENTITY_SWITCH 55 - #define ENTITY_SWITCH_WATER 56 - - #define ENTITY_DOOR_1 57 - #define ENTITY_DOOR_2 58 - #define ENTITY_DOOR_3 59 - #define ENTITY_DOOR_4 60 - #define ENTITY_DOOR_BIG_1 61 - #define ENTITY_DOOR_BIG_2 62 - #define ENTITY_DOOR_5 63 - #define ENTITY_DOOR_6 64 - #define ENTITY_DOOR_FLOOR_1 65 - #define ENTITY_DOOR_FLOOR_2 66 - - #define ENTITY_GUN_SHOTGUN 85 - - #define ENTITY_AMMO_UZI 91 - #define ENTITY_AMMO_SHOTGUN 89 - #define ENTITY_AMMO_MAGNUM 90 - - #define ENTITY_HOLE_PUZZLE 118 - #define ENTITY_HOLE_KEY 137 - #define ENTITY_VIEW_TARGET 169 - #pragma pack(push, 1) struct fixed { @@ -150,6 +82,14 @@ namespace TR { } }; + struct angle { + uint16 value; + + angle() {} + angle(float value) : value(uint16(value / (PI * 0.5f) * 16384.0f)) {} + operator float() const { return value / 16384.0f * PI * 0.5f; }; + }; + struct RGB { uint8 r, g, b; }; @@ -240,7 +180,7 @@ namespace TR { struct Mesh { int32 x, y, z; - uint16 rotation; + angle rotation; uint16 intensity; uint16 meshID; uint16 flags; // ! not exists in file ! @@ -259,8 +199,16 @@ namespace TR { uint16 timer:8, once:1, mask:5, :2; } triggerInfo; struct TriggerCommand { - uint16 args:10, func:5, end:1; + uint16 args:10, action:5, end:1; } triggerCmd; + + enum { + PORTAL = 1, + FLOOR = 2, + CEILING = 3, + TRIGGER = 4, + KILL = 5, + }; }; struct Overlap { @@ -302,14 +250,92 @@ namespace TR { int16 id; // Object Identifier (matched in Models[], or SpriteSequences[], as appropriate) int16 room; // which room contains this item int32 x, y, z; // world coords - int16 rotation; // ((0xc000 >> 14) * 90) degrees + angle rotation; // ((0xc000 >> 14) * 90) degrees int16 intensity; // (constant lighting; -1 means use mesh lighting) uint16 flags; // 0x0100 indicates "initially invisible", 0x3e00 is Activation Mask // 0x3e00 indicates "open" or "activated"; these can be XORed with // related FloorData::FDlist fields (e.g. for switches) // not exists in file uint16 align; + int16 modelIndex; // index of representation in models (index + 1) or spriteSequences (-(index + 1)) arrays void *controller; // Controller implementation or NULL + + enum { + LARA = 0, + + ENEMY_TWIN = 6, + ENEMY_WOLF = 7, + ENEMY_BEAR = 8, + ENEMY_BAT = 9, + ENEMY_CROCODILE_LAND = 10, + ENEMY_CROCODILE_WATER = 11, + ENEMY_LION_MALE = 12, + ENEMY_LION_FEMALE = 13, + ENEMY_PUMA = 14, + ENEMY_GORILLA = 15, + ENEMY_RAT_LAND = 16, + ENEMY_RAT_WATER = 17, + ENEMY_REX = 18, + ENEMY_RAPTOR = 19, + ENEMY_MUTANT = 20, + + ENEMY_CENTAUR = 23, + ENEMY_MUMMY = 24, + ENEMY_LARSON = 27, + + TRAP_FLOOR = 35, + TRAP_BLADE = 36, + TRAP_SPIKES = 37, + TRAP_STONE = 38, + TRAP_DART = 39, + TRAP_DARTGUN = 40, + + SWITCH = 55, + SWITCH_WATER = 56, + DOOR_1 = 57, + DOOR_2 = 58, + DOOR_3 = 59, + DOOR_4 = 60, + DOOR_BIG_1 = 61, + DOOR_BIG_2 = 62, + DOOR_5 = 63, + DOOR_6 = 64, + DOOR_FLOOR_1 = 65, + DOOR_FLOOR_2 = 66, + + LARA_CUT = 77, + + CRYSTAL = 83, // sprite + WEAPON_PISTOLS = 84, // sprite + WEAPON_SHOTGUN = 85, // sprite + WEAPON_MAGNUMS = 86, // sprite + WEAPON_UZIS = 87, // sprite + AMMO_SHOTGUN = 89, // sprite + AMMO_MAGNUMS = 90, // sprite + AMMO_UZIS = 91, // sprite + MEDIKIT_SMALL = 93, // sprite + MEDIKIT_BIG = 94, // sprite + + HOLE_PUZZLE = 118, + + HOLE_KEY = 137, + + ARTIFACT = 143, // sprite + + WATER_SPLASH = 153, // sprite + + BUBBLE = 155, // sprite + + BLOOD = 158, // sprite + + SMOKE = 160, // sprite + + SPARK = 164, // sprite + + VIEW_TARGET = 169, + + GLYPH = 190, // sprite + }; }; struct Animation { @@ -396,8 +422,8 @@ namespace TR { MinMax box[2]; // visible (minX, maxX, minY, maxY, minZ, maxZ) & collision uint16 flags; - void getBox(bool collision, int rotation, vec3 &min, vec3 &max) { - int k = rotation / 16384; + void getBox(bool collision, angle rotation, vec3 &min, vec3 &max) { + int k = rotation.value / 0x4000; MinMax &m = box[collision]; @@ -574,6 +600,7 @@ namespace TR { int32 animTexturesDataSize; uint16 *animTexturesData; + int32 entitiesBaseCount; int32 entitiesCount; Entity *entities; @@ -666,6 +693,8 @@ namespace TR { stream.read(objectTextures, stream.read(objectTexturesCount)); stream.read(spriteTextures, stream.read(spriteTexturesCount)); stream.read(spriteSequences, stream.read(spriteSequencesCount)); + for (int i = 0; i < spriteSequencesCount; i++) + spriteSequences[i].sCount = -spriteSequences[i].sCount; #ifdef TR1_DEMO stream.read(palette, 256); @@ -682,12 +711,17 @@ namespace TR { // animated textures stream.read(animTexturesData, stream.read(animTexturesDataSize)); // entities (enemies, items, lara etc.) - entities = new Entity[stream.read(entitiesCount)]; - for (int i = 0; i < entitiesCount; i++) { - stream.raw(&entities[i], sizeof(entities[i]) - sizeof(entities[i].align) - sizeof(entities[i].controller)); - entities[i].align = 0; - entities[i].controller = NULL; + entitiesCount = stream.read(entitiesBaseCount) + MAX_RESERVED_ENTITIES; + entities = new Entity[entitiesCount]; + for (int i = 0; i < entitiesBaseCount; i++) { + Entity &e = entities[i]; + stream.raw(&e, sizeof(e) - sizeof(e.align) - sizeof(e.controller) - sizeof(e.modelIndex)); + e.align = 0; + e.controller = NULL; + e.modelIndex = getModelIndex(e.id); } + for (int i = entitiesBaseCount; i < entitiesCount; i++) + entities[i].id = -1; // palette stream.seek(32 * 256); // skip lightmap palette @@ -769,6 +803,43 @@ namespace TR { return NULL; } + int16 getModelIndex(int16 id) const { + for (int i = 0; i < modelsCount; i++) + if (id == models[i].id) + return i + 1; + + for (int i = 0; i < spriteSequencesCount; i++) + if (id == spriteSequences[i].id) + return -(i + 1); + + ASSERT(false); + return 0; + } + + int entityAdd(int16 id, int16 room, int32 x, int32 y, int32 z, angle rotation, int16 intensity) { + int entityIndex = -1; + for (int i = entitiesBaseCount; i < entitiesCount; i++) + if (entities[i].id == -1) { + Entity &e = entities[i]; + e.id = id; + e.room = room; + e.x = x; + e.y = y; + e.z = z; + e.rotation = rotation; + e.intensity = intensity; + e.flags = 0; + e.modelIndex = getModelIndex(e.id); + e.controller = NULL; + return i; + } + return -1; + } + + void entityRemove(int entityIndex) { + entities[entityIndex].id = -1; + } + struct FloorInfo { int floor, ceiling; int roomNext, roomBelow, roomAbove; @@ -822,16 +893,16 @@ namespace TR { switch (cmd.func) { - case FD_PORTAL : + case FloorData::PORTAL : info.roomNext = (*fd++).data; break; - case FD_FLOOR : // floor & ceiling - case FD_CEILING : { + case FloorData::FLOOR : // floor & ceiling + case FloorData::CEILING : { FloorData::Slant slant = (*fd++).slant; int sx = (int)slant.x; int sz = (int)slant.z; - if (cmd.func == FD_FLOOR) { + if (cmd.func == FloorData::FLOOR) { info.floor -= sx * (sx > 0 ? (dx - 1024) : dx) >> 2; info.floor -= sz * (sz > 0 ? (dz - 1024) : dz) >> 2; } else { @@ -841,7 +912,7 @@ namespace TR { break; } - case FD_TRIGGER : { + case FloorData::TRIGGER : { info.trigger = cmd.sub; info.trigCmdCount = 0; info.trigInfo = (*fd++).triggerInfo; @@ -849,28 +920,14 @@ namespace TR { do { trigCmd = (*fd++).triggerCmd; // trigger action info.trigCmd[info.trigCmdCount++] = trigCmd; - switch (trigCmd.func) { - case 0 : break; // activate item - case 1 : break; // switch to camera - case 2 : break; // camera delay - case 3 : break; // flip map - case 4 : break; // flip on - case 5 : break; // flip off - case 6 : break; // look at item - case 7 : break; // end level - case 8 : break; // play soundtrack - case 9 : break; // special hadrdcode trigger - case 10 : break; // secret found (playSound(175)) - case 11 : break; // clear bodies - case 12 : break; // flyby camera sequence - case 13 : break; // play cutscene + if (trigCmd.action == Action::CAMERA_SWITCH) { + } - // .. } while (!trigCmd.end); break; } - case FD_KILL : + case FloorData::KILL : info.kill = true; break; diff --git a/src/lara.h b/src/lara.h index 39ba0bc9..2ff3a3a2 100644 --- a/src/lara.h +++ b/src/lara.h @@ -17,7 +17,7 @@ struct Lara : Controller { // http://www.tombraiderforums.com/showthread.php?t=148859&highlight=Explanation+left - enum LaraAnim : int32 { + enum { ANIM_STAND = 11, ANIM_FALL = 34, ANIM_SMASH_JUMP = 32, @@ -35,7 +35,7 @@ struct Lara : Controller { }; // http://www.tombraiderforums.com/showthread.php?t=211681 - enum LaraState : int32 { + enum { STATE_WALK, STATE_RUN, STATE_STOP, @@ -94,7 +94,10 @@ struct Lara : Controller { STATE_WATER_OUT, STATE_MAX }; - Lara(TR::Level *level, int entity) : Controller(level, entity) { + int targetEntity; + float targetTimer; + + Lara(TR::Level *level, int entity) : Controller(level, entity), targetEntity(-1), targetTimer(0.0f) { /* // level 2 (pool) pos = vec3(70067, -256, 29104); @@ -195,6 +198,7 @@ struct Lara : Controller { return; break; default : + LOG("unsupported trigger type %d\n", info.trigger); return; } @@ -203,25 +207,38 @@ struct Lara : Controller { // build trigger activation chain for (int i = 0; i < info.trigCmdCount; i++) { - if (info.trigCmd[i].func != TR::Action::ACTIVATE) continue; // TODO: other trigger types - Controller *controller = (Controller*)level->entities[info.trigCmd[i].args].controller; - if (!controller) { - LOG("! next activation entity %d has no controller\n", level->entities[info.trigCmd[i].args].id); - playSound(2); - return; - } else - controller->nextAction = (i < info.trigCmdCount - 1) ? Action(TR::Action::ACTIVATE, info.trigCmd[i + 1].args, 0.0f) : Action(TR::Action::NONE, 0, 0.0f); + TR::FloorData::TriggerCommand &cmd = info.trigCmd[i]; + switch (cmd.action) { + case TR::Action::ACTIVATE : { + Controller *controller = (Controller*)level->entities[cmd.args].controller; + if (!controller) { + LOG("! next activation entity %d has no controller\n", level->entities[info.trigCmd[i].args].id); + playSound(2); + return; + } else + controller->nextAction = (i < info.trigCmdCount - 1) ? Action(TR::Action::ACTIVATE, info.trigCmd[i + 1].args, 0.0f) : Action(TR::Action::NONE, 0, 0.0f); + break; + } + case TR::Action::LOOK_AT : + level->entities[cmd.args].flags |= ENTITY_FLAG_ACTIVE; + targetEntity = cmd.args; + targetTimer = info.trigInfo.timer; + break; + default : + LOG("unsupported trigger action %d\n", cmd.action); + return; + } } - if (info.trigCmd[0].func != TR::Action::ACTIVATE) return; // see above TODO - // activate first entity in chain Controller *controller = (Controller*)level->entities[info.trigCmd[0].args].controller; - if (info.trigger == TR::TRIGGER_KEY) { - nextAction = controller->nextAction; - controller->nextAction.action = TR::Action::NONE; - } else - controller->activate((float)info.trigInfo.timer); + if (controller) { + if (info.trigger == TR::TRIGGER_KEY) { + nextAction = controller->nextAction; + controller->nextAction.action = TR::Action::NONE; + } else + controller->activate((float)info.trigInfo.timer); + } } virtual Stand getStand() { @@ -381,6 +398,11 @@ struct Lara : Controller { } virtual void updateState() { + if (targetTimer > 0.0f && (targetTimer -= Core::deltaTime) <= 0.0f) { + targetEntity = -1; + targetTimer = 0.0f; + } + performTrigger(); TR::Animation *anim = &level->anims[animIndex]; diff --git a/src/level.h b/src/level.h index 675101b1..70911509 100644 --- a/src/level.h +++ b/src/level.h @@ -25,7 +25,7 @@ struct Level { Texture *atlas; MeshBuilder *mesh; - Controller *lara; + Lara *lara; Camera *camera; float time; @@ -43,60 +43,59 @@ struct Level { for (int i = 0; i < level.entitiesCount; i++) { TR::Entity &entity = level.entities[i]; switch (entity.id) { - case ENTITY_LARA : - case ENTITY_LARA_CUT : + case TR::Entity::LARA : + case TR::Entity::LARA_CUT : entity.controller = (lara = new Lara(&level, i)); break; - case ENTITY_ENEMY_WOLF : + case TR::Entity::ENEMY_WOLF : entity.controller = new Wolf(&level, i); break; - case ENTITY_ENEMY_BEAR : + case TR::Entity::ENEMY_BEAR : entity.controller = new Bear(&level, i); break; - case ENTITY_ENEMY_BAT : + case TR::Entity::ENEMY_BAT : entity.controller = new Bat(&level, i); break; - case ENTITY_ENEMY_TWIN : - case ENTITY_ENEMY_CROCODILE_LAND : - case ENTITY_ENEMY_CROCODILE_WATER : - case ENTITY_ENEMY_LION_MALE : - case ENTITY_ENEMY_LION_FEMALE : - case ENTITY_ENEMY_PUMA : - case ENTITY_ENEMY_GORILLA : - case ENTITY_ENEMY_RAT_LAND : - case ENTITY_ENEMY_RAT_WATER : - case ENTITY_ENEMY_REX : - case ENTITY_ENEMY_RAPTOR : - case ENTITY_ENEMY_MUTANT : - case ENTITY_ENEMY_CENTAUR : - case ENTITY_ENEMY_MUMMY : - case ENTITY_ENEMY_LARSON : + case TR::Entity::ENEMY_TWIN : + case TR::Entity::ENEMY_CROCODILE_LAND : + case TR::Entity::ENEMY_CROCODILE_WATER : + case TR::Entity::ENEMY_LION_MALE : + case TR::Entity::ENEMY_LION_FEMALE : + case TR::Entity::ENEMY_PUMA : + case TR::Entity::ENEMY_GORILLA : + case TR::Entity::ENEMY_RAT_LAND : + case TR::Entity::ENEMY_RAT_WATER : + case TR::Entity::ENEMY_REX : + case TR::Entity::ENEMY_RAPTOR : + case TR::Entity::ENEMY_MUTANT : + case TR::Entity::ENEMY_CENTAUR : + case TR::Entity::ENEMY_MUMMY : + case TR::Entity::ENEMY_LARSON : entity.controller = new Enemy(&level, i); break; - case ENTITY_DOOR_1 : - case ENTITY_DOOR_2 : - case ENTITY_DOOR_3 : - case ENTITY_DOOR_4 : - case ENTITY_DOOR_5 : - case ENTITY_DOOR_6 : - case ENTITY_DOOR_BIG_1 : - case ENTITY_DOOR_BIG_2 : - case ENTITY_DOOR_FLOOR_1 : - case ENTITY_DOOR_FLOOR_2 : - case ENTITY_TRAP_FLOOR : - case ENTITY_TRAP_BLADE : - case ENTITY_TRAP_SPIKES : - case ENTITY_TRAP_STONE : - //case ENTITY_TRAP_DART : + case TR::Entity::DOOR_1 : + case TR::Entity::DOOR_2 : + case TR::Entity::DOOR_3 : + case TR::Entity::DOOR_4 : + case TR::Entity::DOOR_5 : + case TR::Entity::DOOR_6 : + case TR::Entity::DOOR_BIG_1 : + case TR::Entity::DOOR_BIG_2 : + case TR::Entity::DOOR_FLOOR_1 : + case TR::Entity::DOOR_FLOOR_2 : + case TR::Entity::TRAP_FLOOR : + case TR::Entity::TRAP_BLADE : + case TR::Entity::TRAP_SPIKES : + case TR::Entity::TRAP_STONE : entity.controller = new Trigger(&level, i, true); break; - case ENTITY_TRAP_DARTGUN : - entity.controller = new Dart(&level, i); + case TR::Entity::TRAP_DARTGUN : + entity.controller = new Dartgun(&level, i); break; - case ENTITY_SWITCH : - case ENTITY_SWITCH_WATER : - case ENTITY_HOLE_PUZZLE : - case ENTITY_HOLE_KEY : + case TR::Entity::SWITCH : + case TR::Entity::SWITCH_WATER : + case TR::Entity::HOLE_PUZZLE : + case TR::Entity::HOLE_KEY : entity.controller = new Trigger(&level, i, false); break; } @@ -111,7 +110,8 @@ struct Level { Debug::free(); #endif for (int i = 0; i < level.entitiesCount; i++) - delete (Controller*)level.entities[i].controller; + if (level.entities[i].id > -1) + delete (Controller*)level.entities[i].controller; for (int i = 0; i < shMAX; i++) delete shaders[i]; @@ -246,7 +246,7 @@ struct Level { // render static mesh mat4 mTemp = Core::mModel; Core::mModel.translate(offset); - Core::mModel.rotateY(rMesh.rotation / 16384.0f * PI * 0.5f); + Core::mModel.rotateY(rMesh.rotation); renderMesh(sMesh->mesh); Core::mModel = mTemp; } @@ -365,7 +365,7 @@ struct Level { fTime = controller->animTime; } else { anim = &level.anims[model.animation]; - angle = vec3(0.0f, entity.rotation / 16384.0f * PI * 0.5f, 0.0f); + angle = vec3(0.0f, entity.rotation, 0.0f); fTime = time; } @@ -436,6 +436,20 @@ struct Level { } } + void renderSequence(const TR::Entity &entity) { + shaders[shSprite]->bind(); + Core::active.shader->setParam(uModel, Core::mModel); + Core::active.shader->setParam(uColor, Core::color); + + int sIndex = -(entity.modelIndex + 1); + int sFrame; + if (entity.controller) + sFrame = ((SpriteController*)entity.controller)->frame; + else + sFrame = int(time * 10.0f) % level.spriteSequences[sIndex].sCount; + mesh->renderSprite(sIndex, sFrame); + } + int getLightIndex(const vec3 &pos, int &room) { int idx = -1; float dist; @@ -474,6 +488,8 @@ struct Level { } void renderEntity(const TR::Entity &entity) { + if (entity.id < 0) return; + TR::Room &room = level.rooms[entity.room]; if (!(room.flags & TR::ROOM_FLAG_VISIBLE)) // check for room visibility return; @@ -491,42 +507,27 @@ struct Level { // get light parameters for entity getLight(vec3(entity.x, entity.y, entity.z), entity.room); - // render entity models (TODO: remapping or consider model and entity id's) - bool isModel = false; - - for (int i = 0; i < level.modelsCount; i++) - if (entity.id == level.models[i].id) { - isModel = true; - renderModel(level.models[i], entity); - break; - } - + // render entity models + if (entity.modelIndex > 0) + renderModel(level.models[entity.modelIndex - 1], entity); // if entity is billboard - if (!isModel) { + if (entity.modelIndex < 0) { Core::color = vec4(c, c, c, 1.0f); - shaders[shSprite]->bind(); - Core::active.shader->setParam(uModel, Core::mModel); - Core::active.shader->setParam(uColor, Core::color); - for (int i = 0; i < level.spriteSequencesCount; i++) - if (entity.id == level.spriteSequences[i].id) { - mesh->renderSprite(i); - break; - } + renderSequence(entity); } - + Core::mModel = m; } - float tickTextureAnimation = 0.0f; - void update() { time += Core::deltaTime; - for (int i = 0; i < level.entitiesCount; i++) { - Controller *controller = (Controller*)level.entities[i].controller; - if (controller) - controller->update(); - } + for (int i = 0; i < level.entitiesCount; i++) + if (level.entities[i].id > -1) { + Controller *controller = (Controller*)level.entities[i].controller; + if (controller) + controller->update(); + } camera->update(); } @@ -578,6 +579,28 @@ struct Level { for (int i = 0; i < level.entitiesCount; i++) renderEntity(level.entities[i]); + /* + static int modelIndex = 0; + static bool lastStateK = false; + + if (Input::down[ikM]) { + if (!lastStateK) { + lastStateK = true; + // modelIndex = (modelIndex + 1) % level.modelsCount; + modelIndex = (modelIndex + 1) % level.spriteSequencesCount; + LOG("model: %d %d\n", modelIndex, level.spriteSequences[modelIndex].id); + } + } else + lastStateK = false; + + Core::mModel.translate(lara->pos + vec3(512, -512, 0)); + //renderModel(level.models[modelIndex], level.entities[4]); + TR::Entity seq; + seq.modelIndex = -(modelIndex + 1); + seq.controller = NULL; + Core::color = vec4(1.0f); + renderSequence(seq); + */ #ifdef _DEBUG Debug::begin(); Debug::Level::rooms(level, lara->pos, lara->getEntity().room); diff --git a/src/mesh.h b/src/mesh.h index a7f4224b..ae2fef70 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -74,7 +74,7 @@ struct MeshBuilder { int mCount; // sprite sequences - MeshRange *spriteRanges; + MeshRange *sequenceRanges; // indexed mesh Mesh *mesh; @@ -160,14 +160,14 @@ struct MeshBuilder { meshInfo = new MeshInfo[mCount]; // get size of mesh for sprite sequences - spriteRanges = new MeshRange[level.spriteSequencesCount]; + sequenceRanges = new MeshRange[level.spriteSequencesCount]; for (int i = 0; i < level.spriteSequencesCount; i++) { // TODO: sequences not only first frame - spriteRanges[i].vStart = vCount; - spriteRanges[i].iStart = iCount; - spriteRanges[i].iCount = 6; - iCount += 6; - vCount += 4; + sequenceRanges[i].vStart = vCount; + sequenceRanges[i].iStart = iCount; + sequenceRanges[i].iCount = level.spriteSequences[i].sCount * 6; + iCount += level.spriteSequences[i].sCount * 6; + vCount += level.spriteSequences[i].sCount * 4; } // make meshes buffer (single vertex buffer object for all geometry & sprites on level) @@ -363,10 +363,11 @@ struct MeshBuilder { } // build sprite sequences - for (int i = 0; i < level.spriteSequencesCount; i++) { - TR::SpriteTexture &sprite = level.spriteTextures[level.spriteSequences[i].sStart]; - addSprite(indices, vertices, iCount, vCount, vCount, 0, -16, 0, sprite, 255); - } + for (int i = 0; i < level.spriteSequencesCount; i++) + for (int j = 0; j < level.spriteSequences[i].sCount; j++) { + TR::SpriteTexture &sprite = level.spriteTextures[level.spriteSequences[i].sStart + j]; + addSprite(indices, vertices, iCount, vCount, vCount, 0, 0, 0, sprite, 255); + } mesh = new Mesh(indices, iCount, vertices, vCount); delete[] indices; @@ -378,7 +379,7 @@ struct MeshBuilder { delete[] animTexOffsets; delete[] roomRanges; delete[] meshInfo; - delete[] spriteRanges; + delete[] sequenceRanges; delete mesh; } @@ -543,8 +544,12 @@ struct MeshBuilder { renderMesh(&meshInfo[meshIndex]); } - void renderSprite(int spriteIndex) { - mesh->render(spriteRanges[spriteIndex]); + void renderSprite(int sequenceIndex, int frame) { + MeshRange range = sequenceRanges[sequenceIndex]; + range.iCount = 6; + range.iStart += frame * 6; + range.vStart += frame * 4; + mesh->render(range); } }; diff --git a/src/trigger.h b/src/trigger.h index fa21bca7..56dae5db 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -49,35 +49,73 @@ struct Trigger : Controller { } if (!inState()) - setState(state != baseState ? baseState : (entity.id == ENTITY_TRAP_BLADE ? 2 : (baseState ^ 1))); + setState(state != baseState ? baseState : (entity.id == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1))); updateAnimation(true); updateEntity(); } }; -struct Dart : Trigger { - vec3 origin; +struct Dart : Controller { - Dart(TR::Level *level, int entity) : Trigger(level, entity, true), origin(pos) {} + vec3 dir; + bool inWall; // dart starts from wall - virtual bool activate(float timer) { - bool res = Trigger::activate(timer); - if (res) - playSound(151); - return res; + Dart(TR::Level *level, int entity) : Controller(level, entity), inWall(true) { + dir = vec3(sinf(angle.y), 0, cosf(angle.y)); } virtual void update() { - Trigger::update(); - if (state != baseState) { - pos = origin + vec3(angle.x, angle.y) * (animTime * 4096.0f); - updateEntity(); - } else { - pos = origin; - updateEntity(); - } + velocity = dir * level->anims[animIndex].speed; + pos = pos + velocity * (Core::deltaTime * 30.0f); + updateEntity(); + TR::Level::FloorInfo info; + level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info); + if (pos.y > info.floor || pos.y < info.ceiling || !insideRoom(pos, getRoomIndex())) { + if (!inWall) { + TR::Entity &e = getEntity(); + + vec3 p = pos - dir * 64.0f; // wall offset = 64 + int sparkIndex = level->entityAdd(TR::Entity::SPARK, e.room, (int)p.x, (int)p.y, (int)p.z, e.rotation, -1); + if (sparkIndex > -1) + level->entities[sparkIndex].controller = new SpriteController(level, sparkIndex, true, SpriteController::FRAME_RANDOM); + + level->entityRemove(entity); + delete this; + } + } else + inWall = false; } }; +struct Dartgun : Trigger { + vec3 origin; + + Dartgun(TR::Level *level, int entity) : Trigger(level, entity, true), origin(pos) {} + + virtual bool activate(float timer) { + if (!Trigger::activate(timer)) + return false; + + // add dart (bullet) + TR::Entity &entity = getEntity(); + + vec3 pos = vec3(0.0f, -512.0f, 256.0f).rotateY(PI - entity.rotation); + pos = pos + vec3(entity.x, entity.y, entity.z); + + int dartIndex = level->entityAdd(TR::Entity::TRAP_DART, entity.room, (int)pos.x, (int)pos.y, (int)pos.z, entity.rotation, entity.intensity); + if (dartIndex > -1) + level->entities[dartIndex].controller = new Dart(level, dartIndex); + + int smokeIndex = level->entityAdd(TR::Entity::SMOKE, entity.room, (int)pos.x, (int)pos.y, (int)pos.z, entity.rotation, -1); + if (smokeIndex > -1) + level->entities[smokeIndex].controller = new SpriteController(level, smokeIndex); + + playSound(151); + + return true; + } + +}; + #endif \ No newline at end of file diff --git a/src/web/main.cpp b/src/web/main.cpp index b38cec39..1f60d1c3 100644 --- a/src/web/main.cpp +++ b/src/web/main.cpp @@ -3,7 +3,7 @@ #include "game.h" -int lastTime, fpsTime, FPS; +int lastTime, fpsTime, fps; EGLDisplay display; EGLSurface surface; EGLContext context; @@ -18,23 +18,28 @@ void main_loop() { if (time - lastTime <= 0) return; - Core::deltaTime = (time - lastTime) * 0.001f; - lastTime = time; - - if (time > fpsTime) { - fpsTime = time + 1000; - LOG("FPS: %d\n", FPS); - FPS = 0; + float delta = (time - lastTime) * 0.001f; + while (delta > EPS) { + Core::deltaTime = min(delta, 1.0f / 30.0f); + Game::update(); + delta -= Core::deltaTime; } + lastTime = time; int f; emscripten_get_canvas_size(&Core::width, &Core::height, &f); - Game::update(); + Core::stats.dips = 0; + Core::stats.tris = 0; Game::render(); eglSwapBuffers(display, surface); - FPS++; + if (fpsTime < getTime()) { + LOG("FPS: %d DIP: %d TRI: %d\n", fps, Core::stats.dips, Core::stats.tris); + fps = 0; + fpsTime = getTime() + 1000; + } else + fps++; } bool initGL() { @@ -182,7 +187,7 @@ int main() { lastTime = getTime(); fpsTime = lastTime + 1000; - FPS = 0; + fps = 0; emscripten_set_main_loop(main_loop, 0, true); diff --git a/src/win/main.cpp b/src/win/main.cpp index 5bdfe068..94a82144 100644 --- a/src/win/main.cpp +++ b/src/win/main.cpp @@ -196,33 +196,31 @@ int main() { SetWindowLong(hWnd, GWL_WNDPROC, (LONG)&WndProc); ShowWindow(hWnd, SW_SHOWDEFAULT); - DWORD time, lastTime = getTime(); - + DWORD time, lastTime = getTime(), fpsTime = lastTime + 1000, fps = 0; MSG msg; - msg.message = WM_PAINT; - - DWORD fps = 0, fpsTime = getTime() + 1000; - while (msg.message != WM_QUIT) + do { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { + joyUpdate(); + time = getTime(); if (time <= lastTime) continue; - Core::deltaTime = (time - lastTime) * 0.001f; + float delta = (time - lastTime) * 0.001f; + while (delta > EPS) { + Core::deltaTime = min(delta, 1.0f / 30.0f); + Game::update(); + delta -= Core::deltaTime; + } lastTime = time; - joyUpdate(); - Core::stats.dips = 0; Core::stats.tris = 0; - - Game::update(); Game::render(); - SwapBuffers(hDC); if (fpsTime < getTime()) { @@ -232,6 +230,7 @@ int main() { } else fps++; } + } while (msg.message != WM_QUIT); Game::free(); freeGL(hRC);