diff options
Diffstat (limited to 'src/koopatlas/hud.cpp')
-rw-r--r-- | src/koopatlas/hud.cpp | 630 |
1 files changed, 356 insertions, 274 deletions
diff --git a/src/koopatlas/hud.cpp b/src/koopatlas/hud.cpp index 44b0fce..90e769e 100644 --- a/src/koopatlas/hud.cpp +++ b/src/koopatlas/hud.cpp @@ -1,25 +1,143 @@ #include "koopatlas/hud.h" -void CharToWChar(const char *input, wchar_t *output, int length) { for (int i = 0; i < length; i++) output[i] = input[i]; } -int getStarCoinCount() { +dTexMapColouriser_c::dTexMapColouriser_c() { + texmap = 0; + original = 0; + mine = 0; +} - SaveBlock *save = GetSaveFile()->GetBlock(-1); - int coinsSpent = save->credits_hiscore; - int coinsEarned = 0; +void *EGG__Heap__alloc(unsigned long size, int unk, void *heap); +void EGG__Heap__free(void *ptr, void *heap); - for (int w = 0; w < 10; w++) { - for (int l = 0; l < 10; l++) { - u32 conds = save->GetLevelCondition(w, l); +dTexMapColouriser_c::~dTexMapColouriser_c() { + resetAndClear(); +} - if (conds & COND_COIN1) { coinsEarned++; } - if (conds & COND_COIN2) { coinsEarned++; } - if (conds & COND_COIN3) { coinsEarned++; } - } +void dTexMapColouriser_c::resetAndClear() { + texmap = 0; + if (mine) { + EGG__Heap__free(mine, 0); + mine = 0; + } +} + +void dTexMapColouriser_c::setTexMap(nw4r::lyt::TexMap *tm) { + OSReport("Colourising TexMap: %p (w:%d h:%d)\n", tm, tm->width, tm->height); + if (texmap) + resetAndClear(); + + if (tm->mBits.textureFormat != GX_TF_IA8) { + OSReport("Warning: Trying to colourise image whose format is %d not GX_TF_IA8\n", tm->mBits.textureFormat); } - int coinsLeft = coinsEarned - coinsSpent; - return coinsLeft; + texmap = tm; + original = (u16*)tm->image; + mine = (u16*)EGG__Heap__alloc(tm->width * tm->height * 4, 0x20, mHeap::gameHeaps[2]); + tm->image = mine; + tm->mBits.textureFormat = GX_TF_RGBA8; +} + +void dTexMapColouriser_c::applyAlso(nw4r::lyt::TexMap *tm) { + if (!texmap) { + setTexMap(tm); + } else { + tm->image = mine; + tm->mBits.textureFormat = GX_TF_RGBA8; + } +} + +inline static float hslValue(float n1, float n2, float hue) { + if (hue > 6.0f) + hue -= 6.0f; + else if (hue < 0.0f) + hue += 6.0f; + + if (hue < 1.0f) + return n1 + (n1 - n1) * hue; + else if (hue < 3.0f) + return n2; + else if (hue < 4.0f) + return n1 + (n2 - n1) * (4.0f - hue); + else + return n1; +} + +void dTexMapColouriser_c::colourise(int h, int s, int l) { + if (!mine) + return; + + int width = texmap->width, height = texmap->height; + int texelW = width / 4, texelH = height / 4; + + u16 *source = original, *dest = mine; + + float hueParam = h / 360.0f; + float satParam = s / 100.0f; + float lumParam = l / 100.0f; + + for (int texelY = 0; texelY < texelH; texelY++) { + for (int texelX = 0; texelX < texelW; texelX++) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + u8 intensity = *source & 0xFF; + u8 alpha = *source >> 8; + + u8 r, g, b; + + // This is a hack + if (alpha < 250) { + r = g = b = intensity; + } else { + // converting from GIMP's colourise code... + // h and s are always the same + // l is the only thing we need to touch: + // we get the luminance from the source pixel + // (which, conveniently, is the intensity) + // manipulate it using the passed l and then + // convert the whole thing to RGB + + float lum = intensity / 255.0f; + + // manipulate it + if (l > 0) { + lum = lum * (1.0f - lumParam); + lum += (1.0f - (1.0f - lumParam)); + } else if (l < 0) { + lum = lum * (lumParam + 1.0f); + } + + // make it RGB + + if (s == 0) { + r = g = b = lum*255.0f; + } else { + float m1, m2; + if (lum <= 0.5f) + m2 = lum * (1.0f + satParam); + else + m2 = lum + satParam - lum * satParam; + + m1 = 2.0f * lum - m2; + + r = hslValue(m1, m2, hueParam * 6.0f + 2.0) * 255.0f; + g = hslValue(m1, m2, hueParam * 6.0f) * 255.0f; + b = hslValue(m1, m2, hueParam * 6.0f - 2.0) * 255.0f; + } + } + + // now write it + dest[0] = (alpha<<8)|r; + dest[16] = (g<<8)|b; + + source++; + dest++; + } + } + + dest += 16; + } + } } @@ -37,27 +155,22 @@ dWMHud_c *dWMHud_c::build() { dWMHud_c::dWMHud_c() { layoutLoaded = false; + displayedControllerType = -1; } +enum WMHudAnimation { + SHOW_LIVES = 0, + SHOW_HEADER, + SHOW_FOOTER +}; -// TODO: Need to define these in a better way, somehow -#define ANIM_BUTTON_1 0 -#define ANIM_BUTTON_2 1 -#define ANIM_BOTTOM_SHOW 2 -#define ANIM_BOTTOM_HIDE 3 -#define ANIM_TOP_SHOW 4 -#define ANIM_TOP_HIDE 5 int dWMHud_c::onCreate() { if (!layoutLoaded) { - bool gotFile = layout.loadArc("maphud.arc", false); + bool gotFile = layout.loadArc("MapHUD.arc", false); if (!gotFile) return false; - //static const char *brlanNames[3] = {"maphud_hitbutton.brlan", "maphud_in.brlan", "maphud_out.brlan"}; - static const char *brlanNames[5] = {"maphud_hitbutton.brlan", "bottom_in.brlan", "bottom_out.brlan", "top_in.brlan", "top_out.brlan"}; - static const char *groupNames[6] = {"B01_Button", "B02_Button", "A00_Window", "A00_Window", "A01_Window", "A01_Window"}; - bool output = layout.build("maphud.brlyt"); if (!IsWideScreen()) { @@ -70,19 +183,62 @@ int dWMHud_c::onCreate() { layout.layout.rootPane->scale.y = 0.7711f; } - layout.loadAnimations(brlanNames, 5); - layout.loadGroups(groupNames, (int[6]){0, 0, 1, 2, 3, 4}, 6); - layout.disableAllAnimations(); - layout.enableNonLoopAnim(ANIM_BOTTOM_SHOW); + static const char *brlanNames[2] = {"MapHUD_ShowMain.brlan", "MapHUD_ShowHeader.brlan"}; + static const char *groupNames[3] = {"G_Lives", "G_Header", "G_Footer"}; - hidePointBar(); - setWorldText(" "); - setWorldName(); + layout.loadAnimations(brlanNames, 2); + layout.loadGroups(groupNames, (int[3]){0, 1, 0}, 3); + layout.disableAllAnimations(); - if (dScKoopatlas_c::instance->pathManager.mustComplainToMapCreator) - dWMHud_c::instance->setLevelText("Please Fix Your Missing Entrance. Thanks"); + layout.enableNonLoopAnim(SHOW_LIVES); + layout.enableNonLoopAnim(SHOW_FOOTER); + layout.resetAnim(SHOW_HEADER); + + static const char *tbNames[2] = {"MenuButtonInfo", "ItemsButtonInfo"}; + layout.setLangStrings(tbNames, (int[2]){12, 15}, 4, 2); + + static const char *paneNames[] = { + "N_IconPos1P_00", "N_IconPos2P_00", + "N_IconPos3P_00", "N_IconPos4P_00" + }; + layout.getPanes(paneNames, &N_IconPosXP_00[0], 4); + + static const char *pictureNames[] = { + "Header_Centre", "Header_Right", "Footer", + "NormalExitFlag", "SecretExitFlag", + "StarCoinOn0", "StarCoinOn1", "StarCoinOn2", + "P_marioFace_00", "P_luigiFace_00", + "P_BkinoFace_00", "P_YkinoFace_00" + }; + layout.getPictures(pictureNames, &Header_Centre, 12); + + static const char *textBoxNames[] = { + "LevelName", "LevelNameS", + "LevelNumber", "LevelNumberS", + "WorldName", "WorldNameS", + "StarCoinCounter", + "T_lifeNumber_00", "T_lifeNumber_01", + "T_lifeNumber_02", "T_lifeNumber_03" + }; + layout.getTextBoxes(textBoxNames, &LevelName, 11); + + headerCol.setTexMap(Header_Right->material->texMaps); + headerCol.applyAlso(Header_Centre->material->texMaps); + footerCol.setTexMap(Footer->material->texMaps); layoutLoaded = true; + + layout.drawOrder = 0; + + willShowHeader = false; + willShowFooter = false; + + loadFooterInfo(); + + if (!dScKoopatlas_c::instance->pathManager.isMoving) + enteredNode(); + + setupLives(); } return true; @@ -90,15 +246,37 @@ int dWMHud_c::onCreate() { int dWMHud_c::onDelete() { + dWMHud_c::instance = 0; + + if (!layoutLoaded) + return true; + return layout.free(); } int dWMHud_c::onExecute() { - updateLives(); - setWorldName(); - checkPointStatus(); - setPointName(); + if (!layoutLoaded) + return true; + + if (willShowHeader && (!(layout.isAnimOn(SHOW_HEADER)))) { + willShowHeader = false; + loadHeaderInfo(); + playShowAnim(SHOW_HEADER); + } + + if (willShowFooter && (!(layout.isAnimOn(SHOW_FOOTER)))) { + willShowFooter = false; + loadFooterInfo(); + playShowAnim(SHOW_FOOTER); + } + + setupLives(); // FUCK IT + updatePressableButtonThingies(); + + int scCount = getStarCoinCount(); + int scLength = 3; + WriteNumberToTextBox(&scCount, &scLength, StarCoinCounter, false); layout.execAnimations(); layout.update(); @@ -108,303 +286,207 @@ int dWMHud_c::onExecute() { int dWMHud_c::onDraw() { + if (!layoutLoaded) + return true; + layout.scheduleForDrawing(); return true; } -void dWMHud_c::updateLives() { - - static const char *textID[4] = {"M", "L", "B", "Y"}; - static const char *picID[4] = {"P_mariopic", "P_luigipic", "P_toadBlue", "P_toadyellow"}; - - for (int i = 0; i < 4; i++) { - char boxName [13]; - sprintf(boxName, "T_%slifes_01", textID[i]); - nw4r::lyt::TextBox *box = layout.findTextBoxByName(boxName); - nw4r::lyt::Pane *pic = layout.findPictureByName(picID[i]); - if (Player_Active[i] != 0) { - box->alpha = 0xFF; - pic->alpha = 0xFF; +void dWMHud_c::playShowAnim(int id) { + if (!this || !this->layoutLoaded) return; - char lives [3]; - sprintf(lives, "%02d", Player_Lives[Player_ID[i]]); - const char *loaves = lives; - wchar_t life; + layout.enableNonLoopAnim(id); +} - CharToWChar(loaves, &life, 3); +void dWMHud_c::playHideAnim(int id) { + if (!this || !this->layoutLoaded) return; - box->SetString(&life); - } - else { - box->alpha = 0; - pic->alpha = 0; - } + if (!layout.isAnimOn(id)) { + layout.enableNonLoopAnim(id, true); } + layout.grpHandlers[id].frameCtrl.flags = 3; // NO_LOOP | REVERSE +} - nw4r::lyt::TextBox *coinbox = layout.findTextBoxByName("T_coin_count_01"); - - char stars [4]; - int starCoinCount = getStarCoinCount(); - sprintf(stars, "%03d", starCoinCount); - const char *scoins = stars; - wchar_t wcoins; - CharToWChar(scoins, &wcoins, 4); +void dWMHud_c::loadHeaderInfo() { + dLevelInfo_c *levelInfo = &dScKoopatlas_c::instance->levelInfo; - coinbox->SetString(&wcoins); -} + dLevelInfo_c::entry_s *infEntry = levelInfo->search( + nodeForHeader->levelNumber[0]-1, nodeForHeader->levelNumber[1]-1); -void dWMHud_c::setLevelText(const char *str, int length) { - if (str == 0) { - setLevelText("--NULL STRING--"); + if (infEntry == 0) { + LevelName->SetString(L"Unknown Level Name!"); + LevelNameS->SetString(L"Unknown Level Name!"); return; } - if (length == -1) { - length = strlen(str); + // LEVEL NAME + wchar_t convertedLevelName[100]; + const char *sourceLevelName = levelInfo->getNameForLevel(infEntry); + int charCount = 0; + + while (*sourceLevelName != 0 && charCount < 99) { + convertedLevelName[charCount] = *sourceLevelName; + sourceLevelName++; + charCount++; } - - wchar_t newString[128]; - - int i; - for (i = 0; i < length && i < 128; i++) { - newString[i] = str[i]; + convertedLevelName[charCount] = 0; + + LevelName->SetString(convertedLevelName); + LevelNameS->SetString(convertedLevelName); + + // LEVEL NUMBER + wchar_t levelNumber[6]; + levelNumber[0] = '0' + nodeForHeader->levelNumber[0]; + levelNumber[1] = '-'; + if (nodeForHeader->levelNumber[1] >= 10) { + levelNumber[2] = '0' + (nodeForHeader->levelNumber[1] / 10); + levelNumber[3] = '0' + (nodeForHeader->levelNumber[1] % 10); + levelNumber[4] = 0; + } else { + levelNumber[2] = '0' + nodeForHeader->levelNumber[1]; + levelNumber[3] = 0; } - newString[i] = 0; - setLevelText(newString, i); -} + LevelNumber->SetString(levelNumber); + LevelNumberS->SetString(levelNumber); -void dWMHud_c::setLevelText(const wchar_t *str, int length) { - if (str == 0) { - setLevelText("--NULL STRING--"); - return; - } + // INFO + int w = nodeForHeader->levelNumber[0] - 1; + int l = nodeForHeader->levelNumber[1] - 1; - if (length == -1) { - length = wcslen(str); - } - - nw4r::lyt::TextBox *box = layout.findTextBoxByName("T_levelname_01"); + u32 conds = GetSaveFile()->GetBlock(-1)->GetLevelCondition(w, l); + NormalExitFlag->SetVisible(conds & COND_NORMAL); + SecretExitFlag->SetVisible(conds & COND_SECRET); + StarCoinOn[0]->SetVisible(conds & COND_COIN1); + StarCoinOn[1]->SetVisible(conds & COND_COIN2); + StarCoinOn[2]->SetVisible(conds & COND_COIN3); + // SIZE THING nw4r::ut::TextWriter tw; - tw.font = box->font; - tw.SetFontSize(box->fontSizeX, box->fontSizeY); - tw.somethingRelatedToLineHeight = box->lineSpace; - tw.charSpace = box->charSpace; - if (box->tagProc != 0) - tw.tagProcessorMaybe = box->tagProc; - - float width = tw.CalcStringWidth(str, length); - //SpammyReport("Text width: %f\n", width); - - layout.findWindowByName("W_levelname")->size.x = width + 22; - layout.findPictureByName("P_topleftboxbg")->size.x = width; - layout.findPictureByName("P_topthinboxbg")->size.x = 597 - width; + tw.font = LevelName->font; + tw.SetFontSize(LevelName->fontSizeX, LevelName->fontSizeY); + tw.lineSpace = LevelName->lineSpace; + tw.charSpace = LevelName->charSpace; + if (LevelName->tagProc != 0) + tw.tagProcessor = LevelName->tagProc; + + float width = tw.CalcStringWidth(convertedLevelName, charCount); + float totalWidth = width + LevelName->trans.x - 20.0f; + if (totalWidth < 270.0f) + totalWidth = 270.0f; + Header_Centre->size.x = totalWidth; + Header_Right->trans.x = totalWidth; - box->SetString(str); + SaveBlock *save = GetSaveFile()->GetBlock(-1); + headerCol.colourise(save->hudHintH, save->hudHintS, save->hudHintL); } -void dWMHud_c::setPointName() { - // figure this out... - dKPNode_s *node = dScKoopatlas_c::instance->pathManager.currentNode; - - if (node->type == dKPNode_s::LEVEL) { - dLevelInfo_c *li = &dScKoopatlas_c::instance->levelInfo; - dLevelInfo_c::entry_s *entry = li->search(node->levelNumber[0] - 1, node->levelNumber[1] - 1); +void dWMHud_c::loadFooterInfo() { + SaveBlock *save = GetSaveFile()->GetBlock(-1); - setLevelText(li->getNameForLevel(entry)); - } else { - setLevelText(" "); - hidePointBar(); + wchar_t convertedWorldName[36]; + int i; + for (i = 0; i < 36; i++) { + convertedWorldName[i] = save->newerWorldName[i]; + if (convertedWorldName[i] == 0) + break; } -} - -void dWMHud_c::checkPointStatus() { - dKPNode_s *node = dScKoopatlas_c::instance->pathManager.currentNode; - - if ((node->type == dKPNode_s::LEVEL) && ((node->levelNumber[1] < 30) || (node->levelNumber[1] > 37))) { - SaveBlock *save = GetSaveFile()->GetBlock(-1); - - int world = node->levelNumber[0]; - int level = node->levelNumber[1]; - - u32 conds = save->GetLevelCondition(world-1, level-1); - nw4r::lyt::Pane *pic; - - if (conds & COND_COIN1) { - pic = layout.findPictureByName("P_coin_on_01"); - pic->alpha = 0xFF; - - pic = layout.findPictureByName("P_coin_off_01"); - pic->alpha = 0; - } - else { - pic = layout.findPictureByName("P_coin_on_01"); - pic->alpha = 0; - - pic = layout.findPictureByName("P_coin_off_01"); - pic->alpha = 0xFF; - } + convertedWorldName[35] = 0; - if (conds & COND_COIN2) { - pic = layout.findPictureByName("P_coin_on_02"); - pic->alpha = 0xFF; + WorldName->SetString(convertedWorldName); + WorldNameS->SetString(convertedWorldName); - pic = layout.findPictureByName("P_coin_off_02"); - pic->alpha = 0; - } - else { - pic = layout.findPictureByName("P_coin_on_02"); - pic->alpha = 0; - - pic = layout.findPictureByName("P_coin_off_02"); - pic->alpha = 0xFF; - } - - if (conds & COND_COIN3) { - pic = layout.findPictureByName("P_coin_on_03"); - pic->alpha = 0xFF; - - pic = layout.findPictureByName("P_coin_off_03"); - pic->alpha = 0; - } - else { - pic = layout.findPictureByName("P_coin_on_03"); - pic->alpha = 0; - - pic = layout.findPictureByName("P_coin_off_03"); - pic->alpha = 0xFF; - } - - if (conds & COND_NORMAL) { - pic = layout.findPictureByName("P_normalexitflag"); - pic->alpha = 0xFF; - } - else { - pic = layout.findPictureByName("P_normalexitflag"); - pic->alpha = 0; - } - - if (conds & COND_SECRET) { - pic = layout.findPictureByName("P_secretexitflag"); - pic->alpha = 0xFF; - } - else { - pic = layout.findPictureByName("P_secretexitflag"); - pic->alpha = 0; - } + WorldName->colour1 = save->hudTextColours[0]; + WorldName->colour2 = save->hudTextColours[1]; - u8 deaths = save->death_counts[world-1][level-1]; - nw4r::lyt::TextBox *deathbox = layout.findTextBoxByName("T_death_01"); - - char die [4]; - sprintf(die, "%03d", deaths); - const char *dies = die; - wchar_t wdie; - CharToWChar(dies, &wdie, 4); - deathbox->SetString(&wdie); + footerCol.colourise(save->hudHintH, save->hudHintS, save->hudHintL); +} - } else { - nw4r::lyt::Pane *pic; - static const char *picNames[8] = {"P_coin_on_01", "P_coin_off_01", "P_coin_on_02", "P_coin_off_02", - "P_coin_on_03", "P_coin_off_03", "P_normalexitflag", "P_secretexitflag"}; - for (int i = 0; i < 8; i++) { - pic = layout.findPictureByName(picNames[i]); - pic->alpha = 0; - } +void dWMHud_c::enteredNode(dKPNode_s *node) { + if (node == 0) + node = dScKoopatlas_c::instance->pathManager.currentNode; - nw4r::lyt::TextBox *deathbox = layout.findTextBoxByName("T_death_01"); - wchar_t noDie; - CharToWChar("---", &noDie, 4); - deathbox->SetString(&noDie); + if (node->type == dKPNode_s::LEVEL) { + willShowHeader = true; + nodeForHeader = node; } } -void dWMHud_c::setWorldText(const char *str, int length) { - if (str == 0) { - setWorldText("--NULL STRING--"); - return; - } +void dWMHud_c::leftNode() { + if (layout.grpHandlers[SHOW_HEADER].frameCtrl.currentFrame > 0.1f) { + // not hidden - if (length == -1) { - length = strlen(str); + if ((layout.isAnimOn(SHOW_HEADER) && (layout.grpHandlers[SHOW_HEADER].frameCtrl.flags & 2)) + || (!layout.isAnimOn(SHOW_HEADER))) { + // currently being shown, OR fully shown already + playHideAnim(SHOW_HEADER); + } } +} - wchar_t newString[128]; - int i; - for (i = 0; i < length && i < 128; i++) { - newString[i] = str[i]; - } - newString[i] = 0; - - setWorldText(newString, i); +void dWMHud_c::hideAndShowFooter() { + willShowFooter = true; + playHideAnim(SHOW_FOOTER); } -void dWMHud_c::setWorldText(const wchar_t *str, int length) { - if (str == 0) { - setWorldText("--NULL STRING--"); - return; - } - - if (length == -1) { - length = wcslen(str); - } - nw4r::lyt::TextBox *box = layout.findTextBoxByName("T_area_01"); +void dWMHud_c::setupLives() { + static const int LogicalPlayerIDs[] = {0,1,3,2}; - nw4r::ut::TextWriter tw; - tw.font = box->font; - tw.SetFontSize(box->fontSizeX, box->fontSizeY); - tw.somethingRelatedToLineHeight = box->lineSpace; - tw.charSpace = box->charSpace; - if (box->tagProc != 0) - tw.tagProcessorMaybe = box->tagProc; + P_marioFace_00->SetVisible(false); + P_luigiFace_00->SetVisible(false); + P_BkinoFace_00->SetVisible(false); + P_YkinoFace_00->SetVisible(false); - float width = tw.CalcStringWidth(str, length); - //SpammyReport("Text width: %f\n", width); + int playerCount = 0; - box->SetString(str); -} + for (int i = 0; i < 4; i++) { + // The part in setupLives() + int playerID = LogicalPlayerIDs[i]; + int slotID = SearchForIndexOfPlayerID(playerID); + int lives = Player_Lives[slotID]; + int length = 2; -void dWMHud_c::setWorldName() { - // figure this out... - dKPNode_s *node = dScKoopatlas_c::instance->pathManager.currentNode; + WriteNumberToTextBox(&lives, &length, T_lifeNumber[slotID], true); - if (node->type == dKPNode_s::LEVEL) { - int world = node->levelNumber[0]; - int level = node->levelNumber[1]; + // The part in setupIconThings() + if (QueryPlayerAvailability(slotID)) { + playerCount++; - setWorldText(NewerWorldName(NewerWorldForLevelID(world, level))); + nw4r::lyt::Pane *facePane = (&P_marioFace_00)[playerID]; + facePane->trans = N_IconPosXP_00[i]->trans; + facePane->SetVisible(true); + } } -} - -void dWMHud_c::showPointBar() { - dKPNode_s *node = dScKoopatlas_c::instance->pathManager.currentNode; - - if (node->type == dKPNode_s::LEVEL) { - isPointBarShown = true; - layout.enableNonLoopAnim(ANIM_TOP_SHOW); - } + for (int i = 0; i < 4; i++) + N_IconPosXP_00[i]->SetVisible(false); + N_IconPosXP_00[playerCount - 1]->SetVisible(true); } +void dWMHud_c::updatePressableButtonThingies() { + int cntType = RemoconMng->controllers[0]->controllerType; + + if (cntType != displayedControllerType) { + displayedControllerType = cntType; -void dWMHud_c::hidePointBar() { - if (isPointBarShown) { - isPointBarShown = false; + int beef = (cntType == 0) ? 0 : 1; + GameMgrP->currentControllerType = beef; - layout.enableNonLoopAnim(ANIM_TOP_HIDE); + WriteBMGToTextBox( + layout.findTextBoxByName("ItemsButtonInfo"), + GetBMG(), 4, 15, 0); } } - |