1 #ifndef H_INVENTORY 2 #define H_INVENTORY 3 4 #include "format.h" 5 #include "controller.h" 6 #include "ui.h" 7 #include "savegame.h" 8 9 #define INV_MAX_ITEMS 32 10 #define INV_MAX_RADIUS 688.0f 11 #if defined(_OS_PSP) || defined(_OS_3DS) || defined(_OS_GCW0) 12 #define INV_BG_SIZE 256 13 #else 14 #define INV_BG_SIZE 512 15 #endif 16 17 #define INV_HEIGHT 2048.0f 18 #define INV_EYE_SEPARATION 8.0f 19 #define INV_EYE_FOCAL_LENGTH 512.0f 20 #define INV_ZNEAR 32.0f 21 #define INV_ZFAR 2048.0f 22 #define INV_FOV 70.0f 23 24 #define TITLE_LOADING 64.0f 25 #define LINE_HEIGHT 20.0f 26 27 static const struct OptionItem *waitForKey = NULL; 28 29 struct OptionItem { 30 enum Type { 31 TYPE_TITLE, 32 TYPE_EMPTY, 33 TYPE_BUTTON, 34 TYPE_PARAM, 35 TYPE_KEY, 36 } type; 37 StringID title; 38 int32 offset; 39 uint32 color; 40 uint32 icon; 41 uint8 maxValue; 42 bool bar; 43 typeOptionItem44 OptionItem(Type type = TYPE_EMPTY, int title = STR_EMPTY, int32 offset = 0, uint32 color = 0xFFFFFFFF, int icon = 0, uint8 maxValue = 0, bool bar = false) : type(type), title(StringID(title)), offset(offset), color(color), icon(icon), maxValue(maxValue), bar(bar) {} 45 setValueOptionItem46 void setValue(uint8 value, Core::Settings *settings) const { 47 *((uint8*)settings + offset) = value; 48 } 49 checkValueOptionItem50 bool checkValue(uint8 value) const { 51 if (value > maxValue) return false; 52 Core::Settings stg; 53 switch (title) { 54 case STR_OPT_DETAIL_FILTER : stg.detail.setFilter((Core::Settings::Quality)value); return stg.detail.filter == value; 55 case STR_OPT_DETAIL_LIGHTING : stg.detail.setLighting((Core::Settings::Quality)value); return stg.detail.lighting == value; 56 case STR_OPT_DETAIL_SHADOWS : stg.detail.setShadows((Core::Settings::Quality)value); return stg.detail.shadows == value; 57 case STR_OPT_DETAIL_WATER : stg.detail.setWater((Core::Settings::Quality)value); return stg.detail.water == value; 58 default : return true; 59 } 60 } 61 drawParamOptionItem62 float drawParam(float x, float y, float w, StringID oStr, bool active, uint8 value) const { 63 if (oStr != STR_EMPTY) { 64 UI::textOut(vec2(x + 32.0f, y), oStr); 65 x = x + w * 0.5f; 66 w = w * 0.5f - 32.0f; 67 } 68 69 StringID vStr = StringID(color + int(value)); 70 71 uint8 alpha = 255; 72 if (type == TYPE_KEY && waitForKey == this) { 73 vStr = STR_PRESS_ANY_KEY; 74 float t = (Core::getTime() % 1000) / 1000.0f; 75 t = 0.2f + (sinf(t * PI * 2) * 0.5f + 0.5f) * 0.8f; 76 alpha = uint8(t * 255.0f); 77 } 78 79 UI::textOut(vec2(x, y), vStr, UI::aCenter, w, alpha, UI::SHADE_GRAY); // color as StringID 80 81 if (type == TYPE_PARAM && active) { 82 int maxWidth = UI::getTextSize(STR[color + value]).x; 83 maxWidth = maxWidth / 2 + 8; 84 x += w * 0.5f; 85 if (maxValue != 0xFF) { 86 if (checkValue(value - 1)) UI::specOut(vec2(x - maxWidth - 16.0f, y), 108); 87 if (checkValue(value + 1)) UI::specOut(vec2(x + maxWidth, y), 109); 88 } 89 } 90 return y + LINE_HEIGHT; 91 } 92 drawBarOptionItem93 float drawBar(float x, float y, float w, bool active, uint8 value) const { 94 UI::renderBar(CTEX_WHITE_SPRITE, vec2(x + (32.0f + 2.0f), y - LINE_HEIGHT + 6 + 2), vec2(w - (64.0f + 4.0f), LINE_HEIGHT - 6 - 4), value / float(maxValue), color, 0xFF000000, 0xFFA0A0A0, 0xFFA0A0A0, 0xFF000000); 95 UI::specOut(vec2(x + 16.0f, y), icon); 96 if (active) { 97 if (value > 0) UI::specOut(vec2(x, y), 108); 98 if (value < maxValue) UI::specOut(vec2(x + w - 12.0f, y), 109); 99 } 100 return y + LINE_HEIGHT; 101 } 102 renderOptionItem103 float render(float x, float y, float w, bool active, Core::Settings *settings) const { 104 if (active) 105 UI::renderBar(CTEX_OPTION, vec2(x, y - LINE_HEIGHT + 6), vec2(w, LINE_HEIGHT - 6), 1.0f, 0xFFD8377C, 0); 106 107 const uint8 &value = *((uint8*)settings + offset); 108 109 switch (type) { 110 case TYPE_TITLE : 111 UI::renderBar(CTEX_OPTION, vec2(x, y - LINE_HEIGHT + 6), vec2(w, LINE_HEIGHT - 6), 1.0f, 0x802288FF, 0, 0, 0); 112 UI::textOut(vec2(x, y), title, UI::aCenter, w, 255, UI::SHADE_GRAY); 113 case TYPE_EMPTY : break; 114 case TYPE_BUTTON : { 115 const char *caption = STR[offset ? StringID(offset) : title]; 116 UI::textOut(vec2(x, y), caption, UI::aCenter, w); 117 break; 118 } 119 case TYPE_PARAM : 120 case TYPE_KEY : 121 return bar ? drawBar(x, y, w, active, value) : drawParam(x, y, w, title, active, value); 122 } 123 124 return y + LINE_HEIGHT; 125 } 126 }; 127 128 #define SETTINGS(x) int32(OFFSETOF(Core::Settings, x)) 129 130 #if defined(_OS_PSP) || defined(_OS_PSV) || defined(_OS_3DS) || defined(_OS_GCW0) || defined(_OS_CLOVER) || defined(_OS_PSC) || defined(_OS_XBOX) || defined(_OS_XB1) 131 #define INV_GAMEPAD_ONLY 132 #else 133 #define INV_QUALITY 134 #endif 135 136 #if !(defined(_OS_PSP) || defined(_OS_PSV) || defined(_OS_3DS) || defined(_OS_GCW0) || defined(_OS_XBOX) || defined(_OS_XB1)) 137 #define INV_STEREO 138 #endif 139 140 #if defined(_OS_PSP) || defined(_OS_PSV) || defined(_OS_3DS) || defined(_OS_GCW0) 141 #define INV_SINGLE_PLAYER 142 #endif 143 144 #if defined(_OS_PSP) || defined(_OS_PSV) || defined(_OS_3DS) || defined(_OS_CLOVER) || defined(_OS_XBOX) 145 #define INV_GAMEPAD_NO_TRIGGER 146 #endif 147 148 #ifdef INV_SINGLE_PLAYER 149 #define INV_CTRL_START_OPTION 1 150 #else 151 #define INV_CTRL_START_OPTION 2 152 #endif 153 154 #if defined(_OS_WIN) || defined(_OS_LINUX) || defined(_OS_RPI) || defined(_OS_GCW0) || defined(_OS_XBOX) || defined(_OS_XB1) 155 #define INV_VIBRATION 156 #endif 157 158 static const OptionItem optDetail[] = { 159 OptionItem( OptionItem::TYPE_TITLE, STR_SELECT_DETAIL ), 160 OptionItem( ), 161 #ifdef INV_QUALITY 162 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_FILTER, SETTINGS( detail.filter ), STR_QUALITY_LOW, 0, 2 ), 163 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_LIGHTING, SETTINGS( detail.lighting ), STR_QUALITY_LOW, 0, 2 ), 164 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_SHADOWS, SETTINGS( detail.shadows ), STR_QUALITY_LOW, 0, 2 ), 165 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_WATER, SETTINGS( detail.water ), STR_QUALITY_LOW, 0, 2 ), 166 #endif 167 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_SIMPLE_ITEMS, SETTINGS( detail.simple ), STR_OFF, 0, 1 ), 168 #ifdef INV_QUALITY 169 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_RESOLUTION, SETTINGS( detail.scale ), STR_SCALE_100, 0, 3 ), 170 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_VSYNC, SETTINGS( detail.vsync ), STR_OFF, 0, 1 ), 171 #endif 172 #ifdef INV_STEREO 173 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_DETAIL_STEREO, SETTINGS( detail.stereo ), STR_NO_STEREO, 0, 174 #if defined(_OS_WIN) || defined(_OS_ANDROID) 175 4 /* with VR option */ 176 #else 177 3 /* without VR support */ 178 #endif 179 ), 180 #endif 181 OptionItem( ), 182 OptionItem( OptionItem::TYPE_BUTTON, STR_APPLY ), 183 }; 184 185 static const OptionItem optSound[] = { 186 OptionItem( OptionItem::TYPE_TITLE, STR_SET_VOLUMES ), 187 OptionItem( ), 188 OptionItem( OptionItem::TYPE_PARAM, STR_EMPTY, SETTINGS( audio.music ), 0xFF0080FF, 101, SND_MAX_VOLUME, true ), 189 OptionItem( OptionItem::TYPE_PARAM, STR_EMPTY, SETTINGS( audio.sound ), 0xFFFF8000, 102, SND_MAX_VOLUME, true ), 190 OptionItem( OptionItem::TYPE_PARAM, STR_REVERBERATION, SETTINGS( audio.reverb ), STR_OFF, 0, 1 ), 191 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_SUBTITLES, SETTINGS( audio.subtitles ), STR_OFF, 0, 1 ), 192 #ifndef FFP 193 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_LANGUAGE, SETTINGS( audio.language ), STR_LANG_EN, 0, STR_LANG_SV - STR_LANG_EN ), 194 #endif 195 }; 196 197 static const OptionItem optControls[] = { 198 OptionItem( OptionItem::TYPE_TITLE, STR_SET_CONTROLS ), 199 OptionItem( ), 200 #ifndef INV_SINGLE_PLAYER 201 OptionItem( OptionItem::TYPE_PARAM, STR_EMPTY , SETTINGS( playerIndex ), STR_PLAYER_1, 0, 1 ), 202 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_CONTROLS_GAMEPAD , SETTINGS( controls[0].joyIndex ), STR_GAMEPAD_1, 0, 3 ), 203 #endif 204 #ifdef INV_VIBRATION 205 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_CONTROLS_VIBRATION , SETTINGS( controls[0].vibration ), STR_OFF, 0, 1 ), 206 #endif 207 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_CONTROLS_RETARGET , SETTINGS( controls[0].retarget ), STR_OFF, 0, 1 ), 208 OptionItem( OptionItem::TYPE_PARAM, STR_OPT_CONTROLS_MULTIAIM , SETTINGS( controls[0].multiaim ), STR_OFF, 0, 1 ), 209 #ifdef INV_GAMEPAD_ONLY 210 OptionItem( OptionItem::TYPE_PARAM, STR_EMPTY , SETTINGS( ctrlIndex ), STR_OPT_CONTROLS_KEYBOARD, 0, 0xFF ), 211 #else 212 OptionItem( OptionItem::TYPE_PARAM, STR_EMPTY , SETTINGS( ctrlIndex ), STR_OPT_CONTROLS_KEYBOARD, 0, 1 ), 213 #endif 214 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cUp , SETTINGS( controls[0].keys[ cUp ] ), STR_KEY_FIRST ), 215 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cDown , SETTINGS( controls[0].keys[ cDown ] ), STR_KEY_FIRST ), 216 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cRight , SETTINGS( controls[0].keys[ cRight ] ), STR_KEY_FIRST ), 217 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cLeft , SETTINGS( controls[0].keys[ cLeft ] ), STR_KEY_FIRST ), 218 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cWalk , SETTINGS( controls[0].keys[ cWalk ] ), STR_KEY_FIRST ), 219 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cJump , SETTINGS( controls[0].keys[ cJump ] ), STR_KEY_FIRST ), 220 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cAction , SETTINGS( controls[0].keys[ cAction ] ), STR_KEY_FIRST ), 221 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cWeapon , SETTINGS( controls[0].keys[ cWeapon ] ), STR_KEY_FIRST ), 222 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cLook , SETTINGS( controls[0].keys[ cLook ] ), STR_KEY_FIRST ), 223 #if !(defined(INV_GAMEPAD_ONLY) && defined(INV_GAMEPAD_NO_TRIGGER)) 224 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cDuck , SETTINGS( controls[0].keys[ cDuck ] ), STR_KEY_FIRST ), 225 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cDash , SETTINGS( controls[0].keys[ cDash ] ), STR_KEY_FIRST ), 226 #endif 227 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cRoll , SETTINGS( controls[0].keys[ cRoll ] ), STR_KEY_FIRST ), 228 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cInventory , SETTINGS( controls[0].keys[ cInventory ] ), STR_KEY_FIRST ), 229 OptionItem( OptionItem::TYPE_KEY, STR_CTRL_FIRST + cStart , SETTINGS( controls[0].keys[ cStart ] ), STR_KEY_FIRST ), 230 }; 231 232 static OptionItem optControlsPlayer[COUNT(optControls)]; 233 234 struct Inventory { 235 236 enum Page { 237 PAGE_OPTION, 238 PAGE_INVENTORY, 239 PAGE_ITEMS, 240 PAGE_SAVEGAME, 241 PAGE_LEVEL_STATS, 242 PAGE_MAX 243 }; 244 245 IGame *game; 246 Texture *title; 247 Texture *background[3]; // [LEFT EYE or SINGLE, RIGHT EYE, TEMP] 248 Video *video; 249 250 bool playLogo; 251 bool playVideo; 252 253 bool active; 254 bool chosen; 255 float phaseRing, phasePage, phaseChoose, phaseSelect; 256 int index, targetIndex, pageItemIndex[PAGE_MAX]; 257 Page page, targetPage; 258 int itemsCount; 259 int playerIndex; 260 261 float titleTimer; 262 float changeTimer; 263 TR::LevelID nextLevel; // toggle result 264 ControlKey lastKey; 265 266 mat4 head; 267 268 int slot; 269 Core::Settings settings; 270 271 struct Item { 272 TR::Entity::Type type; 273 int count; 274 float angle; 275 Animation *anim; 276 277 int value; 278 vec4 params; 279 280 Array<OptionItem> optLoadSlots; 281 282 struct Desc { 283 StringID str; 284 Page page; 285 int model; 286 DescInventory::Item::Desc287 Desc() {} DescInventory::Item::Desc288 Desc(StringID str, Page page, int model) : str(str), page(page), model(model) {} 289 } desc; 290 ItemInventory::Item291 Item() : anim(NULL) {} 292 typeInventory::Item293 Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f), value(0), params(0.0f) { 294 switch (type) { 295 case TR::Entity::INV_PASSPORT : desc = Desc( STR_GAME, PAGE_OPTION, level->extra.inv.passport ); break; 296 case TR::Entity::INV_PASSPORT_CLOSED : desc = Desc( STR_GAME, PAGE_OPTION, level->extra.inv.passport_closed ); break; 297 case TR::Entity::INV_MAP : desc = Desc( STR_MAP, PAGE_INVENTORY, level->extra.inv.map ); break; 298 case TR::Entity::INV_COMPASS : desc = Desc( STR_COMPASS, PAGE_INVENTORY, level->extra.inv.compass ); break; 299 case TR::Entity::INV_STOPWATCH : desc = Desc( STR_STOPWATCH, PAGE_INVENTORY, level->extra.inv.stopwatch ); break; 300 case TR::Entity::INV_EXPLOSIVE : desc = Desc( STR_EXPLOSIVE, PAGE_INVENTORY, level->extra.inv.explosive ); break; 301 case TR::Entity::INV_HOME : desc = Desc( STR_HOME, PAGE_OPTION, level->extra.inv.home ); break; 302 case TR::Entity::INV_DETAIL : desc = Desc( STR_DETAIL, PAGE_OPTION, level->extra.inv.detail ); break; 303 case TR::Entity::INV_SOUND : desc = Desc( STR_SOUND, PAGE_OPTION, level->extra.inv.sound ); break; 304 case TR::Entity::INV_CONTROLS : desc = Desc( STR_CONTROLS, PAGE_OPTION, level->extra.inv.controls ); break; 305 case TR::Entity::INV_GAMMA : desc = Desc( STR_GAMMA, PAGE_OPTION, level->extra.inv.gamma ); break; 306 307 case TR::Entity::INV_PISTOLS : desc = Desc( STR_PISTOLS, PAGE_INVENTORY, level->extra.inv.weapons[type] ); break; 308 case TR::Entity::INV_SHOTGUN : desc = Desc( STR_SHOTGUN, PAGE_INVENTORY, level->extra.inv.weapons[type] ); break; 309 case TR::Entity::INV_MAGNUMS : desc = Desc( STR_MAGNUMS, PAGE_INVENTORY, level->extra.inv.weapons[type] ); break; 310 case TR::Entity::INV_UZIS : desc = Desc( STR_UZIS, PAGE_INVENTORY, level->extra.inv.weapons[type] ); break; 311 312 case TR::Entity::INV_AMMO_PISTOLS : desc = Desc( STR_AMMO_PISTOLS, PAGE_INVENTORY, level->extra.inv.ammo[type] ); break; 313 case TR::Entity::INV_AMMO_SHOTGUN : desc = Desc( STR_AMMO_SHOTGUN, PAGE_INVENTORY, level->extra.inv.ammo[type] ); break; 314 case TR::Entity::INV_AMMO_MAGNUMS : desc = Desc( STR_AMMO_MAGNUMS, PAGE_INVENTORY, level->extra.inv.ammo[type] ); break; 315 case TR::Entity::INV_AMMO_UZIS : desc = Desc( STR_AMMO_UZIS, PAGE_INVENTORY, level->extra.inv.ammo[type] ); break; 316 317 case TR::Entity::INV_MEDIKIT_SMALL : desc = Desc( STR_MEDI_SMALL, PAGE_INVENTORY, level->extra.inv.medikit[0] ); break; 318 case TR::Entity::INV_MEDIKIT_BIG : desc = Desc( STR_MEDI_BIG, PAGE_INVENTORY, level->extra.inv.medikit[1] ); break; 319 320 case TR::Entity::INV_PUZZLE_1 : desc = Desc( STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[0] ); break; 321 case TR::Entity::INV_PUZZLE_2 : desc = Desc( STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[1] ); break; 322 case TR::Entity::INV_PUZZLE_3 : desc = Desc( STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[2] ); break; 323 case TR::Entity::INV_PUZZLE_4 : desc = Desc( STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[3] ); break; 324 325 case TR::Entity::INV_KEY_ITEM_1 : desc = Desc( STR_KEY, PAGE_ITEMS, level->extra.inv.key[0] ); break; 326 case TR::Entity::INV_KEY_ITEM_2 : desc = Desc( STR_KEY, PAGE_ITEMS, level->extra.inv.key[1] ); break; 327 case TR::Entity::INV_KEY_ITEM_3 : desc = Desc( STR_KEY, PAGE_ITEMS, level->extra.inv.key[2] ); break; 328 case TR::Entity::INV_KEY_ITEM_4 : desc = Desc( STR_KEY, PAGE_ITEMS, level->extra.inv.key[3] ); break; 329 330 case TR::Entity::INV_LEADBAR : desc = Desc( STR_LEAD_BAR, PAGE_ITEMS, level->extra.inv.leadbar ); break; 331 case TR::Entity::INV_SCION : desc = Desc( STR_SCION, PAGE_ITEMS, level->extra.inv.scion ); break; 332 default : desc = Desc( STR_UNKNOWN, PAGE_ITEMS, -1 ); break; 333 } 334 335 if (desc.model > -1 && level->models[desc.model].animation != 0xFFFF) { 336 anim = new Animation(level, &level->models[desc.model]); 337 anim->isEnded = true; 338 } else 339 anim = NULL; 340 } 341 ~ItemInventory::Item342 ~Item() { 343 delete anim; 344 } 345 346 Item& operator = (Item &item) { 347 memcpy(this, &item, sizeof(item)); 348 item.anim = NULL; 349 return *this; 350 } 351 resetInventory::Item352 void reset() { 353 if (anim) { 354 anim->setAnim(0, 0, false); 355 anim->isEnded = true; 356 } 357 } 358 initLoadSlotsInventory::Item359 void initLoadSlots(TR::Level *level) { 360 optLoadSlots.clear(); 361 optLoadSlots.push(OptionItem( OptionItem::TYPE_TITLE, STR_SELECT_LEVEL )); 362 optLoadSlots.push(OptionItem( )); 363 364 for (int i = 0; i < saveSlots.length; i++) { 365 const SaveSlot &slot = saveSlots[i]; 366 367 TR::LevelID id = slot.getLevelID(); 368 369 if (TR::getGameVersionByLevel(id) != (level->version & TR::VER_VERSION)) 370 continue; 371 372 OptionItem item; 373 item.type = OptionItem::TYPE_BUTTON; 374 item.offset = slot.isCheckpoint() ? STR_CURRENT_POSITION : TR::LEVEL_INFO[id].title; 375 item.color = i; // color as slot index 376 optLoadSlots.push(item); 377 } 378 /* 379 #ifdef _DEBUG 380 int passportSlotCount = 0; 381 TR::LevelID passportSlots[32]; 382 383 switch (level->version & TR::VER_VERSION) { 384 case TR::VER_TR1 : 385 #ifdef _OS_WEB 386 passportSlotCount = 2; 387 passportSlots[0] = TR::LVL_TR1_1; 388 passportSlots[1] = TR::LVL_TR1_2; 389 #else 390 passportSlotCount = 0; 391 for (int i = TR::LVL_TR1_1; i <= TR::LVL_TR1_10C; i++) 392 if (!TR::isCutsceneLevel(TR::LevelID(i))) { 393 passportSlots[passportSlotCount++] = TR::LevelID(i); 394 } 395 #endif 396 break; 397 case TR::VER_TR2 : 398 #ifdef _OS_WEB 399 passportSlotCount = 2; 400 passportSlots[0] = TR::LVL_TR2_WALL; 401 passportSlots[1] = TR::LVL_TR2_BOAT; 402 #else 403 passportSlotCount = 0; 404 for (int i = TR::LVL_TR2_WALL; i <= TR::LVL_TR2_HOUSE; i++) 405 if (!TR::isCutsceneLevel(TR::LevelID(i))) { 406 passportSlots[passportSlotCount++] = TR::LevelID(i); 407 } 408 #endif 409 break; 410 case TR::VER_TR3 : 411 #ifdef _OS_WEB 412 passportSlotCount = 1; 413 passportSlots[0] = TR::LVL_TR3_JUNGLE; 414 #else 415 passportSlotCount = 0; 416 for (int i = TR::LVL_TR3_JUNGLE; i <= TR::LVL_TR3_STPAUL; i++) 417 if (!TR::isCutsceneLevel(TR::LevelID(i))) { 418 passportSlots[passportSlotCount++] = TR::LevelID(i); 419 } 420 #endif 421 break; 422 default : ASSERT(false); 423 } 424 425 for (int i = 0; i < passportSlotCount; i++) { 426 OptionItem item; 427 item.type = OptionItem::TYPE_BUTTON; 428 item.offset = intptr_t(TR::LEVEL_INFO[passportSlots[i]].title); // offset as int pointer to level title string 429 item.color = -passportSlots[i]; // color as level ID 430 optLoadSlots.push(item); 431 } 432 #endif 433 */ 434 } 435 getOptionsInventory::Item436 const OptionItem* getOptions(int &optCount) const { 437 switch (type) { 438 case TR::Entity::INV_PASSPORT : 439 if (value != 0) return NULL; 440 optCount = optLoadSlots.length; 441 return optLoadSlots.items; 442 case TR::Entity::INV_DETAIL : 443 optCount = COUNT(optDetail); 444 return optDetail; 445 case TR::Entity::INV_SOUND : 446 optCount = COUNT(optSound); 447 return optSound; 448 case TR::Entity::INV_CONTROLS : { 449 for (int i = 0; i < COUNT(optControls); i++) { 450 OptionItem &opt = optControlsPlayer[i]; 451 opt = optControls[i]; 452 453 if (i > INV_CTRL_START_OPTION && opt.offset != SETTINGS( playerIndex ) && opt.offset != SETTINGS( ctrlIndex ) ) 454 opt.offset += sizeof(Core::Settings::Controls) * Core::settings.playerIndex; 455 456 if (opt.type == OptionItem::TYPE_KEY) { 457 if (Core::settings.ctrlIndex == 1) { 458 opt.offset++; // add offset to joy 459 opt.color = STR_JOY_FIRST; 460 } else 461 opt.color = STR_KEY_FIRST; 462 } 463 } 464 optCount = COUNT(optControlsPlayer); 465 return optControlsPlayer; 466 } 467 default : 468 optCount = 0; 469 return NULL; 470 } 471 } 472 getSettingsInventory::Item473 Core::Settings& getSettings(Inventory *inv) const { 474 return (type == TR::Entity::INV_SOUND || type == TR::Entity::INV_CONTROLS) ? Core::settings : inv->settings; 475 } 476 nextSlotInventory::Item477 void nextSlot(int &slot, int dir) { 478 int optCount; 479 const OptionItem *options = getOptions(optCount); 480 if (!options) return; 481 482 int rep = 0; 483 do { 484 slot = (slot + dir + optCount) % optCount; 485 // check for looping (no available slot) 486 if (slot == 0) { 487 rep++; 488 if (rep > 1) { 489 slot = -1; 490 return; 491 } 492 } 493 } while (options[slot].type == OptionItem::TYPE_TITLE || options[slot].type == OptionItem::TYPE_EMPTY); 494 } 495 controlInventory::Item496 const OptionItem* control(int &slot, ControlKey key, float &timer, Core::Settings *settings) { 497 int optCount; 498 const OptionItem *opt = getOptions(optCount); 499 if (!opt) return NULL; 500 501 opt = opt + slot; 502 503 uint8 &value = *((uint8*)settings + opt->offset); 504 505 switch (key) { 506 case cAction : return (opt->type == OptionItem::TYPE_BUTTON || opt->type == OptionItem::TYPE_KEY) ? opt : NULL; 507 case cUp : nextSlot(slot, -1); break; 508 case cDown : nextSlot(slot, +1); break; 509 case cLeft : 510 if (opt->type == OptionItem::TYPE_PARAM && (opt->maxValue != 0xFF) && opt->checkValue(value - 1)) { 511 value--; 512 timer = 0.2f; 513 return opt; 514 } 515 break; 516 case cRight : 517 if (opt->type == OptionItem::TYPE_PARAM && (opt->maxValue != 0xFF) && opt->checkValue(value + 1)) { 518 value++; 519 timer = 0.2f; 520 return opt; 521 } 522 break; 523 default : ; 524 } 525 526 return NULL; 527 } 528 updateInventory::Item529 void update(bool chosen, float phaseChoose) { 530 if (!anim) return; 531 anim->update(); 532 533 if (type == TR::Entity::INV_PASSPORT && chosen) { 534 float t = (14 + value * 5) / 30.0f; 535 536 if ( (anim->dir > 0.0f && anim->time > t) || 537 (anim->dir < 0.0f && anim->time < t)) { 538 anim->dir = 0.0f; 539 anim->time = t; 540 anim->updateInfo(); 541 } 542 } 543 544 if (type == TR::Entity::INV_COMPASS) { 545 params.z = params.z + (params.y - params.x) * Core::deltaTime * 8.0f; // acceleration 546 params.z -= params.z * Core::deltaTime; // damping 547 params.x += params.z * Core::deltaTime; // apply speed 548 if (chosen && anim->dir > 0.0f) { 549 if (phaseChoose >= 1.0f) { 550 float t = 7.0f / 30.0f; 551 if (anim->time > t) { 552 anim->dir = 0.0f; 553 anim->time = t; 554 anim->updateInfo(); 555 } 556 } else { 557 anim->time = 0.0f; 558 anim->updateInfo(); 559 } 560 } 561 } 562 } 563 renderInventory::Item564 void render(IGame *game, const Basis &basis) { 565 if (!anim) return; 566 567 TR::Level *level = game->getLevel(); 568 TR::Model &m = level->models[desc.model]; 569 Basis joints[MAX_JOINTS]; 570 571 mat4 matrix; 572 matrix.identity(); 573 matrix.setRot(basis.rot); 574 matrix.setPos(basis.pos); 575 576 anim->getJoints(matrix, -1, true, joints); 577 578 if (m.type == TR::Entity::INV_COMPASS) { // override needle animation 579 joints[1].rotate(quat(vec3(0.0f, 1.0f, 0.0f), -params.x)); 580 } 581 582 game->renderModelFull(desc.model, false, joints); 583 } 584 chooseInventory::Item585 void choose() { 586 if (!anim) return; 587 anim->setAnim(0, 0, false); 588 } 589 590 } *items[INV_MAX_ITEMS]; 591 loadTitleBGInventory592 static void loadTitleBG(Stream *stream, void *userData) { 593 Inventory *inv = (Inventory*)userData; 594 595 if (!inv->video) 596 inv->skipVideo(); // play background track etc. 597 598 if (!stream) { 599 inv->titleTimer = 0.0f; 600 return; 601 } 602 inv->titleTimer = inv->game->getLevel()->isTitle() ? 0.0f : 3.0f; 603 604 inv->title = Texture::Load(*stream); 605 inv->background[0] = inv->title; 606 delete stream; 607 } 608 loadVideoInventory609 static void loadVideo(Stream *stream, void *userData) { 610 Inventory *inv = (Inventory*)userData; 611 TR::LevelID id = inv->game->getLevel()->id; 612 if (stream) { 613 inv->video = new Video(stream, id); 614 UI::showSubs(getVideoSubs(id)); 615 } 616 new Stream(TR::getGameScreen(id), loadTitleBG, inv); 617 } 618 loadLogoInventory619 static void loadLogo(Stream *stream, void *userData) { 620 Inventory *inv = (Inventory*)userData; 621 if (stream) { 622 inv->video = new Video(stream, TR::LVL_CUSTOM); 623 } else { 624 inv->skipVideo(); 625 } 626 } 627 InventoryInventory628 Inventory() : game(NULL), title(NULL), itemsCount(0) { 629 memset(background, 0, sizeof(background)); 630 reset(); 631 } 632 ~InventoryInventory633 ~Inventory() { 634 delete video; 635 clear(); 636 } 637 clearInventory638 void clear() { 639 for (int i = 0; i < itemsCount; i++) 640 delete items[i]; 641 itemsCount = 0; 642 643 if (background[0] == title) { 644 background[0] = NULL; 645 } 646 647 for (int i = 0; i < COUNT(background); i++) { 648 delete background[i]; 649 background[i] = NULL; 650 } 651 652 delete title; 653 title = NULL; 654 } 655 resetInventory656 void reset() { 657 clear(); 658 active = false; 659 chosen = false; 660 index = targetIndex = 0; 661 page = targetPage = PAGE_OPTION; 662 663 playerIndex = 0; 664 changeTimer = 0.0f; 665 nextLevel = TR::LVL_MAX; 666 lastKey = cMAX; 667 668 phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f; 669 memset(pageItemIndex, 0, sizeof(pageItemIndex)); 670 671 waitForKey = NULL; 672 video = NULL; 673 674 titleTimer = TITLE_LOADING; 675 676 if (!game) return; 677 678 TR::Level *level = game->getLevel(); 679 680 add(TR::Entity::INV_PASSPORT); 681 add(TR::Entity::INV_DETAIL); 682 add(TR::Entity::INV_SOUND); 683 add(TR::Entity::INV_CONTROLS); 684 685 if (!level->isTitle() && !level->isCutsceneLevel() && !level->isHome()) { 686 if (!TR::isEmptyLevel(level->id)) { 687 add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO); 688 } 689 690 if (level->id == TR::LVL_TR2_HOUSE) { 691 add(TR::Entity::INV_KEY_ITEM_1); 692 add(TR::Entity::INV_PUZZLE_1); 693 } 694 #ifdef _DEBUG 695 addWeapons(); 696 add(TR::Entity::MEDIKIT_BIG); 697 add(TR::Entity::MEDIKIT_SMALL, 2); 698 add(TR::Entity::INV_KEY_ITEM_1, 3); 699 add(TR::Entity::INV_KEY_ITEM_2, 3); 700 add(TR::Entity::INV_KEY_ITEM_3, 3); 701 add(TR::Entity::INV_KEY_ITEM_4, 3); 702 703 add(TR::Entity::INV_PUZZLE_1, 3); 704 add(TR::Entity::INV_PUZZLE_2, 3); 705 add(TR::Entity::INV_PUZZLE_3, 3); 706 add(TR::Entity::INV_PUZZLE_4, 3); 707 708 add(TR::Entity::INV_EXPLOSIVE); 709 add(TR::Entity::INV_LEADBAR, 3); 710 #endif 711 } 712 713 if (level->isTitle()) { 714 add(TR::Entity::INV_HOME); 715 } else { 716 add(TR::Entity::INV_COMPASS); 717 add(TR::Entity::INV_STOPWATCH); 718 } 719 } 720 addWeaponsInventory721 void addWeapons() { 722 TR::Level *level = game->getLevel(); 723 if (level->isTitle() || level->isCutsceneLevel() || level->isHome()) 724 return; 725 726 if (level->version & TR::VER_TR1) { 727 add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO); 728 add(TR::Entity::INV_SHOTGUN, 250); 729 add(TR::Entity::INV_MAGNUMS, 20); 730 add(TR::Entity::INV_UZIS, 100); 731 } 732 } 733 startVideoInventory734 void startVideo() { 735 applySounds(true); 736 new Stream(playVideo ? TR::getGameVideo(game->getLevel()->id) : NULL, loadVideo, this); 737 } 738 initInventory739 void init(bool playLogo, bool playVideo) { 740 active = false; 741 phaseRing = 0.0f; 742 743 this->playLogo = playLogo; 744 this->playVideo = playVideo; 745 746 if (playLogo) { 747 new Stream(TR::getGameLogo(game->getLevel()->version), loadLogo, this); 748 return; 749 } 750 751 if (playVideo) 752 startVideo(); 753 else 754 new Stream(TR::getGameScreen(game->getLevel()->id), loadTitleBG, this); 755 } 756 isActiveInventory757 bool isActive() { 758 return active || phaseRing > 0.0f; 759 } 760 containsInventory761 int contains(TR::Entity::Type type) { 762 type = TR::Level::convToInv(type); 763 for (int i = 0; i < itemsCount; i++) 764 if (items[i]->type == type) 765 return i; 766 return -1; 767 } 768 addAmmoInventory769 void addAmmo(TR::Entity::Type &type, int &count, int clip, TR::Entity::Type wpnType, TR::Entity::Type ammoType) { 770 if (type == wpnType) { 771 count *= clip; 772 int index = contains(ammoType); 773 if (index > -1) { 774 count += items[index]->count * clip; 775 remove(index); 776 } 777 } else { 778 if (contains(wpnType) > -1) { 779 type = wpnType; 780 count *= clip; 781 } 782 } 783 } 784 785 void add(TR::Entity::Type type, int count = 1, bool smart = true) { 786 type = TR::Level::convToInv(type); 787 788 if (smart) { 789 switch (type) { 790 case TR::Entity::INV_PISTOLS : 791 case TR::Entity::INV_AMMO_PISTOLS : 792 count = UNLIMITED_AMMO; // pistols always has unlimited ammo 793 addAmmo(type, count, 10, TR::Entity::INV_PISTOLS, TR::Entity::INV_AMMO_PISTOLS); 794 break; 795 case TR::Entity::INV_SHOTGUN : 796 case TR::Entity::INV_AMMO_SHOTGUN : 797 addAmmo(type, count, 2, TR::Entity::INV_SHOTGUN, TR::Entity::INV_AMMO_SHOTGUN); 798 break; 799 case TR::Entity::INV_MAGNUMS : 800 case TR::Entity::INV_AMMO_MAGNUMS : 801 addAmmo(type, count, 25, TR::Entity::INV_MAGNUMS, TR::Entity::INV_AMMO_MAGNUMS); 802 break; 803 case TR::Entity::INV_UZIS : 804 case TR::Entity::INV_AMMO_UZIS : 805 addAmmo(type, count, 50, TR::Entity::INV_UZIS, TR::Entity::INV_AMMO_UZIS); 806 break; 807 default : ; 808 } 809 } 810 811 int i = contains(type); 812 if (i > -1) { 813 items[i]->count += count; 814 items[i]->count = min(UNLIMITED_AMMO, items[i]->count); 815 return; 816 } 817 818 ASSERT(itemsCount < INV_MAX_ITEMS); 819 820 count = min(UNLIMITED_AMMO, count); 821 822 Item *newItem = new Item(game->getLevel(), type, count); 823 if (newItem->desc.model == -1) { 824 delete newItem; 825 return; 826 } 827 828 int pos = 0; 829 for (; pos < itemsCount; pos++) 830 if (items[pos]->type > type) 831 break; 832 833 if (pos - itemsCount) { 834 for (int i = itemsCount; i > pos; i--) 835 items[i] = items[i - 1]; 836 } 837 838 items[pos] = newItem; 839 itemsCount++; 840 } 841 getCountPtrInventory842 int* getCountPtr(TR::Entity::Type type) { 843 int i = contains(type); 844 if (i < 0) return NULL; 845 return &items[i]->count; 846 } 847 848 void remove(TR::Entity::Type type, int count = 1) { 849 int index = contains(type); 850 if (index == -1) return; 851 852 items[index]->count -= count; 853 if (items[index]->count <= 0) 854 remove(index); 855 } 856 removeInventory857 void remove(int index) { 858 delete items[index]; 859 for (int i = index; i < itemsCount - 1; i++) 860 items[i] = items[i + 1]; 861 itemsCount--; 862 } 863 chooseKeyInventory864 bool chooseKey(int playerIndex, TR::Entity::Type hole) { 865 TR::Entity::Type type = TR::Entity::getItemForHole(hole); 866 if (type == TR::Entity::NONE) 867 return false; 868 int index = contains(type); 869 if (index < 0) 870 return false; 871 toggle(playerIndex, items[index]->desc.page, type); 872 return true; 873 } 874 useInventory875 bool use(TR::Entity::Type type) { 876 if (contains(type) > -1) { 877 remove(type); 878 return true; 879 } 880 return false; 881 } 882 applySoundsInventory883 void applySounds(bool pause) { 884 for (int i = 0; i < Sound::channelsCount; i++) 885 if (Sound::channels[i]->flags & Sound::PAN) { 886 if (pause) 887 Sound::channels[i]->pause(); 888 else 889 Sound::channels[i]->resume(); 890 } 891 } 892 893 void toggle(int playerIndex = 0, Page curPage = PAGE_INVENTORY, TR::Entity::Type type = TR::Entity::NONE) { 894 if (titleTimer != 0.0f || (isActive() != active)) 895 return; 896 897 Input::stopJoyVibration(); 898 899 this->playerIndex = playerIndex; 900 titleTimer = 0.0f; 901 902 if (phaseRing == 0.0f || phaseRing == 1.0f) { 903 active = !active; 904 vec3 p; 905 906 applySounds(active); 907 908 if (curPage == PAGE_SAVEGAME) { 909 phaseRing = active ? 1.0f : 0.0f; 910 slot = 1; 911 } else { 912 if (curPage != PAGE_LEVEL_STATS) 913 game->playSound(active ? TR::SND_INV_SHOW : TR::SND_INV_HIDE, p); 914 } 915 916 chosen = false; 917 918 if (active) { 919 if (curPage != PAGE_LEVEL_STATS) { 920 for (int i = 0; i < itemsCount; i++) 921 items[i]->reset(); 922 } 923 924 nextLevel = TR::LVL_MAX; 925 phasePage = 1.0f; 926 phaseSelect = 1.0f; 927 page = targetPage = curPage; 928 929 if (type != TR::Entity::NONE) { 930 int i = contains(type); 931 if (i >= 0) 932 pageItemIndex[page] = getLocalIndex(i); 933 } 934 935 index = targetIndex = pageItemIndex[page]; 936 937 head.e00 = INF; // mark head matrix as unset 938 939 //if (type == TR::Entity::INV_PASSPORT) // toggle after death 940 // chooseItem(); 941 } 942 } 943 } 944 doPhaseInventory945 void doPhase(bool increase, float speed, float &value) { 946 if (increase) { 947 if (value < 1.0f) { 948 value += Core::deltaTime * speed; 949 if (value > 1.0f) 950 value = 1.0f; 951 } 952 } else { 953 if (value > 0.0f) { 954 value -= Core::deltaTime * speed; 955 if (value < 0.0f) 956 value = 0.0f; 957 } 958 } 959 } 960 getGlobalIndexInventory961 int getGlobalIndex(Page page, int localIndex) { 962 for (int i = 0; i < itemsCount; i++) 963 if (items[i]->desc.page == page) { 964 if (!localIndex) 965 return i; 966 localIndex--; 967 } 968 return 0; 969 } 970 getLocalIndexInventory971 int getLocalIndex(int globalIndex) { 972 Page page = items[globalIndex]->desc.page; 973 974 int idx = 0; 975 for (int i = 0; i < globalIndex; i++) 976 if (items[i]->desc.page == page) 977 idx++; 978 979 return idx; 980 } 981 getAngleInventory982 float getAngle(int index, int count) { 983 return PI * 2.0f / float(count) * index; 984 } 985 getItemsCountInventory986 int getItemsCount(int page) { 987 int count = 0; 988 989 for (int i = 0; i < itemsCount; i++) 990 if (items[i]->desc.page == page) 991 count++; 992 993 return count; 994 } 995 showHealthBarInventory996 bool showHealthBar() { 997 if (!itemsCount) return false; 998 int idx = getGlobalIndex(page, index); 999 TR::Entity::Type type = items[idx]->type; 1000 return active && phaseRing == 1.0f && index == targetIndex && phasePage == 1.0f && (type == TR::Entity::INV_MEDIKIT_SMALL || type == TR::Entity::INV_MEDIKIT_BIG); 1001 } 1002 onChooseInventory1003 void onChoose(Item *item) { 1004 TR::Level *level = game->getLevel(); 1005 1006 switch (item->type) { 1007 case TR::Entity::INV_PASSPORT : { 1008 game->playSound(TR::SND_INV_PAGE); 1009 item->value = 1; 1010 item->initLoadSlots(level); 1011 break; 1012 } 1013 case TR::Entity::INV_COMPASS : { 1014 float angle = normalizeAngle(game->getLara(playerIndex)->angle.y); 1015 item->params = vec4(angle + (randf() - 0.5f) * PI * 2.0f, angle, 0.0f, 0.0f); // initial angle, target angle, initial speed, unused 1016 break; 1017 } 1018 case TR::Entity::INV_CONTROLS : 1019 Core::settings.playerIndex = 0; 1020 #ifdef INV_GAMEPAD_ONLY 1021 Core::settings.ctrlIndex = 1; 1022 #else 1023 Core::settings.ctrlIndex = 0; 1024 #endif 1025 break; 1026 case TR::Entity::INV_DETAIL : 1027 settings = Core::settings; 1028 break; 1029 default : ; 1030 } 1031 1032 slot = -1; 1033 item->nextSlot(slot, 1); 1034 } 1035 controlItemInventory1036 void controlItem(Item *item, ControlKey key) { 1037 TR::Level *level = game->getLevel(); 1038 1039 if (item->type == TR::Entity::INV_PASSPORT) { 1040 // passport pages 1041 int oldValue = item->value; 1042 if (key == cLeft && item->value > 0) { item->value--; item->anim->dir = -1.0f; game->playSound(TR::SND_INV_PAGE); } 1043 if (key == cRight && item->value < 2) { item->value++; item->anim->dir = 1.0f; game->playSound(TR::SND_INV_PAGE); } 1044 1045 if (item->value != oldValue) { 1046 slot = 0; 1047 item->nextSlot(slot, -1); 1048 } 1049 1050 if (key == cAction && phaseChoose == 1.0f && item->value != 0) { 1051 TR::LevelID id = level->id; 1052 switch (item->value) { 1053 case 1 : { 1054 if (level->isTitle()) { // start new game 1055 nextLevel = level->getStartId(); 1056 } else { // restart level 1057 int slot = getSaveSlot(id, false); 1058 if (slot > -1) 1059 game->loadGame(slot); 1060 else 1061 nextLevel = id; 1062 toggle(); 1063 } 1064 break; 1065 } 1066 case 2 : 1067 if (!level->isTitle()) 1068 nextLevel = level->getTitleId(); 1069 else 1070 Core::quit(); // exit game 1071 break; 1072 } 1073 1074 if (nextLevel != TR::LVL_MAX) { 1075 item->anim->dir = -1.0f; 1076 item->value = -100; 1077 toggle(); 1078 } 1079 } 1080 } 1081 1082 Core::Settings &stg = item->getSettings(this); 1083 1084 const OptionItem *opt = item->control(slot, key, changeTimer, &stg); 1085 if (opt) 1086 optionChanged(item, opt, stg); 1087 1088 if (item->type == TR::Entity::INV_HOME && phaseChoose == 1.0f && key == cAction) { 1089 nextLevel = level->getHomeId(); 1090 toggle(); 1091 } 1092 } 1093 optionChangedInventory1094 void optionChanged(Item *item, const OptionItem *opt, Core::Settings &settings) { 1095 if (item->type == TR::Entity::INV_PASSPORT) { 1096 #ifdef _DEBUG 1097 if (int(opt->color) < 0) 1098 nextLevel = TR::LevelID(-int(opt->color)); 1099 else 1100 #endif 1101 game->loadGame(opt->color); 1102 item->anim->dir = -1.0f; 1103 item->value = -100; 1104 toggle(); 1105 } 1106 1107 if (item->type == TR::Entity::INV_SOUND) { 1108 game->applySettings(settings); 1109 if (opt->offset == SETTINGS( audio.sound ) ) 1110 game->playSound(TR::SND_PISTOLS_SHOT); 1111 } 1112 1113 if (item->type == TR::Entity::INV_CONTROLS && opt->type == OptionItem::TYPE_KEY) { 1114 waitForKey = opt; 1115 Input::lastKey = ikNone; 1116 Input::joy[Core::settings.controls[Core::settings.playerIndex].joyIndex].lastKey = jkNone; 1117 } 1118 1119 if (item->type == TR::Entity::INV_SOUND || item->type == TR::Entity::INV_CONTROLS) { 1120 game->applySettings(settings); 1121 } 1122 1123 if (item->type == TR::Entity::INV_DETAIL && opt->title == STR_APPLY) { 1124 game->applySettings(settings); 1125 chosen = false; 1126 } 1127 }; 1128 skipVideoInventory1129 void skipVideo() { 1130 delete video; 1131 video = NULL; 1132 1133 if (playLogo) { 1134 playLogo = false; 1135 if (playVideo) { 1136 startVideo(); 1137 return; 1138 } 1139 } 1140 playVideo = false; 1141 UI::showSubs(STR_EMPTY); 1142 game->playTrack(0); 1143 if (game->getLevel()->isTitle()) { 1144 titleTimer = 0.0f; 1145 toggle(0, Inventory::PAGE_OPTION); 1146 } 1147 Input::reset(); 1148 applySounds(false); 1149 } 1150 updateInventory1151 void update() { 1152 if (Input::lastState[0] == cInventory || Input::lastState[0] == cAction || 1153 Input::lastState[1] == cInventory || Input::lastState[1] == cAction) 1154 { 1155 if (video) { 1156 skipVideo(); 1157 } else if (titleTimer > 1.0f && titleTimer < 2.5f) { 1158 titleTimer = 1.0f; 1159 } 1160 } 1161 1162 if (video) { 1163 video->update(); 1164 if (video->isPlaying) 1165 return; 1166 skipVideo(); 1167 } 1168 1169 if (video || titleTimer == TITLE_LOADING) return; 1170 1171 if (titleTimer != TITLE_LOADING && titleTimer > 0.0f) { 1172 titleTimer -= Core::deltaTime; 1173 if (titleTimer < 0.0f) 1174 titleTimer = 0.0f; 1175 } 1176 1177 if (!isActive()) { 1178 lastKey = cMAX; 1179 return; 1180 } 1181 1182 float lastChoose = phaseChoose; 1183 1184 if (phaseChoose == 0.0f) 1185 doPhase(active, 2.0f, phaseRing); 1186 doPhase(true, 1.6f, phasePage); 1187 doPhase(chosen, 4.0f, phaseChoose); 1188 doPhase(true, 2.5f, phaseSelect); 1189 1190 if (phaseChoose == 1.0f && lastChoose != 1.0f) 1191 onChoose(items[getGlobalIndex(page, index)]); 1192 1193 if (page != targetPage && phasePage == 1.0f) { 1194 page = targetPage; 1195 index = targetIndex = pageItemIndex[page]; 1196 } 1197 1198 if (index != targetIndex && phaseSelect == 1.0f) 1199 index = pageItemIndex[page] = targetIndex; 1200 1201 int count = getItemsCount(page); 1202 1203 bool ready = active && phaseRing == 1.0f && phasePage == 1.0f; 1204 1205 Input::Joystick &joy = Input::joy[Core::settings.controls[playerIndex].joyIndex]; 1206 Input::Joystick &joyMain = Input::joy[0]; 1207 1208 ControlKey key = cMAX; 1209 if (Input::down[ikCtrl] || Input::down[ikEnter] || Input::lastState[playerIndex] == cAction || joy.down[jkA]) 1210 key = cAction; 1211 else if (Input::down[ikAlt] || joy.down[jkB]) 1212 key = cInventory; 1213 else if (Input::down[ikLeft] || joy.down[jkLeft] || joy.L.x < -0.5f || joyMain.down[jkLeft] || joyMain.L.x < -0.5f) 1214 key = cLeft; 1215 else if (Input::down[ikRight] || joy.down[jkRight] || joy.L.x > 0.5f || joyMain.down[jkRight] || joyMain.L.x > 0.5f) 1216 key = cRight; 1217 else if (Input::down[ikUp] || joy.down[jkUp] || joy.L.y < -0.5f) 1218 key = cUp; 1219 else if (Input::down[ikDown] || joy.down[jkDown] || joy.L.y > 0.5f) 1220 key = cDown; 1221 1222 #if defined(_OS_SWITCH) || defined(_OS_3DS) || defined(_OS_GCW0) 1223 // swap A/B keys for Nintendo (Japanese) UX style 1224 if (Input::touchTimerVis == 0.0f) { 1225 if (key == cAction) { 1226 key = cInventory; 1227 } else if (key == cInventory) { 1228 key = cAction; 1229 } 1230 } 1231 #endif 1232 1233 if (Input::lastState[playerIndex] == cInventory) 1234 key = cInventory; 1235 1236 Item *item = items[getGlobalIndex(page, index)]; 1237 1238 if (page == PAGE_LEVEL_STATS) { 1239 if (Input::lastState[playerIndex] != cMAX) { 1240 toggle(playerIndex, targetPage); 1241 } 1242 } else if (page == PAGE_SAVEGAME) { 1243 if (Input::lastState[playerIndex] == cLeft || Input::lastState[playerIndex] == cRight) 1244 slot ^= 1; 1245 1246 if (key == cAction) { 1247 if (slot == 1) { 1248 if (index > -1) { 1249 TR::Entity &e = game->getLevel()->entities[index]; 1250 Controller *controller = (Controller*)e.controller; 1251 controller->deactivate(true); 1252 } 1253 game->saveGame(game->getLevel()->id, index > -1, false); 1254 } 1255 toggle(playerIndex, targetPage); 1256 } 1257 1258 } else if (index == targetIndex && targetPage == page && ready) { 1259 if (!chosen) { 1260 if ((key == cUp && !canFlipPage(-1)) || (key == cDown && !canFlipPage( 1))) 1261 key = cMAX; 1262 1263 switch (key) { 1264 case cLeft : { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; } break; 1265 case cRight : { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; } break; 1266 case cUp : { phasePage = 0.0f; targetPage = Page(page + 1); } break; 1267 case cDown : { phasePage = 0.0f; targetPage = Page(page - 1); } break; 1268 default : ; 1269 } 1270 1271 if (index != targetIndex) 1272 game->playSound(TR::SND_INV_SPIN); 1273 1274 if (lastKey != key && key == cAction && phaseChoose == 0.0f) 1275 chooseItem(); 1276 } else { 1277 if (changeTimer > 0.0f) { 1278 changeTimer -= Core::deltaTime; 1279 if (changeTimer <= 0.0f) { 1280 changeTimer = 0.0f; 1281 lastKey = cMAX; 1282 } 1283 } 1284 1285 if (waitForKey) { 1286 int newKey = -1; 1287 if (Core::settings.ctrlIndex == 0 && Input::lastKey != ikNone) { 1288 newKey = Input::lastKey; 1289 } else { 1290 JoyKey jk = Input::joy[Core::settings.controls[Core::settings.playerIndex].joyIndex].lastKey; 1291 if (Core::settings.ctrlIndex == 1 && jk != jkNone) { 1292 newKey = jk; 1293 } else if (Input::lastKey != ikNone) { 1294 waitForKey = NULL; 1295 } 1296 } 1297 1298 if (newKey != -1) { 1299 waitForKey->setValue(newKey, &Core::settings); 1300 waitForKey = NULL; 1301 lastKey = key; 1302 game->applySettings(Core::settings); 1303 } 1304 } 1305 1306 if (key != cMAX && lastKey != key && changeTimer == 0.0f && phaseChoose == 1.0f) { 1307 controlItem(item, key); 1308 } 1309 } 1310 1311 if ((key == cInventory || key == cJump) && lastKey != key) { 1312 lastKey = key; 1313 if (chosen) { 1314 if (phaseChoose == 1.0f) { 1315 chosen = false; 1316 item->anim->dir = 1.0f; 1317 item->angle = 0.0f; 1318 } 1319 } else 1320 if (!game->getLevel()->isTitle()) 1321 toggle(playerIndex, targetPage); 1322 } 1323 } 1324 lastKey = key; 1325 1326 if (page == PAGE_SAVEGAME || page == PAGE_LEVEL_STATS) 1327 return; 1328 1329 ready = active && phaseRing == 1.0f && phasePage == 1.0f; 1330 1331 float w = 90.0f * DEG2RAD * Core::deltaTime; 1332 1333 int itemIndex = index == targetIndex ? getGlobalIndex(page, index) : -1; 1334 1335 for (int i = 0; i < itemsCount; i++) { 1336 items[i]->update(chosen && itemIndex == i, phaseChoose); 1337 float &angle = items[i]->angle; 1338 1339 if (itemIndex != i || chosen) { 1340 if (angle == 0.0f) { 1341 continue; 1342 } else if (angle < 0.0f) { 1343 angle += w; 1344 if (angle > 0.0f) 1345 angle = 0.0f; 1346 } else if (angle > 0.0f) { 1347 angle -= w; 1348 if (angle < 0.0f) 1349 angle = 0.0f; 1350 } 1351 } else 1352 angle += w; 1353 1354 angle = clampAngle(angle); 1355 } 1356 1357 if (ready && chosen && phaseChoose == 1.0f && item->anim->isEnded) { 1358 TR::Entity::Type type = item->type; 1359 1360 switch (type) { 1361 case TR::Entity::INV_PASSPORT : 1362 case TR::Entity::INV_PASSPORT_CLOSED : 1363 case TR::Entity::INV_MAP : 1364 case TR::Entity::INV_COMPASS : 1365 case TR::Entity::INV_STOPWATCH : 1366 case TR::Entity::INV_HOME : 1367 case TR::Entity::INV_DETAIL : 1368 case TR::Entity::INV_SOUND : 1369 case TR::Entity::INV_CONTROLS : 1370 case TR::Entity::INV_GAMMA : 1371 case TR::Entity::INV_AMMO_PISTOLS : 1372 case TR::Entity::INV_AMMO_SHOTGUN : 1373 case TR::Entity::INV_AMMO_MAGNUMS : 1374 case TR::Entity::INV_AMMO_UZIS : break; 1375 default : 1376 game->invUse(playerIndex, type); 1377 toggle(); 1378 } 1379 } 1380 1381 if (!isActive() && nextLevel != TR::LVL_MAX) 1382 game->loadLevel(nextLevel); 1383 } 1384 chooseItemInventory1385 void chooseItem() { 1386 Item *item = items[getGlobalIndex(page, index)]; 1387 1388 vec3 p; 1389 chosen = true; 1390 switch (item->type) { 1391 case TR::Entity::INV_COMPASS : game->playSound(TR::SND_INV_COMPASS); break; 1392 case TR::Entity::INV_HOME : game->playSound(TR::SND_INV_HOME); break; 1393 case TR::Entity::INV_CONTROLS : game->playSound(TR::SND_INV_CONTROLS); break; 1394 case TR::Entity::INV_PISTOLS : 1395 case TR::Entity::INV_SHOTGUN : 1396 case TR::Entity::INV_MAGNUMS : 1397 case TR::Entity::INV_UZIS : game->playSound(TR::SND_INV_WEAPON); break; 1398 default : game->playSound(TR::SND_INV_SHOW); break; 1399 } 1400 item->choose(); 1401 } 1402 canFlipPageInventory1403 bool canFlipPage(int dir) { 1404 if (game->getLevel()->isTitle() || (game->getLara(playerIndex) && ((Character*)game->getLara(playerIndex))->health <= 0.0f)) 1405 return false; 1406 if (dir == -1) return page < PAGE_ITEMS && getItemsCount(page + 1); 1407 if (dir == 1) return page > PAGE_OPTION && getItemsCount(page - 1); 1408 return false; 1409 } 1410 getBackgroundTargetInventory1411 Texture* getBackgroundTarget(int view) { 1412 if (background[0] == title) { 1413 background[0] = NULL; 1414 } 1415 1416 for (int i = 0; i < COUNT(background); i++) { 1417 if (!background[i]) { 1418 background[i] = new Texture(INV_BG_SIZE, INV_BG_SIZE, 1, FMT_RGB16, OPT_TARGET); 1419 } 1420 } 1421 1422 return background[view]; 1423 } 1424 blurInventory1425 void blur(Texture *texInOut, Texture *tmp) { 1426 #ifdef _GAPI_C3D 1427 return; // TODO 1428 #endif 1429 #ifdef FFP 1430 return; // TODO 1431 #endif 1432 game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false); 1433 float s = 1.0f / INV_BG_SIZE; 1434 // vertical 1435 Core::setTarget(tmp, NULL, RT_STORE_COLOR); 1436 Core::validateRenderState(); 1437 Core::active.shader->setParam(uParam, vec4(0, s, 0, s)); 1438 texInOut->bind(sDiffuse); 1439 game->getMesh()->renderQuad(); 1440 // horizontal 1441 Core::setTarget(texInOut, NULL, RT_STORE_COLOR); 1442 Core::validateRenderState(); 1443 game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false); 1444 Core::active.shader->setParam(uParam, vec4(s, 0, 0, s)); 1445 tmp->bind(sDiffuse); 1446 game->getMesh()->renderQuad(); 1447 } 1448 grayscaleInventory1449 void grayscale(Texture *texIn, Texture *texOut) { 1450 #ifdef FFP 1451 return; // TODO 1452 #endif 1453 float s = 1.0f / INV_BG_SIZE; 1454 game->setShader(Core::passFilter, Shader::FILTER_GRAYSCALE, false, false); 1455 Core::setTarget(texOut, NULL, RT_STORE_COLOR); 1456 Core::validateRenderState(); 1457 Core::active.shader->setParam(uParam, vec4(0.75f, 0.75f, 1.0f, s)); 1458 texIn->bind(sDiffuse); 1459 game->getMesh()->renderQuad(); 1460 } 1461 prepareBackgroundInventory1462 void prepareBackground() { 1463 #ifdef FFP 1464 return; 1465 #endif 1466 1467 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) 1468 return; 1469 1470 #ifdef _GAPI_GU 1471 return; 1472 #endif 1473 1474 #ifdef _OS_3DS 1475 GAPI::rotate90 = false; 1476 #endif 1477 1478 game->renderGame(false, true); 1479 1480 Core::setDepthTest(false); 1481 Core::setBlendMode(bmNone); 1482 1483 int viewsCount = (Core::settings.detail.stereo == Core::Settings::STEREO_OFF) ? 1 : 2; 1484 1485 mat4 mProj, mView; 1486 mView.identity(); 1487 mProj = GAPI::ortho(-1, +1, -1, +1, 0, 1); 1488 mProj.scale(vec3(1.0f / 32767.0f)); 1489 Core::setViewProj(mView, mProj); 1490 1491 for (int view = 0; view < viewsCount; view++) { 1492 blur(background[view], background[2]); 1493 grayscale(background[view], background[2]); 1494 swap(background[view], background[2]); 1495 } 1496 1497 #ifdef _OS_3DS 1498 GAPI::rotate90 = true; 1499 #endif 1500 1501 Core::setDepthTest(true); 1502 } 1503 getEyeOffsetInventory1504 float getEyeOffset() { 1505 return -Core::eye * INV_EYE_SEPARATION * 0.75f; 1506 } 1507 renderItemCountInventory1508 void renderItemCount(const Item *item, const vec2 &pos, float width) { 1509 char spec; 1510 switch (item->type) { 1511 case TR::Entity::INV_SHOTGUN : spec = 12; break; 1512 case TR::Entity::INV_MAGNUMS : spec = 13; break; 1513 case TR::Entity::INV_UZIS : spec = 14; break; 1514 default : spec = 0; 1515 } 1516 1517 if ((item->count > 1 || spec) && item->count < UNLIMITED_AMMO) { 1518 char buf[16]; 1519 sprintf(buf, "%d %c", item->count, spec); 1520 for (int i = 0; buf[i] != ' '; i++) 1521 buf[i] -= 47; 1522 UI::textOut(pos, buf, UI::aRight, width, 255, UI::SHADE_NONE); 1523 } 1524 } 1525 renderPassportInventory1526 void renderPassport(Item *item) { 1527 if (item->anim->dir != 0.0f) return; // check for "Load Game" page 1528 1529 StringID str = STR_LOAD_GAME; 1530 1531 if (game->getLevel()->isTitle()) { 1532 if (item->value == 1) str = STR_START_GAME; 1533 if (item->value == 2) str = STR_EXIT_GAME; 1534 } else { 1535 if (item->value == 1) str = STR_RESTART_LEVEL; 1536 if (item->value == 2) str = STR_EXIT_TO_TITLE; 1537 } 1538 1539 float eye = getEyeOffset(); 1540 1541 UI::textOut(vec2(eye, 480 - 32), str, UI::aCenter, UI::width); 1542 int tw = UI::getTextSize(STR[str]).x; 1543 1544 if (item->value > 0) UI::specOut(vec2((UI::width - tw) * 0.5f - 32.0f + eye, 480 - 32), 108); 1545 if (item->value < 2) UI::specOut(vec2((UI::width + tw) * 0.5f + 16.0f + eye, 480 - 32), 109); 1546 1547 if (item->value != 0) return; 1548 1549 if (slot == -1) { 1550 // 1551 } else 1552 renderOptions(item); 1553 } 1554 renderOptionsInventory1555 void renderOptions(Item *item) { 1556 int optionsCount; 1557 const OptionItem *options = item->getOptions(optionsCount); 1558 1559 if (!options) 1560 return; 1561 1562 float width = 320.0f; 1563 float height = optionsCount * LINE_HEIGHT + 8; 1564 1565 if (item->type == TR::Entity::INV_CONTROLS || item->type == TR::Entity::INV_DETAIL) 1566 width += 80; 1567 1568 float x = ( UI::width - width ) * 0.5f + getEyeOffset(); 1569 float y = ( UI::height - height ) * 0.5f + LINE_HEIGHT; 1570 1571 // background 1572 UI::renderBar(CTEX_OPTION, vec2(x, y - 16.0f), vec2(width, height), 0.0f, 0, 0xC0000000); 1573 1574 x += 8.0f; 1575 width -= 16.0f; 1576 1577 Core::Settings &stg = item->getSettings(this); 1578 for (int i = 0; i < optionsCount; i++) 1579 y = options[i].render(x, y, width, slot == i, &stg); 1580 } 1581 1582 getItemNameInventory1583 StringID getItemName(StringID def, TR::LevelID id, TR::Entity::Type type) { 1584 if (!TR::Entity::isPuzzleItem(type) && !TR::Entity::isKeyItem(type)) 1585 return def; 1586 1587 #define LVLCHECK(L, T, S) if (id == TR::L && type == TR::Entity::INV_##T) return S; 1588 1589 LVLCHECK(LVL_TR1_2, KEY_ITEM_1, STR_KEY_SILVER); 1590 LVLCHECK(LVL_TR1_2, PUZZLE_1, STR_PUZZLE_GOLD_IDOL); 1591 1592 LVLCHECK(LVL_TR1_3A, PUZZLE_1, STR_PUZZLE_COG); 1593 1594 LVLCHECK(LVL_TR1_4, KEY_ITEM_1, STR_KEY_NEPTUNE); 1595 LVLCHECK(LVL_TR1_4, KEY_ITEM_2, STR_KEY_ATLAS); 1596 LVLCHECK(LVL_TR1_4, KEY_ITEM_3, STR_KEY_DAMOCLES); 1597 LVLCHECK(LVL_TR1_4, KEY_ITEM_4, STR_KEY_THOR); 1598 1599 LVLCHECK(LVL_TR1_5, KEY_ITEM_1, STR_KEY_RUSTY); 1600 1601 LVLCHECK(LVL_TR1_6, PUZZLE_1, STR_PUZZLE_GOLD_BAR); 1602 1603 LVLCHECK(LVL_TR1_7A, KEY_ITEM_1, STR_KEY_GOLD); 1604 LVLCHECK(LVL_TR1_7A, KEY_ITEM_2, STR_KEY_SILVER); 1605 LVLCHECK(LVL_TR1_7A, KEY_ITEM_3, STR_KEY_RUSTY); 1606 1607 LVLCHECK(LVL_TR1_7B, KEY_ITEM_1, STR_KEY_GOLD); 1608 LVLCHECK(LVL_TR1_7B, KEY_ITEM_2, STR_KEY_RUSTY); 1609 LVLCHECK(LVL_TR1_7B, KEY_ITEM_3, STR_KEY_RUSTY); 1610 1611 LVLCHECK(LVL_TR1_8A, KEY_ITEM_1, STR_KEY_SAPPHIRE); 1612 1613 LVLCHECK(LVL_TR1_8B, KEY_ITEM_1, STR_KEY_SAPPHIRE); 1614 LVLCHECK(LVL_TR1_8B, PUZZLE_2, STR_PUZZLE_SCARAB); 1615 LVLCHECK(LVL_TR1_8B, PUZZLE_3, STR_PUZZLE_HORUS); 1616 LVLCHECK(LVL_TR1_8B, PUZZLE_4, STR_PUZZLE_ANKH); 1617 LVLCHECK(LVL_TR1_8B, PUZZLE_1, STR_PUZZLE_HORUS); 1618 1619 LVLCHECK(LVL_TR1_8C, KEY_ITEM_1, STR_KEY_GOLD); 1620 LVLCHECK(LVL_TR1_8C, PUZZLE_1, STR_PUZZLE_ANKH); 1621 LVLCHECK(LVL_TR1_8C, PUZZLE_2, STR_PUZZLE_SCARAB); 1622 1623 LVLCHECK(LVL_TR1_10A, PUZZLE_1, STR_PUZZLE_FUSE); 1624 LVLCHECK(LVL_TR1_10A, PUZZLE_2, STR_PUZZLE_PYRAMID); 1625 1626 LVLCHECK(LVL_TR1_EGYPT, KEY_ITEM_1, STR_KEY_GOLD); 1627 LVLCHECK(LVL_TR1_CAT, KEY_ITEM_1, STR_KEY_ORNATE); 1628 1629 #undef LVLCHECK 1630 1631 return def; 1632 } 1633 renderItemTextInventory1634 void renderItemText(Item *item) { 1635 float eye = getEyeOffset() * 0.5f; 1636 1637 if (item->type == TR::Entity::INV_PASSPORT && phaseChoose == 1.0f) { 1638 // 1639 } else { 1640 StringID str = getItemName(item->desc.str, game->getLevel()->id, item->type); 1641 UI::textOut(vec2(eye, 480 - 32), str, UI::aCenter, UI::width); 1642 } 1643 1644 renderItemCount(item, vec2(UI::width / 2 - 160, 480 - 96), 320); 1645 1646 // show health bar in inventory when selector is over medikit 1647 if (item->type == TR::Entity::INV_MEDIKIT_BIG || item->type == TR::Entity::INV_MEDIKIT_SMALL) { 1648 Character *lara = (Character*)game->getLara(playerIndex); 1649 if (lara) { 1650 float health = lara->health / 1000.0f; // LARA_MAX_HEALTH 1651 1652 vec2 size = vec2(180, 10); 1653 vec2 pos; 1654 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) { 1655 pos = vec2((UI::width - size.x) * 0.5f, 96); 1656 } else { 1657 if (game->getLara(1) && playerIndex == 0) { 1658 pos = vec2(32, 32); 1659 } else { 1660 pos = vec2(UI::width - 32 - size.x, 32); 1661 } 1662 } 1663 1664 UI::renderBar(CTEX_HEALTH, pos, size, health); 1665 } 1666 } 1667 1668 if (phaseChoose == 1.0f) { 1669 switch (item->type) { 1670 case TR::Entity::INV_PASSPORT : 1671 renderPassport(item); 1672 break; 1673 case TR::Entity::INV_DETAIL : 1674 case TR::Entity::INV_SOUND : 1675 case TR::Entity::INV_CONTROLS : 1676 renderOptions(item); 1677 break; 1678 case TR::Entity::INV_GAMMA : 1679 case TR::Entity::INV_STOPWATCH : 1680 case TR::Entity::INV_MAP : 1681 UI::textOut(vec2(0, 240), STR_EMPTY, UI::aCenter, UI::width); 1682 break; 1683 default : ; 1684 } 1685 } 1686 } 1687 renderPageInventory1688 void renderPage(int page) { 1689 float phase = page == targetPage ? phasePage : (1.0f - phasePage); 1690 1691 float alpha = 1.0f - phaseRing * phase; 1692 alpha *= alpha; 1693 alpha = 1.0f - alpha; 1694 Core::setMaterial(1.0f, 0.0f, 0.0f, alpha); 1695 1696 int count = getItemsCount(page); 1697 1698 vec2 cpos(1286, 256 + 1280 * (1.0f - phaseRing)); 1699 float ringTilt = cpos.angle(); 1700 float radius = phaseRing * INV_MAX_RADIUS * phase; 1701 float collapseAngle = phaseRing * phase * PI - PI; 1702 float ringHeight = lerp(float(this->page), float(targetPage), quintic(phasePage)) * INV_HEIGHT; 1703 float angle = getAngle(pageItemIndex[page], count); 1704 1705 if (phaseSelect < 1.0f) 1706 angle = lerpAngle(angle, getAngle(targetIndex, count), quintic(phaseSelect)); 1707 1708 Basis basis = Basis(quat(vec3(1, 0, 0), ringTilt), vec3(0)); 1709 1710 int itemIndex = 0; 1711 for (int i = 0; i < itemsCount; i++) { 1712 Item *item = items[i]; 1713 1714 if (item->desc.page != page) 1715 continue; 1716 1717 float a = getAngle(itemIndex, count) - angle - collapseAngle; 1718 float ia = item->angle; 1719 float rd = radius; 1720 float rh = ringHeight; 1721 1722 if (itemIndex == pageItemIndex[page] && (chosen || phaseChoose > 0.0f)) { 1723 ia *= 1.0f - phaseChoose; 1724 rh -= 128 * phaseChoose; 1725 rd += 296 * phaseChoose; 1726 } 1727 1728 Basis b = basis * Basis(quat(vec3(0, 1, 0), PI + ia - a), vec3(sinf(a), 0, -cosf(a)) * rd - vec3(0, item->desc.page * INV_HEIGHT - rh, 0)); 1729 1730 if (item->type == TR::Entity::INV_COMPASS) { 1731 b.rotate(quat(vec3(1.0f, 0.0f, 0.0f), -phaseChoose * PI * 0.1f)); 1732 } 1733 1734 item->render(game, b); 1735 1736 itemIndex++; 1737 } 1738 } 1739 1740 void renderTitleBG(float sx = 1.0f, float sy = 1.0f, uint8 alpha = 255, float cropW = 1.0f, float cropH = 1.0f) { 1741 float aspectSrc, ax, ay, tx, ty; 1742 1743 if (background[0]) { 1744 Texture *tex = background[0]; 1745 float origW = float(tex->origWidth) * cropW; 1746 float origH = float(tex->origHeight) * cropH; 1747 tx = 0.5f * (tex->origWidth - origW) / tex->width; 1748 ty = 0.5f * (tex->origHeight - origH) / tex->height; 1749 float ox = sx * origW; 1750 float oy = sy * origH; 1751 aspectSrc = ox / oy; 1752 ax = origW / tex->width; 1753 ay = origH / tex->height; 1754 } else { 1755 tx = ty = 0.0f; 1756 aspectSrc = ax = ay = 1.0f; 1757 } 1758 1759 float aspectDst = float(Core::width) / float(Core::height) * Core::aspectFix; 1760 float aspectImg = aspectSrc / aspectDst; 1761 1762 #ifdef FFP 1763 mat4 m; 1764 m.identity(); 1765 Core::setViewProj(m, m); 1766 Core::mModel.identity(); 1767 Core::mModel.scale(vec3(1.0f / 32767.0f)); 1768 #endif 1769 1770 short o_frame = 32767; 1771 short i_frame = 16384; 1772 1773 short2 size = short2(short(i_frame * aspectImg), i_frame); 1774 if (aspectImg < 1.0f) { 1775 size.x = short(i_frame * aspectImg); 1776 size.y = i_frame; 1777 } else { 1778 size.x = i_frame; 1779 size.y = short(i_frame / aspectImg); 1780 } 1781 1782 float eye = -getEyeOffset() * size.x / 320.0f; 1783 if (titleTimer > 0.0f || video) { 1784 eye = 0.0f; 1785 } 1786 1787 Index indices[10 * 3] = { 0,1,2, 0,2,3, 8,9,5, 8,5,4, 9,10,6, 9,6,5, 10,11,7, 10,7,6, 11,8,4, 11,4,7 }; 1788 Vertex vertices[4 * 3]; 1789 1790 vertices[ 0].coord = short4(-size.x, size.y, 0, 1); 1791 vertices[ 1].coord = short4( size.x, size.y, 0, 1); 1792 vertices[ 2].coord = short4( size.x, -size.y, 0, 1); 1793 vertices[ 3].coord = short4(-size.x, -size.y, 0, 1); 1794 1795 vertices[ 4].coord = vertices[0].coord; 1796 vertices[ 5].coord = vertices[1].coord; 1797 vertices[ 6].coord = vertices[2].coord; 1798 vertices[ 7].coord = vertices[3].coord; 1799 1800 vertices[ 8].coord = short4(-o_frame, o_frame, 0, 1); 1801 vertices[ 9].coord = short4( o_frame, o_frame, 0, 1); 1802 vertices[10].coord = short4( o_frame, -o_frame, 0, 1); 1803 vertices[11].coord = short4(-o_frame, -o_frame, 0, 1); 1804 1805 vertices[ 0].light = 1806 vertices[ 1].light = 1807 vertices[ 2].light = 1808 vertices[ 3].light = ubyte4(255, 255, 255, alpha); 1809 vertices[ 4].light = 1810 vertices[ 5].light = 1811 vertices[ 6].light = 1812 vertices[ 7].light = 1813 vertices[ 8].light = 1814 vertices[ 9].light = 1815 vertices[10].light = 1816 vertices[11].light = ubyte4(0, 0, 0, alpha); 1817 1818 short2 t0(short(tx * 32767), short(ty * 32767)); 1819 short2 t1(t0.x + short(ax * 32767), t0.y + short(ay * 32767)); 1820 1821 vertices[ 0].texCoord = short4(t0.x, t0.y, 0, 0); 1822 vertices[ 1].texCoord = short4(t1.x, t0.y, 0, 0); 1823 vertices[ 2].texCoord = short4(t1.x, t1.y, 0, 0); 1824 vertices[ 3].texCoord = short4(t0.x, t1.y, 0, 0); 1825 vertices[ 4].texCoord = 1826 vertices[ 5].texCoord = 1827 vertices[ 6].texCoord = 1828 vertices[ 7].texCoord = 1829 vertices[ 8].texCoord = 1830 vertices[ 9].texCoord = 1831 vertices[10].texCoord = 1832 vertices[11].texCoord = short4(0, 0, 0, 0); 1833 1834 if ((Core::settings.detail.stereo == Core::Settings::STEREO_VR && !video) || !background[0]) { 1835 Core::blackTex->bind(sDiffuse); // black background 1836 } else { 1837 background[0]->bind(sDiffuse); 1838 } 1839 1840 Core::setBlendMode(alpha < 255 ? bmAlpha : bmNone); 1841 1842 mat4 mProj, mView; 1843 mView.identity(); 1844 mProj = GAPI::ortho(-1, +1, -1, +1, 0, 1); 1845 mProj.scale(vec3(1.0f / max(size.x, size.y))); 1846 mProj.translate(vec3(eye, 0.0f, 0.0f)); 1847 Core::setViewProj(mView, mProj); 1848 1849 game->setShader(Core::passFilter, Shader::FILTER_UPSCALE, false, false); 1850 Core::active.shader->setParam(uParam, vec4(float(Core::active.textures[sDiffuse]->width), float(Core::active.textures[sDiffuse]->height), 0.0f, 0.0f)); 1851 game->getMesh()->renderBuffer(indices, COUNT(indices), vertices, COUNT(vertices)); 1852 } 1853 renderGameBGInventory1854 void renderGameBG(int view) { 1855 Index indices[6] = { 0, 1, 2, 0, 2, 3 }; 1856 Vertex vertices[4]; 1857 vertices[0].coord = short4(-32767, 32767, 0, 1); 1858 vertices[1].coord = short4( 32767, 32767, 0, 1); 1859 vertices[2].coord = short4( 32767, -32767, 0, 1); 1860 vertices[3].coord = short4(-32767, -32767, 0, 1); 1861 vertices[0].light = 1862 vertices[1].light = 1863 vertices[2].light = 1864 vertices[3].light = ubyte4(255, 255, 255, uint8(phaseRing * 255)); 1865 vertices[0].texCoord = short4( 0, 32767, 0, 0); 1866 vertices[1].texCoord = short4(32767, 32767, 0, 0); 1867 vertices[2].texCoord = short4(32767, 0, 0, 0); 1868 vertices[3].texCoord = short4( 0, 0, 0, 0); 1869 1870 Texture *backTex = NULL; 1871 #ifdef FFP 1872 backTex = Core::blackTex; 1873 1874 mat4 m; 1875 m.identity(); 1876 Core::setViewProj(m, m); 1877 Core::mModel.identity(); 1878 1879 #else 1880 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR || !background[0]) { 1881 backTex = Core::blackTex; // black background 1882 } else { 1883 // blured grayscale image 1884 if (Core::settings.detail.stereo == Core::Settings::STEREO_SPLIT) { 1885 backTex = background[view]; 1886 } else { 1887 backTex = background[Core::eye <= 0.0f ? 0 : 1]; 1888 } 1889 } 1890 #endif 1891 backTex->bind(sDiffuse); 1892 1893 mat4 mProj, mView; 1894 mView.identity(); 1895 mProj = GAPI::ortho(-1, +1, -1, +1, 0, 1); 1896 mProj.scale(vec3(1.0f / 32767.0f)); 1897 Core::setViewProj(mView, mProj); 1898 1899 game->setShader(Core::passFilter, Shader::FILTER_UPSCALE, false, false); 1900 Core::active.shader->setParam(uParam, vec4(float(Core::active.textures[sDiffuse]->width), float(Core::active.textures[sDiffuse]->height), 0.0f, 0.0f)); 1901 1902 Core::setBlendMode(phaseRing < 1.0f ? bmAlpha : bmNone); 1903 game->getMesh()->renderBuffer(indices, COUNT(indices), vertices, COUNT(vertices)); 1904 } 1905 renderBackgroundInventory1906 void renderBackground(int view) { 1907 if (!isActive() && titleTimer == 0.0f) 1908 return; 1909 1910 Core::setDepthTest(false); 1911 Core::setDepthWrite(false); 1912 1913 uint8 alpha; 1914 if (!isActive() && titleTimer > 0.0f && titleTimer < 1.0f) 1915 alpha = uint8(titleTimer * 255); 1916 else 1917 alpha = 255; 1918 1919 float sy = 1.0f; 1920 1921 if (background[0] && background[0]->origWidth / background[0]->origHeight == 2) // PSX images aspect correction 1922 sy = (480.0f / 640.0f) * ((float)background[0]->origWidth / (float)background[0]->origHeight); 1923 1924 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) { 1925 if (game->getLevel()->isTitle()) 1926 renderTitleBG(1.0f, sy, alpha); 1927 else 1928 renderGameBG(view); 1929 } else { 1930 if (background[1]) 1931 renderGameBG(view); 1932 else 1933 renderTitleBG(1.0f, sy, alpha); 1934 } 1935 1936 Core::setBlendMode(bmPremult); 1937 Core::setDepthTest(true); 1938 Core::setDepthWrite(true); 1939 } 1940 1941 void setupCamera(float aspect, bool ui = false) { 1942 vec3 pos = vec3(0, 0, -1286); 1943 1944 if (ui) { 1945 pos.x += UI::width * 0.5f; 1946 pos.y += UI::height * 0.5f; 1947 pos.z += 1024.0f; 1948 } 1949 1950 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) 1951 pos.z -= 256.0f; 1952 1953 if (Core::settings.detail.stereo == Core::Settings::STEREO_SBS || Core::settings.detail.stereo == Core::Settings::STEREO_ANAGLYPH) 1954 pos.x += Core::eye * INV_EYE_SEPARATION; 1955 1956 Core::mViewInv = mat4(pos, pos + vec3(0, 0, 1), vec3(0, -1, 0)); 1957 1958 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) { 1959 if (head.e00 == INF) 1960 head = Input::hmd.head.inverseOrtho(); 1961 Core::mViewInv = Core::mViewInv * head * Input::hmd.eye[Core::eye == -1.0f ? 0 : 1]; 1962 } else 1963 head.e00 = INF; 1964 1965 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) { 1966 Core::mProj = Input::hmd.proj[Core::eye == -1.0f ? 0 : 1]; 1967 } else { 1968 float eyeSep = Core::eye * INV_EYE_SEPARATION * INV_ZNEAR / INV_EYE_FOCAL_LENGTH; 1969 Core::mProj = GAPI::perspective(INV_FOV, aspect, INV_ZNEAR, INV_ZFAR, eyeSep); 1970 } 1971 1972 Core::mView = Core::mViewInv.inverseOrtho(); 1973 Core::viewPos = Core::mViewInv.getPos(); 1974 1975 Core::setViewProj(Core::mView, Core::mProj); 1976 } 1977 renderInventory1978 void render(float aspect) { 1979 if (video) { 1980 Core::setDepthTest(false); 1981 video->render(); 1982 1983 Texture *tmp = background[0]; 1984 1985 float sy = 1.0f; 1986 float ch = 1.0f; 1987 if ((game->getLevel()->version & TR::VER_TR1) && !playLogo) { 1988 sy = 1.2f; 1989 if (video->format == Video::PSX) { 1990 ch = 120.0f / 208.0f; 1991 } 1992 } 1993 1994 Core::resetLights(); 1995 1996 background[0] = video->frameTex[0]; 1997 renderTitleBG(1.0f, sy, 255, 1.0f, ch); 1998 1999 background[0] = video->frameTex[1]; 2000 renderTitleBG(1.0f, sy, clamp(int((video->stepTimer / video->step) * 255), 0, 255), 1.0f, ch); 2001 2002 background[0] = tmp; 2003 2004 Core::setDepthTest(true); 2005 return; 2006 } 2007 2008 if (!isActive() && titleTimer == 0.0f) 2009 return; 2010 2011 if (game->getLevel()->isCutsceneLevel() || !isActive()) 2012 return; 2013 2014 // items 2015 setupCamera(aspect); 2016 2017 UI::setupInventoryShading(vec3(0.0f)); 2018 2019 renderPage(page); 2020 if (page != targetPage) 2021 renderPage(targetPage); 2022 } 2023 showLevelStatsInventory2024 void showLevelStats(const vec2 &pos) { 2025 char buf[256]; 2026 char time[16]; 2027 2028 int secretsMax = TR::LEVEL_INFO[saveStats.level].secrets; 2029 int secrets = ((saveStats.secrets & (1 << 0)) != 0) + 2030 ((saveStats.secrets & (1 << 1)) != 0) + 2031 ((saveStats.secrets & (1 << 2)) != 0) + 2032 ((saveStats.secrets & (1 << 3)) != 0) + 2033 ((saveStats.secrets & (1 << 4)) != 0); 2034 2035 int s = saveStats.time % 60; 2036 int m = saveStats.time / 60 % 60; 2037 int h = saveStats.time / 3600; 2038 2039 if (h) 2040 sprintf(time, "%d:%02d:%02d", h, m, s); 2041 else 2042 sprintf(time, "%d:%02d", m, s); 2043 2044 sprintf(buf, STR[STR_LEVEL_STATS], 2045 STR[TR::LEVEL_INFO[saveStats.level].title], 2046 saveStats.kills, 2047 saveStats.pickups, 2048 secrets, secretsMax, time); 2049 2050 UI::textOut(pos, buf, UI::aCenter, UI::width); 2051 } 2052 renderUIInventory2053 void renderUI() { 2054 if (!active || phaseRing < 1.0f) return; 2055 2056 Core::resetLights(); 2057 2058 static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_ITEMS, STR_SAVEGAME, STR_LEVEL_STATS }; 2059 2060 if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) { 2061 setupCamera(1.0f, true); 2062 Core::active.shader->setParam(uViewProj, Core::mViewProj); 2063 } 2064 2065 float eye = getEyeOffset() * 0.5f; 2066 2067 if (page == PAGE_SAVEGAME) { 2068 UI::renderBar(CTEX_OPTION, vec2(UI::width / 2 - 120, 240 - 14), vec2(240, LINE_HEIGHT - 6), 1.0f, 0x802288FF, 0, 0, 0); 2069 UI::textOut(vec2(0, 240), pageTitle[page], UI::aCenter, UI::width); 2070 UI::renderBar(CTEX_OPTION, vec2(-slot * 48 + UI::width / 2, 240 + 24 - 16), vec2(48, 18), 1.0f, 0xFFD8377C, 0); 2071 UI::textOut(vec2(UI::width / 2 - 48, 240 + 24), STR_YES, UI::aCenter, 48); 2072 UI::textOut(vec2(UI::width / 2, 240 + 24), STR_NO, UI::aCenter, 48); 2073 return; 2074 } 2075 2076 if (page == PAGE_LEVEL_STATS) { 2077 showLevelStats(vec2(0, 180)); 2078 return; 2079 } 2080 2081 if (!game->getLevel()->isTitle()) 2082 UI::textOut(vec2(eye, 32), pageTitle[page], UI::aCenter, UI::width); 2083 2084 if (canFlipPage(-1)) { 2085 UI::textOut(vec2(16, 32), "[", UI::aLeft, UI::width); 2086 UI::textOut(vec2( 0, 32), "[", UI::aRight, UI::width - 20); 2087 } 2088 2089 if (canFlipPage(1)) { 2090 UI::textOut(vec2(16, 480 - 16), "]", UI::aLeft, UI::width); 2091 UI::textOut(vec2( 0, 480 - 16), "]", UI::aRight, UI::width - 20); 2092 } 2093 2094 // inventory controls help 2095 if (page == targetPage && Input::touchTimerVis <= 0.0f) { 2096 float dx = 32.0f; 2097 char buf[64]; 2098 const char *bSelect = STR[STR_KEY_FIRST + ikEnter]; 2099 const char *bBack = STR[STR_KEY_FIRST + Core::settings.controls[playerIndex].keys[cInventory].key]; 2100 2101 #if defined(_OS_SWITCH) || defined(_OS_3DS) || defined(_OS_GCW0) || defined(_OS_XBOX) || defined(_OS_XB1) 2102 bSelect = "A"; 2103 bBack = "B"; 2104 #endif 2105 2106 sprintf(buf, STR[STR_HELP_SELECT], bSelect); 2107 UI::textOut(vec2(eye + dx, 480 - 64), buf, UI::aLeft, UI::width); 2108 if (chosen) { 2109 sprintf(buf, STR[STR_HELP_BACK], bBack); 2110 UI::textOut(vec2(eye, 480 - 64), buf, UI::aRight, UI::width - dx); 2111 } 2112 } 2113 2114 if (index == targetIndex && page == targetPage) { 2115 renderItemText(items[getGlobalIndex(page, index)]); 2116 } 2117 } 2118 }; 2119 2120 Inventory *inventory; 2121 2122 #undef SETTINGS 2123 #undef LINE_HEIGHT 2124 2125 #endif 2126