//============================================================================ //---------------------------------------------------------------------------- // House.c //---------------------------------------------------------------------------- //============================================================================ #include "PLKeyEncoding.h" #include "PLNumberFormatting.h" #include "PLPasStr.h" #include "PLResources.h" #include "PLSound.h" #include "PLSysCalls.h" #include "DialogUtils.h" #include "Externs.h" #include "FileBrowserUI.h" #include "FileManager.h" #include "FontFamily.h" #include "House.h" #include "MacFileInfo.h" #include "PLStandardColors.h" #include "PLStringCompare.h" #include "PLTimeTaggedVOSEvent.h" #include "RectUtils.h" #include "ResourceManager.h" #include #define kGoToDialogID 1043 void WriteOutPrefs(void); void UpdateGoToDialog (Dialog *); int16_t GoToFilter (void *context, Dialog *dial, const TimeTaggedVOSEvent *evt); extern PortabilityLayer::IResourceArchive *houseResFork; extern PortabilityLayer::CompositeFile *houseCFile; houseHand thisHouse; linksPtr linksList; Str32 thisHouseName; short srcLocations[kMaxRoomObs]; short destLocations[kMaxRoomObs]; short wasFloor, wasSuite; retroLink retroLinkList[kMaxRoomObs]; Boolean houseUnlocked; extern gameType smallGame; extern short numberRooms, mapLeftRoom, mapTopRoom, numStarsRemaining; extern Boolean houseOpen, noRoomAtAll; extern Boolean twoPlayerGame, wardBitSet, phoneBitSet; struct FBUI_House_Context { FBUI_House_Context(); bool m_deletedAny; }; FBUI_House_Context::FBUI_House_Context() : m_deletedAny(false) { } static void FBUI_House_DrawLabels(void *context, DrawSurface *surface, const Point &basePoint) { } static void FBUI_House_DrawFileDetails(void *context, DrawSurface *surface, const Point &basePoint, const Rect &constraintRect, void *fileDetails) { } static void *FBUI_House_LoadFileDetails(void *context, PortabilityLayer::VirtualDirectory_t dirID, const PLPasStr &filename) { return nullptr; } static void FBUI_House_FreeFileDetails(void *context, void *fileDetails) { } static bool FBUI_House_FilterFile(void *context, PortabilityLayer::VirtualDirectory_t dirID, const PLPasStr &filename) { PortabilityLayer::CompositeFile *cfile = PortabilityLayer::FileManager::GetInstance()->OpenCompositeFile(dirID, filename); return PortabilityLayer::ResTypeIDCodec::Decode(cfile->GetProperties().m_fileType) == 'gliH'; } static bool FBUI_House_IsDeleteValid(void *context, PortabilityLayer::VirtualDirectory_t dirID, const PLPasStr &filename) { if (dirID != PortabilityLayer::VirtualDirectories::kUserData) return false; return !StrCmp::EqualCaseInsensitive(thisHouseName, filename); } static void FBUI_House_OnDeleted(void *context, PortabilityLayer::VirtualDirectory_t dirID, const PLPasStr &filename) { if (dirID != PortabilityLayer::VirtualDirectories::kUserData) return; static_cast(context)->m_deletedAny = true; } static PortabilityLayer::FileBrowserUI_DetailsCallbackAPI GetHouseDetailsAPI(FBUI_House_Context *context) { PortabilityLayer::FileBrowserUI_DetailsCallbackAPI api; api.m_context = context; api.m_drawLabelsCallback = FBUI_House_DrawLabels; api.m_drawFileDetailsCallback = FBUI_House_DrawFileDetails; api.m_loadFileDetailsCallback = FBUI_House_LoadFileDetails; api.m_freeFileDetailsCallback = FBUI_House_FreeFileDetails; api.m_filterFileCallback = FBUI_House_FilterFile; api.m_isDeleteValidCallback = FBUI_House_IsDeleteValid; api.m_onDeletedCallback = FBUI_House_OnDeleted; return api; } //============================================================== Functions //-------------------------------------------------------------- CreateNewHouse // Called to create a new house file. #ifndef COMPILEDEMO Boolean CreateNewHouse (void) { size_t actualSize; VFileSpec tempSpec; VFileSpec theSpec; PLError_t theErr; PortabilityLayer::FileManager *fm = PortabilityLayer::FileManager::GetInstance(); theSpec.m_dir = PortabilityLayer::VirtualDirectories::kUserData; PasStringCopy(PSTR("My House"), theSpec.m_name); char savePath[sizeof(theSpec.m_name) + 1]; size_t savePathLength = 0; FBUI_House_Context fbuiContext; if (!fm->PromptSaveFile(theSpec.m_dir, ".gpf", savePath, savePathLength, sizeof(theSpec.m_name), PSTR("My House"), PSTR("Create House"), true, GetHouseDetailsAPI(&fbuiContext))) { if (fbuiContext.m_deletedAny) { BuildHouseList(); InitCursor(); } return false; } assert(savePathLength < sizeof(theSpec.m_name) - 1); theSpec.m_name[0] = static_cast(savePathLength); memcpy(theSpec.m_name + 1, savePath, savePathLength); // Don't try to overwrite the current house if (houseCFile && theSpec.m_dir == houseCFile->GetDirectory() && !StrCmp::CompareCaseInsensitive(theSpec.m_name, houseCFile->GetFileName())) { CheckFileError(PLErrors::kFileIsBusy, theSpec.m_name); return (false); } if (fm->CompositeFileExists(theSpec.m_dir, theSpec.m_name)) { if (!fm->DeleteCompositeFile(theSpec.m_dir, theSpec.m_name)) { CheckFileError(PLErrors::kAccessDenied, theSpec.m_name); return (false); } } if (houseOpen) { if (!CloseHouse()) return (false); } theErr = fm->CreateFileAtCurrentTime(theSpec.m_dir, theSpec.m_name, 'ozm5', 'gliH'); if (!CheckFileError(theErr, PSTR("New House"))) return (false); theErr = PortabilityLayer::ResourceManager::GetInstance()->CreateBlankResFile(theSpec.m_dir, theSpec.m_name); if (theErr != PLErrors::kNone) YellowAlert(kYellowFailedResCreate, theErr); PasStringCopy(theSpec.m_name, thisHouseName); AddExtraHouse(theSpec); BuildHouseList(); InitCursor(); if (!OpenHouse(false)) return (false); WriteOutPrefs(); return (true); } #endif //-------------------------------------------------------------- InitializeEmptyHouse // Initializes all the structures for an empty (new) house. #ifndef COMPILEDEMO Boolean InitializeEmptyHouse (void) { houseType *thisHousePtr; Str255 tempStr; if (thisHouse != nil) thisHouse.Dispose(); const size_t houseSizeNoRooms = sizeof(sizeof(houseType) - sizeof(roomType)); thisHouse = NewHandle(houseSizeNoRooms).StaticCast(); if (thisHouse == nil) { YellowAlert(kYellowUnaccounted, 1); return (false); } thisHousePtr = *thisHouse; thisHousePtr->version = kHouseVersion; thisHousePtr->firstRoom = -1; thisHousePtr->timeStamp = 0L; thisHousePtr->flags = 0L; thisHousePtr->initial.h = 32; thisHousePtr->initial.v = 32; ZeroHighScores(); GetLocalizedString(11, tempStr); PasStringCopy(tempStr, thisHousePtr->banner); GetLocalizedString(12, tempStr); PasStringCopy(tempStr, thisHousePtr->trailer); thisHousePtr->hasGame = false; thisHousePtr->nRooms = 0; wardBitSet = false; phoneBitSet = false; numberRooms = 0; mapLeftRoom = 60; mapTopRoom = 50; thisRoomNumber = kRoomIsEmpty; previousRoom = -1; houseUnlocked = true; OpenMapWindow(); UpdateMapWindow(); noRoomAtAll = true; fileDirty = true; return (true); } Boolean InitializeEmptyHouseInEditor (void) { if (!InitializeEmptyHouse()) return (false); UpdateMenus(false); ReflectCurrentRoom(true); return (true); } #endif //-------------------------------------------------------------- RealRoomNumberCount // Returns the real number of rooms in a house (some rooms may stillÉ // be place-holders - they were deleted earlier and are flagged asÉ // deleted but still occupy space in the file). short RealRoomNumberCount (void) { short realRoomCount, i; char wasState; realRoomCount = (*thisHouse)->nRooms; if (realRoomCount != 0) { for (i = 0; i < (*thisHouse)->nRooms; i++) { if ((*thisHouse)->rooms[i].suite == kRoomIsEmpty) realRoomCount--; } } return (realRoomCount); } //-------------------------------------------------------------- GetFirstRoomNumber // Returns the room number (indicee into house file) of the room whereÉ // the player is to begin. short GetFirstRoomNumber (void) { short firstRoom; char wasState; if ((*thisHouse)->nRooms <= 0) { firstRoom = -1; noRoomAtAll = true; } else { firstRoom = (*thisHouse)->firstRoom; if ((firstRoom >= (*thisHouse)->nRooms) || (firstRoom < 0)) firstRoom = 0; } return (firstRoom); } //-------------------------------------------------------------- WhereDoesGliderBegin // Returns a rectangle indicating where in the first room the player'sÉ // glider is to appear. void WhereDoesGliderBegin (Rect *theRect, short mode) { Point initialPt; char wasState; if (mode == kResumeGameMode) initialPt = smallGame.where; else if (mode == kNewGameMode) initialPt = (*thisHouse)->initial; QSetRect(theRect, 0, 0, kGliderWide, kGliderHigh); QOffsetRect(theRect, initialPt.h, initialPt.v); } //-------------------------------------------------------------- HouseHasOriginalPicts // Returns true is the current house has custom artwork imbedded. Boolean HouseHasOriginalPicts (void) { return houseResFork->HasAnyResourcesOfType('PICT'); } //-------------------------------------------------------------- CountHouseLinks // Counts up the number of linked objects in a house. short CountHouseLinks (void) { houseType *thisHousePtr; short numRooms, numLinks; short r, i, what; char wasState; numLinks = 0; thisHousePtr = *thisHouse; numRooms = thisHousePtr->nRooms; for (r = 0; r < numRooms; r++) { for (i = 0; i < kMaxRoomObs; i++) { what = thisHousePtr->rooms[r].objects[i].what; switch (what) { case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: case kTrigger: case kLgTrigger: if (thisHousePtr->rooms[r].objects[i].data.e.where != -1) numLinks++; break; case kMailboxLf: case kMailboxRt: case kFloorTrans: case kCeilingTrans: case kInvisTrans: case kDeluxeTrans: if (thisHousePtr->rooms[r].objects[i].data.d.where != -1) numLinks++; break; } } } return (numLinks); } //-------------------------------------------------------------- GenerateLinksList // Generates a list of all objects that have links and what roomsÉ // and objects they are linked to. It is called in order to preserveÉ // the links if the objects or rooms in a house are to be shuffledÉ // around. #ifndef COMPILEDEMO void GenerateLinksList (void) { houseType *thisHousePtr; objectType thisObject; short numLinks, numRooms, r, i, what; short floor, suite, roomLinked, objectLinked; char wasState; thisHousePtr = *thisHouse; numRooms = thisHousePtr->nRooms; numLinks = 0; for (r = 0; r < numRooms; r++) { for (i = 0; i < kMaxRoomObs; i++) { what = thisHousePtr->rooms[r].objects[i].what; switch (what) { case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: case kTrigger: case kLgTrigger: thisObject = thisHousePtr->rooms[r].objects[i]; if (thisObject.data.e.where != -1) { ExtractFloorSuite(*thisHouse, thisObject.data.e.where, &floor, &suite); roomLinked = GetRoomNumber(floor, suite); objectLinked = (short)thisObject.data.e.who; linksList[numLinks].srcRoom = r; linksList[numLinks].srcObj = i; linksList[numLinks].destRoom = roomLinked; linksList[numLinks].destObj = objectLinked; numLinks++; } break; case kMailboxLf: case kMailboxRt: case kFloorTrans: case kCeilingTrans: case kInvisTrans: case kDeluxeTrans: thisObject = thisHousePtr->rooms[r].objects[i]; if (thisObject.data.d.where != -1) { ExtractFloorSuite(*thisHouse, thisObject.data.d.where, &floor, &suite); roomLinked = GetRoomNumber(floor, suite); objectLinked = (short)thisObject.data.d.who; linksList[numLinks].srcRoom = r; linksList[numLinks].srcObj = i; linksList[numLinks].destRoom = roomLinked; linksList[numLinks].destObj = objectLinked; numLinks++; } break; } } } } #endif //-------------------------------------------------------------- SortRoomObjects // I'm a little fuzzy on what this does. #ifndef COMPILEDEMO void SortRoomsObjects (short which) { short probe, probe2, room, obj; Boolean busy, looking; busy = true; probe = 0; do { if ((*thisHouse)->rooms[which].objects[probe].what == kObjectIsEmpty) { looking = true; probe2 = probe + 1; // begin by looking at the next object do { if ((*thisHouse)->rooms[which].objects[probe2].what != kObjectIsEmpty) { (*thisHouse)->rooms[which].objects[probe] = (*thisHouse)->rooms[which].objects[probe2]; (*thisHouse)->rooms[which].objects[probe2].what = kObjectIsEmpty; if (srcLocations[probe2] != -1) linksList[srcLocations[probe2]].srcObj = probe; if (destLocations[probe2] != -1) { linksList[destLocations[probe2]].destObj = probe; room = linksList[destLocations[probe2]].srcRoom; obj = linksList[destLocations[probe2]].srcObj; (*thisHouse)->rooms[room].objects[obj].data.e.who = static_cast(probe); } fileDirty = true; looking = false; } probe2++; if ((probe2 >= kMaxRoomObs) && (looking)) { looking = false; busy = false; } } while (looking); } probe++; if (probe >= (kMaxRoomObs - 1)) busy = false; } while (busy); } #endif //-------------------------------------------------------------- SortHouseObjects // I'm a little fuzzy on what this does exactly either. #ifndef COMPILEDEMO void SortHouseObjects (void) { houseType *thisHousePtr; short numLinks, numRooms, r, i, l; char wasState; SpinCursor(3); CopyThisRoomToRoom(); numLinks = CountHouseLinks(); if (numLinks == 0) return; linksList = nil; linksList = (linksPtr)NewPtr(sizeof(linksType) * numLinks); if (linksList == nil) RedAlert(kErrNoMemory); GenerateLinksList(); thisHousePtr = *thisHouse; numRooms = thisHousePtr->nRooms; for (r = 0; r < numRooms; r++) { for (i = 0; i < kMaxRoomObs; i++) // initialize arrays { srcLocations[i] = -1; destLocations[i] = -1; } for (i = 0; i < kMaxRoomObs; i++) // walk object list { for (l = 0; l < numLinks; l++) // walk link list { if ((linksList[l].srcRoom == r) && (linksList[l].srcObj == i)) srcLocations[i] = l; if ((linksList[l].destRoom == r) && (linksList[l].destObj == i)) destLocations[i] = l; } } SortRoomsObjects(r); if ((r & 0x0007) == 0x0007) IncrementCursor(); } SpinCursor(3); if (linksList != nil) DisposePtr((Ptr)linksList); ForceThisRoom(thisRoomNumber); } #endif //-------------------------------------------------------------- CountRoomsVisited // Goes through and counts the number of rooms a player has been to inÉ // the current game. short CountRoomsVisited (void) { houseType *thisHousePtr; short numRooms, r, count; char wasState; thisHousePtr = *thisHouse; numRooms = thisHousePtr->nRooms; count = 0; for (r = 0; r < numRooms; r++) { if (thisHousePtr->rooms[r].visited) count++; } return (count); } //-------------------------------------------------------------- GenerateRetroLinks // Walk entire house looking for objects which are linked to objectsÉ // in the current room. void GenerateRetroLinks (void) { houseType *thisHousePtr; objectType thisObject; short i, r, numRooms, floor, suite; short what, roomLinked, objectLinked; char wasState; for (i = 0; i < kMaxRoomObs; i++) // Initialize array. retroLinkList[i].room = -1; thisHousePtr = *thisHouse; numRooms = thisHousePtr->nRooms; for (r = 0; r < numRooms; r++) { for (i = 0; i < kMaxRoomObs; i++) { what = thisHousePtr->rooms[r].objects[i].what; switch (what) { case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: case kTrigger: case kLgTrigger: thisObject = thisHousePtr->rooms[r].objects[i]; if (thisObject.data.e.where != -1) { ExtractFloorSuite(*thisHouse, thisObject.data.e.where, &floor, &suite); roomLinked = GetRoomNumber(floor, suite); if (roomLinked == thisRoomNumber) { objectLinked = (short)thisObject.data.e.who; if (retroLinkList[objectLinked].room == -1) { retroLinkList[objectLinked].room = r; retroLinkList[objectLinked].object = i; } } } break; case kMailboxLf: case kMailboxRt: case kFloorTrans: case kCeilingTrans: case kInvisTrans: case kDeluxeTrans: thisObject = thisHousePtr->rooms[r].objects[i]; if (thisObject.data.d.where != -1) { ExtractFloorSuite(*thisHouse, thisObject.data.d.where, &floor, &suite); roomLinked = GetRoomNumber(floor, suite); if (roomLinked == thisRoomNumber) { objectLinked = (short)thisObject.data.d.who; if (retroLinkList[objectLinked].room == -1) { retroLinkList[objectLinked].room = r; retroLinkList[objectLinked].object = i; } } } break; } } } } //-------------------------------------------------------------- UpdateGoToDialog // Redraws the "Go To Room..." dialog. void UpdateGoToDialog (Dialog *theDialog) { DrawDefaultButton(theDialog); FrameDialogItemC(theDialog, 10, kRedOrangeColor8); } //-------------------------------------------------------------- GoToFilter // Dialog filter for the "Go To Room..." dialog. int16_t GoToFilter(void *context, Dialog *dial, const TimeTaggedVOSEvent *evt) { if (!evt) return -1; if (evt->IsKeyDownEvent()) { const GpKeyboardInputEvent &keyEvt = evt->m_vosEvent.m_event.m_keyboardInputEvent; switch (PackVOSKeyCode(keyEvt)) { case PL_KEY_SPECIAL(kEnter): case PL_KEY_NUMPAD_SPECIAL(kEnter): FlashDialogButton(dial, kOkayButton); return kOkayButton; default: return -1; } } return -1; } //-------------------------------------------------------------- DoGoToDialog // "Go To Room..." dialog. void DoGoToDialog (void) { #define kGoToFirstButt 2 #define kGoToPrevButt 3 #define kGoToFSButt 4 #define kFloorEditText 5 #define kSuiteEditText 6 Dialog *theDialog; long tempLong; short item, roomToGoTo; Boolean leaving, canceled; BringUpDialog(&theDialog, kGoToDialogID, nullptr); if (GetFirstRoomNumber() == thisRoomNumber) MyDisableControl(theDialog, kGoToFirstButt); if ((!RoomNumExists(previousRoom)) || (previousRoom == thisRoomNumber)) MyDisableControl(theDialog, kGoToPrevButt); SetDialogNumToStr(theDialog, kFloorEditText, (long)wasFloor); SetDialogNumToStr(theDialog, kSuiteEditText, (long)wasSuite); SelectDialogItemText(theDialog, kFloorEditText, 0, 1024); leaving = false; canceled = false; UpdateGoToDialog(theDialog); while (!leaving) { item = theDialog->ExecuteModal(nullptr, PL_FILTER_FUNC(GoToFilter)); if (item == kOkayButton) { roomToGoTo = -1; canceled = true; leaving = true; } else if (item == kGoToFirstButt) { roomToGoTo = GetFirstRoomNumber(); leaving = true; } else if (item == kGoToPrevButt) { roomToGoTo = previousRoom; leaving = true; } else if (item == kGoToFSButt) { GetDialogNumFromStr(theDialog, kFloorEditText, &tempLong); wasFloor = (short)tempLong; GetDialogNumFromStr(theDialog, kSuiteEditText, &tempLong); wasSuite = (short)tempLong; roomToGoTo = GetRoomNumber(wasFloor, wasSuite); leaving = true; } } theDialog->Destroy(); if (!canceled) { if (RoomNumExists(roomToGoTo)) { DeselectObject(); CopyRoomToThisRoom(roomToGoTo); ReflectCurrentRoom(false); } else SysBeep(1); } } //-------------------------------------------------------------- ConvertHouseVer1To2 // This function goes through an old version 1 house and converts itÉ // to version 2. void ConvertHouseVer1To2 (void) { Str255 roomStr, message; short wasRoom, floor, suite; short i, h, numRooms; char wasState; CopyThisRoomToRoom(); wasRoom = thisRoomNumber; GetLocalizedString(13, message); OpenMessageWindow(message); SpinCursor(3); numRooms = (*thisHouse)->nRooms; for (i = 0; i < numRooms; i++) { if ((*thisHouse)->rooms[i].suite != kRoomIsEmpty) { NumToString((long)i, roomStr); GetLocalizedString(14, message); PasStringConcat(message, roomStr); SetMessageWindowMessage(message, StdColors::Black()); SpinCursor(1); ForceThisRoom(i); for (h = 0; h < kMaxRoomObs; h++) { switch (thisRoom->objects[h].what) { case kMailboxLf: case kMailboxRt: case kFloorTrans: case kCeilingTrans: case kInvisTrans: case kDeluxeTrans: if (thisRoom->objects[h].data.d.where != -1) { ExtractFloorSuite(*thisHouse, thisRoom->objects[h].data.d.where, &floor, &suite); floor += kNumUndergroundFloors; thisRoom->objects[h].data.d.where = MergeFloorSuite(floor, suite); } break; case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: case kTrigger: case kLgTrigger: if (thisRoom->objects[h].data.e.where != -1) { ExtractFloorSuite(*thisHouse, thisRoom->objects[h].data.e.where, &floor, &suite); floor += kNumUndergroundFloors; thisRoom->objects[h].data.e.where = MergeFloorSuite(floor, suite); } break; } } CopyThisRoomToRoom(); } } (*thisHouse)->version = kHouseVersion; InitCursor(); CloseMessageWindow(); ForceThisRoom(wasRoom); } //-------------------------------------------------------------- ShiftWholeHouse void ShiftWholeHouse (short howFar) { short wasRoom; short i, h, numRooms; char wasState; OpenMessageWindow(PSTR("Shifting Whole House\xc9")); SpinCursor(3); CopyThisRoomToRoom(); wasRoom = thisRoomNumber; numRooms = (*thisHouse)->nRooms; for (i = 0; i < numRooms; i++) { if ((*thisHouse)->rooms[i].suite != kRoomIsEmpty) { SpinCursor(1); ForceThisRoom(i); for (h = 0; h < kMaxRoomObs; h++) { } CopyThisRoomToRoom(); } } ForceThisRoom(wasRoom); InitCursor(); CloseMessageWindow(); } PL_IMPLEMENT_FILTER_FUNCTION(GoToFilter)