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