mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-14 00:54:05 +02:00
Merge remote-tracking branch 'refs/remotes/XProger/master'
This commit is contained in:
BIN
bin/OpenLara.exe
BIN
bin/OpenLara.exe
Binary file not shown.
40
src/cache.h
40
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
|
||||
#endif
|
||||
|
@@ -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() {
|
||||
|
@@ -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; }
|
||||
};
|
||||
|
16
src/core.h
16
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;
|
||||
}
|
||||
|
||||
|
10
src/enemy.h
10
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;
|
||||
|
71
src/format.h
71
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;
|
||||
|
10
src/game.h
10
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);
|
||||
|
366
src/inventory.h
366
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
216
src/lara.h
216
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) {
|
||||
|
93
src/level.h
93
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;
|
||||
}
|
||||
|
||||
|
148
src/mesh.h
148
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
|
@@ -143,12 +143,7 @@
|
||||
<input type="button" value="Browse Level" onclick="document.getElementById('browseFile').click();" /> (.PHD, .PSX)
|
||||
<input type="checkbox" id="isHome"><label>alternative model (home suit, gold etc.)</label>
|
||||
<p style="margin:8px">
|
||||
OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br><br>
|
||||
controls:<br>
|
||||
keyboad: move - arrow keys, jump - Alt, action - Ctrl, draw weapon - Space, inventory - Tab, walk - Shift, side steps - ZX, change view - V)<br>
|
||||
gamepad: PSX controls for Xbox controller<br>
|
||||
Time Control: R - slow motion, T - fast motion<br>
|
||||
FullScreen: Alt + Enter
|
||||
OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br>
|
||||
</p>
|
||||
</span>
|
||||
|
||||
|
@@ -215,12 +215,9 @@
|
||||
<ClInclude Include="..\..\utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\shaders\depth.glsl" />
|
||||
<None Include="..\..\shaders\filter.glsl" />
|
||||
<None Include="..\..\shaders\gui.glsl" />
|
||||
<None Include="..\..\shaders\shader.glsl" />
|
||||
<None Include="..\..\shaders\shadow.glsl" />
|
||||
<None Include="..\..\shaders\volume.glsl" />
|
||||
<None Include="..\..\shaders\water.glsl" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
|
@@ -42,9 +42,6 @@
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\shaders\depth.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="..\..\shaders\filter.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
@@ -54,12 +51,6 @@
|
||||
<None Include="..\..\shaders\shader.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="..\..\shaders\shadow.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="..\..\shaders\volume.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="..\..\shaders\water.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
|
@@ -3,10 +3,12 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Editor|Win32'">
|
||||
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerCommandArguments>data/LEVEL3A.PSX</LocalDebuggerCommandArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerCommandArguments>data/LEVEL2.PHD</LocalDebuggerCommandArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">
|
||||
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
|
||||
@@ -15,5 +17,6 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerCommandArguments>data/LEVEL2.PSX</LocalDebuggerCommandArguments>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@@ -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
|
||||
)===="
|
@@ -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
|
||||
)===="
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
)===="
|
@@ -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
|
||||
)===="
|
116
src/ui.h
116
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user