diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index 1168379..0b255ee 100644 Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ diff --git a/src/cache.h b/src/cache.h index 670d756..ecde068 100644 --- a/src/cache.h +++ b/src/cache.h @@ -11,7 +11,7 @@ #define SHADOW_TEX_SIZE 1024 #define FOG_DIST (18 * 1024) -#define WATER_FOG_DIST (8 * 1024) +#define WATER_FOG_DIST (6 * 1024) //#define WATER_USE_GRID #define UNDERWATER_COLOR "#define UNDERWATER_COLOR vec3(0.6, 0.9, 0.9)\n" @@ -27,10 +27,6 @@ const char FILTER[] = #include "shaders/filter.glsl" ; -const char VOLUME[] = - #include "shaders/volume.glsl" -; - const char GUI[] = #include "shaders/gui.glsl" ; @@ -45,15 +41,15 @@ struct ShaderCache { memset(shaders, 0, sizeof(shaders)); LOG("shader: cache warm up...\n"); - if (Core::settings.shadows) + if (Core::settings.detail.shadows) compile(Core::passShadow, Shader::ENTITY, FX_NONE); - if (Core::settings.ambient) { + if (Core::settings.detail.ambient) { compile(Core::passAmbient, Shader::ROOM, FX_NONE); compile(Core::passAmbient, Shader::ROOM, FX_ALPHA_TEST); compile(Core::passAmbient, Shader::ROOM, FX_UNDERWATER); compile(Core::passAmbient, Shader::SPRITE, FX_ALPHA_TEST); - if (Core::settings.water) { + if (Core::settings.detail.water) { compile(Core::passAmbient, Shader::ROOM, FX_CLIP_PLANE); compile(Core::passAmbient, Shader::ROOM, FX_ALPHA_TEST | FX_CLIP_PLANE); compile(Core::passAmbient, Shader::ROOM, FX_UNDERWATER | FX_CLIP_PLANE); @@ -61,7 +57,7 @@ struct ShaderCache { } } - if (Core::settings.water) { + if (Core::settings.detail.water) { compile(Core::passWater, Shader::WATER_MASK, FX_NONE); compile(Core::passWater, Shader::WATER_STEP, FX_NONE); compile(Core::passWater, Shader::WATER_CAUSTICS, FX_NONE); @@ -83,7 +79,7 @@ struct ShaderCache { compile(Core::passCompose, Shader::SPRITE, FX_ALPHA_TEST); compile(Core::passCompose, Shader::SPRITE, FX_UNDERWATER | FX_ALPHA_TEST); compile(Core::passCompose, Shader::FLASH, FX_ALPHA_TEST); - if (Core::settings.water) { + if (Core::settings.detail.water) { compile(Core::passCompose, Shader::ROOM, FX_CLIP_PLANE); compile(Core::passCompose, Shader::ROOM, FX_ALPHA_TEST | FX_CLIP_PLANE); compile(Core::passCompose, Shader::ROOM, FX_UNDERWATER | FX_CLIP_PLANE); @@ -108,7 +104,7 @@ struct ShaderCache { Shader* compile(Core::Pass pass, Shader::Type type, int fx) { char def[1024], ext[255]; ext[0] = 0; - if (Core::settings.shadows) { + if (Core::settings.detail.shadows) { if (Core::support.shadowSampler) { #ifdef MOBILE strcat(ext, "#extension GL_EXT_shadow_samplers : require\n"); @@ -139,10 +135,10 @@ struct ShaderCache { if (fx & FX_UNDERWATER) strcat(def, "#define UNDERWATER\n" UNDERWATER_COLOR); if (fx & FX_ALPHA_TEST) strcat(def, "#define ALPHA_TEST\n"); if (fx & FX_CLIP_PLANE) strcat(def, "#define CLIP_PLANE\n"); - if (Core::settings.ambient) strcat(def, "#define OPT_AMBIENT\n"); - if (Core::settings.lighting) strcat(def, "#define OPT_LIGHTING\n"); - if (Core::settings.shadows) strcat(def, "#define OPT_SHADOW\n"); - if (Core::settings.water) strcat(def, "#define OPT_WATER\n"); + if (Core::settings.detail.ambient) strcat(def, "#define OPT_AMBIENT\n"); + if (Core::settings.detail.lighting) strcat(def, "#define OPT_LIGHTING\n"); + if (Core::settings.detail.shadows) strcat(def, "#define OPT_SHADOW\n"); + if (Core::settings.detail.water) strcat(def, "#define OPT_WATER\n"); break; } case Core::passWater : { @@ -162,13 +158,6 @@ struct ShaderCache { sprintf(def, "%s#define PASS_%s\n#define FILTER_%s\n", ext, passNames[pass], typ); break; } - case Core::passVolume : { - static const char *typeNames[] = { "DEFAULT" }; - src = VOLUME; - typ = typeNames[type]; - sprintf(def, "%s#define PASS_%s\n", ext, passNames[pass]); - break; - } case Core::passGUI : { static const char *typeNames[] = { "DEFAULT" }; src = GUI; @@ -421,6 +410,9 @@ struct WaterCache { caustics = new Texture(512, 512, Texture::RGB16, false); mask = new Texture(w, h, Texture::RGB16, false, m, false); delete[] m; + + Core::setTarget(data[0], true); + Core::setTarget(NULL); blank = false; @@ -582,7 +574,7 @@ struct WaterCache { while (item.timer >= SIMULATE_TIMESTEP) { // water step item.data[0]->bind(sDiffuse); - Core::setTarget(item.data[1], 0, true); + Core::setTarget(item.data[1], true); Core::setViewport(0, 0, int(item.size.x * DETAIL * 2.0f + 0.5f), int(item.size.z * DETAIL * 2.0f + 0.5f)); game->getMesh()->renderQuad(); Core::invalidateTarget(false, true); @@ -890,4 +882,4 @@ struct ZoneCache { #undef UNDERWATER_COLOR -#endif \ No newline at end of file +#endif diff --git a/src/character.h b/src/character.h index fb54a25..45635ed 100644 --- a/src/character.h +++ b/src/character.h @@ -5,7 +5,7 @@ #include "trigger.h" struct Character : Controller { - int health; + float health; float tilt; quat rotHead, rotChest; @@ -37,7 +37,7 @@ struct Character : Controller { Collision collision; - Character(IGame *game, int entity, int health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), velocity(0.0f), angleExt(0.0f) { + Character(IGame *game, int entity, float health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), velocity(0.0f), angleExt(0.0f) { animation.initOverrides(); rotHead = rotChest = quat(0, 0, 0, 1); @@ -68,8 +68,8 @@ struct Character : Controller { angle.x = clamp(angle.x + delta, -PI * 0.49f, PI * 0.49f); } - virtual void hit(int damage, Controller *enemy = NULL) { - health -= damage; + virtual void hit(float damage, Controller *enemy = NULL) { + health = max(0.0f, health - damage); }; virtual void checkRoom() { diff --git a/src/controller.h b/src/controller.h index c4a8a13..230d481 100644 --- a/src/controller.h +++ b/src/controller.h @@ -12,6 +12,8 @@ #define MAX_LAYERS 4 +#define UNLIMITED_AMMO 10000 + struct Controller; struct IGame { @@ -31,8 +33,10 @@ struct IGame { virtual void renderCompose(int roomIndex, bool genShadowMask = false) {} virtual void fxQuake(float time) {} - virtual bool invUse(TR::Entity::Type item, TR::Entity::Type slot) { return false; } + virtual bool invUse(TR::Entity::Type type) { return false; } virtual void invAdd(TR::Entity::Type type, int count = 1) {} + virtual int* invCount(TR::Entity::Type type) { return NULL; } + virtual bool invChooseKey(TR::Entity::Type hole) { return false; } virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const { return NULL; } }; diff --git a/src/core.h b/src/core.h index 4b908c4..6ee1269 100644 --- a/src/core.h +++ b/src/core.h @@ -308,7 +308,7 @@ namespace Core { Texture *blackTex, *whiteTex; - enum Pass { passCompose, passShadow, passAmbient, passWater, passFilter, passVolume, passGUI, passMAX } pass; + enum Pass { passCompose, passShadow, passAmbient, passWater, passFilter, passGUI, passMAX } pass; GLuint FBO, defaultFBO; Texture *defaultTarget; @@ -362,10 +362,16 @@ namespace Core { } stats; struct { - bool ambient; - bool lighting; - bool shadows; - bool water; + struct { + bool ambient; + bool lighting; + bool shadows; + bool water; + } detail; + + struct { + bool retarget; + } controls; } settings; } diff --git a/src/enemy.h b/src/enemy.h index 3aaf10e..7505cf8 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -72,7 +72,7 @@ struct Enemy : Character { bool targetFromView; // enemy in target view zone bool targetCanAttack; - Enemy(IGame *game, int entity, int health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), target(NULL), path(NULL) { + Enemy(IGame *game, int entity, float health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), target(NULL), path(NULL) { stepHeight = 256; dropHeight = -256; @@ -257,12 +257,12 @@ struct Enemy : Character { return 0; } - virtual void hit(int damage, Controller *enemy = NULL) { + virtual void hit(float damage, Controller *enemy = NULL) { Character::hit(damage, enemy); wound = true; }; - void bite(const vec3 &pos, int damage) { + void bite(const vec3 &pos, float damage) { ASSERT(target); target->hit(damage, this); Sprite::add(game, TR::Entity::BLOOD, target->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED); @@ -605,7 +605,7 @@ struct Wolf : Enemy { case STATE_ATTACK : case STATE_BITE : if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) { - bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_ATTACK ? 50 : 100); + bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_ATTACK ? 50.0f : 100.0f); nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL; } return state == STATE_ATTACK ? STATE_RUN : state; @@ -753,7 +753,7 @@ struct Bear : Enemy { case STATE_BITE : case STATE_ATTACK : if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) { - bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_BITE ? 200 : 400); + bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_BITE ? 200.0f : 400.0f); nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL; } break; diff --git a/src/format.h b/src/format.h index 429b28e..625694a 100644 --- a/src/format.h +++ b/src/format.h @@ -93,10 +93,10 @@ E( INV_PASSPORT_CLOSED ) \ E( INV_MAP ) \ E( CRYSTAL ) \ - E( WEAPON_PISTOLS ) \ - E( WEAPON_SHOTGUN ) \ - E( WEAPON_MAGNUMS ) \ - E( WEAPON_UZIS ) \ + E( PISTOLS ) \ + E( SHOTGUN ) \ + E( MAGNUMS ) \ + E( UZIS ) \ E( AMMO_PISTOLS ) \ E( AMMO_SHOTGUN ) \ E( AMMO_MAGNUMS ) \ @@ -393,12 +393,13 @@ namespace TR { Color32(uint8 r, uint8 g, uint8 b, uint8 a) : r(r), g(g), b(b), a(a) {} }; - struct Color24 { uint8 r, g, b; Color24() {} Color24(uint8 r, uint8 g, uint8 b) : r(r), g(g), b(b) {} + + operator Color32() const { return Color32(r, g, b, 255); } }; struct Color16 { @@ -629,17 +630,66 @@ namespace TR { return (type >= DOOR_1 && type <= DOOR_6) || type == DOOR_LIFT; } - int isItem() { - return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) || + bool isItem() { + return (type >= PISTOLS && type <= AMMO_UZIS) || (type >= PUZZLE_1 && type <= PUZZLE_4) || (type >= KEY_1 && type <= KEY_4) || (type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == SCION_1); // TODO: recheck all items } + bool isKeyHole() { + return type >= KEY_HOLE_1 && type <= KEY_HOLE_2; + } + bool isBlock() { return type >= TR::Entity::BLOCK_1 && type <= TR::Entity::BLOCK_2; } + static Type convToInv(Type type) { + switch (type) { + case PISTOLS : return INV_PISTOLS; + case SHOTGUN : return INV_SHOTGUN; + case MAGNUMS : return INV_MAGNUMS; + case UZIS : return INV_UZIS; + + case AMMO_PISTOLS : return INV_AMMO_PISTOLS; + case AMMO_SHOTGUN : return INV_AMMO_SHOTGUN; + case AMMO_MAGNUMS : return INV_AMMO_MAGNUMS; + case AMMO_UZIS : return INV_AMMO_UZIS; + + case MEDIKIT_SMALL : return INV_MEDIKIT_SMALL; + case MEDIKIT_BIG : return INV_MEDIKIT_BIG; + + case PUZZLE_1 : return INV_PUZZLE_1; + case PUZZLE_2 : return INV_PUZZLE_2; + case PUZZLE_3 : return INV_PUZZLE_3; + case PUZZLE_4 : return INV_PUZZLE_4; + + case KEY_1 : return INV_KEY_1; + case KEY_2 : return INV_KEY_2; + case KEY_3 : return INV_KEY_3; + case KEY_4 : return INV_KEY_4; + + case LEADBAR : return INV_LEADBAR; + //case TR::Entity::SCION : return TR::Entity::INV_SCION; + default : return type; + } + } + + static Type getKeyForHole(Type hole) { + switch (hole) { + case PUZZLE_HOLE_1 : return PUZZLE_1; break; + case PUZZLE_HOLE_2 : return PUZZLE_2; break; + case PUZZLE_HOLE_3 : return PUZZLE_3; break; + case PUZZLE_HOLE_4 : return PUZZLE_4; break; + case KEY_HOLE_1 : return KEY_1; break; + case KEY_HOLE_2 : return KEY_2; break; + case KEY_HOLE_3 : return KEY_3; break; + case KEY_HOLE_4 : return KEY_4; break; + default : return NONE; + } + } + static void fixOpaque(Type type, bool &opaque) { if (type >= LARA && type <= ENEMY_GIANT_MUTANT && type != ENEMY_REX @@ -984,7 +1034,7 @@ namespace TR { struct { int16 muzzleFlash; - int16 puzzleSet; + int16 puzzleDone[4]; int16 weapons[4]; int16 braid; @@ -1229,7 +1279,10 @@ namespace TR { for (int i = 0; i < modelsCount; i++) switch (models[i].type) { case Entity::MUZZLE_FLASH : extra.muzzleFlash = i; break; - case Entity::PUZZLE_DONE_1 : extra.puzzleSet = i; break; + case Entity::PUZZLE_DONE_1 : extra.puzzleDone[0] = i; break; + case Entity::PUZZLE_DONE_2 : extra.puzzleDone[1] = i; break; + case Entity::PUZZLE_DONE_3 : extra.puzzleDone[2] = i; break; + case Entity::PUZZLE_DONE_4 : extra.puzzleDone[3] = i; break; case Entity::LARA_PISTOLS : extra.weapons[0] = i; break; case Entity::LARA_SHOTGUN : extra.weapons[1] = i; break; case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break; diff --git a/src/game.h b/src/game.h index 05b41c9..6d88517 100644 --- a/src/game.h +++ b/src/game.h @@ -19,10 +19,12 @@ namespace Game { void init(Stream *lvl, Stream *snd) { Core::init(); - Core::settings.ambient = true; - Core::settings.lighting = true; - Core::settings.shadows = true; - Core::settings.water = Core::support.texFloat || Core::support.texHalf; + Core::settings.detail.ambient = true; + Core::settings.detail.lighting = true; + Core::settings.detail.shadows = true; + Core::settings.detail.water = Core::support.texFloat || Core::support.texHalf; + + Core::settings.controls.retarget = true; level = NULL; startLevel(lvl, snd, false, false); diff --git a/src/inventory.h b/src/inventory.h index 3b5c3e0..1c0897f 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -5,7 +5,7 @@ #include "controller.h" #include "ui.h" -#define INVENTORY_MAX_ITEMS 64 +#define INVENTORY_MAX_ITEMS 32 #define INVENTORY_MAX_RADIUS 688.0f #define INVENTORY_BG_SIZE 512 #define INVENTORY_HEIGHT 2048.0f @@ -37,7 +37,7 @@ struct Inventory { struct Desc { const char *name; - int page; + Page page; int model; } desc; @@ -45,42 +45,42 @@ struct Inventory { Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f) { switch (type) { - case TR::Entity::INV_PASSPORT : desc = { "Game", 0, level->extra.inv.passport }; break; - case TR::Entity::INV_PASSPORT_CLOSED : desc = { "Game", 0, level->extra.inv.passport_closed }; break; - case TR::Entity::INV_MAP : desc = { "Map", 1, level->extra.inv.map }; break; - case TR::Entity::INV_COMPASS : desc = { "Compass", 1, level->extra.inv.compass }; break; - case TR::Entity::INV_HOME : desc = { "Lara's Home", 0, level->extra.inv.home }; break; - case TR::Entity::INV_DETAIL : desc = { "Detail Levels", 0, level->extra.inv.detail }; break; - case TR::Entity::INV_SOUND : desc = { "Sound", 0, level->extra.inv.sound }; break; - case TR::Entity::INV_CONTROLS : desc = { "Controls", 0, level->extra.inv.controls }; break; - case TR::Entity::INV_GAMMA : desc = { "Gamma", 0, level->extra.inv.gamma }; break; + case TR::Entity::INV_PASSPORT : desc = { "Game", PAGE_OPTION, level->extra.inv.passport }; break; + case TR::Entity::INV_PASSPORT_CLOSED : desc = { "Game", PAGE_OPTION, level->extra.inv.passport_closed }; break; + case TR::Entity::INV_MAP : desc = { "Map", PAGE_INVENTORY, level->extra.inv.map }; break; + case TR::Entity::INV_COMPASS : desc = { "Compass", PAGE_INVENTORY, level->extra.inv.compass }; break; + case TR::Entity::INV_HOME : desc = { "Lara's Home", PAGE_OPTION, level->extra.inv.home }; break; + case TR::Entity::INV_DETAIL : desc = { "Detail Levels", PAGE_OPTION, level->extra.inv.detail }; break; + case TR::Entity::INV_SOUND : desc = { "Sound", PAGE_OPTION, level->extra.inv.sound }; break; + case TR::Entity::INV_CONTROLS : desc = { "Controls", PAGE_OPTION, level->extra.inv.controls }; break; + case TR::Entity::INV_GAMMA : desc = { "Gamma", PAGE_OPTION, level->extra.inv.gamma }; break; - case TR::Entity::INV_PISTOLS : desc = { "Pistols", 1, level->extra.inv.weapon[0] }; break; - case TR::Entity::INV_SHOTGUN : desc = { "Shotgun", 1, level->extra.inv.weapon[1] }; break; - case TR::Entity::INV_MAGNUMS : desc = { "Magnums", 1, level->extra.inv.weapon[2] }; break; - case TR::Entity::INV_UZIS : desc = { "Uzis", 1, level->extra.inv.weapon[3] }; break; + case TR::Entity::INV_PISTOLS : desc = { "Pistols", PAGE_INVENTORY, level->extra.inv.weapon[0] }; break; + case TR::Entity::INV_SHOTGUN : desc = { "Shotgun", PAGE_INVENTORY, level->extra.inv.weapon[1] }; break; + case TR::Entity::INV_MAGNUMS : desc = { "Magnums", PAGE_INVENTORY, level->extra.inv.weapon[2] }; break; + case TR::Entity::INV_UZIS : desc = { "Uzis", PAGE_INVENTORY, level->extra.inv.weapon[3] }; break; - case TR::Entity::INV_AMMO_PISTOLS : desc = { "Pistol Clips", 1, level->extra.inv.ammo[0] }; break; - case TR::Entity::INV_AMMO_SHOTGUN : desc = { "Shotgun Shells", 1, level->extra.inv.ammo[1] }; break; - case TR::Entity::INV_AMMO_MAGNUMS : desc = { "Magnum Clips", 1, level->extra.inv.ammo[2] }; break; - case TR::Entity::INV_AMMO_UZIS : desc = { "Uzi Clips", 1, level->extra.inv.ammo[3] }; break; + case TR::Entity::INV_AMMO_PISTOLS : desc = { "Pistol Clips", PAGE_INVENTORY, level->extra.inv.ammo[0] }; break; + case TR::Entity::INV_AMMO_SHOTGUN : desc = { "Shotgun Shells", PAGE_INVENTORY, level->extra.inv.ammo[1] }; break; + case TR::Entity::INV_AMMO_MAGNUMS : desc = { "Magnum Clips", PAGE_INVENTORY, level->extra.inv.ammo[2] }; break; + case TR::Entity::INV_AMMO_UZIS : desc = { "Uzi Clips", PAGE_INVENTORY, level->extra.inv.ammo[3] }; break; - case TR::Entity::INV_MEDIKIT_SMALL : desc = { "Small Medi Pack", 1, level->extra.inv.medikit[0] }; break; - case TR::Entity::INV_MEDIKIT_BIG : desc = { "Large Medi Pack", 1, level->extra.inv.medikit[1] }; break; + case TR::Entity::INV_MEDIKIT_SMALL : desc = { "Small Medi Pack", PAGE_INVENTORY, level->extra.inv.medikit[0] }; break; + case TR::Entity::INV_MEDIKIT_BIG : desc = { "Large Medi Pack", PAGE_INVENTORY, level->extra.inv.medikit[1] }; break; - case TR::Entity::INV_PUZZLE_1 : desc = { "Puzzle", 2, level->extra.inv.puzzle[0] }; break; - case TR::Entity::INV_PUZZLE_2 : desc = { "Puzzle", 2, level->extra.inv.puzzle[1] }; break; - case TR::Entity::INV_PUZZLE_3 : desc = { "Puzzle", 2, level->extra.inv.puzzle[2] }; break; - case TR::Entity::INV_PUZZLE_4 : desc = { "Puzzle", 2, level->extra.inv.puzzle[3] }; break; - - case TR::Entity::INV_KEY_1 : desc = { "Key", 2, level->extra.inv.key[0] }; break; - case TR::Entity::INV_KEY_2 : desc = { "Key", 2, level->extra.inv.key[1] }; break; - case TR::Entity::INV_KEY_3 : desc = { "Key", 2, level->extra.inv.key[2] }; break; - case TR::Entity::INV_KEY_4 : desc = { "Key", 2, level->extra.inv.key[3] }; break; - - case TR::Entity::INV_LEADBAR : desc = { "Lead Bar", 2, level->extra.inv.leadbar }; break; - case TR::Entity::INV_SCION : desc = { "Scion", 2, level->extra.inv.scion }; break; - default : desc = { "unknown", 2, -1 }; break; + case TR::Entity::INV_PUZZLE_1 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[0] }; break; + case TR::Entity::INV_PUZZLE_2 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[1] }; break; + case TR::Entity::INV_PUZZLE_3 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[2] }; break; + case TR::Entity::INV_PUZZLE_4 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[3] }; break; + + case TR::Entity::INV_KEY_1 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[0] }; break; + case TR::Entity::INV_KEY_2 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[1] }; break; + case TR::Entity::INV_KEY_3 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[2] }; break; + case TR::Entity::INV_KEY_4 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[3] }; break; + + case TR::Entity::INV_LEADBAR : desc = { "Lead Bar", PAGE_ITEMS, level->extra.inv.leadbar }; break; + case TR::Entity::INV_SCION : desc = { "Scion", PAGE_ITEMS, level->extra.inv.scion }; break; + default : desc = { "unknown", PAGE_ITEMS, -1 }; break; } if (desc.model > -1) { @@ -129,32 +129,30 @@ struct Inventory { if (anim) anim->setAnim(0, 0, false); } - } items[INVENTORY_MAX_ITEMS]; + } *items[INVENTORY_MAX_ITEMS]; Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0) { - TR::Level *level = game->getLevel(); - add(TR::Entity::INV_PASSPORT); add(TR::Entity::INV_DETAIL); add(TR::Entity::INV_SOUND); add(TR::Entity::INV_CONTROLS); + /* add(TR::Entity::INV_COMPASS); - if (level->extra.inv.map != -1) add(TR::Entity::INV_MAP); if (level->extra.inv.gamma != -1) add(TR::Entity::INV_GAMMA); + */ + add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO); + add(TR::Entity::INV_SHOTGUN, 10); + add(TR::Entity::INV_MAGNUMS, 10); + add(TR::Entity::INV_UZIS, 50); +// add(TR::Entity::INV_MEDIKIT_SMALL, 999); +// add(TR::Entity::INV_MEDIKIT_BIG, 999); - add(TR::Entity::INV_PISTOLS, 999); - add(TR::Entity::INV_SHOTGUN, 999); - add(TR::Entity::INV_MAGNUMS, 999); - add(TR::Entity::INV_UZIS, 999); - add(TR::Entity::INV_MEDIKIT_SMALL, 999); - add(TR::Entity::INV_MEDIKIT_BIG, 999); - - add(TR::Entity::INV_SCION, 1); - add(TR::Entity::INV_KEY_1, 1); - add(TR::Entity::INV_PUZZLE_1, 1); +// add(TR::Entity::INV_SCION, 1); +// add(TR::Entity::INV_KEY_1, 1); +// add(TR::Entity::INV_PUZZLE_1, 1); phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f; memset(pageItemIndex, 0, sizeof(pageItemIndex)); @@ -164,6 +162,9 @@ struct Inventory { } ~Inventory() { + for (int i = 0; i < itemsCount; i++) + delete items[i]; + for (int i = 0; i < COUNT(background); i++) delete background[i]; } @@ -173,16 +174,51 @@ struct Inventory { } int contains(TR::Entity::Type type) { + type = TR::Entity::convToInv(type); for (int i = 0; i < itemsCount; i++) - if (items[i].type == type) + if (items[i]->type == type) return i; return -1; } + void addAmmo(TR::Entity::Type &type, int &count, int clip, TR::Entity::Type wpnType, TR::Entity::Type ammoType) { + if (type == wpnType) { + count *= clip; + int index = contains(ammoType); + if (index > -1) { + count += items[index]->count * clip; + remove(index); + } + } else { + if (contains(wpnType) > -1) { + type = wpnType; + count *= clip; + } + } + } + void add(TR::Entity::Type type, int count = 1) { + type = TR::Entity::convToInv(type); + + switch (type) { + case TR::Entity::INV_SHOTGUN : + case TR::Entity::INV_AMMO_SHOTGUN : + addAmmo(type, count, 2, TR::Entity::INV_SHOTGUN, TR::Entity::INV_AMMO_SHOTGUN); + break; + case TR::Entity::INV_MAGNUMS : + case TR::Entity::INV_AMMO_MAGNUMS : + addAmmo(type, count, 25, TR::Entity::INV_MAGNUMS, TR::Entity::INV_AMMO_MAGNUMS); + break; + case TR::Entity::INV_UZIS : + case TR::Entity::INV_AMMO_UZIS : + addAmmo(type, count, 50, TR::Entity::INV_UZIS, TR::Entity::INV_AMMO_UZIS); + break; + default : ; + } + int i = contains(type); if (i > -1) { - items[i].count += count; + items[i]->count += count; return; } @@ -190,7 +226,7 @@ struct Inventory { int pos = 0; for (int pos = 0; pos < itemsCount; pos++) - if (items[pos].type > type) + if (items[pos]->type > type) break; if (pos - itemsCount) { @@ -198,40 +234,52 @@ struct Inventory { items[i] = items[i - 1]; } - Item it(game->getLevel(), type, count); - items[pos] = it; + items[pos] = new Item(game->getLevel(), type, count); itemsCount++; } - int getCount(TR::Entity::Type type) { + int* getCountPtr(TR::Entity::Type type) { int i = contains(type); - if (i < 0) return 0; - return items[i].count; + if (i < 0) return NULL; + return &items[i]->count; } void remove(TR::Entity::Type type, int count = 1) { - int i = contains(type); - if (i > -1) - items[i].count -= count; + int index = contains(type); + if (index == -1) return; + + items[index]->count -= count; + if (items[index]->count <= 0) + remove(index); } - bool use(TR::Entity::Type item, TR::Entity::Type slot) { - if (item == TR::Entity::NONE) { - switch (slot) { - case TR::Entity::KEY_HOLE_1 : item = TR::Entity::KEY_1; break; // TODO: 1-4 - case TR::Entity::PUZZLE_HOLE_1 : item = TR::Entity::PUZZLE_1; break; - default : return false; - } - } + void remove(int index) { + delete items[index]; + for (int i = index; i < itemsCount - 1; i++) + items[i] = items[i + 1]; + itemsCount--; + } + + bool chooseKey(TR::Entity::Type hole) { + TR::Entity::Type type = TR::Entity::getKeyForHole(hole); + if (type == TR::Entity::NONE) + return false; + int index = contains(type); + if (index < 0) + return false; + toggle(items[index]->desc.page, type); + return true; + } - if (getCount(item) > 0) { - remove(item); + bool use(TR::Entity::Type type) { + if (contains(type) > -1) { + remove(type); return true; } return false; } - bool toggle(Page curPage = PAGE_INVENTORY) { + bool toggle(Page curPage = PAGE_INVENTORY, TR::Entity::Type type = TR::Entity::NONE) { if (phaseRing == 0.0f || phaseRing == 1.0f) { active = !active; vec3 p; @@ -240,11 +288,18 @@ struct Inventory { if (active) { for (int i = 0; i < itemsCount; i++) - items[i].reset(); + items[i]->reset(); phasePage = 1.0f; phaseSelect = 1.0f; page = targetPage = curPage; + + if (type != TR::Entity::NONE) { + int i = contains(type); + if (i >= 0) + pageItemIndex[page] = getItemIndex(page, i); + } + index = targetIndex = pageItemIndex[page]; } } @@ -269,7 +324,7 @@ struct Inventory { int getItemIndex(Page page, int index) { for (int i = 0; i < itemsCount; i++) - if (items[i].desc.page == page) { + if (items[i]->desc.page == page) { if (!index) return i; index--; @@ -277,8 +332,29 @@ struct Inventory { return 0; } + float getAngle(int index, int count) { + return PI * 2.0f / float(count) * index; + } + + int getItemsCount(int page) { + int count = 0; + + for (int i = 0; i < itemsCount; i++) + if (items[i]->desc.page == page) + count++; + + return count; + } + + bool showHealthBar() { + int idx = getItemIndex(page, index); + TR::Entity::Type type = items[idx]->type; + return active && phaseRing == 1.0f && index == targetIndex && phasePage == 1.0f && (type == TR::Entity::INV_MEDIKIT_SMALL || type == TR::Entity::INV_MEDIKIT_BIG); + } + void update() { - doPhase(active, 2.0f, phaseRing); + if (phaseChoose == 0.0f) + doPhase(active, 2.0f, phaseRing); doPhase(true, 1.6f, phasePage); doPhase(chosen, 4.0f, phaseChoose); doPhase(true, 2.5f, phaseSelect); @@ -309,15 +385,15 @@ struct Inventory { vec3 p; - Item &item = items[getItemIndex(page, index)]; + Item *item = items[getItemIndex(page, index)]; if (index == targetIndex && ready) { - if (Input::state[cAction] && (phaseChoose == 0.0f || (phaseChoose == 1.0f && item.anim->isEnded))) { + if (Input::state[cAction] && (phaseChoose == 0.0f || (phaseChoose == 1.0f && item->anim->isEnded))) { chosen = !chosen; if (!chosen) { - item.angle = 0.0f; + item->angle = 0.0f; } else { - switch (item.type) { + switch (item->type) { case TR::Entity::INV_COMPASS : game->playSound(TR::SND_INV_COMPASS, p, 0, 0); break; case TR::Entity::INV_HOME : game->playSound(TR::SND_INV_HOME, p, 0, 0); break; case TR::Entity::INV_CONTROLS : game->playSound(TR::SND_INV_CONTROLS, p, 0, 0); break; @@ -327,18 +403,18 @@ struct Inventory { case TR::Entity::INV_UZIS : game->playSound(TR::SND_INV_WEAPON, p, 0, 0); break; default : game->playSound(TR::SND_INV_SHOW, p, 0, 0); break; } - item.choose(); + item->choose(); } } } float w = 90.0f * DEG2RAD * Core::deltaTime; - int itemIndex = getItemIndex(page, index); + int itemIndex = index == targetIndex ? getItemIndex(page, index) : -1; for (int i = 0; i < itemsCount; i++) { - items[i].update(); - float &angle = items[i].angle; + items[i]->update(); + float &angle = items[i]->angle; if (itemIndex != i || chosen) { if (angle == 0.0f) { @@ -358,16 +434,27 @@ struct Inventory { angle = clampAngle(angle); } - if (ready && chosen && phaseChoose == 1.0f && item.anim->isEnded) { - TR::Entity::Type type = item.type; + if (ready && chosen && phaseChoose == 1.0f && item->anim->isEnded) { + TR::Entity::Type type = item->type; - if (type == TR::Entity::INV_PISTOLS || type == TR::Entity::INV_SHOTGUN || type == TR::Entity::INV_MAGNUMS || type == TR::Entity::INV_UZIS || - type == TR::Entity::INV_MEDIKIT_SMALL || type == TR::Entity::INV_MEDIKIT_BIG) { - - game->invUse(type, TR::Entity::NONE); - toggle(); + switch (type) { + case TR::Entity::INV_PASSPORT : + case TR::Entity::INV_PASSPORT_CLOSED : + case TR::Entity::INV_MAP : + case TR::Entity::INV_COMPASS : + case TR::Entity::INV_HOME : + case TR::Entity::INV_DETAIL : + case TR::Entity::INV_SOUND : + case TR::Entity::INV_CONTROLS : + case TR::Entity::INV_GAMMA : + case TR::Entity::INV_AMMO_PISTOLS : + case TR::Entity::INV_AMMO_SHOTGUN : + case TR::Entity::INV_AMMO_MAGNUMS : + case TR::Entity::INV_AMMO_UZIS : break; + default : + game->invUse(type); + toggle(); } - } } @@ -400,38 +487,41 @@ struct Inventory { Core::setDepthTest(true); } - void renderItemText(const Item &item, float width) { - UI::textOut(game, vec2(0, 480 - 16), item.desc.name, UI::aCenter, width); - - if (item.count > 1) { - char spec; - switch (item.type) { - case TR::Entity::INV_SHOTGUN : spec = 12; break; - case TR::Entity::INV_MAGNUMS : spec = 13; break; - case TR::Entity::INV_UZIS : spec = 14; break; - default : spec = 0; - } + void renderItemCount(const Item *item, const vec2 &pos, float width) { + char spec; + switch (item->type) { + case TR::Entity::INV_SHOTGUN : spec = 12; break; + case TR::Entity::INV_MAGNUMS : spec = 13; break; + case TR::Entity::INV_UZIS : spec = 14; break; + default : spec = 0; + } + if ((item->count > 1 || spec) && item->count < UNLIMITED_AMMO) { char buf[16]; - sprintf(buf, "%d %c", item.count, spec); + sprintf(buf, "%d %c", item->count, spec); for (int i = 0; buf[i] != ' '; i++) buf[i] -= 47; - UI::textOut(game, vec2(width / 2 - 160, 480 - 96), buf, UI::aRight, 320); + UI::textOut(pos, buf, UI::aRight, width); } } - float getAngle(int index, int count) { - return PI * 2.0f / float(count) * index; - } + void renderItemText(const Item *item, float width) { + UI::textOut(vec2(0, 480 - 16), item->desc.name, UI::aCenter, width); + renderItemCount(item, vec2(width / 2 - 160, 480 - 96), 320); - int getItemsCount(int page) { - int count = 0; - - for (int i = 0; i < itemsCount; i++) - if (items[i].desc.page == page) - count++; - - return count; + if (phaseChoose == 1.0f) { + if (item->type == TR::Entity::INV_PASSPORT || + item->type == TR::Entity::INV_MAP || + item->type == TR::Entity::INV_COMPASS || + item->type == TR::Entity::INV_HOME || + item->type == TR::Entity::INV_DETAIL || + item->type == TR::Entity::INV_SOUND || + item->type == TR::Entity::INV_CONTROLS || + item->type == TR::Entity::INV_GAMMA) + { + UI::textOut(vec2(0, 240), "Not implemented yet!", UI::aCenter, width); + } + } } void renderPage(int page) { @@ -456,26 +546,27 @@ struct Inventory { int itemIndex = 0; for (int i = 0; i < itemsCount; i++) { - Item &item = items[i]; + Item *item = items[i]; - if (item.desc.page != page) + if (item->desc.page != page) continue; float a = getAngle(itemIndex, count) - angle - collapseAngle; - float ia = item.angle; + float ia = item->angle; float ra = ringTilt; float rd = radius; + float rh = ringHeight; if (itemIndex == pageItemIndex[page] && (chosen || phaseChoose > 0.0f)) { ia *= 1.0f - phaseChoose; - ra *= 1.0f - phaseChoose; + rh -= 128 * phaseChoose; rd += 296 * phaseChoose; } Basis basis = Basis(quat(vec3(1, 0, 0), ra), vec3(0.0f)); - basis = basis * Basis(quat(vec3(0, 1, 0), PI + ia - a), vec3(sinf(a), 0, -cosf(a)) * rd - vec3(0, item.desc.page * INVENTORY_HEIGHT - ringHeight, 0)); + basis = basis * Basis(quat(vec3(0, 1, 0), PI + ia - a), vec3(sinf(a), 0, -cosf(a)) * rd - vec3(0, item->desc.page * INVENTORY_HEIGHT - rh, 0)); - item.render(game, basis); + item->render(game, basis); itemIndex++; } @@ -531,46 +622,27 @@ struct Inventory { renderPage(page); if (page != targetPage) renderPage(targetPage); + } - if (phaseRing < 1.0f) - return; - - Core::setDepthTest(false); - Core::setBlending(bmAlpha); - Core::setCulling(cfNone); - game->setupBinding(); - - float w = 480 * aspect; - Core::mViewProj = mat4(0.0f, w, 480, 0.0f, 0.0f, 1.0f); - - game->setShader(Core::passGUI, Shader::DEFAULT); - Core::active.shader->setParam(uMaterial, vec4(1.0f)); - Core::active.shader->setParam(uPosScale, vec4(0.0f, 0.0f, 1.0f, 1.0f)); - - UI::textBegin(); + void renderUI() { + if (!active || phaseRing < 1.0f) return; static const char* pageTitle[PAGE_MAX] = { "OPTION", "INVENTORY", "ITEMS" }; - UI::textOut(game, vec2( 0, 32), pageTitle[page], UI::aCenter, w); + UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width); if (page < PAGE_ITEMS && getItemsCount(page + 1)) { - UI::textOut(game, vec2(16, 32), "\x5B", UI::aLeft, w); - UI::textOut(game, vec2( 0, 32), "\x5B", UI::aRight, w - 20); + UI::textOut(vec2(16, 32), "[", UI::aLeft, UI::width); + UI::textOut(vec2( 0, 32), "[", UI::aRight, UI::width - 20); } if (page > PAGE_OPTION && getItemsCount(page - 1)) { - UI::textOut(game, vec2(16, 480 - 16), "\x5D", UI::aLeft, w); - UI::textOut(game, vec2(0, 480 - 16), "\x5D", UI::aRight, w - 20); + UI::textOut(vec2(16, 480 - 16), "]", UI::aLeft, UI::width); + UI::textOut(vec2(0, 480 - 16), "]", UI::aRight, UI::width - 20); } if (index == targetIndex) - renderItemText(items[getItemIndex(page, index)], w); - - UI::textEnd(game); - - Core::setCulling(cfFront); - Core::setBlending(bmNone); - Core::setDepthTest(true); + renderItemText(items[getItemIndex(page, index)], UI::width); } }; diff --git a/src/lara.h b/src/lara.h index efe94e2..a80352a 100644 --- a/src/lara.h +++ b/src/lara.h @@ -20,7 +20,8 @@ #define LARA_TILT_SPEED (DEG2RAD * 37.5f) #define LARA_TILT_MAX (DEG2RAD * 10.0f) -#define LARA_MAX_HEALTH 1000 +#define LARA_MAX_HEALTH 1000.0f +#define LARA_MAX_OXYGEN 60.0f #define LARA_HANG_OFFSET 724 #define LARA_HEIGHT 762 @@ -36,6 +37,8 @@ #define LARA_WET_SPECULAR 0.5f #define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec +#define LARA_DAMAGE_TIME (40.0f / 30.0f) + #define PICKUP_FRAME_GROUND 40 #define PICKUP_FRAME_UNDERWATER 18 #define PUZZLE_FRAME 80 @@ -189,18 +192,18 @@ struct Lara : Character { }; bool home; + bool dozy; struct Weapon { enum Type { EMPTY = -1, PISTOLS, SHOTGUN, MAGNUMS, UZIS, MAX }; enum State { IS_HIDDEN, IS_ARMED, IS_FIRING }; enum Anim { NONE, PREPARE, UNHOLSTER, HOLSTER, HOLD, AIM, FIRE }; - - int ammo; // if -1 weapon is not available - } weapons[Weapon::MAX]; + }; Weapon::Type wpnCurrent; Weapon::Type wpnNext; Weapon::State wpnState; + int *wpnAmmo; vec3 chestOffset; struct Arm { @@ -214,11 +217,16 @@ struct Lara : Character { ActionCommand actionList[MAX_TRIGGER_ACTIONS]; - int lastPickUp; + TR::Entity::Type usedKey; + TR::Entity *puzzleEntity; + TR::Entity *pickupEntity; + int viewTarget; int roomPrev; // water out from room vec2 rotFactor; + float oxygen; + float damageTime; float hitTime; int hitDir; vec3 collisionOffset; @@ -384,7 +392,7 @@ struct Lara : Character { } *braid; - Lara(IGame *game, int entity, bool home) : Character(game, entity, LARA_MAX_HEALTH), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), viewTarget(-1), braid(NULL) { + Lara(IGame *game, int entity, bool home) : Character(game, entity, LARA_MAX_HEALTH), home(home), dozy(false), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), viewTarget(-1), braid(NULL) { if (getEntity().type == TR::Entity::LARA) { if (getRoom().flags.water) @@ -393,24 +401,17 @@ struct Lara : Character { animation.setAnim(ANIM_STAND); } - hitDir = -1; - hitTime = 0.0f; + oxygen = LARA_MAX_OXYGEN; + hitDir = -1; + damageTime = LARA_DAMAGE_TIME; + hitTime = 0.0f; getEntity().flags.active = 1; initMeshOverrides(); - weapons[Weapon::PISTOLS].ammo = -1; - weapons[Weapon::SHOTGUN].ammo = -1; - weapons[Weapon::MAGNUMS].ammo = -1; - weapons[Weapon::UZIS ].ammo = -1; - - if (!home) { - weapons[Weapon::PISTOLS].ammo = 0; - weapons[Weapon::SHOTGUN].ammo = 9000; - weapons[Weapon::MAGNUMS].ammo = 9000; - weapons[Weapon::UZIS ].ammo = 9000; + if (!home) wpnSet(Weapon::PISTOLS); - } else + else meshSwap(1, TR::MODEL_LARA_SPEC, BODY_UPPER | BODY_LOWER); for (int i = 0; i < 2; i++) { @@ -431,7 +432,7 @@ struct Lara : Character { //reset(43, vec3(31400, -2560, 25200), PI); // level 2 (reach) //reset(16, vec3(60907, 0, 39642), PI * 3 / 2); // level 2 (hang & climb) //reset(19, vec3(60843, 1024, 30557), PI); // level 2 (block) - //reset(1, vec3(62630, -1280, 19633), 0); // level 2 (dark medikit) + reset(1, vec3(62630, -1280, 19633), 0); // level 2 (dark medikit) //reset(7, vec3(64108, -512, 16514), -PI * 0.5f); // level 2 (bat trigger) //reset(15, vec3(70082, -512, 26935), PI * 0.5f); // level 2 (bear) //reset(63, vec3(31390, -2048, 33472), 0.0f); // level 2 (trap floor) @@ -440,7 +441,7 @@ struct Lara : Character { //reset(5, vec3(38643, -3072, 92370), PI * 0.5f); // level 3a (gears) //reset(43, vec3(64037, 6656, 48229), PI); // level 3b (movingblock) //reset(0, vec3(40913, -1012, 42252), PI); // level 8c - //reset(10, vec3(90443, 11264 - 256, 114614), PI, true); // villa mortal 2 + //reset(10, vec3(90443, 11264 - 256, 114614), PI, STAND_ONWATER); // villa mortal 2 #endif chestOffset = animation.getJoints(getMatrix(), 7).pos; } @@ -464,7 +465,7 @@ struct Lara : Character { return TR::NO_ROOM; } - void reset(int room, const vec3 &pos, float angle, bool onwater = false) { + void reset(int room, const vec3 &pos, float angle, Stand forceStand = STAND_GROUND) { if (room == TR::NO_ROOM) { stand = STAND_AIR; room = getRoomByPos(pos); @@ -483,18 +484,38 @@ struct Lara : Character { getEntity().room = room; this->pos = pos; this->angle = vec3(0.0f, angle, 0.0f); - if (onwater) { - stand = STAND_ONWATER; - animation.setAnim(ANIM_TO_ONWATER); + + if (forceStand != STAND_GROUND) { + stand = forceStand; + switch (stand) { + case STAND_ONWATER : animation.setAnim(ANIM_TO_ONWATER); break; + case STAND_UNDERWATER : animation.setAnim(ANIM_UNDERWATER); break; + default : ; + } } + updateEntity(); updateLights(false); } + TR::Entity::Type getCurrentWeaponInv() { + switch (wpnCurrent) { + case Weapon::Type::PISTOLS : return TR::Entity::PISTOLS; + case Weapon::Type::SHOTGUN : return TR::Entity::SHOTGUN; + case Weapon::Type::MAGNUMS : return TR::Entity::MAGNUMS; + case Weapon::Type::UZIS : return TR::Entity::UZIS; + default : return TR::Entity::NONE; + } + } + void wpnSet(Weapon::Type wType) { wpnCurrent = wType; wpnState = Weapon::IS_FIRING; + TR::Entity::Type invType = getCurrentWeaponInv(); + + wpnAmmo = game->invCount(invType); + arms[0].animation = arms[1].animation = Animation(level, &level->models[wType == Weapon::SHOTGUN ? TR::MODEL_SHOTGUN : TR::MODEL_PISTOLS]); wpnSetAnim(arms[0], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); @@ -520,7 +541,7 @@ struct Lara : Character { wpnSetState(wState); } - int wpnGetDamage() { + float wpnGetDamage() { switch (wpnCurrent) { case Weapon::PISTOLS : return 1; case Weapon::SHOTGUN : return 1; @@ -569,7 +590,7 @@ struct Lara : Character { if (wpnCurrent != Weapon::SHOTGUN) { meshSwap(1, level->extra.weapons[wpnCurrent], mask); // have a shotgun in inventory place it on the back if another weapon is in use - meshSwap(2, level->extra.weapons[Weapon::SHOTGUN], (weapons[Weapon::SHOTGUN].ammo != -1) ? BODY_CHEST : 0); + meshSwap(2, level->extra.weapons[Weapon::SHOTGUN], game->invCount(TR::Entity::INV_SHOTGUN) ? BODY_CHEST : 0); } else { meshSwap(2, level->extra.weapons[wpnCurrent], mask); } @@ -594,6 +615,8 @@ struct Lara : Character { } bool canDrawWeapon() { + if (dozy) return true; + return wpnCurrent != Weapon::EMPTY && emptyHands() && animation.index != ANIM_CLIMB_3 @@ -748,7 +771,7 @@ struct Lara : Character { float nearDist = 32.0f * 1024.0f; vec3 nearPos; - bool hasShot = false; + int shots = 0; for (int i = 0; i < count; i++) { int armIndex; @@ -761,8 +784,15 @@ struct Lara : Character { } Arm *arm = &arms[armIndex]; + if (wpnAmmo && *wpnAmmo != UNLIMITED_AMMO) { + if (*wpnAmmo <= 0) + continue; + if (wpnCurrent != Weapon::SHOTGUN) + *wpnAmmo -= 1; + } + arm->shotTimer = 0.0f; - hasShot = true; + shots++; int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); @@ -791,9 +821,16 @@ struct Lara : Character { Core::lightColor[1 + armIndex] = FLASH_LIGHT_COLOR; } - if (hasShot) { + if (shots) { playSound(wpnGetSound(), pos, Sound::Flags::PAN); playSound(TR::SND_RICOCHET, nearPos, Sound::Flags::PAN); + + if (wpnAmmo && *wpnAmmo != UNLIMITED_AMMO && wpnCurrent == Weapon::SHOTGUN) + *wpnAmmo -= 1; + } + + if (wpnAmmo && *wpnAmmo != UNLIMITED_AMMO && *wpnAmmo <= 0) { + wpnChange(Weapon::PISTOLS); } } @@ -1059,8 +1096,18 @@ struct Lara : Character { return; } + // auto retarget + bool retarget = false; + if (Core::settings.controls.retarget) { + for (int i = 0; i < 2; i++) + if (arms[i].tracking == -1 || ((Character*)level->entities[arms[i].tracking].controller)->health <= 0.0f) { + retarget = true; + break; + } + } + int count = wpnCurrent != Weapon::SHOTGUN ? 2 : 1; - if (!(input & ACTION)) { + if (!(input & ACTION) || retarget) { getTargets(arms[0].tracking, arms[1].tracking); if (count == 1) arms[1].tracking = -1; @@ -1219,8 +1266,12 @@ struct Lara : Character { } } - virtual void hit(int damage, Controller *enemy = NULL) { - health -= damage; + virtual void hit(float damage, Controller *enemy = NULL) { + if (dozy) return; + + damageTime = LARA_DAMAGE_TIME; + + Character::hit(damage, enemy); if (damage == 10000) { // T-Rex attack (fatal) pos = enemy->pos; angle = enemy->angle; @@ -1236,7 +1287,7 @@ struct Lara : Character { Core::lightColor[1 + 0] = Core::lightColor[1 + 1] = vec4(0, 0, 0, 1); }; - void useItem(TR::Entity::Type item) { + bool useItem(TR::Entity::Type item) { switch (item) { case TR::Entity::INV_PISTOLS : wpnChange(Lara::Weapon::PISTOLS); break; case TR::Entity::INV_SHOTGUN : wpnChange(Lara::Weapon::SHOTGUN); break; @@ -1244,11 +1295,25 @@ struct Lara : Character { case TR::Entity::INV_UZIS : wpnChange(Lara::Weapon::UZIS); break; case TR::Entity::INV_MEDIKIT_SMALL : case TR::Entity::INV_MEDIKIT_BIG : + damageTime = LARA_DAMAGE_TIME; health = min(LARA_MAX_HEALTH, health + (item == TR::Entity::INV_MEDIKIT_SMALL ? LARA_MAX_HEALTH / 2 : LARA_MAX_HEALTH)); playSound(TR::SND_HEALTH, pos, Sound::PAN); break; - default : ; + case TR::Entity::INV_PUZZLE_1 : + case TR::Entity::INV_PUZZLE_2 : + case TR::Entity::INV_PUZZLE_3 : + case TR::Entity::INV_PUZZLE_4 : + case TR::Entity::INV_KEY_1 : + case TR::Entity::INV_KEY_2 : + case TR::Entity::INV_KEY_3 : + case TR::Entity::INV_KEY_4 : + if (usedKey == item) + return false; + usedKey = item; + break; + default : return false; } + return true; } bool waterOut() { @@ -1309,7 +1374,7 @@ struct Lara : Character { if (stand == STAND_UNDERWATER) angle.x = -25 * DEG2RAD; - lastPickUp = i; + pickupEntity = &item; return true; } } @@ -1384,19 +1449,33 @@ struct Lara : Character { } break; case TR::Level::Trigger::KEY : - if (level->entities[info.trigCmd[0].args].flags.active) + if (level->entities[info.trigCmd[0].args].flags.active || state != STATE_STOP) return; - actionState = level->entities[info.trigCmd[0].args].type == TR::Entity::KEY_HOLE_1 ? STATE_USE_KEY : STATE_USE_PUZZLE; + + actionState = level->entities[info.trigCmd[0].args].isKeyHole() ? STATE_USE_KEY : STATE_USE_PUZZLE; if (!animation.canSetState(actionState)) return; - limit = actionState == STATE_USE_KEY ? &TR::Limits::KEY_HOLE : &TR::Limits::PUZZLE_HOLE; - if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION))) - return; - if (!game->invUse(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) { - playSound(TR::SND_NO, pos, Sound::PAN); + + limit = actionState == STATE_USE_PUZZLE ? &TR::Limits::PUZZLE_HOLE : &TR::Limits::KEY_HOLE; + if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION) || usedKey != TR::Entity::NONE)) + return; + + if (!animation.canSetState(actionState)) + return; + + if (usedKey == TR::Entity::NONE) { + if (isPressed(ACTION) && !game->invChooseKey(level->entities[info.trigCmd[0].args].type)) + playSound(TR::SND_NO, pos, Sound::PAN); // no compatible items in inventory return; } - lastPickUp = info.trigCmd[0].args; // TODO: it's not pickup, it's key/puzzle hole + + if (TR::Entity::convToInv(TR::Entity::getKeyForHole(level->entities[info.trigCmd[0].args].type)) != usedKey) { // check compatibility if user select other + playSound(TR::SND_NO, pos, Sound::PAN); // uncompatible item + return; + } + + puzzleEntity = actionState == STATE_USE_PUZZLE ? &level->entities[info.trigCmd[0].args] : NULL; + game->invUse(usedKey); break; case TR::Level::Trigger::PICKUP : if (!isActive) // check if item is not picked up @@ -1460,6 +1539,8 @@ struct Lara : Character { } virtual Stand getStand() { + if (dozy) return STAND_UNDERWATER; + if (state == STATE_HANG || state == STATE_HANG_LEFT || state == STATE_HANG_RIGHT) { if (input & ACTION) return STAND_HANG; @@ -1866,7 +1947,7 @@ struct Lara : Character { virtual int getStateDeath() { velocity = vec3(0.0f); - return STATE_DEATH; + return (stand == STAND_UNDERWATER || stand == STAND_ONWATER) ? STATE_UNDERWATER_DEATH : STATE_DEATH; } virtual int getStateDefault() { @@ -1883,6 +1964,19 @@ struct Lara : Character { virtual int getInput() { // TODO: updateInput if (level->cutEntity > -1) return 0; + input = 0; + + if (!dozy && Input::state[cAction] && Input::state[cJump] && Input::state[cLook] && Input::state[cStepRight]) { + dozy = true; + reset(getRoomIndex(), pos - vec3(0, 512, 0), angle.y, STAND_UNDERWATER); + return input; + } + + if (dozy && Input::state[cWalk]) { + dozy = false; + return input; + } + input = Character::getInput(); if (input & DEATH) return input; @@ -1931,20 +2025,22 @@ struct Lara : Character { virtual void doCustomCommand(int curFrame, int prevFrame) { switch (state) { case STATE_PICK_UP : { - TR::Entity &item = level->entities[lastPickUp]; - if (!item.flags.invisible) { + if (pickupEntity && !pickupEntity->flags.invisible) { int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; if (animation.isFrameActive(pickupFrame)) { - item.flags.invisible = true; - game->invAdd(item.type, 1); + pickupEntity->flags.invisible = true; + game->invAdd(pickupEntity->type, 1); + pickupEntity = NULL; } } break; } case STATE_USE_PUZZLE : { - TR::Entity &item = level->entities[lastPickUp]; - if (animation.isFrameActive(PUZZLE_FRAME)) - ((Controller*)item.controller)->meshSwap(0, level->extra.puzzleSet); + if (puzzleEntity && animation.isFrameActive(PUZZLE_FRAME)) { + int doneIdx = TR::Entity::convToInv(TR::Entity::getKeyForHole(puzzleEntity->type)) - TR::Entity::INV_PUZZLE_1; + ((Controller*)puzzleEntity->controller)->meshSwap(0, level->extra.puzzleDone[doneIdx]); + puzzleEntity = NULL; + } break; } } @@ -1952,6 +2048,20 @@ struct Lara : Character { virtual void update() { Character::update(); + + if (damageTime > 0.0f) + damageTime = max(0.0f, damageTime - Core::deltaTime); + + if (stand == STAND_UNDERWATER && !dozy) { + if (oxygen > 0.0f) + oxygen -= Core::deltaTime; + else + hit(Core::deltaTime * 150.0f); + } else + if (oxygen < LARA_MAX_OXYGEN) + oxygen = min(LARA_MAX_OXYGEN, oxygen += Core::deltaTime * 10.0f); + + usedKey = TR::Entity::NONE; } virtual void updateAnimation(bool commands) { @@ -2317,7 +2427,9 @@ struct Lara : Character { } updateEntity(); + if (dozy) stand = STAND_GROUND; checkRoom(); + if (dozy) stand = STAND_UNDERWATER; } virtual void applyFlow(TR::Camera &sink) { diff --git a/src/level.h b/src/level.h index 896765e..4253331 100644 --- a/src/level.h +++ b/src/level.h @@ -127,15 +127,24 @@ struct Level : IGame { camera->shake = time; } - virtual bool invUse(TR::Entity::Type item, TR::Entity::Type slot) { - lara->useItem(item); - return inventory.use(item, slot); + virtual bool invUse(TR::Entity::Type type) { + if (!lara->useItem(type)) + return inventory.use(type); + return true; } virtual void invAdd(TR::Entity::Type type, int count) { inventory.add(type, count); } + virtual int* invCount(TR::Entity::Type type) { + return inventory.getCountPtr(type); + } + + virtual bool invChooseKey(TR::Entity::Type hole) { + return inventory.chooseKey(hole); + } + virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const { if (level.version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET) return NULL; @@ -295,10 +304,10 @@ struct Level : IGame { level.cameraController = camera; - ambientCache = Core::settings.ambient ? new AmbientCache(this) : NULL; - waterCache = Core::settings.water ? new WaterCache(this) : NULL; + ambientCache = Core::settings.detail.ambient ? new AmbientCache(this) : NULL; + waterCache = Core::settings.detail.water ? new WaterCache(this) : NULL; zoneCache = new ZoneCache(this); - shadow = Core::settings.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL; + shadow = Core::settings.detail.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL; initReflections(); @@ -379,11 +388,29 @@ struct Level : IGame { int i = y * 1024 + x; data[i].r = data[i].g = data[i].b = data[i].a = 255; // white texel for colored triangles } -/* + +// uint32 healthBar[1+5+1] = { 0xFF2C5C70, 0xFF2C5C70, 0xFF4878A4, 0xFF2C5C70, 0xFF004458, 0xFF143050, 0xFF143050 }; + uint32 healthBar[1+5+1] = { 0xFF2C5D71, 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F, 0xFF16304F }; + + for (int y = 0; y < COUNT(healthBar); y++) + for (int x = 0; x < 2; x++) { + int i = (TEX_HEALTH_BAR_Y + y) * 1024 + (TEX_HEALTH_BAR_X + x); + *((uint32*)&data[i]) = healthBar[y]; + } + + uint32 oxygenBar[1+5+1] = { 0xFF647464, 0xFF647464, 0xFFA47848, 0xFF647464, 0xFF4C504C, 0xFF303030, 0xFF303030 }; + for (int y = 0; y < COUNT(oxygenBar); y++) + for (int x = 0; x < 2; x++) { + int i = (TEX_OXYGEN_BAR_Y + y) * 1024 + (TEX_OXYGEN_BAR_X + x); + *((uint32*)&data[i]) = oxygenBar[y]; + } + + + /* FILE *f = fopen("atlas.raw", "wb"); fwrite(data, 1024 * 1024 * 4, 1, f); fclose(f); -*/ + */ atlas = new Texture(1024, 1024, Texture::RGBA, false, data); PROFILE_LABEL(TEXTURE, atlas->ID, "atlas"); @@ -590,7 +617,7 @@ struct Level : IGame { if (isModel) { // model vec3 pos = controller->getPos(); - if (Core::settings.ambient) { + if (Core::settings.detail.ambient) { AmbientCache::Cube cube; if (Core::stats.frame != controller->frameIndex) { ambientCache->getAmbient(entity.room, pos, cube); @@ -614,8 +641,8 @@ struct Level : IGame { void update() { #ifdef LEVEL_EDITOR - if (Input::down[ikCtrl]) { - Input::down[ikCtrl] = false; + if (Input::down[ik0]) { + Input::down[ik0] = false; lara->reset(TR::NO_ROOM, camera->pos, camera->angle.y, false); } #endif @@ -647,11 +674,9 @@ struct Level : IGame { sndCurrent = camera->isUnderwater() ? sndUnderwater : sndSoundtrack; - if (sndCurrent) { - if (sndSoundtrack && sndCurrent != sndSoundtrack) sndSoundtrack->volume = 0.0f; - if (sndUnderwater && sndCurrent != sndUnderwater) sndUnderwater->volume = 0.0f; - sndCurrent->volume = 1.0f; - } + if (sndSoundtrack && sndCurrent != sndSoundtrack) sndSoundtrack->volume = 0.0f; + if (sndUnderwater && sndCurrent != sndUnderwater) sndUnderwater->volume = 0.0f; + if (sndCurrent) sndCurrent->volume = 1.0f; if (waterCache) waterCache->update(); @@ -949,6 +974,40 @@ struct Level : IGame { inventory.render(); } + void renderUI() { + UI::begin(); + + // render health & oxygen bars + vec2 size = vec2(180, 10); + + float health = lara->health / float(LARA_MAX_HEALTH); + float oxygen = lara->oxygen / float(LARA_MAX_OXYGEN); + + if ((params->time - int(params->time)) < 0.5f) { // blinking + if (health <= 0.2f) health = 0.0f; + if (oxygen <= 0.2f) oxygen = 0.0f; + } + + if (inventory.showHealthBar() || (!inventory.active && (!lara->emptyHands() || lara->damageTime > 0.0f || health <= 0.2f))) { + UI::renderBar(0, vec2(UI::width - 32 - size.x, 32), size, health); + + if (!inventory.active && !lara->emptyHands()) { // ammo + int index = inventory.contains(lara->getCurrentWeaponInv()); + if (index > -1) + inventory.renderItemCount(inventory.items[index], vec2(UI::width - 32 - size.x, 64), size.x); + } + } + + if (!lara->dozy && (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER)) + UI::renderBar(1, vec2(32, 32), size, oxygen); + + inventory.renderUI(); + + UI::renderHelp(); + + UI::end(); + } + void render() { bool title = isTitle(); bool copyBg = title && lastTitle != title; @@ -970,6 +1029,8 @@ struct Level : IGame { if (title) renderInventory(); + renderUI(); + lastTitle = title; } diff --git a/src/mesh.h b/src/mesh.h index cf2a008..c311917 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -4,6 +4,12 @@ #include "core.h" #include "format.h" +#define TEX_HEALTH_BAR_X 1000 +#define TEX_HEALTH_BAR_Y 1000 + +#define TEX_OXYGEN_BAR_X 1002 +#define TEX_OXYGEN_BAR_Y 1000 + typedef unsigned short Index; struct Vertex { @@ -184,7 +190,6 @@ struct MeshBuilder { MeshRange *sequences; // procedured MeshRange shadowBlob, shadowBox; - MeshRange bar; MeshRange quad, circle; MeshRange plane; @@ -227,7 +232,7 @@ struct MeshBuilder { iCount += d.rCount * 6 + d.tCount * 3; vCount += d.rCount * 4 + d.tCount * 3; - if (Core::settings.water) + if (Core::settings.detail.water) roomRemoveWaterSurfaces(r, iCount, vCount); for (int j = 0; j < r.meshesCount; j++) { @@ -288,13 +293,6 @@ struct MeshBuilder { iCount += shadowBox.iCount; vCount += 4 * 6; - // bar (health, oxygen) - bar.vStart = vCount; - bar.iStart = iCount; - bar.iCount = 2 * 3; - iCount += bar.iCount; - vCount += 4; - // quad (post effect filter) quad.vStart = vCount; quad.iStart = iCount; @@ -498,22 +496,6 @@ struct MeshBuilder { aCount++; } - // white bar - addQuad(indices, iCount, vCount, bar.vStart, vertices, &whiteTile); - vertices[vCount + 0].coord = { 0, 0, 0, 0 }; - vertices[vCount + 1].coord = { 1, 0, 0, 0 }; - vertices[vCount + 2].coord = { 1, 1, 0, 0 }; - vertices[vCount + 3].coord = { 0, 1, 0, 0 }; - - for (int i = 0; i < 4; i++) { - Vertex &v = vertices[vCount + i]; - v.normal = { 0, 0, 0, 0 }; - v.color = { 255, 255, 255, 255 }; - v.texCoord = { 32688, 32688, 0, 0 }; - } - vCount += 4; - aCount++; - // quad addQuad(indices, iCount, vCount, quad.vStart, vertices, &whiteTile); vertices[vCount + 3].coord = { -1, -1, 0, 0 }; @@ -595,7 +577,6 @@ struct MeshBuilder { mesh->initRange(models[i].geometry); mesh->initRange(shadowBlob); mesh->initRange(shadowBox); - mesh->initRange(bar); mesh->initRange(quad); mesh->initRange(circle); mesh->initRange(plane); @@ -978,6 +959,93 @@ struct MeshBuilder { vCount += 4; } + void addBar(Index *indices, Vertex *vertices, int &iCount, int &vCount, int type, const vec2 &pos, const vec2 &size, uint32 color) { + addQuad(indices, iCount, vCount, 0, vertices, NULL); + + int16 minX = int16(pos.x); + int16 minY = int16(pos.y); + int16 maxX = int16(size.x) + minX; + int16 maxY = int16(size.y) + minY; + + vertices[vCount + 0].coord = { minX, minY, 0, 0 }; + vertices[vCount + 1].coord = { maxX, minY, 0, 0 }; + vertices[vCount + 2].coord = { maxX, maxY, 0, 0 }; + vertices[vCount + 3].coord = { minX, maxY, 0, 0 }; + + for (int i = 0; i < 4; i++) { + Vertex &v = vertices[vCount + i]; + v.normal = { 0, 0, 0, 0 }; + v.color = *((ubyte4*)&color); + + int16 s, t; + + if (type == 0) { // health bar + s = TEX_HEALTH_BAR_X + 1; + t = TEX_HEALTH_BAR_Y + 1; + } else { // oxygen bar + s = TEX_OXYGEN_BAR_X + 1; + t = TEX_OXYGEN_BAR_Y + 1; + } + + if (i > 1) t += 5; + + s = int(s) * 32767 / 1024; + t = int(t) * 32767 / 1024; + + v.texCoord = { s, t, 0, 0 }; + } + + vCount += 4; + } + + void addFrame(Index *indices, Vertex *vertices, int &iCount, int &vCount, const vec2 &pos, const vec2 &size, uint32 color1, uint32 color2) { + int16 minX = int16(pos.x); + int16 minY = int16(pos.y); + int16 maxX = int16(size.x) + minX; + int16 maxY = int16(size.y) + minY; + + vertices[vCount + 0].coord = { minX, minY, 0, 0 }; + vertices[vCount + 1].coord = { maxX, minY, 0, 0 }; + vertices[vCount + 2].coord = { maxX, int16(minY + 1), 0, 0 }; + vertices[vCount + 3].coord = { minX, int16(minY + 1), 0, 0 }; + + vertices[vCount + 4].coord = { minX, minY, 0, 0 }; + vertices[vCount + 5].coord = { int16(minX + 1), minY, 0, 0 }; + vertices[vCount + 6].coord = { int16(minX + 1), maxY, 0, 0 }; + vertices[vCount + 7].coord = { minX, maxY, 0, 0 }; + + for (int i = 0; i < 8; i++) { + Vertex &v = vertices[vCount + i]; + v.normal = { 0, 0, 0, 0 }; + v.color = *((ubyte4*)&color1); + v.texCoord = { 32688, 32688, 0, 0 }; + } + + addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; + addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; + + vertices[vCount + 0].coord = { minX, int16(maxY - 1), 0, 0 }; + vertices[vCount + 1].coord = { maxX, int16(maxY - 1), 0, 0 }; + vertices[vCount + 2].coord = { maxX, maxY, 0, 0 }; + vertices[vCount + 3].coord = { minX, maxY, 0, 0 }; + + vertices[vCount + 4].coord = { int16(maxX - 1), minY, 0, 0 }; + vertices[vCount + 5].coord = { maxX, minY, 0, 0 }; + vertices[vCount + 6].coord = { maxX, maxY, 0, 0 }; + vertices[vCount + 7].coord = { int16(maxX - 1), maxY, 0, 0 }; + + for (int i = 0; i < 8; i++) { + Vertex &v = vertices[vCount + i]; + v.normal = { 0, 0, 0, 0 }; + v.color = *((ubyte4*)&color2); + v.texCoord = { 32688, 32688, 0, 0 }; + } + + addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; + addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; + } + + void bind() { mesh->bind(); } @@ -1029,34 +1097,6 @@ struct MeshBuilder { void renderPlane() { mesh->render(plane); } - - void renderBar(const vec2 &size, float value) { - /* - float w = size.y / 9.0f; - // health bar - enum Colors { - clBlack = 0xFF000000, - clGrayL = 0xFF748474, - clGrayD = 0xFF4C504C, - clRed1 = 0xFF705C2C, - clRed2 = 0xFFA47848, - clRed3 = 0xFF705C2C, - clRed4 = 0xFF584400, - clRed5 = 0xFF503014, - }; - - uint32 *d = (uint32*)&data[0]; - d[0] = clGrayD; d[1] = clGrayD; d[2] = clGrayD; d[3] = clGrayD; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clBlack; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clRed1; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clRed2; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clRed3; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clRed4; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clRed5; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clBlack; d[2] = clBlack; d[3] = clBlack; d[4] = clGrayL; d+= 1024; - d[0] = clGrayD; d[1] = clGrayL; d[2] = clGrayL; d[3] = clGrayL; d[4] = clGrayL; d+= 1024; - */ - } }; #endif \ No newline at end of file diff --git a/src/platform/web/index.html b/src/platform/web/index.html index 1a80360..4c29612 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -143,12 +143,7 @@ (.PHD, .PSX)

