/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * * Handles the inventory and conversation windows. * * And the save/load game windows. Some of this will be platform * specific - I'll try to separate this ASAP. * * And there's still a bit of tidying and commenting to do yet. */ #include "tinsel/actors.h" #include "tinsel/anim.h" #include "tinsel/background.h" #include "tinsel/config.h" #include "tinsel/cursor.h" #include "tinsel/dw.h" #include "tinsel/film.h" #include "tinsel/font.h" #include "tinsel/graphics.h" #include "tinsel/handle.h" #include "tinsel/inventory.h" #include "tinsel/multiobj.h" #include "tinsel/music.h" #include "tinsel/polygons.h" #include "tinsel/savescn.h" #include "tinsel/sched.h" #include "tinsel/serializer.h" #include "tinsel/sound.h" #include "tinsel/strres.h" #include "tinsel/text.h" #include "tinsel/timers.h" // For ONE_SECOND constant #include "tinsel/tinsel.h" // For engine access #include "tinsel/token.h" #include "tinsel/pcode.h" #include "tinsel/pid.h" namespace Tinsel { //----------------- EXTERNAL GLOBAL DATA -------------------- // In DOS_DW.C extern bool bRestart; // restart flag - set to restart the game #ifdef MAC_OPTIONS // In MAC_SOUND.C extern int volMaster; #endif //----------------- EXTERNAL FUNCTIONS --------------------- // Tag functions in PDISPLAY.C extern void EnableTags(void); extern void DisableTags(void); //----------------- LOCAL DEFINES -------------------- //#define ALL_CURSORS #define INV_PICKUP BE_SLEFT // Local names #define INV_LOOK BE_SRIGHT // for button events #define INV_ACTION BE_DLEFT // // For SlideSlider() and similar enum SSFN { S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN }; /** attribute values - may become bit field if further attributes are added */ enum { IO_ONLYINV1 = 0x01, IO_ONLYINV2 = 0x02, IO_DROPCODE = 0x04 }; //----------------------- // Moveable window translucent rectangle position limits enum { MAXLEFT = 315, // MINRIGHT = 3, // These values keep 2 pixcells MINTOP = -13, // of header on the screen. MAXTOP = 195 // }; //----------------------- // Indices into winPartsf's reels #define IX_SLIDE 0 // Slider #define IX_V26 1 #define IX_V52 2 #define IX_V78 3 #define IX_V104 4 #define IX_V130 5 #define IX_H26 6 #define IX_H52 7 #define IX_H78 8 #define IX_H104 9 #define IX_H130 10 #define IX_H156 11 #define IX_H182 12 #define IX_H208 13 #define IX_H234 14 #define IX_TL 15 // Top left corner #define IX_TR 16 // Top right corner #define IX_BL 17 // Bottom left corner #define IX_BR 18 // Bottom right corner #define IX_H25 19 #define IX_V11 20 #define IX_RTL 21 // Re-sizing top left corner #define IX_RTR 22 // Re-sizing top right corner #define IX_RBR 23 // Re-sizing bottom right corner #define IX_CURLR 24 // } #define IX_CURUD 25 // } #define IX_CURDU 26 // } Custom cursors #define IX_CURDD 27 // } #define IX_CURUP 28 // } #define IX_CURDOWN 29 // } #define IX_MDGROOVE 30 // 'Mixing desk' slider background #define IX_MDSLIDER 34 // 'Mixing desk' slider #define IX_BLANK1 35 // #define IX_BLANK2 36 // #define IX_BLANK3 37 // #define IX_CIRCLE1 38 // #define IX_CIRCLE2 39 // #define IX_CROSS1 40 // #define IX_CROSS2 41 // #define IX_CROSS3 42 // #define IX_QUIT1 43 // #define IX_QUIT2 44 // #define IX_QUIT3 45 // #define IX_TICK1 46 // #define IX_TICK2 47 // #define IX_TICK3 48 // #define IX_NTR 49 // New top right corner #define HOPEDFORREELS 50 #define NORMGRAPH 0 #define DOWNGRAPH 1 #define HIGRAPH 2 //----------------------- #define FIX_UK 0 #define FIX_FR 1 #define FIX_GR 2 #define FIX_IT 3 #define FIX_SP 4 #define FIX_USA 5 #define HOPEDFORFREELS 6 // Expected flag reels //----------------------- #define MAX_ININV 150 // Max in an inventory #define MAX_CONVBASIC 10 // Max permanent conversation icons #define MAXHICONS 10 // Max dimensions of #define MAXVICONS 6 // an inventory window #define ITEM_WIDTH 25 // Dimensions of an icon #define ITEM_HEIGHT 25 // // Number of objects that makes up an empty window #define MAX_WCOMP 21 // 4 corners + (3+3) sides + (2+2) extra sides // + Bground + title + slider // + more Needed for save game window #define MAX_ICONS MAXHICONS*MAXVICONS //----------------- LOCAL GLOBAL DATA -------------------- //----- Permanent data (compiled in) ----- // Save game name editing cursor #define CURSOR_CHAR '_' char sCursor[2] = { CURSOR_CHAR, 0 }; static const int hFillers[MAXHICONS] = { IX_H26, // 2 icons wide IX_H52, // 3 IX_H78, // 4 IX_H104, // 5 IX_H130, // 6 IX_H156, // 7 IX_H182, // 8 IX_H208, // 9 IX_H234 // 10 icons wide }; static const int vFillers[MAXVICONS] = { IX_V26, // 2 icons high IX_V52, // 3 IX_V78, // 4 IX_V104, // 5 IX_V130 // 6 icons high }; //----- Permanent data (set once) ----- static SCNHANDLE winPartsf = 0; // Window members and cursors' graphic data static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data static SCNHANDLE configStrings[20]; static INV_OBJECT *pio = 0; // Inventory objects' data static int numObjects = 0; // Number of inventory objects //----- Permanent data (updated, valid while inventory closed) ----- static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState; static int HeldItem = INV_NOICON; // Current held item struct INV_DEF { int MinHicons; // } int MinVicons; // } Dimension limits int MaxHicons; // } int MaxVicons; // } int NoofHicons; // } int NoofVicons; // } Current dimentsions int ItemOrder[MAX_ININV]; // Contained items int NoofItems; // Current number of held items int FirstDisp; // Index to first item currently displayed int inventoryX; // } Display position int inventoryY; // } int otherX; // } Display position int otherY; // } int MaxInvObj; // Max. allowed contents SCNHANDLE hInvTitle; // Window heading bool resizable; // Re-sizable window? bool moveable; // Moveable window? int sNoofHicons; // } int sNoofVicons; // } Current dimensions bool bMax; // Maximised last time open? }; static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ... // Permanent contents of conversation inventory static int Inv0Order[MAX_CONVBASIC]; // Basic items i.e. permanent contents static int Num0Order = 0; // - copy to conv. inventory at pop-up time //----- Data pertinant to current active inventory ----- static int ino = 0; // Which inventory is currently active static bool InventoryHidden = false; static bool InventoryMaximised = false; static enum { ID_NONE, ID_MOVE, ID_SLIDE, ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT, ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT, ID_CSLIDE, ID_MDCONT } InvDragging; static int SuppH = 0; // 'Linear' element of static int SuppV = 0; // dimensions during re-sizing static int Ychange = 0; // static int Ycompensate = 0; // All to do with re-sizing. static int Xchange = 0; // static int Xcompensate = 0; // static bool ItemsChanged = 0; // When set, causes items to be re-drawn static bool bOpenConf = 0; static int TL = 0, TR = 0, BL = 0, BR = 0; // Used during window construction static int TLwidth = 0, TLheight = 0; // static int TRwidth = 0; // static int BLheight = 0; // static OBJECT *objArray[MAX_WCOMP]; // Current display objects (window) static OBJECT *iconArray[MAX_ICONS]; // Current display objects (icons) static ANIM iconAnims[MAX_ICONS]; static OBJECT *DobjArray[MAX_WCOMP]; // Current display objects (re-sizing window) static OBJECT *RectObject = 0, *SlideObject = 0; // Current display objects, for reference // objects are in objArray. static int slideY = 0; // For positioning the slider static int slideYmax = 0, slideYmin = 0; // // Also to do with the slider static struct { int n; int y; } slideStuff[MAX_ININV+1]; #define MAXSLIDES 4 struct MDSLIDES { int num; OBJECT *obj; int min, max; }; static MDSLIDES mdSlides[MAXSLIDES]; static int numMdSlides = 0; static int GlitterIndex = 0; static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block static int thisConvIcon = 0; // Passed to polygon code via convIcon() static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call static volatile int PointedWaitCount = 0; // used by InvTinselProcess - fix the 'repeated pressing bug' static int sX = 0; // used by SlideMSlider() - current x-coordinate static int lX = 0; // used by SlideMSlider() - last x-coordinate //----- Data pertinant to configure (incl. load/save game) ----- #define COL_MAINBOX TBLUE1 // Base blue colour #define COL_BOX TBLUE1 #define COL_HILIGHT TBLUE4 #ifdef JAPAN #define BOX_HEIGHT 17 #define EDIT_BOX1_WIDTH 149 #else #define BOX_HEIGHT 13 #define EDIT_BOX1_WIDTH 145 #endif #define EDIT_BOX2_WIDTH 166 // RGROUP Radio button group - 1 is selectable at a time. Action on double click // ARSBUT Action if a radio button is selected // AABUT Action always // AATBUT Action always, text box // AAGBUT Action always, graphic button // SLIDER Not a button at all enum BTYPE { RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER, TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING }; enum BFUNC { NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN, OPENLOAD, OPENSAVE, OPENREST, OPENSOUND, OPENCONT, OPENSUBT, OPENQUIT, INITGAME, MIDIVOL, CLANG, RLANG #ifdef MAC_OPTIONS , MASTERVOL, SAMPVOL #endif }; struct CONFBOX { BTYPE boxType; BFUNC boxFunc; char *boxText; int ixText; int xpos; int ypos; int w; // Doubles as max value for SLIDERs int h; // Doubles as iteration size for SLIDERs int *ival; int bi; // Base index for AAGBUTs }; #define NO_HEADING (-1) #define USE_POINTER (-1) #define SIX_LOAD_OPTION 0 #define SIX_SAVE_OPTION 1 #define SIX_RESTART_OPTION 2 #define SIX_SOUND_OPTION 3 #define SIX_CONTROL_OPTION 4 #define SIX_SUBTITLES_OPTION 5 #define SIX_QUIT_OPTION 6 #define SIX_RESUME_OPTION 7 #define SIX_LOAD_HEADING 8 #define SIX_SAVE_HEADING 9 #define SIX_RESTART_HEADING 10 #define SIX_MVOL_SLIDER 11 #define SIX_SVOL_SLIDER 12 #define SIX_VVOL_SLIDER 13 #define SIX_DCLICK_SLIDER 14 #define SIX_DCLICK_TEST 15 #define SIX_SWAP_TOGGLE 16 #define SIX_TSPEED_SLIDER 17 #define SIX_STITLE_TOGGLE 18 #define SIX_QUIT_HEADING 19 /*-------------------------------------------------------------*\ | This is the main menu (that comes up when you hit F1 on a PC) | \*-------------------------------------------------------------*/ #ifdef JAPAN #define FBY 11 // y-offset of first button #define FBX 13 // x-offset of first button #else #define FBY 20 // y-offset of first button #define FBX 15 // x-offset of first button #endif CONFBOX optionBox[] = { { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION, FBX, FBY + 4*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, #ifdef JAPAN // TODO: If in JAPAN mode, simply disable the subtitles button? { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } #else { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } #endif }; /*-------------------------------------------------------------*\ | These are the load and save game menus. | \*-------------------------------------------------------------*/ #ifdef JAPAN #define NUM_SL_RGROUP 7 // number of visible slots #define SY 32 // y-position of first slot #else #define NUM_SL_RGROUP 9 // number of visible slots #define SY 31 // y-position of first slot #endif CONFBOX loadBox[NUM_SL_RGROUP+2] = { { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, #ifndef JAPAN { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, #endif { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 }, { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 } }; CONFBOX saveBox[NUM_SL_RGROUP+2] = { { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, #ifndef JAPAN { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, #endif { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 }, { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 } }; /*-------------------------------------------------------------*\ | This is the restart confirmation 'menu'. | \*-------------------------------------------------------------*/ CONFBOX restartBox[] = { #ifdef JAPAN { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 }, { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 } #else { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX_TICK1 }, { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 } #endif }; /*-------------------------------------------------------------*\ | This is the sound control 'menu'. | \*-------------------------------------------------------------*/ #ifdef MAC_OPTIONS CONFBOX soundBox[] = { { SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER, 142, 20, 100, 2, &volMaster, 0 }, { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 20+40, 100, 2, &volMidi, 0 }, { SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER, 142, 20+2*40, 100, 2, &volSound, 0 }, { SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER, 142, 20+3*40, 100, 2, &volVoice, 0 } }; #else CONFBOX soundBox[] = { { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 25, Audio::Mixer::kMaxChannelVolume, 2, &volMidi, 0 }, { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, Audio::Mixer::kMaxChannelVolume, 2, &volSound, 0 }, { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, Audio::Mixer::kMaxChannelVolume, 2, &volVoice, 0 } }; #endif /*-------------------------------------------------------------*\ | This is the (mouse) control 'menu'. | \*-------------------------------------------------------------*/ int bFlipped; // looks like this is just so the code has something to alter! #ifdef MAC_OPTIONS CONFBOX controlBox[] = { { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 } }; #else CONFBOX controlBox[] = { { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 }, #ifdef JAPAN { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 } #else { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 } #endif }; #endif /*-------------------------------------------------------------*\ | This is the subtitles 'menu'. | \*-------------------------------------------------------------*/ CONFBOX subtitlesBox[] = { { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, }; CONFBOX subtitlesBox3Flags[] = { { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP }, { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } }; CONFBOX subtitlesBox4Flags[] = { { FRGROUP, NOFUNC, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP }, { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } }; CONFBOX subtitlesBox5Flags[] = { { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT }, { FRGROUP, NOFUNC, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP }, { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } }; /*-------------------------------------------------------------*\ | This is the quit confirmation 'menu'. | \*-------------------------------------------------------------*/ CONFBOX quitBox[] = { #ifdef g { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 }, { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 } #else { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX_TICK1 }, { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 } #endif }; CONFBOX topwinBox[] = { { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 } }; struct CONFINIT { int h; int v; int x; int y; bool bExtraWin; CONFBOX *Box; int NumBoxes; int ixHeading; }; CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, ARRAYSIZE(optionBox), NO_HEADING }; CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, ARRAYSIZE(loadBox), SIX_LOAD_HEADING }; CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, ARRAYSIZE(saveBox), SIX_SAVE_HEADING }; #ifdef JAPAN CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING }; #else CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING }; #endif CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, ARRAYSIZE(soundBox), NO_HEADING }; #ifdef MAC_OPTIONS CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; #else CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; #endif CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING }; CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, ARRAYSIZE(quitBox), SIX_QUIT_HEADING }; CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING }; #define NOBOX (-1) // Conf window globals static struct { CONFBOX *Box; int NumBoxes; bool bExtraWin; int ixHeading; bool editableRgroup; int selBox; int pointBox; // Box pointed to on last call int saveModifier; int fileBase; int numSaved; } cd = { NULL, 0, false, 0, false, NOBOX, NOBOX, 0, 0, 0 }; // For editing save game names char sedit[SG_DESC_LEN+2]; #define HL1 0 // Hilight that moves with the cursor #define HL2 1 // Hilight on selected RGROUP box #define HL3 2 // Text on selected RGROUP box #define NUMHL 3 // Data for button press/toggle effects static struct { bool bButAnim; CONFBOX *box; bool press; // true = button press; false = button toggle } g_buttonEffect = { false, 0, false }; //----- LOCAL FORWARD REFERENCES ----- enum { IB_NONE = -1, // IB_UP = -2, // negative numbers returned IB_DOWN = -3, // by WhichInvBox() IB_SLIDE = -4, // IB_SLIDE_UP = -5, // IB_SLIDE_DOWN = -6 // }; enum { HI_BIT = ((uint)MIN_INT >> 1), // The next to top bit IS_LEFT = HI_BIT, IS_SLIDER = (IS_LEFT >> 1), IS_RIGHT = (IS_SLIDER >> 1), IS_MASK = (IS_LEFT | IS_SLIDER | IS_RIGHT) }; static int WhichInvBox(int curX, int curY, bool bSlides); static void SlideMSlider(int x, SSFN fn); static OBJECT *AddObject(const FREEL *pfreel, int num); static void AddBoxes(bool posnSlide); static void ConfActionSpecial(int i); /*-------------------------------------------------------------------------*/ /*** Magic numbers ***/ #define M_SW 5 // Side width #define M_TH 5 // Top height #ifdef JAPAN #define M_TOFF 6 // Title text Y offset from top #define M_TBB 20 // Title box bottom Y offset #else #define M_TOFF 4 // Title text Y offset from top #define M_TBB 14 // Title box bottom Y offset #endif #define M_SBL 26 // Scroll bar left X offset #define M_SH 5 // Slider height (*) #define M_SW 5 // Slider width (*) #define M_SXOFF 9 // Slider X offset from right-hand side #ifdef JAPAN #define M_IUT 22 // Y offset of top of up arrow #define M_IUB 30 // Y offset of bottom of up arrow #else #define M_IUT 16 // Y offset of top of up arrow #define M_IUB 24 // Y offset of bottom of up arrow #endif #define M_IDT 10 // Y offset (from bottom) of top of down arrow #define M_IDB 3 // Y offset (from bottom) of bottom of down arrow #define M_IAL 12 // X offset (from right) of left of scroll arrows #define M_IAR 3 // X offset (from right) of right of scroll arrows #define START_ICONX (M_SW+1) // } Relative offset of first icon #define START_ICONY (M_TBB+M_TH+1) // } within the inventory window /*-------------------------------------------------------------------------*/ bool LanguageChange(void) { LANGUAGE nLang = TXT_ENGLISH; if (_vm->getFeatures() & GF_USE_3FLAGS) { // VERY quick dodgy bodge if (cd.selBox == 0) nLang = TXT_FRENCH; // = 1 else if (cd.selBox == 1) nLang = TXT_GERMAN; // = 2 else nLang = TXT_SPANISH; // = 4 } else if (_vm->getFeatures() & GF_USE_4FLAGS) { nLang = (LANGUAGE)(cd.selBox + 1); } else if (_vm->getFeatures() & GF_USE_5FLAGS) { nLang = (LANGUAGE)cd.selBox; } if (nLang != g_language) { KillInventory(); ChangeLanguage(nLang); g_language = nLang; return true; } else return false; } /**************************************************************************/ /******************** Some miscellaneous functions ************************/ /**************************************************************************/ /** * Delete all the objects in iconArray[] */ static void DumpIconArray(void){ for (int i = 0; i < MAX_ICONS; i++) { if (iconArray[i] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]); iconArray[i] = NULL; } } } /** * Delete all the objects in DobjArray[] */ static void DumpDobjArray(void) { for (int i = 0; i < MAX_WCOMP; i++) { if (DobjArray[i] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]); DobjArray[i] = NULL; } } } /** * Delete all the objects in objArray[] */ static void DumpObjArray(void) { for (int i = 0; i < MAX_WCOMP; i++) { if (objArray[i] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]); objArray[i] = NULL; } } } /** * Convert item ID number to pointer to item's compiled data * i.e. Image data and Glitter code. */ INV_OBJECT *findInvObject(int num) { INV_OBJECT *retval = pio; for (int i = 0; i < numObjects; i++, retval++) { if (retval->id == num) return retval; } error("Trying to manipulate undefined inventory icon"); } /** * Returns position of an item in one of the inventories. * The actual position is not important for the uses that this is put to. */ int InventoryPos(int num) { int i; for (i = 0; i < InvD[INV_1].NoofItems; i++) // First inventory if (InvD[INV_1].ItemOrder[i] == num) return i; for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory if (InvD[INV_2].ItemOrder[i] == num) return i; if (HeldItem == num) return INV_HELDNOTIN; // Held, but not in either inventory return INV_NOICON; // Not held, not in either inventory } bool IsInInventory(int object, int invnum) { assert(invnum == INV_1 || invnum == INV_2); for (int i = 0; i < InvD[invnum].NoofItems; i++) // First inventory if (InvD[invnum].ItemOrder[i] == object) return true; return false; } /** * Returns which item is held (INV_NOICON (-1) if none) */ int WhichItemHeld(void) { return HeldItem; } /** * Called from the cursor module when it re-initialises (at the start of * a new scene). For if we are holding something at scene-change time. */ void InventoryIconCursor(void) { INV_OBJECT *invObj; if (HeldItem != INV_NOICON) { invObj = findInvObject(HeldItem); SetAuxCursor(invObj->hFilm); } } /** * Returns TRUE if the inventory is active. */ bool InventoryActive(void) { return (InventoryState == ACTIVE_INV); } int WhichInventoryOpen(void) { if (InventoryState != ACTIVE_INV) return 0; else return ino; } /**************************************************************************/ /************** Running inventory item's Glitter code *********************/ /**************************************************************************/ struct ITP_INIT { INV_OBJECT *pinvo; USER_EVENT event; BUTEVENT bev; }; /** * Run inventory item's Glitter code */ static void InvTinselProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; INT_CONTEXT *pic; int ThisPointedWait; // Fix the 'repeated pressing bug' CORO_END_CONTEXT(_ctx); // get the stuff copied to process when it was created ITP_INIT *to = (ITP_INIT *)param; CORO_BEGIN_CODE(_ctx); CORO_INVOKE_1(AllowDclick, to->bev); _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo); CORO_INVOKE_1(Interpret, _ctx->pic); if (to->event == POINTED) { _ctx->ThisPointedWait = ++PointedWaitCount; while (1) { CORO_SLEEP(1); int x, y; GetCursorXY(&x, &y, false); if (InvItemId(x, y) != to->pinvo->id) break; // Fix the 'repeated pressing bug' if (_ctx->ThisPointedWait != PointedWaitCount) CORO_KILL_SELF(); } _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo); CORO_INVOKE_1(Interpret, _ctx->pic); } CORO_END_CODE; } /** * Run inventory item's Glitter code */ void RunInvTinselCode(INV_OBJECT *pinvo, USER_EVENT event, BUTEVENT be, int index) { ITP_INIT to = { pinvo, event, be }; if (InventoryHidden) return; GlitterIndex = index; g_scheduler->createProcess(PID_TCODE, InvTinselProcess, &to, sizeof(to)); } /**************************************************************************/ /****************** Load/Save game specific functions *********************/ /**************************************************************************/ /** * Set first load/save file entry displayed. * Point Box[] text pointers to appropriate file descriptions. */ void firstFile(int first) { int i, j; i = getList(); cd.numSaved = i; if (first < 0) first = 0; else if (first > MAX_SFILES-NUM_SL_RGROUP) first = MAX_SFILES-NUM_SL_RGROUP; if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) { // Blank first entry for new save cd.Box[0].boxText = NULL; cd.saveModifier = j = 1; } else { cd.saveModifier = j = 0; } for (i = first; j < NUM_SL_RGROUP; j++, i++) { cd.Box[j].boxText = ListEntry(i, LE_DESC); } cd.fileBase = first; } /** * Save the game using filename from selected slot & current description. */ void InvSaveGame(void) { if (cd.selBox != NOBOX) { #ifndef JAPAN sedit[strlen(sedit)-1] = 0; // Don't include the cursor! #endif SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit); } } /** * Load the selected saved game. */ void InvLoadGame(void) { int rGame; if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) { rGame = cd.selBox; cd.selBox = NOBOX; if (iconArray[HL3] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); iconArray[HL3] = NULL; } if (iconArray[HL2] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); iconArray[HL2] = NULL; } if (iconArray[HL1] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = NULL; } RestoreGame(rGame+cd.fileBase); } } /** * Edit the string in sedit[] * Returns TRUE if the string was altered. */ #ifndef JAPAN bool UpdateString(const Common::KeyState &kbd) { int cpos; if (!cd.editableRgroup) return false; cpos = strlen(sedit)-1; if (kbd.keycode == Common::KEYCODE_BACKSPACE) { if (!cpos) return false; sedit[cpos] = 0; cpos--; sedit[cpos] = CURSOR_CHAR; return true; // } else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) { } else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) { if (cpos == SG_DESC_LEN) return false; sedit[cpos] = kbd.ascii; cpos++; sedit[cpos] = CURSOR_CHAR; sedit[cpos+1] = 0; return true; } return false; } #endif /** * Keystrokes get sent here when load/save screen is up. */ bool InvKeyIn(const Common::KeyState &kbd) { if (kbd.keycode == Common::KEYCODE_PAGEUP || kbd.keycode == Common::KEYCODE_PAGEDOWN || kbd.keycode == Common::KEYCODE_HOME || kbd.keycode == Common::KEYCODE_END) return true; // Key needs processing if (kbd.keycode == 0 && kbd.ascii == 0) { ; } else if (kbd.keycode == Common::KEYCODE_RETURN) { return true; // Key needs processing } else if (kbd.keycode == Common::KEYCODE_ESCAPE) { return true; // Key needs processing } else { #ifndef JAPAN if (UpdateString(kbd)) { /* * Delete display of text currently being edited, * and replace it with freshly edited text. */ if (iconArray[HL3] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); iconArray[HL3] = NULL; } iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, hTagFontHandle(), 0); if (MultiRightmost(iconArray[HL3]) > 213) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE)); iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, hTagFontHandle(), 0); } MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); } #endif } return false; } /*---------------------------------------------------------------------*\ | Select() | |-----------------------------------------------------------------------| | Highlights selected box. | | If it's editable (save game), copy existing description and add a | | cursor. | \*---------------------------------------------------------------------*/ void Select(int i, bool force) { #ifdef JAPAN time_t secs_now; struct tm *time_now; #endif i &= ~IS_MASK; if (cd.selBox == i && !force) return; cd.selBox = i; // Clear previous selected highlight and text if (iconArray[HL2] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); iconArray[HL2] = NULL; } if (iconArray[HL3] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); iconArray[HL3] = NULL; } // New highlight box switch (cd.Box[i].boxType) { case RGROUP: iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h); MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); MultiSetAniXY(iconArray[HL2], InvD[ino].inventoryX + cd.Box[i].xpos, InvD[ino].inventoryY + cd.Box[i].ypos); // Z-position of box, and add edit text if appropriate if (cd.editableRgroup) { MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1); assert(cd.Box[i].ixText == USE_POINTER); #ifdef JAPAN // Current date and time time(&secs_now); time_now = localtime(&secs_now); strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now); #else // Current description with cursor appended if (cd.Box[i].boxText != NULL) { strcpy(sedit, cd.Box[i].boxText); strcat(sedit, sCursor); } else { strcpy(sedit, sCursor); } #endif iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, InvD[ino].inventoryX + cd.Box[i].xpos + 2, #ifdef JAPAN InvD[ino].inventoryY + cd.Box[i].ypos + 2, #else InvD[ino].inventoryY + cd.Box[i].ypos, #endif hTagFontHandle(), 0); MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); } else { MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1); } _vm->divertKeyInput(InvKeyIn); break; case FRGROUP: assert((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)); iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6); MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); MultiSetAniXY(iconArray[HL2], InvD[ino].inventoryX + cd.Box[i].xpos - 2, InvD[ino].inventoryY + cd.Box[i].ypos - 2); MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1); break; default: break; } } /**************************************************************************/ /***/ /**************************************************************************/ /** * If the item is not already held, hold it. */ void HoldItem(int item) { INV_OBJECT *invObj; if (HeldItem != item) { if (item == INV_NOICON && HeldItem != INV_NOICON) DelAuxCursor(); // no longer aux cursor if (item != INV_NOICON) { invObj = findInvObject(item); SetAuxCursor(invObj->hFilm); // and is aux. cursor } HeldItem = item; // Item held } // Redraw contents - held item not displayed as a content. ItemsChanged = true; } /** * Stop holding an item. */ void DropItem(int item) { if (HeldItem == item) { HeldItem = INV_NOICON; // Item not held DelAuxCursor(); // no longer aux cursor } // Redraw contents - held item was not displayed as a content. ItemsChanged = true; } /** * Stick the item into an inventory list (ItemOrder[]), and hold the * item if requested. */ void AddToInventory(int invno, int icon, bool hold) { int i; bool bOpen; #ifdef DEBUG INV_OBJECT *invObj; #endif assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory if (invno == INV_OPEN) { assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open invno = ino; bOpen = true; // Make sure it doesn't get in both! RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon); } else bOpen = false; #ifdef DEBUG invObj = findInvObject(icon); if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1) || (invObj->attribute & IO_ONLYINV2 && invno != INV_2)) error("Trying to add resticted object to wrong inventory"); #endif if (invno == INV_1) RemFromInventory(INV_2, icon); else if (invno == INV_2) RemFromInventory(INV_1, icon); // See if it's already there for (i = 0; i < InvD[invno].NoofItems; i++) { if (InvD[invno].ItemOrder[i] == icon) break; } // Add it if it isn't already there if (i == InvD[invno].NoofItems) { if (!bOpen) { if (invno == INV_CONV) { // For conversation, insert before last icon // which will always be the goodbye icon InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1]; InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon; InvD[invno].NoofItems++; } else { InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon; } ItemsChanged = true; } else { // It could be that the index is beyond what you'd expect // as delinv may well have been called if (GlitterIndex < InvD[invno].NoofItems) { memmove(&InvD[invno].ItemOrder[GlitterIndex + 1], &InvD[invno].ItemOrder[GlitterIndex], (InvD[invno].NoofItems-GlitterIndex)*sizeof(int)); InvD[invno].ItemOrder[GlitterIndex] = icon; } else { InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon; } InvD[invno].NoofItems++; } } // Hold it if requested if (hold) HoldItem(icon); } /** * Take the item from the inventory list (ItemOrder[]). * Return FALSE if item wasn't present, true if it was. */ bool RemFromInventory(int invno, int icon) { int i; assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory // See if it's there for (i = 0; i < InvD[invno].NoofItems; i++) { if (InvD[invno].ItemOrder[i] == icon) break; } if (i == InvD[invno].NoofItems) return false; // Item wasn't there else { memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int)); InvD[invno].NoofItems--; ItemsChanged = true; return true; // Item removed } } /**************************************************************************/ /***/ /**************************************************************************/ /*---------------------------------------------------------------------*\ | InvArea() | |-----------------------------------------------------------------------| | Work out which area of the inventory window the cursor is in. | |-----------------------------------------------------------------------| | This used to be worked out with appropriately defined magic numbers. | | Then the graphic changed and I got it right again. Then the graphic | | changed and I got fed up of faffing about. It's probably easier just | | to rework all this. | \*---------------------------------------------------------------------*/ enum { I_NOTIN, I_MOVE, I_BODY, I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT, I_TOP, I_BOTTOM, I_LEFT, I_RIGHT, I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN, I_ENDCHANGE }; #define EXTRA 1 // This was introduced when we decided to increase // the active area of the borders for re-sizing. /*---------------------------------*/ #define LeftX InvD[ino].inventoryX #define TopY InvD[ino].inventoryY /*---------------------------------*/ int InvArea(int x, int y) { int RightX = MultiRightmost(RectObject) + 1; int BottomY = MultiLowest(RectObject) + 1; // Outside the whole rectangle? if (x <= LeftX - EXTRA || x > RightX + EXTRA || y <= TopY - EXTRA || y > BottomY + EXTRA) return I_NOTIN; // The bottom line if (y > BottomY - 2 - EXTRA) { // Below top of bottom line? if (x <= LeftX + 2 + EXTRA) return I_BLEFT; // Bottom left corner else if (x > RightX - 2 - EXTRA) return I_BRIGHT; // Bottom right corner else return I_BOTTOM; // Just plain bottom } // The top line if (y <= TopY + 2 + EXTRA) { // Above bottom of top line? if (x <= LeftX + 2 + EXTRA) return I_TLEFT; // Top left corner else if (x > RightX - 2 - EXTRA) return I_TRIGHT; // Top right corner else return I_TOP; // Just plain top } // Sides if (x <= LeftX + 2 + EXTRA) // Left of right of left side? return I_LEFT; else if (x > RightX - 2 - EXTRA) // Right of left of right side? return I_RIGHT; // From here down still needs fixing up properly /* * In the move area? */ if (ino != INV_CONF && x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 && y >= TopY + M_TH - 2 && y < TopY + M_TBB + 2) return I_MOVE; /* * Scroll bits */ if (ino == INV_CONF && cd.bExtraWin) { } else { if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) { if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1) return I_UP; if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1) return I_DOWN; if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) { if (y < TopY + slideY) return I_SLIDE_UP; if (y < TopY + slideY + M_SH) return I_SLIDE; else return I_SLIDE_DOWN; } } } return I_BODY; } /** * Returns the id of the icon displayed under the given position. * Also return co-ordinates of items tag display position, if requested. */ int InvItem(int *x, int *y, bool update) { int itop, ileft; int row, col; int item; int IconsX; itop = InvD[ino].inventoryY + START_ICONY; IconsX = InvD[ino].inventoryX + START_ICONX; for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { ileft = IconsX; for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { if (*x >= ileft && *x < ileft + ITEM_WIDTH && *y >= itop && *y < itop + ITEM_HEIGHT) { if (update) { *x = ileft + ITEM_WIDTH/2; *y = itop /*+ ITEM_HEIGHT/4*/; } return item; } ileft += ITEM_WIDTH + 1; } itop += ITEM_HEIGHT + 1; } return INV_NOICON; } /** * Returns the id of the icon displayed under the given position. */ int InvItemId(int x, int y) { int itop, ileft; int row, col; int item; if (InventoryHidden || InventoryState == IDLE_INV) return INV_NOICON; itop = InvD[ino].inventoryY + START_ICONY; int IconsX = InvD[ino].inventoryX + START_ICONX; for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { ileft = IconsX; for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { if (x >= ileft && x < ileft + ITEM_WIDTH && y >= itop && y < itop + ITEM_HEIGHT) { return InvD[ino].ItemOrder[item]; } ileft += ITEM_WIDTH + 1; } itop += ITEM_HEIGHT + 1; } return INV_NOICON; } /** * Finds which box the cursor is in. */ static int WhichInvBox(int curX, int curY, bool bSlides) { enum { MD_YSLIDTOP = 7, MD_YSLIDBOT = 18, MD_YBUTTOP = 9, MD_YBUTBOT = 16, MD_XLBUTL = 1, MD_XLBUTR = 10, MD_XRBUTL = 105, MD_XRBUTR = 114 }; if (bSlides) { for (int i = 0; i < numMdSlides; i++) { if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj) && curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj)) return mdSlides[i].num | IS_SLIDER; } } curX -= InvD[ino].inventoryX; curY -= InvD[ino].inventoryY; for (int i = 0; i < cd.NumBoxes; i++) { switch (cd.Box[i].boxType) { case SLIDER: if (bSlides) { if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) { if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR) return i | IS_LEFT; if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR) return i | IS_RIGHT; } } break; case AAGBUT: case ARSGBUT: case TOGGLE: case FLIP: if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h && curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) return i; break; default: // 'Normal' box if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h && curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) return i; break; } } if (cd.bExtraWin) { if (curX > 20 + 181 && curX < 20 + 181 + 8 && curY > 24 + 2 && curY < 24 + 139 + 5) { if (curY < 24 + 2 + 5) { return IB_UP; } else if (curY > 24 + 139) { return IB_DOWN; } else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) { return IB_SLIDE; } else if (curY+InvD[ino].inventoryY < slideY) { return IB_SLIDE_UP; } else if (curY+InvD[ino].inventoryY >= slideY + 5) { return IB_SLIDE_DOWN; } } } return IB_NONE; } /**************************************************************************/ /***/ /**************************************************************************/ /** * InBoxes */ void InvBoxes(bool InBody, int curX, int curY) { int index; // Box pointed to on this call const FILM *pfilm; // Find out which icon is currently pointed to if (!InBody) index = -1; else { index = WhichInvBox(curX, curY, false); } // If no icon pointed to, or points to (logical position of) // currently held icon, then no icon is pointed to! if (index < 0) { // unhigh-light box (if one was) cd.pointBox = NOBOX; if (iconArray[HL1] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = NULL; } } else if (index != cd.pointBox) { cd.pointBox = index; // A new box is pointed to - high-light it if (iconArray[HL1] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = NULL; } if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) || ///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP || cd.Box[cd.pointBox].boxType == AATBUT || cd.Box[cd.pointBox].boxType == AABUT) { iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h); MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); } else if (cd.Box[cd.pointBox].boxType == AAGBUT || cd.Box[cd.pointBox].boxType == ARSGBUT || cd.Box[cd.pointBox].boxType == TOGGLE) { pfilm = (const FILM *)LockMem(winPartsf); iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); } } } static void ButtonPress(CORO_PARAM, CONFBOX *box) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); const FILM *pfilm; assert(box->boxType == AAGBUT || box->boxType == ARSGBUT); // Replace highlight image with normal image pfilm = (const FILM *)LockMem(winPartsf); if (iconArray[HL1] != NULL) MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); pfilm = (const FILM *)LockMem(winPartsf); iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); // Hold normal image for 1 frame CORO_SLEEP(1); if (iconArray[HL1] == NULL) return; // Replace normal image with depresses image pfilm = (const FILM *)LockMem(winPartsf); MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); // Hold depressed image for 2 frames CORO_SLEEP(2); if (iconArray[HL1] == NULL) return; // Replace depressed image with normal image pfilm = (const FILM *)LockMem(winPartsf); MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); CORO_SLEEP(1); CORO_END_CODE; } static void ButtonToggle(CORO_PARAM, CONFBOX *box) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); const FILM *pfilm; assert(box->boxType == TOGGLE); // Remove hilight image if (iconArray[HL1] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = NULL; } // Hold normal image for 1 frame CORO_SLEEP(1); if (InventoryState != ACTIVE_INV) return; // Add depressed image pfilm = (const FILM *)LockMem(winPartsf); iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); // Hold depressed image for 1 frame CORO_SLEEP(1); if (iconArray[HL1] == NULL) return; // Toggle state (*box->ival) = *(box->ival) ^ 1; // XOR with true box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1; AddBoxes(false); // Keep highlight (e.g. flag) if (cd.selBox != NOBOX) Select(cd.selBox, true); // New state, depressed image pfilm = (const FILM *)LockMem(winPartsf); if (iconArray[HL1] != NULL) MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); // Hold new depressed image for 1 frame CORO_SLEEP(1); if (iconArray[HL1] == NULL) return; // New state, normal MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = NULL; // Hold normal image for 1 frame CORO_SLEEP(1); if (InventoryState != ACTIVE_INV) return; // New state, highlighted pfilm = (const FILM *)LockMem(winPartsf); if (iconArray[HL1] != NULL) MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1); MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); CORO_END_CODE; } /** * Monitors for POINTED event for inventory icons. */ void InvLabels(bool InBody, int aniX, int aniY) { int index; // Icon pointed to on this call INV_OBJECT *invObj; // Find out which icon is currently pointed to if (!InBody) index = INV_NOICON; else { index = InvItem(&aniX, &aniY, false); if (index != INV_NOICON) { if (index >= InvD[ino].NoofItems) index = INV_NOICON; else index = InvD[ino].ItemOrder[index]; } } // If no icon pointed to, or points to (logical position of) // currently held icon, then no icon is pointed to! if (index == INV_NOICON || index == HeldItem) { pointedIcon = INV_NOICON; } else if (index != pointedIcon) { // A new icon is pointed to - run its script with POINTED event invObj = findInvObject(index); if (invObj->hScript) RunInvTinselCode(invObj, POINTED, BE_NONE, index); pointedIcon = index; } } /**************************************************************************/ /***/ /**************************************************************************/ /** * All to do with the slider. * I can't remember how it works - or, indeed, what it does. * It seems to set up slideStuff[], an array of possible first-displayed * icons set against the matching y-positions of the slider. */ void AdjustTop(void) { int tMissing, bMissing, nMissing; int nslideY; int rowsWanted; int slideRange; int n, i; // Only do this if there's a slider if (!SlideObject) return; rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons; while (rowsWanted < InvD[ino].NoofVicons) { if (InvD[ino].FirstDisp) { InvD[ino].FirstDisp -= InvD[ino].NoofHicons; if (InvD[ino].FirstDisp < 0) InvD[ino].FirstDisp = 0; rowsWanted++; } else break; } tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0; bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0; nMissing = tMissing + bMissing; slideRange = slideYmax - slideYmin; if (!tMissing) nslideY = slideYmin; else if (!bMissing) nslideY = slideYmax; else { nslideY = tMissing*slideRange/nMissing; nslideY += slideYmin; } if (nMissing) { n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons; for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) { slideStuff[i].n = n; slideStuff[i].y = (i*slideRange/nMissing) + slideYmin; } if (slideStuff[0].n < 0) slideStuff[0].n = 0; assert(i < MAX_ININV + 1); slideStuff[i].n = -1; } else { slideStuff[0].n = 0; slideStuff[0].y = slideYmin; slideStuff[1].n = -1; } if (nslideY != slideY) { MultiMoveRelXY(SlideObject, 0, nslideY - slideY); slideY = nslideY; } } /** * Insert an inventory icon object onto the display list. */ OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) { INV_OBJECT *invObj; // Icon data const MULTI_INIT *pmi; // Its INIT structure - from the reel IMAGE *pim; // ... you get the picture OBJECT *pPlayObj; // The object we insert invObj = findInvObject(num); // Get pointer to image pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm); // Poke in the background palette pim->hImgPal = TO_LE_32(BackPal()); // Set up the multi-object pPlayObj = MultiInitObject(pmi); MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); return pPlayObj; } /** * Create display objects for the displayed icons in an inventory window. */ void FillInInventory(void) { int Index; // Index into ItemOrder[] int n = 0; // index into iconArray[] int xpos, ypos; int row, col; const FREEL *pfr; const FILM *pfilm; DumpIconArray(); if (InvDragging != ID_SLIDE) AdjustTop(); // Set up slideStuff[] Index = InvD[ino].FirstDisp; // Start from first displayed object n = 0; ypos = START_ICONY; // Y-offset of first display row for (row = 0; row < InvD[ino].NoofVicons; row++, ypos += ITEM_HEIGHT + 1) { xpos = START_ICONX; // X-offset of first display column for (col = 0; col < InvD[ino].NoofHicons; col++) { if (Index >= InvD[ino].NoofItems) break; else if (InvD[ino].ItemOrder[Index] != HeldItem) { // Create a display object and position it iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm); MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos); MultiSetZPosition(iconArray[n], Z_INV_ICONS); InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); n++; } Index++; xpos += ITEM_WIDTH + 1; // X-offset of next display column } } } /** * Set up a rectangle as the background to the inventory window. * Additionally, sticks the window title up. */ enum {FROM_HANDLE, FROM_STRING}; void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) { // Why not 2 ???? int width = TLwidth + extraH + TRwidth - 3; int height = TLheight + extraV + BLheight - 3; // Create a rectangle object RectObject = *rect = TranslucentObject(width, height); // add it to display list and position it MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect); MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1); MultiSetZPosition(*rect, Z_INV_BRECT); // Create text object using title string if (textFrom == FROM_HANDLE) { LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ); *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, hTagFontHandle(), TXT_CENTRE); assert(*title); // Inventory title string produced NULL text MultiSetZPosition(*title, Z_INV_HTEXT); } else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) { LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ); *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, hTagFontHandle(), TXT_CENTRE); assert(*title); // Inventory title string produced NULL text MultiSetZPosition(*title, Z_INV_HTEXT); } } /** * Insert a part of the inventory window frame onto the display list. */ static OBJECT *AddObject(const FREEL *pfreel, int num) { const MULTI_INIT *pmi; // Get the MULTI_INIT structure IMAGE *pim; OBJECT *pPlayObj; // Get pointer to image pim = GetImageFromReel(pfreel, &pmi); // Poke in the background palette pim->hImgPal = TO_LE_32(BackPal()); // Horrible bodge involving global variables to save // width and/or height of some window frame components if (num == TL) { TLwidth = FROM_LE_16(pim->imgWidth); TLheight = FROM_LE_16(pim->imgHeight); } else if (num == TR) { TRwidth = FROM_LE_16(pim->imgWidth); } else if (num == BL) { BLheight = FROM_LE_16(pim->imgHeight); } // Set up and insert the multi-object pPlayObj = MultiInitObject(pmi); MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); return pPlayObj; } /** * Display the scroll bar slider. */ void AddSlider(OBJECT **slide, const FILM *pfilm) { SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY); MultiSetZPosition(*slide, Z_INV_MFRAME); } enum { SLIDE_RANGE = 81, SLIDE_MINX = 8, SLIDE_MAXX = 8+SLIDE_RANGE, MDTEXT_YOFF = 6, MDTEXT_XOFF = -4 }; /** * Display a box with some text in it. */ void AddBox(int *pi, int i) { int x = InvD[ino].inventoryX + cd.Box[i].xpos; int y = InvD[ino].inventoryY + cd.Box[i].ypos; int *pival = cd.Box[i].ival; int xdisp; const FILM *pfilm; switch (cd.Box[i].boxType) { default: // Give us a box iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h); MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]); MultiSetAniXY(iconArray[*pi], x, y); MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); *pi += 1; // Stick in the text if (cd.Box[i].ixText == USE_POINTER) { if (cd.Box[i].boxText != NULL) { if (cd.Box[i].boxType == RGROUP) { iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, #ifdef JAPAN x+2, y+2, hTagFontHandle(), 0); #else x+2, y, hTagFontHandle(), 0); #endif } else { iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, #ifdef JAPAN // Note: it never seems to go here! x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); #else x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); #endif } MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); *pi += 1; } } else { LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code! iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, #ifdef JAPAN x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); #else x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); #endif MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); *pi += 1; } break; case AAGBUT: case ARSGBUT: pfilm = (const FILM *)LockMem(winPartsf); iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); MultiSetAniXY(iconArray[*pi], x, y); MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); *pi += 1; break; case FRGROUP: assert((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)); assert(flagFilm != 0); // Language flags not declared! pfilm = (const FILM *)LockMem(flagFilm); if (bAmerica && cd.Box[i].bi == FIX_UK) cd.Box[i].bi = FIX_USA; iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); MultiSetAniXY(iconArray[*pi], x, y); MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2); *pi += 1; break; case FLIP: pfilm = (const FILM *)LockMem(winPartsf); if (*(cd.Box[i].ival)) iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); else iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1); MultiSetAniXY(iconArray[*pi], x, y); MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); *pi += 1; // Stick in the text assert(cd.Box[i].ixText != USE_POINTER); LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); *pi += 1; break; case TOGGLE: pfilm = (const FILM *)LockMem(winPartsf); cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1; iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); MultiSetAniXY(iconArray[*pi], x, y); MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); *pi += 1; // Stick in the text assert(cd.Box[i].ixText != USE_POINTER); LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); *pi += 1; break; case SLIDER: pfilm = (const FILM *)LockMem(winPartsf); xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w; iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1); MultiSetAniXY(iconArray[*pi], x, y); MultiSetZPosition(iconArray[*pi], Z_MDGROOVE); *pi += 1; iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1); MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y); MultiSetZPosition(iconArray[*pi], Z_MDSLIDER); assert(numMdSlides < MAXSLIDES); mdSlides[numMdSlides].num = i; mdSlides[numMdSlides].min = x+SLIDE_MINX; mdSlides[numMdSlides].max = x+SLIDE_MAXX; mdSlides[numMdSlides++].obj = iconArray[*pi]; *pi += 1; // Stick in the text assert(cd.Box[i].ixText != USE_POINTER); LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); *pi += 1; break; } } /** * Display some boxes. */ static void AddBoxes(bool posnSlide) { int oCount = NUMHL; // Object count - allow for HL1, HL2 etc. DumpIconArray(); numMdSlides = 0; for (int i = 0; i < cd.NumBoxes; i++) { AddBox(&oCount, i); } if (cd.bExtraWin) { if (posnSlide) slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP); MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY); } assert(oCount < MAX_ICONS); // added too many icons } /** * Display the scroll bar slider. */ void AddEWSlider(OBJECT **slide, const FILM *pfilm) { SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY); MultiSetZPosition(*slide, Z_INV_MFRAME); } /** * AddExtraWindow */ int AddExtraWindow(int x, int y, OBJECT **retObj) { int n = 0; const FILM *pfilm; // Get the frame's data pfilm = (const FILM *)LockMem(winPartsf); x += 20; y += 24; // Draw the four corners retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1); // Top left MultiSetAniXY(retObj[n], x, y); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1); // Top right MultiSetAniXY(retObj[n], x + 152, y); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left MultiSetAniXY(retObj[n], x, y + 124); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right MultiSetAniXY(retObj[n], x + 152, y + 124); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; // Draw the edges retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top MultiSetAniXY(retObj[n], x + 6, y); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom MultiSetAniXY(retObj[n], x + 6, y + 143); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left MultiSetAniXY(retObj[n], x, y + 20); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1 MultiSetAniXY(retObj[n], x + 179, y + 20); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2 MultiSetAniXY(retObj[n], x + 188, y + 20); MultiSetZPosition(retObj[n], Z_INV_MFRAME); n++; slideY = slideYmin = y + 9; slideYmax = y + 134; AddEWSlider(&retObj[n++], pfilm); return n; } enum InventoryType { EMPTY, FULL, CONF }; /** * Construct an inventory window - either a standard one, with * background, slider and icons, or a re-sizing window. */ void ConstructInventory(InventoryType filling) { int eH, eV; // Extra width and height int n = 0; // Index into object array int zpos; // Z-position of frame int invX = InvD[ino].inventoryX; int invY = InvD[ino].inventoryY; OBJECT **retObj; const FILM *pfilm; extern bool RePosition(void); // Forward reference // Select the object array to use if (filling == FULL || filling == CONF) { retObj = objArray; // Standard window zpos = Z_INV_MFRAME; } else { retObj = DobjArray; // Re-sizing window zpos = Z_INV_RFRAME; } // Dispose of anything it may be replacing for (int i = 0; i < MAX_WCOMP; i++) { if (retObj[i] != NULL) { MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]); retObj[i] = NULL; } } // Get the frame's data pfilm = (const FILM *)LockMem(winPartsf); // Standard window is of granular dimensions if (filling == FULL) { // Round-up/down to nearest number of icons if (SuppH > ITEM_WIDTH / 2) InvD[ino].NoofHicons++; if (SuppV > ITEM_HEIGHT / 2) InvD[ino].NoofVicons++; SuppH = SuppV = 0; } // Extra width and height eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH; eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV; // Which window frame corners to use if (filling == FULL && ino != INV_CONV) { TL = IX_TL; TR = IX_TR; BL = IX_BL; BR = IX_BR; } else { TL = IX_RTL; TR = IX_RTR; BL = IX_BL; BR = IX_RBR; } // Draw the four corners retObj[n] = AddObject(&pfilm->reels[TL], TL); MultiSetAniXY(retObj[n], invX, invY); MultiSetZPosition(retObj[n], zpos); n++; retObj[n] = AddObject(&pfilm->reels[TR], TR); MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY); MultiSetZPosition(retObj[n], zpos); n++; retObj[n] = AddObject(&pfilm->reels[BL], BL); MultiSetAniXY(retObj[n], invX, invY + TLheight + eV); MultiSetZPosition(retObj[n], zpos); n++; retObj[n] = AddObject(&pfilm->reels[BR], BR); MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV); MultiSetZPosition(retObj[n], zpos); n++; // Draw extra Top and bottom parts if (InvD[ino].NoofHicons > 1) { // Top side retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); MultiSetAniXY(retObj[n], invX + TLwidth, invY); MultiSetZPosition(retObj[n], zpos); n++; // Bottom of header box if (filling == FULL) { retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1); MultiSetZPosition(retObj[n], zpos); n++; // Extra bits for conversation - hopefully temporary if (ino == INV_CONV) { retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1); MultiSetZPosition(retObj[n], zpos); n++; retObj[n] = AddObject(&pfilm->reels[IX_H52], -1); MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1); MultiSetZPosition(retObj[n], zpos); n++; } } // Bottom side retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1); MultiSetZPosition(retObj[n], zpos); n++; } if (SuppH) { int offx = TLwidth + eH - 26; if (offx < TLwidth) // Not too far! offx = TLwidth; // Top side extra retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); MultiSetAniXY(retObj[n], invX + offx, invY); MultiSetZPosition(retObj[n], zpos); n++; // Bottom side extra retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1); MultiSetZPosition(retObj[n], zpos); n++; } // Draw extra side parts if (InvD[ino].NoofVicons > 1) { // Left side retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); MultiSetAniXY(retObj[n], invX, invY + TLheight); MultiSetZPosition(retObj[n], zpos); n++; // Left side of scroll bar if (filling == FULL && ino != INV_CONV) { retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight); MultiSetZPosition(retObj[n], zpos); n++; } // Right side retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight); MultiSetZPosition(retObj[n], zpos); n++; } if (SuppV) { int offy = TLheight + eV - 26; if (offy < 5) offy = 5; // Left side extra retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); MultiSetAniXY(retObj[n], invX, invY + offy); MultiSetZPosition(retObj[n], zpos); n++; // Right side extra retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy); MultiSetZPosition(retObj[n], zpos); n++; } OBJECT **rect, **title; // Draw background, slider and icons if (filling == FULL) { rect = &retObj[n++]; title = &retObj[n++]; AddBackground(rect, title, eH, eV, FROM_HANDLE); if (ino == INV_CONV) SlideObject = NULL; else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) { slideYmin = TLheight - 2; slideYmax = TLheight + eV + 10; AddSlider(&retObj[n++], pfilm); } FillInInventory(); } else if (filling == CONF) { rect = &retObj[n++]; title = &retObj[n++]; AddBackground(rect, title, eH, eV, FROM_STRING); if (cd.bExtraWin) n += AddExtraWindow(invX, invY, &retObj[n]); AddBoxes(true); } assert(n < MAX_WCOMP); // added more parts than we can handle! // Reposition returns TRUE if needs to move if (InvD[ino].moveable && filling == FULL && RePosition()) { ConstructInventory(FULL); } } /** * Call this when drawing a 'FULL', movable inventory. Checks that the * position of the Translucent object is within limits. If it isn't, * adjusts the x/y position of the current inventory and returns TRUE. */ bool RePosition(void) { int p; bool bMoveitMoveit = false; assert(RectObject); // no recangle object! // Test for off-screen horizontally p = MultiLeftmost(RectObject); if (p > MAXLEFT) { // Too far to the right InvD[ino].inventoryX += MAXLEFT - p; bMoveitMoveit = true; // I like to.... } else { // Too far to the left? p = MultiRightmost(RectObject); if (p < MINRIGHT) { InvD[ino].inventoryX += MINRIGHT - p; bMoveitMoveit = true; // I like to.... } } // Test for off-screen vertically p = MultiHighest(RectObject); if (p < MINTOP) { // Too high InvD[ino].inventoryY += MINTOP - p; bMoveitMoveit = true; // I like to.... } else if (p > MAXTOP) { // Too low InvD[ino].inventoryY += MAXTOP - p; bMoveitMoveit = true; // I like to.... } return bMoveitMoveit; } /**************************************************************************/ /***/ /**************************************************************************/ /** * Get the cursor's reel, poke in the background palette, * and customise the cursor. */ void AlterCursor(int num) { const FREEL *pfreel; IMAGE *pim; // Get pointer to image pim = GetImageFromFilm(winPartsf, num, &pfreel); // Poke in the background palette pim->hImgPal = TO_LE_32(BackPal()); SetTempCursor(FROM_LE_32(pfreel->script)); } enum InvCursorFN {IC_AREA, IC_DROP}; /** * InvCursor */ void InvCursor(InvCursorFN fn, int CurX, int CurY) { static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR, IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL; // FIXME: local static var int area; // The part of the window the cursor is over bool restoreMain = false; // If currently dragging, don't be messing about with the cursor shape if (InvDragging != ID_NONE) return; switch (fn) { case IC_DROP: ICursor = IC_NORMAL; InvCursor(IC_AREA, CurX, CurY); break; case IC_AREA: area = InvArea(CurX, CurY); // Check for POINTED events if (ino == INV_CONF) InvBoxes(area == I_BODY, CurX, CurY); else InvLabels(area == I_BODY, CurX, CurY); // No cursor trails while within inventory window if (area == I_NOTIN) UnHideCursorTrails(); else HideCursorTrails(); switch (area) { case I_NOTIN: restoreMain = true; break; case I_TLEFT: case I_BRIGHT: if (!InvD[ino].resizable) restoreMain = true; else if (ICursor != IC_DR) { AlterCursor(IX_CURDD); ICursor = IC_DR; } break; case I_TRIGHT: case I_BLEFT: if (!InvD[ino].resizable) restoreMain = true; else if (ICursor != IC_UR) { AlterCursor(IX_CURDU); ICursor = IC_UR; } break; case I_TOP: case I_BOTTOM: if (!InvD[ino].resizable) { restoreMain = true; break; } if (ICursor != IC_TB) { AlterCursor(IX_CURUD); ICursor = IC_TB; } break; case I_LEFT: case I_RIGHT: if (!InvD[ino].resizable) restoreMain = true; else if (ICursor != IC_LR) { AlterCursor(IX_CURLR); ICursor = IC_LR; } break; case I_UP: case I_SLIDE_UP: case I_DOWN: case I_SLIDE_DOWN: case I_SLIDE: case I_MOVE: case I_BODY: restoreMain = true; break; } break; } if (restoreMain && ICursor != IC_NORMAL) { RestoreMainCursor(); ICursor = IC_NORMAL; } } /*-------------------------------------------------------------------------*/ /**************************************************************************/ /******************** Conversation specific functions *********************/ /**************************************************************************/ void ConvAction(int index) { assert(ino == INV_CONV); // not conv. window! switch (index) { case INV_NOICON: return; case INV_CLOSEICON: thisConvIcon = -1; // Postamble break; case INV_OPENICON: thisConvIcon = -2; // Preamble break; default: thisConvIcon = InvD[ino].ItemOrder[index]; break; } RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true); } /*-------------------------------------------------------------------------*/ void AddIconToPermanentDefaultList(int icon) { int i; // See if it's already there for (i = 0; i < Num0Order; i++) { if (Inv0Order[i] == icon) break; } // Add it if it isn't already there if (i == Num0Order) { Inv0Order[Num0Order++] = icon; } } /*-------------------------------------------------------------------------*/ void convPos(int fn) { if (fn == CONV_DEF) InvD[INV_CONV].inventoryY = 8; else if (fn == CONV_BOTTOM) InvD[INV_CONV].inventoryY = 150; } void ConvPoly(HPOLYGON hPoly) { thisConvPoly = hPoly; } int convIcon(void) { return thisConvIcon; } void CloseDownConv(void) { if (InventoryState == ACTIVE_INV && ino == INV_CONV) { KillInventory(); } } void convHide(bool hide) { int aniX, aniY; int i; if (InventoryState == ACTIVE_INV && ino == INV_CONV) { if (hide) { for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0); } for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0); } InventoryHidden = true; InvLabels(false, 0, 0); } else { InventoryHidden = false; for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0); } // Don't flash if items changed. If they have, will be redrawn anyway. if (!ItemsChanged) { for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0); } } GetCursorXY(&aniX, &aniY, false); InvLabels(true, aniX, aniY); } } } bool convHid(void) { return InventoryHidden; } /**************************************************************************/ /******************* Open and closing functions ***************************/ /**************************************************************************/ /** * Start up an inventory window. */ void PopUpInventory(int invno) { assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory if (InventoryState == IDLE_INV) { bOpenConf = false; // Better safe than sorry... DisableTags(); // Tags disabled during inventory if (invno == INV_CONV) { // Conversation window? // Start conversation with permanent contents memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int)); memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int)); InvD[INV_CONV].NoofItems = Num0Order; thisConvIcon = 0; } else if (invno == INV_CONF) { // Configuration window? cd.selBox = NOBOX; cd.pointBox = NOBOX; } ino = invno; // The open inventory ItemsChanged = false; // Nothing changed InvDragging = ID_NONE; // Not dragging InventoryState = ACTIVE_INV; // Inventory actiive InventoryHidden = false; // Not hidden InventoryMaximised = InvD[ino].bMax; if (invno != INV_CONF) // Configuration window? ConstructInventory(FULL); // Draw it up else { ConstructInventory(CONF); // Draw it up } } } void SetConfGlobals(CONFINIT *ci) { InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h; InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v; InvD[INV_CONF].inventoryX = ci->x; InvD[INV_CONF].inventoryY = ci->y; cd.bExtraWin = ci->bExtraWin; cd.Box = ci->Box; cd.NumBoxes = ci->NumBoxes; cd.ixHeading = ci->ixHeading; } /** * PopupConf */ void PopUpConf(CONFTYPE type) { int curX, curY; if (InventoryState != IDLE_INV) return; InvD[INV_CONF].resizable = false; InvD[INV_CONF].moveable = false; switch (type) { case SAVE: case LOAD: if (type == SAVE) { SetCursorScreenXY(262, 91); SetConfGlobals(&ciSave); cd.editableRgroup = true; } else { SetConfGlobals(&ciLoad); cd.editableRgroup = false; } firstFile(0); break; case QUIT: #ifdef JAPAN SetCursorScreenXY(180, 106); #else SetCursorScreenXY(180, 90); #endif SetConfGlobals(&ciQuit); break; case RESTART: #ifdef JAPAN SetCursorScreenXY(180, 106); #else SetCursorScreenXY(180, 90); #endif SetConfGlobals(&ciRestart); break; case OPTION: SetConfGlobals(&ciOption); break; case CONTROLS: SetConfGlobals(&ciControl); break; case SOUND: SetConfGlobals(&ciSound); break; case SUBT: if (_vm->getFeatures() & GF_USE_3FLAGS) { ciSubtitles.v = 6; ciSubtitles.Box = subtitlesBox3Flags; ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox3Flags); } else if (_vm->getFeatures() & GF_USE_4FLAGS) { ciSubtitles.v = 6; ciSubtitles.Box = subtitlesBox4Flags; ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags); } else if (_vm->getFeatures() & GF_USE_5FLAGS) { ciSubtitles.v = 6; ciSubtitles.Box = subtitlesBox4Flags; ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags); } else { ciSubtitles.v = 3; ciSubtitles.Box = subtitlesBox; ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox); } SetConfGlobals(&ciSubtitles); break; case TOPWIN: SetConfGlobals(&ciTopWin); ino = INV_CONF; ConstructInventory(CONF); // Draw it up InventoryState = BOGUS_INV; return; default: return; } if (HeldItem != INV_NOICON) DelAuxCursor(); // no longer aux cursor PopUpInventory(INV_CONF); if (type == SAVE || type == LOAD) Select(0, false); else if (type == SUBT) { if (_vm->getFeatures() & GF_USE_3FLAGS) { // VERY quick dirty bodges if (g_language == TXT_FRENCH) Select(0, false); else if (g_language == TXT_GERMAN) Select(1, false); else Select(2, false); } else if (_vm->getFeatures() & GF_USE_4FLAGS) { Select(g_language-1, false); } else if (_vm->getFeatures() & GF_USE_5FLAGS) { Select(g_language, false); } } GetCursorXY(&curX, &curY, false); InvCursor(IC_AREA, curX, curY); } /** * Close down an inventory window. */ void KillInventory(void) { if (objArray[0] != NULL) { DumpObjArray(); DumpDobjArray(); DumpIconArray(); } if (InventoryState == ACTIVE_INV) { EnableTags(); InvD[ino].bMax = InventoryMaximised; UnHideCursorTrails(); _vm->divertKeyInput(NULL); } InventoryState = IDLE_INV; if (bOpenConf) { bOpenConf = false; PopUpConf(OPTION); // Write config changes WriteConfig(); } else if (ino == INV_CONF) InventoryIconCursor(); } void CloseInventory(void) { // If not active, ignore this if (InventoryState != ACTIVE_INV) return; // If hidden, a conversation action is still underway - ignore this if (InventoryHidden) return; // If conversation, this is a closeing event if (ino == INV_CONV) ConvAction(INV_CLOSEICON); KillInventory(); RestoreMainCursor(); } /**************************************************************************/ /************************ The inventory process ***************************/ /**************************************************************************/ /** * Redraws the icons if appropriate. Also handle button press/toggle effects */ void InventoryProcess(CORO_PARAM, const void *) { // COROUTINE CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); while (1) { CORO_SLEEP(1); // allow scheduling if (objArray[0] != NULL) { if (ItemsChanged && ino != INV_CONF && !InventoryHidden) { FillInInventory(); // Needed when clicking on scroll bar. int curX, curY; GetCursorXY(&curX, &curY, false); InvCursor(IC_AREA, curX, curY); ItemsChanged = false; } if (ino != INV_CONF) { for (int i = 0; i < MAX_ICONS; i++) { if (iconArray[i] != NULL) StepAnimScript(&iconAnims[i]); } } if (InvDragging == ID_MDCONT) { // Mixing desk control int sval, index, *pival; index = cd.selBox & ~IS_MASK; pival = cd.Box[index].ival; sval = *pival; if (cd.selBox & IS_LEFT) { *pival -= cd.Box[index].h; if (*pival < 0) *pival = 0; } else if (cd.selBox & IS_RIGHT) { *pival += cd.Box[index].h; if (*pival > cd.Box[index].w) *pival = cd.Box[index].w; } if (sval != *pival) { SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN); } } } if (g_buttonEffect.bButAnim) { assert(g_buttonEffect.box); if (g_buttonEffect.press) { if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT) CORO_INVOKE_1(ButtonPress, g_buttonEffect.box); switch (g_buttonEffect.box->boxFunc) { case SAVEGAME: KillInventory(); InvSaveGame(); break; case LOADGAME: KillInventory(); InvLoadGame(); break; case IQUITGAME: _vm->quitGame(); break; case CLOSEWIN: KillInventory(); break; case OPENLOAD: KillInventory(); PopUpConf(LOAD); break; case OPENSAVE: KillInventory(); PopUpConf(SAVE); break; case OPENREST: KillInventory(); PopUpConf(RESTART); break; case OPENSOUND: KillInventory(); PopUpConf(SOUND); break; case OPENCONT: KillInventory(); PopUpConf(CONTROLS); break; case OPENSUBT: KillInventory(); PopUpConf(SUBT); break; case OPENQUIT: KillInventory(); PopUpConf(QUIT); break; case INITGAME: KillInventory(); bRestart = true; break; case CLANG: if (!LanguageChange()) KillInventory(); break; case RLANG: KillInventory(); break; default: break; } } else CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box); g_buttonEffect.bButAnim = false; } } CORO_END_CODE; } /**************************************************************************/ /*************** Drag stuff - Resizing and moving window ******************/ /**************************************************************************/ /** * Appears to find the nearest entry in slideStuff[] to the supplied * y-coordinate. */ int NearestSlideY(int fity) { int nearDist = 1000; int thisDist; int nearI = 0; // Index of nearest fit int i = 0; do { thisDist = ABS(slideStuff[i].y - fity); if (thisDist < nearDist) { nearDist = thisDist; nearI = i; } } while (slideStuff[++i].n != -1); return nearI; } /** * Gets called at the start and end of a drag on the slider, and upon * y-movement during such a drag. */ void SlideSlider(int y, SSFN fn) { static int newY = 0, lasti = 0; // FIXME: local static var int gotoY, ati; // Only do this if there's a slider if (!SlideObject) return; switch (fn) { case S_START: // Start of a drag on the slider newY = slideY; lasti = NearestSlideY(slideY); break; case S_SLIDE: // Y-movement during drag newY = newY + y; // New y-position if (newY < slideYmin) gotoY = slideYmin; // Above top limit else if (newY > slideYmax) gotoY = slideYmax; // Below bottom limit else gotoY = newY; // Hunky-Dory // Move slider to new position MultiMoveRelXY(SlideObject, 0, gotoY - slideY); slideY = gotoY; // Re-draw icons if necessary ati = NearestSlideY(slideY); if (ati != lasti) { InvD[ino].FirstDisp = slideStuff[ati].n; assert(InvD[ino].FirstDisp >= 0); // negative first displayed ItemsChanged = true; lasti = ati; } break; case S_END: // End of a drag on the slider // Draw icons from new start icon ati = NearestSlideY(slideY); InvD[ino].FirstDisp = slideStuff[ati].n; ItemsChanged = true; break; default: break; } } /** * Gets called at the start and end of a drag on the slider, and upon * y-movement during such a drag. */ void SlideCSlider(int y, SSFN fn) { static int newY = 0; // FIXME: local static var int gotoY; int fc; // Only do this if there's a slider if (!SlideObject) return; switch (fn) { case S_START: // Start of a drag on the slider newY = slideY; break; case S_SLIDE: // Y-movement during drag newY = newY + y; // New y-position if (newY < slideYmin) gotoY = slideYmin; // Above top limit else if (newY > slideYmax) gotoY = slideYmax; // Below bottom limit else gotoY = newY; // Hunky-Dory slideY = gotoY; fc = cd.fileBase; firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin)); if (fc != cd.fileBase) { AddBoxes(false); fc -= cd.fileBase; cd.selBox += fc; if (cd.selBox < 0) cd.selBox = 0; else if (cd.selBox >= NUM_SL_RGROUP) cd.selBox = NUM_SL_RGROUP-1; Select(cd.selBox, true); } break; case S_END: // End of a drag on the slider break; default: break; } } /** * Gets called at the start and end of a drag on a mixing desk slider, * and upon x-movement during such a drag. */ static void SlideMSlider(int x, SSFN fn) { static int newX = 0; // FIXME: local static var int gotoX; int index, i; if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN) ; else if (!(cd.selBox & IS_SLIDER)) return; // Work out the indices index = cd.selBox & ~IS_MASK; for (i = 0; i < numMdSlides; i++) if (mdSlides[i].num == index) break; assert(i < numMdSlides); switch (fn) { case S_START: // Start of a drag on the slider // can use index as a throw-away value GetAniPosition(mdSlides[i].obj, &newX, &index); lX = sX = newX; break; case S_SLIDE: // X-movement during drag if (x == 0) return; newX = newX + x; // New x-position if (newX < mdSlides[i].min) gotoX = mdSlides[i].min; // Below bottom limit else if (newX > mdSlides[i].max) gotoX = mdSlides[i].max; // Above top limit else gotoX = newX; // Hunky-Dory // Move slider to new position MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0); sX = gotoX; if (lX != sX) { *cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE; if (cd.Box[index].boxFunc == MIDIVOL) SetMidiVolume(*cd.Box[index].ival); #ifdef MAC_OPTIONS if (cd.Box[index].boxFunc == MASTERVOL) SetSystemVolume(*cd.Box[index].ival); if (cd.Box[index].boxFunc == SAMPVOL) SetSampleVolume(*cd.Box[index].ival); #endif lX = sX; } break; case S_TIMEUP: case S_TIMEDN: gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w; MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX); if (cd.Box[index].boxFunc == MIDIVOL) SetMidiVolume(*cd.Box[index].ival); #ifdef MAC_OPTIONS if (cd.Box[index].boxFunc == MASTERVOL) SetSystemVolume(*cd.Box[index].ival); if (cd.Box[index].boxFunc == SAMPVOL) SetSampleVolume(*cd.Box[index].ival); #endif break; case S_END: // End of a drag on the slider AddBoxes(false); // Might change position slightly if (ino == INV_CONF && cd.Box == subtitlesBox) Select(g_language, false); break; } } /** * Called from ChangeingSize() during re-sizing. */ void GettingTaller(void) { if (SuppV) { Ychange += SuppV; if (Ycompensate == 'T') InvD[ino].inventoryY += SuppV; SuppV = 0; } while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) { Ychange -= (ITEM_HEIGHT+1); InvD[ino].NoofVicons++; if (Ycompensate == 'T') InvD[ino].inventoryY -= (ITEM_HEIGHT+1); } if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) { SuppV = Ychange; Ychange = 0; if (Ycompensate == 'T') InvD[ino].inventoryY -= SuppV; } } /** * Called from ChangeingSize() during re-sizing. */ void GettingShorter(void) { int StartNvi = InvD[ino].NoofVicons; int StartUv = SuppV; if (SuppV) { Ychange += (SuppV - (ITEM_HEIGHT+1)); InvD[ino].NoofVicons++; SuppV = 0; } while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) { Ychange += (ITEM_HEIGHT+1); InvD[ino].NoofVicons--; } if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) { SuppV = (ITEM_HEIGHT+1) + Ychange; InvD[ino].NoofVicons--; Ychange = 0; } if (Ycompensate == 'T') InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv); } /** * Called from ChangeingSize() during re-sizing. */ void GettingWider(void) { int StartNhi = InvD[ino].NoofHicons; int StartUh = SuppH; if (SuppH) { Xchange += SuppH; SuppH = 0; } while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) { Xchange -= (ITEM_WIDTH+1); InvD[ino].NoofHicons++; } if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) { SuppH = Xchange; Xchange = 0; } if (Xcompensate == 'L') InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); } /** * Called from ChangeingSize() during re-sizing. */ void GettingNarrower(void) { int StartNhi = InvD[ino].NoofHicons; int StartUh = SuppH; if (SuppH) { Xchange += (SuppH - (ITEM_WIDTH+1)); InvD[ino].NoofHicons++; SuppH = 0; } while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) { Xchange += (ITEM_WIDTH+1); InvD[ino].NoofHicons--; } if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) { SuppH = (ITEM_WIDTH+1) + Xchange; InvD[ino].NoofHicons--; Xchange = 0; } if (Xcompensate == 'L') InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); } /** * Called from Xmovement()/Ymovement() during re-sizing. */ void ChangeingSize(void) { /* Make it taller or shorter if necessary. */ if (Ychange > 0) GettingTaller(); else if (Ychange < 0) GettingShorter(); /* Make it wider or narrower if necessary. */ if (Xchange > 0) GettingWider(); else if (Xchange < 0) GettingNarrower(); ConstructInventory(EMPTY); } /** * Called from cursor module when cursor moves while inventory is up. */ void Xmovement(int x) { int aniX, aniY; int i; if (x && objArray[0] != NULL) { switch (InvDragging) { case ID_MOVE: GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY); InvD[ino].inventoryX +=x; MultiSetAniX(objArray[0], InvD[ino].inventoryX); for (i = 1; objArray[i] && i < MAX_WCOMP; i++) MultiMoveRelXY(objArray[i], x, 0); for (i = 0; iconArray[i] && i < MAX_ICONS; i++) MultiMoveRelXY(iconArray[i], x, 0); break; case ID_LEFT: case ID_TLEFT: case ID_BLEFT: Xchange -= x; ChangeingSize(); break; case ID_RIGHT: case ID_TRIGHT: case ID_BRIGHT: Xchange += x; ChangeingSize(); break; case ID_NONE: GetCursorXY(&aniX, &aniY, false); InvCursor(IC_AREA, aniX, aniY); break; case ID_MDCONT: SlideMSlider(x, S_SLIDE); break; default: break; } } } /** * Called from cursor module when cursor moves while inventory is up. */ void Ymovement(int y) { int aniX, aniY; int i; if (y && objArray[0] != NULL) { switch (InvDragging) { case ID_MOVE: GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY); InvD[ino].inventoryY +=y; MultiSetAniY(objArray[0], InvD[ino].inventoryY); for (i = 1; objArray[i] && i < MAX_WCOMP; i++) MultiMoveRelXY(objArray[i], 0, y); for (i = 0; iconArray[i] && i < MAX_ICONS; i++) MultiMoveRelXY(iconArray[i], 0, y); break; case ID_SLIDE: SlideSlider(y, S_SLIDE); break; case ID_CSLIDE: SlideCSlider(y, S_SLIDE); break; case ID_BOTTOM: case ID_BLEFT: case ID_BRIGHT: Ychange += y; ChangeingSize(); break; case ID_TOP: case ID_TLEFT: case ID_TRIGHT: Ychange -= y; ChangeingSize(); break; case ID_NONE: GetCursorXY(&aniX, &aniY, false); InvCursor(IC_AREA, aniX, aniY); break; default: break; } } } /** * Called when a drag is commencing. */ void InvDragStart(void) { int curX, curY; // cursor's animation position GetCursorXY(&curX, &curY, false); /* * Do something different for Save/Restore screens */ if (ino == INV_CONF) { int whichbox; whichbox = WhichInvBox(curX, curY, true); if (whichbox == IB_SLIDE) { InvDragging = ID_CSLIDE; SlideCSlider(0, S_START); } else if (whichbox > 0 && (whichbox & IS_MASK)) { InvDragging = ID_MDCONT; // Mixing desk control cd.selBox = whichbox; SlideMSlider(0, S_START); } return; } /* * Normal operation */ switch (InvArea(curX, curY)) { case I_MOVE: if (InvD[ino].moveable) { InvDragging = ID_MOVE; } break; case I_SLIDE: InvDragging = ID_SLIDE; SlideSlider(0, S_START); break; case I_BOTTOM: if (InvD[ino].resizable) { Ychange = 0; InvDragging = ID_BOTTOM; Ycompensate = 'B'; } break; case I_TOP: if (InvD[ino].resizable) { Ychange = 0; InvDragging = ID_TOP; Ycompensate = 'T'; } break; case I_LEFT: if (InvD[ino].resizable) { Xchange = 0; InvDragging = ID_LEFT; Xcompensate = 'L'; } break; case I_RIGHT: if (InvD[ino].resizable) { Xchange = 0; InvDragging = ID_RIGHT; Xcompensate = 'R'; } break; case I_TLEFT: if (InvD[ino].resizable) { Ychange = 0; Ycompensate = 'T'; Xchange = 0; Xcompensate = 'L'; InvDragging = ID_TLEFT; } break; case I_TRIGHT: if (InvD[ino].resizable) { Ychange = 0; Ycompensate = 'T'; Xchange = 0; Xcompensate = 'R'; InvDragging = ID_TRIGHT; } break; case I_BLEFT: if (InvD[ino].resizable) { Ychange = 0; Ycompensate = 'B'; Xchange = 0; Xcompensate = 'L'; InvDragging = ID_BLEFT; } break; case I_BRIGHT: if (InvD[ino].resizable) { Ychange = 0; Ycompensate = 'B'; Xchange = 0; Xcompensate = 'R'; InvDragging = ID_BRIGHT; } break; } } /** * Called when a drag is over. */ void InvDragEnd(void) { int curX, curY; // cursor's animation position GetCursorXY(&curX, &curY, false); if (InvDragging != ID_NONE) { if (InvDragging == ID_SLIDE) { SlideSlider(0, S_END); } else if (InvDragging == ID_CSLIDE) { ; // No action } else if (InvDragging == ID_MDCONT) { SlideMSlider(0, S_END); } else if (InvDragging == ID_MOVE) { ; // No action } else { // Were re-sizing. Redraw the whole thing. DumpDobjArray(); DumpObjArray(); ConstructInventory(FULL); // If this was the maximised, it no longer is! if (InventoryMaximised) { InventoryMaximised = false; InvD[ino].otherX = InvD[ino].inventoryX; InvD[ino].otherY = InvD[ino].inventoryY; } } InvDragging = ID_NONE; } // Cursor could well now be inappropriate InvCursor(IC_AREA, curX, curY); Xchange = Ychange = 0; // Probably no need, but does no harm! } /**************************************************************************/ /************** Incoming events - further processing **********************/ /**************************************************************************/ /** * ConfAction */ void ConfAction(int i, bool dbl) { if (i >= 0) { switch (cd.Box[i].boxType) { case FLIP: if (dbl) { *(cd.Box[i].ival) ^= 1; // XOR with true AddBoxes(false); } break; case TOGGLE: if (!g_buttonEffect.bButAnim) { g_buttonEffect.bButAnim = true; g_buttonEffect.box = &cd.Box[i]; g_buttonEffect.press = false; } break; case RGROUP: if (dbl) { // Already highlighted switch (cd.Box[i].boxFunc) { case SAVEGAME: KillInventory(); InvSaveGame(); break; case LOADGAME: KillInventory(); InvLoadGame(); break; default: break; } } else { Select(i, false); } break; case FRGROUP: assert((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)); if (dbl) { Select(i, false); LanguageChange(); } else { Select(i, false); } break; case AAGBUT: case ARSGBUT: case ARSBUT: case AABUT: case AATBUT: if (g_buttonEffect.bButAnim) break; g_buttonEffect.bButAnim = true; g_buttonEffect.box = &cd.Box[i]; g_buttonEffect.press = true; break; default: break; } } else { ConfActionSpecial(i); } } static void ConfActionSpecial(int i) { switch (i) { case IB_NONE: break; case IB_UP: // Scroll up if (cd.fileBase > 0) { firstFile(cd.fileBase-1); AddBoxes(true); if (cd.selBox < NUM_SL_RGROUP-1) cd.selBox += 1; Select(cd.selBox, true); } break; case IB_DOWN: // Scroll down if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { firstFile(cd.fileBase+1); AddBoxes(true); if (cd.selBox) cd.selBox -= 1; Select(cd.selBox, true); } break; case IB_SLIDE_UP: if (cd.fileBase > 0) { firstFile(cd.fileBase-(NUM_SL_RGROUP-1)); AddBoxes(true); cd.selBox = 0; Select(cd.selBox, true); } break; case IB_SLIDE_DOWN: // Scroll down if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { firstFile(cd.fileBase+(NUM_SL_RGROUP-1)); AddBoxes(true); cd.selBox = NUM_SL_RGROUP-1; Select(cd.selBox, true); } break; } } // SLIDE_UP and SLIDE_DOWN on d click?????? void InvPutDown(int index) { int aniX, aniY; // index is the drop position int hiIndex; // Current position of held item (if in) // Find where the held item is positioned in this inventory (if it is) for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++) if (InvD[ino].ItemOrder[hiIndex] == HeldItem) break; // If drop position would leave a gap, move it up if (index >= InvD[ino].NoofItems) { if (hiIndex == InvD[ino].NoofItems) // Not in, add it index = InvD[ino].NoofItems; else index = InvD[ino].NoofItems - 1; } if (hiIndex == InvD[ino].NoofItems) { // Not in, add it if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) { InvD[ino].NoofItems++; // Don't leave it in the other inventory! if (InventoryPos(HeldItem) != INV_HELDNOTIN) RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem); } else { // No room at the inn! return; } } // Position it in the inventory if (index < hiIndex) { memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int)); InvD[ino].ItemOrder[index] = HeldItem; } else if (index > hiIndex) { memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int)); InvD[ino].ItemOrder[index] = HeldItem; } else { InvD[ino].ItemOrder[index] = HeldItem; } HeldItem = INV_NOICON; ItemsChanged = true; DelAuxCursor(); RestoreMainCursor(); GetCursorXY(&aniX, &aniY, false); InvCursor(IC_DROP, aniX, aniY); } void InvPdProcess(CORO_PARAM, const void *param) { // COROUTINE CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); GetToken(TOKEN_LEFT_BUT); CORO_SLEEP(dclickSpeed+1); FreeToken(TOKEN_LEFT_BUT); // get the stuff copied to process when it was created int *pindex = (int *)param; InvPutDown(*pindex); CORO_END_CODE; } void InvPickup(int index) { INV_OBJECT *invObj; if (index != INV_NOICON) { if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { // Pick-up invObj = findInvObject(InvD[ino].ItemOrder[index]); if (invObj->hScript) RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index); } else if (HeldItem != INV_NOICON) { // Put icon down // Put-down invObj = findInvObject(HeldItem); if (invObj->attribute & IO_DROPCODE && invObj->hScript) RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index); else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1) && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2)) g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index)); } } } /** * Pick up/put down icon */ void InvSLClick(void) { int i; int aniX, aniY; // Cursor's animation position GetCursorXY(&aniX, &aniY, false); switch (InvArea(aniX, aniY)) { case I_NOTIN: if (ino == INV_CONV) ConvAction(INV_CLOSEICON); KillInventory(); break; case I_SLIDE_UP: if (InvD[ino].NoofVicons == 1) InvD[ino].FirstDisp -= InvD[ino].NoofHicons; for (i = 1; i < InvD[ino].NoofVicons; i++) InvD[ino].FirstDisp -= InvD[ino].NoofHicons; if (InvD[ino].FirstDisp < 0) InvD[ino].FirstDisp = 0; ItemsChanged = true; break; case I_UP: InvD[ino].FirstDisp -= InvD[ino].NoofHicons; if (InvD[ino].FirstDisp < 0) InvD[ino].FirstDisp = 0; ItemsChanged = true; break; case I_SLIDE_DOWN: if (InvD[ino].NoofVicons == 1) if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) InvD[ino].FirstDisp += InvD[ino].NoofHicons; for (i = 1; i < InvD[ino].NoofVicons; i++) { if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) InvD[ino].FirstDisp += InvD[ino].NoofHicons; } ItemsChanged = true; break; case I_DOWN: if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { InvD[ino].FirstDisp += InvD[ino].NoofHicons; ItemsChanged = true; } break; case I_BODY: if (ino == INV_CONF) { if (!InventoryHidden) ConfAction(WhichInvBox(aniX, aniY, false), false); } else { i = InvItem(&aniX, &aniY, false); // Special bodge for David, to // cater for drop in dead space between icons if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) { aniX += 1; // 1 to the right i = InvItem(&aniX, &aniY, false); if (i == INV_NOICON) { aniX -= 1; // 1 down aniY += 1; i = InvItem(&aniX, &aniY, false); if (i == INV_NOICON) { aniX += 1; // 1 down-right i = InvItem(&aniX, &aniY, false); } } } if (ino == INV_CONV) { ConvAction(i); } else InvPickup(i); } break; } } void InvAction(void) { int index; INV_OBJECT *invObj; int aniX, aniY; int i; GetCursorXY(&aniX, &aniY, false); switch (InvArea(aniX, aniY)) { case I_BODY: if (ino == INV_CONF) { if (!InventoryHidden) ConfAction(WhichInvBox(aniX, aniY, false), true); } else if (ino == INV_CONV) { index = InvItem(&aniX, &aniY, false); ConvAction(index); } else { index = InvItem(&aniX, &aniY, false); if (index != INV_NOICON) { if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { invObj = findInvObject(InvD[ino].ItemOrder[index]); if (invObj->hScript) RunInvTinselCode(invObj, ACTION, INV_ACTION, index); } } } break; case I_MOVE: // Maximise/unmaximise inventory if (!InvD[ino].resizable) break; if (!InventoryMaximised) { InvD[ino].sNoofHicons = InvD[ino].NoofHicons; InvD[ino].sNoofVicons = InvD[ino].NoofVicons; InvD[ino].NoofHicons = InvD[ino].MaxHicons; InvD[ino].NoofVicons = InvD[ino].MaxVicons; InventoryMaximised = true; i = InvD[ino].inventoryX; InvD[ino].inventoryX = InvD[ino].otherX; InvD[ino].otherX = i; i = InvD[ino].inventoryY; InvD[ino].inventoryY = InvD[ino].otherY; InvD[ino].otherY = i; } else { InvD[ino].NoofHicons = InvD[ino].sNoofHicons; InvD[ino].NoofVicons = InvD[ino].sNoofVicons; InventoryMaximised = false; i = InvD[ino].inventoryX; InvD[ino].inventoryX = InvD[ino].otherX; InvD[ino].otherX = i; i = InvD[ino].inventoryY; InvD[ino].inventoryY = InvD[ino].otherY; InvD[ino].otherY = i; } // Delete current, and re-draw DumpDobjArray(); DumpObjArray(); ConstructInventory(FULL); break; case I_UP: InvD[ino].FirstDisp -= InvD[ino].NoofHicons; if (InvD[ino].FirstDisp < 0) InvD[ino].FirstDisp = 0; ItemsChanged = true; break; case I_DOWN: if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { InvD[ino].FirstDisp += InvD[ino].NoofHicons; ItemsChanged = true; } break; } } void InvLook(void) { int index; INV_OBJECT *invObj; int aniX, aniY; GetCursorXY(&aniX, &aniY, false); switch (InvArea(aniX, aniY)) { case I_BODY: index = InvItem(&aniX, &aniY, false); if (index != INV_NOICON) { if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { invObj = findInvObject(InvD[ino].ItemOrder[index]); if (invObj->hScript) RunInvTinselCode(invObj, LOOK, INV_LOOK, index); } } break; case I_NOTIN: if (ino == INV_CONV) ConvAction(INV_CLOSEICON); KillInventory(); break; } } /**************************************************************************/ /********************* Incoming events ************************************/ /**************************************************************************/ void ButtonToInventory(BUTEVENT be) { if (InventoryHidden) return; switch (be) { case INV_PICKUP: // BE_SLEFT InvSLClick(); break; case INV_LOOK: // BE_SRIGHT if (IsConfWindow()) InvSLClick(); else InvLook(); break; case INV_ACTION: // BE_DLEFT if (InvDragging != ID_MDCONT) InvDragEnd(); InvAction(); break; case BE_LDSTART: // Left drag start InvDragStart(); break; case BE_LDEND: // Left drag end InvDragEnd(); break; // case BE_DLEFT: // Double click left (also ends left drag) // ButtonToInventory(LDEND); // break; case BE_RDSTART: case BE_RDEND: case BE_UNKNOWN: break; default: break; } } void KeyToInventory(KEYEVENT ke) { int i; switch (ke) { case ESC_KEY: if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox) bOpenConf = true; CloseInventory(); break; case PGDN_KEY: if (ino == INV_CONF) { // Only act if load or save screen if (cd.Box != loadBox && cd.Box != saveBox) break; ConfActionSpecial(IB_SLIDE_DOWN); } else { // This code is a copy of SLClick on IB_SLIDE_DOWN // TODO: So share this duplicate code if (InvD[ino].NoofVicons == 1) if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) InvD[ino].FirstDisp += InvD[ino].NoofHicons; for (i = 1; i < InvD[ino].NoofVicons; i++) { if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) InvD[ino].FirstDisp += InvD[ino].NoofHicons; } ItemsChanged = true; } break; case PGUP_KEY: if (ino == INV_CONF) { // Only act if load or save screen if (cd.Box != loadBox && cd.Box != saveBox) break; ConfActionSpecial(IB_SLIDE_UP); } else { // This code is a copy of SLClick on I_SLIDE_UP // TODO: So share this duplicate code if (InvD[ino].NoofVicons == 1) InvD[ino].FirstDisp -= InvD[ino].NoofHicons; for (i = 1; i < InvD[ino].NoofVicons; i++) InvD[ino].FirstDisp -= InvD[ino].NoofHicons; if (InvD[ino].FirstDisp < 0) InvD[ino].FirstDisp = 0; ItemsChanged = true; } break; case HOME_KEY: if (ino == INV_CONF) { // Only act if load or save screen if (cd.Box != loadBox && cd.Box != saveBox) break; firstFile(0); AddBoxes(true); cd.selBox = 0; Select(cd.selBox, true); } else { InvD[ino].FirstDisp = 0; ItemsChanged = true; } break; case END_KEY: if (ino == INV_CONF) { // Only act if load or save screen if (cd.Box != loadBox && cd.Box != saveBox) break; firstFile(MAX_SFILES); // Will get reduced to appropriate value AddBoxes(true); cd.selBox = 0; Select(cd.selBox, true); } else { InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons; if (InvD[ino].FirstDisp < 0) InvD[ino].FirstDisp = 0; ItemsChanged = true; } break; default: error("We're at KeyToInventory(), with default"); } } /**************************************************************************/ /************************* Odds and Ends **********************************/ /**************************************************************************/ /** * Called from Glitter function invdepict() * Changes (permanently) the animation film for that object. */ void invObjectFilm(int object, SCNHANDLE hFilm) { INV_OBJECT *invObj; invObj = findInvObject(object); invObj->hFilm = hFilm; if (HeldItem != object) ItemsChanged = true; } /** * (Un)serialize the inventory data for save/restore game. */ void syncInvInfo(Serializer &s) { for (int i = 0; i < NUM_INV; i++) { s.syncAsSint32LE(InvD[i].MinHicons); s.syncAsSint32LE(InvD[i].MinVicons); s.syncAsSint32LE(InvD[i].MaxHicons); s.syncAsSint32LE(InvD[i].MaxVicons); s.syncAsSint32LE(InvD[i].NoofHicons); s.syncAsSint32LE(InvD[i].NoofVicons); for (int j = 0; j < MAX_ININV; j++) { s.syncAsSint32LE(InvD[i].ItemOrder[j]); } s.syncAsSint32LE(InvD[i].NoofItems); s.syncAsSint32LE(InvD[i].FirstDisp); s.syncAsSint32LE(InvD[i].inventoryX); s.syncAsSint32LE(InvD[i].inventoryY); s.syncAsSint32LE(InvD[i].otherX); s.syncAsSint32LE(InvD[i].otherY); s.syncAsSint32LE(InvD[i].MaxInvObj); s.syncAsSint32LE(InvD[i].hInvTitle); s.syncAsSint32LE(InvD[i].resizable); s.syncAsSint32LE(InvD[i].moveable); s.syncAsSint32LE(InvD[i].sNoofHicons); s.syncAsSint32LE(InvD[i].sNoofVicons); s.syncAsSint32LE(InvD[i].bMax); } } /**************************************************************************/ /************************ Initialisation stuff ****************************/ /**************************************************************************/ /** * Called from PlayGame(), stores handle to inventory objects' data - * its id, animation film and Glitter script. */ // Note: the SCHANDLE type here has been changed to a void* void RegisterIcons(void *cptr, int num) { numObjects = num; pio = (INV_OBJECT *) cptr; } /** * Called from Glitter function 'dec_invw()' - Declare the bits that the * inventory windows are constructed from, and special cursors. */ void setInvWinParts(SCNHANDLE hf) { #ifdef DEBUG const FILM *pfilm; #endif winPartsf = hf; #ifdef DEBUG pfilm = (const FILM *)LockMem(hf); assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected #endif } /** * Called from Glitter function 'dec_flags()' - Declare the language * flag films */ void setFlagFilms(SCNHANDLE hf) { #ifdef DEBUG const FILM *pfilm; #endif flagFilm = hf; #ifdef DEBUG pfilm = (const FILM *)LockMem(hf); assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected #endif } void setConfigStrings(SCNHANDLE *tp) { memcpy(configStrings, tp, sizeof(configStrings)); } /** * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() * - Declare the heading text and dimensions etc. */ void idec_inv(int num, SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight, int startx, int starty, bool moveable) { if (MaxWidth > MAXHICONS) MaxWidth = MAXHICONS; // Max window width if (MaxHeight > MAXVICONS) MaxHeight = MAXVICONS; // Max window height if (MaxContents > MAX_ININV) MaxContents = MAX_ININV; // Max contents if (StartWidth > MaxWidth) StartWidth = MaxWidth; if (StartHeight > MaxHeight) StartHeight = MaxHeight; InventoryState = IDLE_INV; InvD[num].MaxHicons = MaxWidth; InvD[num].MinHicons = MinWidth; InvD[num].MaxVicons = MaxHeight; InvD[num].MinVicons = MinHeight; InvD[num].NoofHicons = StartWidth; InvD[num].NoofVicons = StartHeight; memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder)); InvD[num].NoofItems = 0; InvD[num].FirstDisp = 0; InvD[num].inventoryX = startx; InvD[num].inventoryY = starty; InvD[num].otherX = 21; InvD[num].otherY = 15; InvD[num].MaxInvObj = MaxContents; InvD[num].hInvTitle = text; if (MaxWidth != MinWidth && MaxHeight != MinHeight) InvD[num].resizable = true; InvD[num].moveable = moveable; InvD[num].bMax = false; } /** * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() * - Declare the heading text and dimensions etc. */ void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight, 20, 8, true); } /** * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() * - Declare the heading text and dimensions etc. */ void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight, 100, 100, true); } /** * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() * - Declare the heading text and dimensions etc. */ void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight, 100, 100, true); } int InvGetLimit(int invno) { assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported return InvD[invno].MaxInvObj; } void InvSetLimit(int invno, int MaxContents) { assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents if (MaxContents > MAX_ININV) MaxContents = MAX_ININV; // Max contents InvD[invno].MaxInvObj = MaxContents; } void InvSetSize(int invno, int MinWidth, int MinHeight, int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported if (StartWidth > MaxWidth) StartWidth = MaxWidth; if (StartHeight > MaxHeight) StartHeight = MaxHeight; InvD[invno].MaxHicons = MaxWidth; InvD[invno].MinHicons = MinWidth; InvD[invno].MaxVicons = MaxHeight; InvD[invno].MinVicons = MinHeight; InvD[invno].NoofHicons = StartWidth; InvD[invno].NoofVicons = StartHeight; if (MaxWidth != MinWidth && MaxHeight != MinHeight) InvD[invno].resizable = true; else InvD[invno].resizable = false; InvD[invno].bMax = false; } /**************************************************************************/ bool IsTopWindow(void) { return (InventoryState == BOGUS_INV); } bool IsConfWindow(void) { return (InventoryState == ACTIVE_INV && ino == INV_CONF); } bool IsConvWindow(void) { return (InventoryState == ACTIVE_INV && ino == INV_CONV); } } // end of namespace Tinsel