diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index 1168379..eca9d29 100644 Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ diff --git a/src/controller.h b/src/controller.h index cf70cf5..230d481 100644 --- a/src/controller.h +++ b/src/controller.h @@ -33,9 +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/format.h b/src/format.h index 804a317..625694a 100644 --- a/src/format.h +++ b/src/format.h @@ -630,17 +630,66 @@ namespace TR { return (type >= DOOR_1 && type <= DOOR_6) || type == DOOR_LIFT; } - int isItem() { + 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 @@ -985,7 +1034,7 @@ namespace TR { struct { int16 muzzleFlash; - int16 puzzleSet; + int16 puzzleDone[4]; int16 weapons[4]; int16 braid; @@ -1230,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/inventory.h b/src/inventory.h index 2da92fd..1c0897f 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -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) { @@ -132,8 +132,6 @@ struct Inventory { } *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); @@ -175,39 +173,8 @@ struct Inventory { return active || phaseRing > 0.0f; } - TR::Entity::Type convToInv(TR::Entity::Type type) { - switch (type) { - case TR::Entity::PISTOLS : return TR::Entity::INV_PISTOLS; - case TR::Entity::SHOTGUN : return TR::Entity::INV_SHOTGUN; - case TR::Entity::MAGNUMS : return TR::Entity::INV_MAGNUMS; - case TR::Entity::UZIS : return TR::Entity::INV_UZIS; - - case TR::Entity::AMMO_PISTOLS : return TR::Entity::INV_AMMO_PISTOLS; - case TR::Entity::AMMO_SHOTGUN : return TR::Entity::INV_AMMO_SHOTGUN; - case TR::Entity::AMMO_MAGNUMS : return TR::Entity::INV_AMMO_MAGNUMS; - case TR::Entity::AMMO_UZIS : return TR::Entity::INV_AMMO_UZIS; - - case TR::Entity::MEDIKIT_SMALL : return TR::Entity::INV_MEDIKIT_SMALL; - case TR::Entity::MEDIKIT_BIG : return TR::Entity::INV_MEDIKIT_BIG; - - case TR::Entity::PUZZLE_1 : return TR::Entity::INV_PUZZLE_1; - case TR::Entity::PUZZLE_2 : return TR::Entity::INV_PUZZLE_2; - case TR::Entity::PUZZLE_3 : return TR::Entity::INV_PUZZLE_3; - case TR::Entity::PUZZLE_4 : return TR::Entity::INV_PUZZLE_4; - - case TR::Entity::KEY_1 : return TR::Entity::INV_KEY_1; - case TR::Entity::KEY_2 : return TR::Entity::INV_KEY_2; - case TR::Entity::KEY_3 : return TR::Entity::INV_KEY_3; - case TR::Entity::KEY_4 : return TR::Entity::INV_KEY_4; - - case TR::Entity::LEADBAR : return TR::Entity::INV_LEADBAR; - //case TR::Entity::SCION : return TR::Entity::INV_SCION; - } - return type; - } - int contains(TR::Entity::Type type) { - type = convToInv(type); + type = TR::Entity::convToInv(type); for (int i = 0; i < itemsCount; i++) if (items[i]->type == type) return i; @@ -215,35 +182,38 @@ struct Inventory { } void addAmmo(TR::Entity::Type &type, int &count, int clip, TR::Entity::Type wpnType, TR::Entity::Type ammoType) { - count *= clip; if (type == wpnType) { + count *= clip; int index = contains(ammoType); if (index > -1) { - count += items[index]->count; + count += items[index]->count * clip; remove(index); } } else { - if (contains(wpnType) > -1) + if (contains(wpnType) > -1) { type = wpnType; + count *= clip; + } } } void add(TR::Entity::Type type, int count = 1) { - type = convToInv(type); + type = TR::Entity::convToInv(type); switch (type) { case TR::Entity::INV_SHOTGUN : case TR::Entity::INV_AMMO_SHOTGUN : - addAmmo(type, count, 12, TR::Entity::INV_SHOTGUN, 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, 50, TR::Entity::INV_MAGNUMS, 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, 100, TR::Entity::INV_UZIS, TR::Entity::INV_AMMO_UZIS); + addAmmo(type, count, 50, TR::Entity::INV_UZIS, TR::Entity::INV_AMMO_UZIS); break; + default : ; } int i = contains(type); @@ -289,33 +259,27 @@ struct Inventory { 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; + } - bool use(TR::Entity::Type item, TR::Entity::Type slot) { - switch (slot) { - case TR::Entity::PUZZLE_HOLE_1 : item = TR::Entity::INV_PUZZLE_1; break; - case TR::Entity::PUZZLE_HOLE_2 : item = TR::Entity::INV_PUZZLE_2; break; - case TR::Entity::PUZZLE_HOLE_3 : item = TR::Entity::INV_PUZZLE_3; break; - case TR::Entity::PUZZLE_HOLE_4 : item = TR::Entity::INV_PUZZLE_4; break; - case TR::Entity::KEY_HOLE_1 : item = TR::Entity::INV_KEY_1; break; - case TR::Entity::KEY_HOLE_2 : item = TR::Entity::INV_KEY_2; break; - case TR::Entity::KEY_HOLE_3 : item = TR::Entity::INV_KEY_3; break; - case TR::Entity::KEY_HOLE_4 : item = TR::Entity::INV_KEY_4; break; - case TR::Entity::INV_PISTOLS : - case TR::Entity::INV_SHOTGUN : - case TR::Entity::INV_MAGNUMS : - case TR::Entity::INV_UZIS : return false; - default : return false; - } - - if (getCountPtr(item)) { - 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; @@ -329,6 +293,13 @@ struct Inventory { 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]; } } @@ -382,7 +353,8 @@ struct Inventory { } 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); @@ -465,13 +437,24 @@ struct Inventory { 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(); } - } } @@ -518,13 +501,27 @@ struct Inventory { sprintf(buf, "%d %c", item->count, spec); for (int i = 0; buf[i] != ' '; i++) buf[i] -= 47; - UI::textOut(game, pos, buf, UI::aRight, width); + UI::textOut(pos, buf, UI::aRight, width); } } void renderItemText(const Item *item, float width) { - UI::textOut(game, vec2(0, 480 - 16), item->desc.name, UI::aCenter, width); + UI::textOut(vec2(0, 480 - 16), item->desc.name, UI::aCenter, width); renderItemCount(item, vec2(width / 2 - 160, 480 - 96), 320); + + 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) { @@ -632,16 +629,16 @@ struct Inventory { static const char* pageTitle[PAGE_MAX] = { "OPTION", "INVENTORY", "ITEMS" }; - UI::textOut(game, vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width); + 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, UI::width); - UI::textOut(game, vec2( 0, 32), "\x5B", UI::aRight, UI::width - 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, UI::width); - UI::textOut(game, vec2(0, 480 - 16), "\x5D", UI::aRight, UI::width - 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) diff --git a/src/lara.h b/src/lara.h index 0ed9c93..a80352a 100644 --- a/src/lara.h +++ b/src/lara.h @@ -192,6 +192,7 @@ struct Lara : Character { }; bool home; + bool dozy; struct Weapon { enum Type { EMPTY = -1, PISTOLS, SHOTGUN, MAGNUMS, UZIS, MAX }; @@ -216,7 +217,10 @@ 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; @@ -388,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) @@ -437,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; } @@ -461,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); @@ -480,10 +484,16 @@ 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); } @@ -605,6 +615,8 @@ struct Lara : Character { } bool canDrawWeapon() { + if (dozy) return true; + return wpnCurrent != Weapon::EMPTY && emptyHands() && animation.index != ANIM_CLIMB_3 @@ -1255,6 +1267,8 @@ struct Lara : Character { } virtual void hit(float damage, Controller *enemy = NULL) { + if (dozy) return; + damageTime = LARA_DAMAGE_TIME; Character::hit(damage, enemy); @@ -1273,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; @@ -1285,8 +1299,21 @@ struct Lara : Character { 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() { @@ -1347,7 +1374,7 @@ struct Lara : Character { if (stand == STAND_UNDERWATER) angle.x = -25 * DEG2RAD; - lastPickUp = i; + pickupEntity = &item; return true; } } @@ -1422,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 @@ -1498,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; @@ -1921,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; @@ -1969,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; } } @@ -1994,7 +2052,7 @@ struct Lara : Character { if (damageTime > 0.0f) damageTime = max(0.0f, damageTime - Core::deltaTime); - if (stand == STAND_UNDERWATER) { + if (stand == STAND_UNDERWATER && !dozy) { if (oxygen > 0.0f) oxygen -= Core::deltaTime; else @@ -2002,6 +2060,8 @@ struct Lara : Character { } 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) { @@ -2367,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 37cfc5b..4253331 100644 --- a/src/level.h +++ b/src/level.h @@ -127,9 +127,10 @@ 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) { @@ -140,6 +141,10 @@ struct Level : IGame { 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; @@ -636,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 @@ -993,11 +998,13 @@ struct Level : IGame { } } - if (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER) + 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(); } diff --git a/src/ui.h b/src/ui.h index 907d756..d38f24f 100644 --- a/src/ui.h +++ b/src/ui.h @@ -7,6 +7,8 @@ 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, @@ -84,7 +86,7 @@ namespace UI { Core::setDepthTest(true); } - void textOut(IGame *game, const vec2 &pos, const char *text, Align align = aLeft, float width = 0) { + void textOut(const vec2 &pos, const char *text, Align align = aLeft, float width = 0) { if (!text) return; TR::Level *level = game->getLevel(); @@ -101,6 +103,8 @@ namespace UI { if (align == aRight) x += int(width - getTextSize(text).x); + int left = x; + while (char c = *text++) { if (c == ' ' || c == '_') { x += 6; @@ -108,7 +112,7 @@ namespace UI { } if (c == '@') { - x = int(pos.x); + x = left; y += 16; continue; } @@ -130,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) { @@ -181,6 +193,38 @@ namespace UI { 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@@" +"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); + } }; #endif \ No newline at end of file