- OpenLara on github & facebook

- controls:
- keyboad: move - arrow keys, jump - Alt, action - Ctrl, draw weapon - Space, inventory - Tab, walk - Shift, side steps - ZX, change view - V)
- gamepad: PSX controls for Xbox controller
- Time Control: R - slow motion, T - fast motion
- FullScreen: Alt + Enter + OpenLara on github & facebook

diff --git a/src/platform/win/OpenLara.vcxproj b/src/platform/win/OpenLara.vcxproj index 55723da..9483e76 100644 --- a/src/platform/win/OpenLara.vcxproj +++ b/src/platform/win/OpenLara.vcxproj @@ -215,12 +215,9 @@ - - - diff --git a/src/platform/win/OpenLara.vcxproj.filters b/src/platform/win/OpenLara.vcxproj.filters index 3226bd0..7b33c53 100644 --- a/src/platform/win/OpenLara.vcxproj.filters +++ b/src/platform/win/OpenLara.vcxproj.filters @@ -42,9 +42,6 @@ - - shaders - shaders @@ -54,12 +51,6 @@ shaders - - shaders - - - shaders - shaders diff --git a/src/platform/win/OpenLara.vcxproj.user b/src/platform/win/OpenLara.vcxproj.user index a4941d7..a2075f5 100644 --- a/src/platform/win/OpenLara.vcxproj.user +++ b/src/platform/win/OpenLara.vcxproj.user @@ -3,10 +3,12 @@ ../../../bin WindowsLocalDebugger + data/LEVEL3A.PSX ../../../bin WindowsLocalDebugger + data/LEVEL2.PHD ../../../bin @@ -15,5 +17,6 @@ ../../../bin WindowsLocalDebugger + data/LEVEL2.PSX \ No newline at end of file diff --git a/src/shaders/depth.glsl b/src/shaders/depth.glsl deleted file mode 100644 index 859efcd..0000000 --- a/src/shaders/depth.glsl +++ /dev/null @@ -1,80 +0,0 @@ -R"====( -#ifdef GL_ES - precision lowp int; - precision highp float; -#endif - -#if defined(ALPHA_TEST) - varying vec2 vTexCoord; -#endif - -#define TEXCOORD_SCALE (1.0 / 32767.0) - -#ifdef VERTEX - attribute vec4 aCoord; - - #if defined(TYPE_SPRITE) || defined(ALPHA_TEST) - attribute vec4 aTexCoord; - #endif - - #ifdef TYPE_SPRITE - uniform mat4 uViewInv; - #endif - - uniform mat4 uViewProj; - - #ifdef TYPE_ENTITY - uniform vec4 uBasis[32 * 2]; - #else - uniform vec4 uBasis[2]; - #endif - - vec3 mulQuat(vec4 q, vec3 v) { - return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w); - } - - vec3 mulBasis(vec4 rot, vec4 pos, vec3 v) { - return mulQuat(rot, v) + pos.xyz; - } - - vec4 _transform() { - #ifdef TYPE_ENTITY - int index = int(aCoord.w * 2.0); - vec4 rBasisRot = uBasis[index]; - vec4 rBasisPos = uBasis[index + 1]; - #else - vec4 rBasisRot = uBasis[0]; - vec4 rBasisPos = uBasis[1]; - #endif - - vec4 coord = vec4(mulBasis(rBasisRot, rBasisPos, aCoord.xyz), rBasisPos.w); - - #ifdef TYPE_SPRITE - coord.xyz += uViewInv[0].xyz * aTexCoord.z - uViewInv[1].xyz * aTexCoord.w; - #endif - - #if defined(ALPHA_TEST) - vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; - #endif - - return coord; - } - - void main() { - gl_Position = uViewProj * _transform(); - } -#else - #ifdef ALPHA_TEST - uniform sampler2D sDiffuse; - #endif - - void main() { - #ifdef ALPHA_TEST - if (texture2D(sDiffuse, vTexCoord).w <= 0.5) - discard; - #endif - - gl_FragColor = vec4(0.0); - } -#endif -)====" \ No newline at end of file diff --git a/src/shaders/gui.glsl b/src/shaders/gui.glsl index 4d157a3..4764b7c 100644 --- a/src/shaders/gui.glsl +++ b/src/shaders/gui.glsl @@ -5,26 +5,29 @@ R"====( #endif varying vec2 vTexCoord; +varying vec4 vColor; #ifdef VERTEX uniform mat4 uViewProj; uniform vec4 uPosScale; + uniform vec4 uMaterial; attribute vec4 aCoord; attribute vec4 aTexCoord; + attribute vec4 aColor; #define TEXCOORD_SCALE (1.0 / 32767.0) void main() { vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; + vColor = aColor * uMaterial; gl_Position = uViewProj * vec4(aCoord.xy * uPosScale.zw + uPosScale.xy, 0.0, 1.0); } #else uniform sampler2D sDiffuse; - uniform vec4 uMaterial; void main() { - gl_FragColor = texture2D(sDiffuse, vTexCoord) * uMaterial; + gl_FragColor = texture2D(sDiffuse, vTexCoord) * vColor; } #endif )====" \ No newline at end of file diff --git a/src/shaders/shader.glsl b/src/shaders/shader.glsl index 2900fdb..4345cf3 100644 --- a/src/shaders/shader.glsl +++ b/src/shaders/shader.glsl @@ -119,14 +119,17 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - caustics coords float fog; #ifdef UNDERWATER - float d = abs((vViewVec.w - max(uViewPos.y, uParam.y)) / normalize(vViewVec.xyz).y); - d *= step(0.0, vViewVec.w - uParam.y); + float d; + if (uViewPos.y < uParam.y) + d = abs((coord.y - uParam.y) / normalize(vViewVec.xyz).y); + else + d = length(uViewPos - coord.xyz); fog = d * WATER_FOG_DIST; #else fog = length(vViewVec.xyz); #endif - vNormal.w = 1.0 - clamp(1.0 / exp(fog), 0.0, 1.0); + vNormal.w = clamp(1.0 / exp(fog), 0.0, 1.0); #endif return coord; @@ -457,9 +460,9 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - caustics coords #if defined(PASS_COMPOSE) && !defined(TYPE_FLASH) #ifdef UNDERWATER - color.xyz = mix(color.xyz, UNDERWATER_COLOR * 0.2, vNormal.w); + color.xyz = mix(UNDERWATER_COLOR * 0.2, color.xyz, vNormal.w); #else - color.xyz = mix(color.xyz, vec3(0.0), vNormal.w); + color.xyz = mix(vec3(0.0), color.xyz, vNormal.w); #endif #endif diff --git a/src/shaders/shadow.glsl b/src/shaders/shadow.glsl deleted file mode 100644 index d06affb..0000000 --- a/src/shaders/shadow.glsl +++ /dev/null @@ -1,135 +0,0 @@ -R"====( -#ifdef GL_ES - precision lowp int; - precision highp float; -#endif - -varying vec3 vNormal; -varying vec4 vLightProj; -varying vec3 vLightVec; - -uniform vec3 uLightPos[MAX_LIGHTS]; -uniform vec4 uLightColor[MAX_LIGHTS]; // xyz - color, w - radius * intensity - -#ifdef VERTEX - attribute vec4 aCoord; - attribute vec4 aNormal; - - uniform mat4 uViewProj; - uniform mat4 uLightProj; - - #ifdef TYPE_ENTITY - uniform vec4 uBasis[32 * 2]; - #else - uniform vec4 uBasis[2]; - #endif - - vec3 mulQuat(vec4 q, vec3 v) { - return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w); - } - - vec3 mulBasis(vec4 rot, vec4 pos, vec3 v) { - return mulQuat(rot, v) + pos.xyz; - } - - void main() { - #ifdef TYPE_ENTITY - int index = int(aCoord.w * 2.0); - vec4 rBasisRot = uBasis[index]; - vec4 rBasisPos = uBasis[index + 1]; - #else - vec4 rBasisRot = uBasis[0]; - vec4 rBasisPos = uBasis[1]; - #endif - - vNormal = normalize(mulQuat(rBasisRot, aNormal.xyz)); - - vec4 coord = vec4(mulBasis(rBasisRot, rBasisPos, aCoord.xyz), rBasisPos.w); - - vLightProj = uLightProj * coord; - vLightVec = (uLightPos[0].xyz - coord.xyz) * uLightColor[0].w; - - gl_Position = uViewProj * coord; - } -#else - #ifdef SHADOW_SAMPLER - uniform sampler2DShadow sShadow; - #ifdef GL_ES - #define SHADOW(V) (shadow2DEXT(sShadow, V)) - #else - #define SHADOW(V) (shadow2D(sShadow, V).x) - #endif - #else - uniform sampler2D sShadow; - #define CMP(a,b) step(b, a) - - #ifdef SHADOW_DEPTH - #define compare(p, z) CMP(texture2D(sShadow, (p)).x, (z)); - #elif defined(SHADOW_COLOR) - float unpack(vec4 value) { - vec4 bitSh = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0); - return dot(value, bitSh); - } - #define compare(p, z) CMP(unpack(texture2D(sShadow, (p))), (z)); - #endif - - float SHADOW(vec3 p) { - return compare(p.xy, p.z); - } - #endif - - #define SHADOW_TEXEL (2.0 / 1024.0) - - float random(vec3 seed, float freq) { - float dt = dot(floor(seed * freq), vec3(53.1215, 21.1352, 9.1322)); - return fract(sin(dt) * 2105.2354); - } - - float randomAngle(vec3 seed, float freq) { - return random(seed, freq) * 6.283285; - } - - vec3 rotate(vec2 sc, vec2 v) { - return vec3(v.x * sc.y + v.y * sc.x, v.x * -sc.x + v.y * sc.y, 0.0); - } - - float getShadow(vec4 lightProj) { - vec3 p = lightProj.xyz / lightProj.w; - - float rShadow = SHADOW(SHADOW_TEXEL * vec3(-0.93289, -0.03146, 0.0) + p) + - SHADOW(SHADOW_TEXEL * vec3( 0.81628, -0.05965, 0.0) + p) + - SHADOW(SHADOW_TEXEL * vec3(-0.18455, 0.97225, 0.0) + p) + - SHADOW(SHADOW_TEXEL * vec3( 0.04032, -0.85898, 0.0) + p); - - if (rShadow > 0.1 && rShadow < 3.9) { - float angle = randomAngle(gl_FragCoord.xyz, 15.0); - vec2 sc = vec2(sin(angle), cos(angle)); - - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2(-0.54316, 0.21186)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2(-0.03925, -0.34345)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2( 0.07695, 0.40667)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2(-0.66378, -0.54068)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2(-0.54130, 0.66730)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2( 0.69301, 0.46990)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2( 0.37228, 0.03811)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2( 0.28597, 0.80228)) + p); - rShadow += SHADOW(SHADOW_TEXEL * rotate(sc, vec2( 0.44801, -0.43844)) + p); - rShadow /= 13.0; - } else - rShadow /= 4.0; - - float fade = clamp(dot(vLightVec, vLightVec), 0.0, 1.0); - - return mix(rShadow, 1.0, fade); - } - - float getShadow() { - return min(dot(vNormal.xyz, vLightVec), vLightProj.w) > 0.0 ? getShadow(vLightProj) : 1.0; - } - - void main() { - float s = getShadow(); - gl_FragColor = vec4(s, s, s, 1.0); - } -#endif -)====" \ No newline at end of file diff --git a/src/shaders/volume.glsl b/src/shaders/volume.glsl deleted file mode 100644 index 10ca26b..0000000 --- a/src/shaders/volume.glsl +++ /dev/null @@ -1,45 +0,0 @@ -R"====( -#ifdef GL_ES - precision lowp int; - precision highp float; -#endif - - -varying vec3 color; - -#ifdef VERTEX - attribute vec4 aCoord; - attribute vec4 aNormal; - attribute vec4 aColor; - - uniform mat4 uViewProj; - uniform vec4 uBasis[2]; // quat rot, pos - uniform vec3 uPosScale; // xyz - scale - uniform vec4 uLightPos; // xyz - pos, w - radius - - vec3 mulQuat(vec4 q, vec3 v) { - return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w); - } - - vec3 mulBasis(vec4 rot, vec4 pos, vec3 v) { - return mulQuat(rot, v) + pos.xyz; - } - - void main() { - vec4 c = vec4(mulBasis(uBasis[0], uBasis[1], aCoord.xyz * uPosScale), 1.0); - vec3 n = mulQuat(uBasis[0], normalize(aNormal.xyz)); - vec3 lv = uLightPos.xyz - c.xyz; - - if (dot(lv, n) < 0.0) - c.xyz -= normalize(lv) * uLightPos.w; - - color = aColor.xyz; - - gl_Position = uViewProj * c; - } -#else - void main() { - gl_FragColor = vec4(color, 1.0); - } -#endif -)====" \ No newline at end of file diff --git a/src/ui.h b/src/ui.h index 8d4df2e..323152e 100644 --- a/src/ui.h +++ b/src/ui.h @@ -6,6 +6,9 @@ namespace UI { IGame *game; + float width; + float helpTipTime; + bool showHelp; const static uint8 char_width[110] = { 14, 11, 11, 11, 11, 11, 11, 13, 8, 11, 12, 11, 13, 13, 12, 11, 12, 12, 11, 12, 13, 13, 13, 12, @@ -25,13 +28,22 @@ namespace UI { return c > 10 ? (c > 15 ? char_map[c - 32] : c + 91) : c + 81; } - int getTextWidth(const char *text) { - int width = 0; + vec2 getTextSize(const char *text) { + int x = 0, w = 0, h = 16; - while (char c = *text++) - width += (c == ' ' || c == '_') ? 6 : (char_width[charRemap(c)] + 1); + while (char c = *text++) { + if (c == ' ' || c == '_') { + x += 6; + } else if (c == '@') { + w = max(w, x); + h += 16; + x = 0; + } else + x += char_width[charRemap(c)] + 1; + } + w = max(w, x); - return width - 1; + return vec2(float(w), float(h)); } #define MAX_CHARS DYN_MESH_QUADS @@ -43,18 +55,38 @@ namespace UI { int vCount; } buffer; - void textBegin() { + void begin() { + Core::setDepthTest(false); + Core::setBlending(bmAlpha); + Core::setCulling(cfNone); + game->setupBinding(); + + float aspect = float(Core::width) / float(Core::height); + width = 480 * aspect; + Core::mViewProj = mat4(0.0f, width, 480, 0.0f, 0.0f, 1.0f); + + game->setShader(Core::passGUI, Shader::DEFAULT); + Core::active.shader->setParam(uMaterial, vec4(1)); + Core::active.shader->setParam(uPosScale, vec4(0, 0, 1, 1)); + buffer.iCount = buffer.vCount = 0; } - void textEnd(IGame *game) { + void flush() { if (buffer.iCount > 0) { game->getMesh()->renderBuffer(buffer.indices, buffer.iCount, buffer.vertices, buffer.vCount); buffer.iCount = buffer.vCount = 0; } } - void textOut(IGame *game, const vec2 &pos, const char *text, Align align = aLeft, float width = 0) { + void end() { + flush(); + Core::setCulling(cfFront); + Core::setBlending(bmNone); + Core::setDepthTest(true); + } + + void textOut(const vec2 &pos, const char *text, Align align = aLeft, float width = 0) { if (!text) return; TR::Level *level = game->getLevel(); @@ -66,10 +98,12 @@ namespace UI { int y = int(pos.y); if (align == aCenter) - x += (int(width) - getTextWidth(text)) / 2; + x += int((width - getTextSize(text).x) / 2); if (align == aRight) - x += int(width) - getTextWidth(text); + x += int(width - getTextSize(text).x); + + int left = x; while (char c = *text++) { if (c == ' ' || c == '_') { @@ -77,10 +111,16 @@ namespace UI { continue; } + if (c == '@') { + x = left; + y += 16; + continue; + } + int frame = charRemap(c); if (buffer.iCount == MAX_CHARS * 6) - textEnd(game); + flush(); TR::SpriteTexture &sprite = level->spriteTextures[level->spriteSequences[seq].sStart + frame]; mesh->addSprite(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, 0, x, y, 0, sprite, 255, true); @@ -94,10 +134,18 @@ namespace UI { void init(IGame *game) { UI::game = game; + showHelp = false; + helpTipTime = 5.0f; } void update() { - // + if (Input::down[ikH]) { + Input::down[ikH] = false; + showHelp = !showHelp; + helpTipTime = 0.0f; + } + if (helpTipTime > 0.0f) + helpTipTime -= Core::deltaTime; } void renderControl(const vec2 &pos, float size, bool active) { @@ -137,8 +185,48 @@ namespace UI { Core::setDepthTest(true); } - void renderBar(const vec2 &pos, const vec2 &size, float value) { - // + void renderBar(int type, const vec2 &pos, const vec2 &size, float value) { + MeshBuilder *mesh = game->getMesh(); + + mesh->addFrame(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, pos - 2.0f, size + 4.0f, 0xFF4C504C, 0xFF748474); + mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, type, pos - 1.0f, size + 2.0f, 0x80000000); + if (value > 0.0f) + mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, type, pos, vec2(size.x * value, size.y), 0xFFFFFFFF); + } + +const char *helpText = \ +"Controls gamepad, touch and keyboard:@"\ +" H - Show or hide this help@"\ +" TAB - Inventory@"\ +" LEFT - Left@"\ +" RIGHT - Right@"\ +" UP - Run@"\ +" DOWN - Back@"\ +" SHIFT - Walk@"\ +" SPACE - Draw Weapon@"\ +" CTRL - Action@"\ +" ALT - Jump@"\ +" Z - Step Left@"\ +" X - Step Right@"\ +" A - Roll@"\ +" C - Look # not implemented #@"\ +" V - First Person View@" +" R - slow motion@"\ +" T - fast motion@"\ +" ALT + ENTER - Fullscreen@@"\ +"Actions:@"\ +" Out of water - Run + Action@"\ +" Handstand - Run + Walk@"\ +" Swan dive - Run + Walk + jump@"\ +" DOZY on - Look + Step Right + Action + Jump@"\ +" DOZY off - Walk@"; + + void renderHelp() { + if (showHelp) + textOut(vec2(0, 64), helpText, aRight, width - 32); + else + if (helpTipTime > 0.0f) + textOut(vec2(0, 480 - 32), "Press H for help", aCenter, width); } };