diff --git a/lang/en.txt b/lang/en.txt index c85607c4d..2a864d9e7 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -589,13 +589,13 @@ thou makest use of them.# # skill levels -363 none# -364 novice# -365 basic# -366 skilled# -367 expert# -368 master# -369 legend# +363 None# +364 Novice# +365 Basic# +366 Skilled# +367 Expert# +368 Master# +369 Legend# # rest of the character sheet @@ -5995,5 +5995,21 @@ Changing this gameflag is disabled in current game mode.# 4047 No interactions available# 4048 You don't feel like yourself.# 4049 Quick-cast# +4050 Grab# +4051 Level %d# +4052 Floor %d# +4053 Close# +4054 Next Page# +4055 Previous Page# +4056 LEGEND BONUS# +4057 None# +4058 All items# +4059 2 Metal +/ 0 Magic# +4060 1 Metal +/ 0 Magic# +4061 Tier# +4062 Scroll up/down# +4063 Scroll# 4099 end# \ No newline at end of file diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index d51b1a30e..2458c9694 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -22,6 +22,7 @@ #include "interface/interface.hpp" #include "items.hpp" #include "scores.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -384,6 +385,119 @@ void Entity::actStalagColumn() } +void actStatue(Entity* my) +{ + if ( my->statueInit == 0 && StatueManager.allStatues.size() > 0 ) + { + // needs to init. + if ( StatueManager.allStatues.find(my->statueId) != StatueManager.allStatues.end() ) + { + my->statueInit = 1; + if ( my->statueDir >= 0 && my->statueDir < StatueManager.directionKeys.size() ) + { + int index = 0; + real_t baseHeight = 0.0; + std::string directionString = StatueManager.directionKeys[my->statueDir]; + for ( auto& limb : StatueManager.allStatues[my->statueId].limbs[directionString] ) + { + Entity* childEntity = newEntity(limb.sprite, 1, map.entities, nullptr); + childEntity->parent = my->getUID(); + childEntity->x = my->x - limb.x; + childEntity->y = my->y - limb.y; + childEntity->z = limb.z + StatueManager.allStatues[my->statueId].heightOffset; + childEntity->focalx = limb.focalx; + childEntity->focaly = limb.focaly; + childEntity->focalz = limb.focalz; + childEntity->pitch = limb.pitch; + childEntity->roll = limb.roll; + childEntity->yaw = limb.yaw; + childEntity->flags[PASSABLE] = true; + childEntity->grayscaleGLRender = 1.0; + node_t* tempNode = list_AddNodeLast(&my->children); + tempNode->element = childEntity; // add the node to the children list. + tempNode->deconstructor = &emptyDeconstructor; + tempNode->size = sizeof(Entity*); + ++index; + } + } + } + } +} + +void actStatueAnimator(Entity* my) +{ + if ( !my ) + { + return; + } + + if ( StatueManager.processStatueExport() == 1 ) // in progress + { + if ( Entity* player = uidToEntity(StatueManager.editingPlayerUid) ) + { + player->yaw += PI / 2; + while ( player->yaw >= 2 * PI ) + { + player->yaw -= 2 * PI; + } + } + } + + for ( int i = 0; i < MAXPLAYERS; i++ ) + { + if ( (i == 0 && selectedEntity[0] == my) || (client_selected[i] == my) || (splitscreen && selectedEntity[i] == my) ) + { + if ( inrange[i] ) + { + if ( !StatueManager.activeEditing ) + { + StatueManager.statueEditorHeightOffset = 0.0; + } + StatueManager.activeEditing = !StatueManager.activeEditing; + messagePlayer(0, "Statue editing mode: %d", StatueManager.activeEditing); + + my->skill[0] = StatueManager.activeEditing ? 1 : 0; + } + } + } + + if ( StatueManager.activeEditing ) + { + if ( !players[1]->entity ) + { + client_disconnected[1] = false; + Entity* entity = newEntity(0, 1, map.entities, nullptr); + entity->behavior = &actPlayer; + entity->addToCreatureList(map.creatures); + + entity->x = my->x; + entity->y = my->y; + entity->z = my->z; + + entity->z -= 15 + StatueManager.statueEditorHeightOffset; + entity->focalx = limbs[HUMAN][0][0]; // 0 + entity->focaly = limbs[HUMAN][0][1]; // 0 + entity->focalz = limbs[HUMAN][0][2]; // -1.5 + entity->sprite = 113; // head model + entity->sizex = 4; + entity->sizey = 4; + entity->flags[GENIUS] = true; + entity->flags[BLOCKSIGHT] = true; + entity->flags[PASSABLE] = true; + entity->skill[2] = 1; // skill[2] == PLAYER_NUM + players[1]->entity = entity; + StatueManager.editingPlayerUid = entity->getUID(); + } + else + { + players[1]->entity->x = my->x; + players[1]->entity->y = my->y; + players[1]->entity->z = my->z; + players[1]->entity->z -= 15 + StatueManager.statueEditorHeightOffset; + } + } +} + void actColumn(Entity* my) { //TODO: something? diff --git a/src/actplayer.cpp b/src/actplayer.cpp index f5f764f29..8c9ae26f3 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -27,6 +27,7 @@ #include "colors.hpp" #include "draw.hpp" #include "mod_tools.hpp" +#include "classdescriptions.hpp" bool smoothmouse = false; bool settings_smoothmouse = false; @@ -993,10 +994,11 @@ void Player::PlayerMovement_t::handlePlayerMovement(bool useRefreshRateDelta) PLAYER_VELX *= pow(0.75, refreshRateDelta); PLAYER_VELY *= pow(0.75, refreshRateDelta); - /*if ( keystatus[SDL_SCANCODE_G] ) - { - messagePlayer(0, "X: %5.5f, Y: %5.5f", PLAYER_VELX, PLAYER_VELY); - }*/ + //if ( keystatus[SDL_SCANCODE_G] ) + //{ + // //messagePlayer(0, "X: %5.5f, Y: %5.5f", PLAYER_VELX, PLAYER_VELY); + // //messagePlayer(0, "Vel: %5.5f", sqrt(pow(PLAYER_VELX, 2) + pow(PLAYER_VELY, 2))); + //} for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) //Since looking for players only, don't search full entity list. Best idea would be to directly example players[] though. { @@ -1128,6 +1130,426 @@ void Player::PlayerMovement_t::handlePlayerCameraPosition(bool useRefreshRateDel } } +void statueCycleItem(Item& item, bool dirForward) +{ + int cat = items[item.type].item_slot; + item.appearance = rand(); + if ( dirForward ) + { + for ( int i = item.type + 1; i <= NUMITEMS && i != item.type; ++i ) + { + if ( i == NUMITEMS ) + { + i = 0; + } + if ( items[i].item_slot == cat ) + { + item.type = ItemType(i); + break; + } + } + } + else + { + for ( int i = item.type - 1; i < NUMITEMS && i != item.type; --i ) + { + if ( i < 0 ) + { + i = NUMITEMS - 1; + } + if ( items[i].item_slot == cat ) + { + item.type = ItemType(i); + break; + } + } + } +} + +void doStatueEditor(int player) +{ + if ( !StatueManager.activeEditing ) { return; } + if ( player != clientnum ) { return; } + + Sint32 mouseX = inputs.getMouse(player, Inputs::OX); + Sint32 mouseY = inputs.getMouse(player, Inputs::OY); + bool shootmode = players[player]->shootmode; + + if ( ticks % 5 == 0 ) + { + Entity* underMouse = nullptr; + Uint32 uidnum = 0; + if ( !shootmode ) + { + uidnum = GO_GetPixelU32(mouseX, yres - mouseY, cameras[player]); + if ( uidnum > 0 ) + { + underMouse = uidToEntity(uidnum); + } + } + else + { + uidnum = GO_GetPixelU32(cameras[player].winw / 2, yres - cameras[player].winh / 2, cameras[player]); + if ( uidnum > 0 ) + { + underMouse = uidToEntity(uidnum); + } + } + if ( underMouse ) + { + underMouse->highlightForUI = 1.0; + if ( underMouse->behavior == &actPlayerLimb ) + { + underMouse = players[underMouse->skill[2]]->entity; + } + if ( underMouse->behavior == &actPlayer ) + { + StatueManager.editingPlayerUid = underMouse->getUID(); + StatueManager.lastEntityUnderMouse = uidnum; + } + } + } + + + if ( Entity* playerEntity = uidToEntity(StatueManager.editingPlayerUid) ) + { + playerEntity->highlightForUI = 0.0; + if ( StatueManager.drawGreyscale ) + { + playerEntity->grayscaleGLRender = 1.0; + } + else + { + playerEntity->grayscaleGLRender = 0.0; + } + for ( auto& bodypart : playerEntity->bodyparts ) + { + bodypart->highlightForUI = 0.0; + if ( StatueManager.drawGreyscale ) + { + bodypart->grayscaleGLRender = 1.0; + } + else + { + bodypart->grayscaleGLRender = 0.0; + } + } + } + + if ( !command ) + { + if ( Entity* limb = uidToEntity(StatueManager.lastEntityUnderMouse) ) + { + limb->highlightForUI = 1.0; + if ( keystatus[SDL_SCANCODE_O] ) + { + keystatus[SDL_SCANCODE_O] = 0; + limb->pitch += PI / 32; + } + if ( keystatus[SDL_SCANCODE_P] ) + { + keystatus[SDL_SCANCODE_P] = 0; + limb->pitch -= PI / 32; + } + if ( keystatus[SDL_SCANCODE_K] ) + { + keystatus[SDL_SCANCODE_K] = 0; + limb->roll += PI / 32; + } + if ( keystatus[SDL_SCANCODE_L] ) + { + keystatus[SDL_SCANCODE_L] = 0; + limb->roll -= PI / 32; + } + if ( keystatus[SDL_SCANCODE_COMMA] ) + { + keystatus[SDL_SCANCODE_COMMA] = 0; + limb->yaw += PI / 32; + } + if ( keystatus[SDL_SCANCODE_PERIOD] ) + { + keystatus[SDL_SCANCODE_PERIOD] = 0; + limb->yaw -= PI / 32; + } + } + + if ( Entity* playerEntity = uidToEntity(StatueManager.editingPlayerUid) ) + { + Stat* stats = playerEntity->getStats(); + + if ( keystatus[SDL_SCANCODE_LEFTBRACKET] ) + { + keystatus[SDL_SCANCODE_LEFTBRACKET] = 0; + StatueManager.statueEditorHeightOffset -= .25; + } + if ( keystatus[SDL_SCANCODE_RIGHTBRACKET] ) + { + keystatus[SDL_SCANCODE_RIGHTBRACKET] = 0; + StatueManager.statueEditorHeightOffset += .25; + } + + if ( keystatus[SDL_SCANCODE_F1] ) + { + keystatus[SDL_SCANCODE_F1] = 0; + + ++stats->playerRace; + if ( playerEntity->getMonsterFromPlayerRace(stats->playerRace) == HUMAN && stats->playerRace > 0 ) + { + stats->playerRace = RACE_HUMAN; + } + if ( playerEntity->getMonsterFromPlayerRace(stats->playerRace) != HUMAN ) + { + stats->appearance = 0; + } + } + if ( keystatus[SDL_SCANCODE_F2] ) + { + keystatus[SDL_SCANCODE_F2] = 0; + + ++stats->appearance; + if ( stats->appearance >= NUMAPPEARANCES ) + { + stats->appearance = 0; + } + } + if ( keystatus[SDL_SCANCODE_F3] ) + { + keystatus[SDL_SCANCODE_F3] = 0; + if ( stats->sex == MALE ) + { + stats->sex = FEMALE; + } + else + { + stats->sex = MALE; + } + } + if ( keystatus[SDL_SCANCODE_F4] ) + { + keystatus[SDL_SCANCODE_F4] = 0; + StatueManager.drawGreyscale = !StatueManager.drawGreyscale; + } + + if ( keystatus[SDL_SCANCODE_1] ) + { + keystatus[SDL_SCANCODE_1] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->helmet ) + { + list_RemoveNode(stats->helmet->node); + stats->helmet = nullptr; + } + } + else + { + if ( !stats->helmet ) + { + stats->helmet = newItem(LEATHER_HELM, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->helmet, false); + } + else + { + statueCycleItem(*stats->helmet, true); + } + } + } + if ( keystatus[SDL_SCANCODE_2] ) + { + keystatus[SDL_SCANCODE_2] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->breastplate ) + { + list_RemoveNode(stats->breastplate->node); + stats->breastplate = nullptr; + } + } + else + { + if ( !stats->breastplate ) + { + stats->breastplate = newItem(LEATHER_BREASTPIECE, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->breastplate, false); + } + else + { + statueCycleItem(*stats->breastplate, true); + } + } + } + if ( keystatus[SDL_SCANCODE_3] ) + { + keystatus[SDL_SCANCODE_3] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->gloves ) + { + list_RemoveNode(stats->gloves->node); + stats->gloves = nullptr; + } + } + else + { + if ( !stats->gloves ) + { + stats->gloves = newItem(GLOVES, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->gloves, false); + } + else + { + statueCycleItem(*stats->gloves, true); + } + } + } + if ( keystatus[SDL_SCANCODE_4] ) + { + keystatus[SDL_SCANCODE_4] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->shoes ) + { + list_RemoveNode(stats->shoes->node); + stats->shoes = nullptr; + } + } + else + { + if ( !stats->shoes ) + { + stats->shoes = newItem(LEATHER_BOOTS, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->shoes, false); + } + else + { + statueCycleItem(*stats->shoes, true); + } + } + } + if ( keystatus[SDL_SCANCODE_5] ) + { + keystatus[SDL_SCANCODE_5] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->weapon ) + { + list_RemoveNode(stats->weapon->node); + stats->weapon = nullptr; + } + } + else + { + if ( !stats->weapon ) + { + stats->weapon = newItem(BRONZE_SWORD, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->weapon, false); + } + else + { + statueCycleItem(*stats->weapon, true); + } + } + } + if ( keystatus[SDL_SCANCODE_6] ) + { + keystatus[SDL_SCANCODE_6] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->shield ) + { + list_RemoveNode(stats->shield->node); + stats->shield = nullptr; + } + } + else + { + if ( !stats->shield ) + { + stats->shield = newItem(WOODEN_SHIELD, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->shield, false); + } + else + { + statueCycleItem(*stats->shield, true); + } + } + } + if ( keystatus[SDL_SCANCODE_7] ) + { + keystatus[SDL_SCANCODE_7] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->mask ) + { + list_RemoveNode(stats->mask->node); + stats->mask = nullptr; + } + } + else + { + if ( !stats->mask ) + { + stats->mask = newItem(TOOL_GLASSES, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->mask, false); + } + else + { + statueCycleItem(*stats->mask, true); + } + } + } + if ( keystatus[SDL_SCANCODE_8] ) + { + keystatus[SDL_SCANCODE_8] = 0; + if ( keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT] ) + { + if ( stats->cloak ) + { + list_RemoveNode(stats->cloak->node); + stats->cloak = nullptr; + } + } + else + { + if ( !stats->cloak ) + { + stats->cloak = newItem(CLOAK, EXCELLENT, 0, 1, rand(), true, &stats->inventory); + } + if ( keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT] ) + { + statueCycleItem(*stats->cloak, false); + } + else + { + statueCycleItem(*stats->cloak, true); + } + } + } + } + } +} + void actPlayer(Entity* my) { if (!my) @@ -2099,11 +2521,11 @@ void actPlayer(Entity* my) } // debug stuff - if ( !command && keystatus[SDL_SCANCODE_O] ) + /*if ( !command && keystatus[SDL_SCANCODE_O] ) { consoleCommand("/facebaralternate"); keystatus[SDL_SCANCODE_O] = 0; - } + }*/ if ( inputs.hasController(PLAYER_NUM) ) { //if ( keystatus[SDL_SCANCODE_KP_1] ) @@ -2961,23 +3383,19 @@ void actPlayer(Entity* my) my->effectTimes(); } + bool freezeLimbMovements = false; + if ( StatueManager.editingPlayerUid > 0 + && uidToEntity(StatueManager.editingPlayerUid) == players[PLAYER_NUM]->entity ) + { + freezeLimbMovements = true; + } + // invisibility if ( !intro ) { - if ( players[PLAYER_NUM]->isLocalPlayer() || multiplayer == SERVER ) + if ( players[PLAYER_NUM]->isLocalPlayer() || multiplayer == SERVER || StatueManager.activeEditing ) { - if ( stats[PLAYER_NUM]->ring != NULL ) - if ( stats[PLAYER_NUM]->ring->type == RING_INVISIBILITY ) - { - wearingring = true; - } - if ( stats[PLAYER_NUM]->cloak != NULL ) - if ( stats[PLAYER_NUM]->cloak->type == CLOAK_INVISIBILITY ) - { - wearingring = true; - } - //if ( stats[PLAYER_NUM]->EFFECTS[EFF_INVISIBLE] == true || wearingring == true ) - if ( my->isInvisible() ) + if ( my->isInvisible() && !freezeLimbMovements ) { if ( !my->flags[INVISIBLE] ) { @@ -3457,6 +3875,7 @@ void actPlayer(Entity* my) if ( intro == false ) { clickDescription(PLAYER_NUM, NULL); // inspecting objects + doStatueEditor(PLAYER_NUM); if ( followerMenu.optionSelected == ALLY_CMD_ATTACK_SELECT ) { @@ -3943,7 +4362,7 @@ void actPlayer(Entity* my) } // server controls players primarily - if ( players[PLAYER_NUM]->isLocalPlayer() || multiplayer == SERVER ) + if ( players[PLAYER_NUM]->isLocalPlayer() || multiplayer == SERVER || StatueManager.activeEditing ) { // set head model if ( playerRace != HUMAN ) @@ -4722,7 +5141,7 @@ void actPlayer(Entity* my) dist = sqrt(PLAYER_VELX * PLAYER_VELX + PLAYER_VELY * PLAYER_VELY); } - if ( (players[PLAYER_NUM]->isLocalPlayer()) && ticks % 65 == 0 ) + if ( (players[PLAYER_NUM]->isLocalPlayer()) && ticks % 65 == 0 && stats[PLAYER_NUM]->EFFECTS[EFF_TELEPATH] ) { for ( node_t* mapNode = map.creatures->first; mapNode != nullptr; mapNode = mapNode->next ) { @@ -4929,20 +5348,23 @@ void actPlayer(Entity* my) } else { - if ( entity->pitch < 0 ) + if ( !freezeLimbMovements ) { - entity->pitch += 1 / fmax(limbSpeed * .1, 10.0); - if ( entity->pitch > 0 ) + if ( entity->pitch < 0 ) { - entity->pitch = 0; + entity->pitch += 1 / fmax(limbSpeed * .1, 10.0); + if ( entity->pitch > 0 ) + { + entity->pitch = 0; + } } - } - else if ( entity->pitch > 0 ) - { - entity->pitch -= 1 / fmax(limbSpeed * .1, 10.0); - if ( entity->pitch < 0 ) + else if ( entity->pitch > 0 ) { - entity->pitch = 0; + entity->pitch -= 1 / fmax(limbSpeed * .1, 10.0); + if ( entity->pitch < 0 ) + { + entity->pitch = 0; + } } } } @@ -5339,20 +5761,23 @@ void actPlayer(Entity* my) } else { - if ( entity->pitch < 0 ) + if ( !freezeLimbMovements ) { - entity->pitch += 1 / fmax(limbSpeed * .1, 10.0); - if ( entity->pitch > 0 ) + if ( entity->pitch < 0 ) { - entity->pitch = 0; + entity->pitch += 1 / fmax(limbSpeed * .1, 10.0); + if ( entity->pitch > 0 ) + { + entity->pitch = 0; + } } - } - else if ( entity->pitch > 0 ) - { - entity->pitch -= 1 / fmax(limbSpeed * .1, 10.0); - if ( entity->pitch < 0 ) + else if ( entity->pitch > 0 ) { - entity->pitch = 0; + entity->pitch -= 1 / fmax(limbSpeed * .1, 10.0); + if ( entity->pitch < 0 ) + { + entity->pitch = 0; + } } } } diff --git a/src/actthrown.cpp b/src/actthrown.cpp index 1a6d63083..ca524df52 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -1102,7 +1102,10 @@ void actThrown(Entity* my) { if ( hitstats->type == LICH || hitstats->type == LICH_ICE || hitstats->type == LICH_FIRE ) { - steamAchievementClient(parent->skill[2], "BARONY_ACH_SECRET_WEAPON"); + if ( client_classes[parent->skill[2]] == CLASS_BREWER ) + { + steamAchievementClient(parent->skill[2], "BARONY_ACH_SECRET_WEAPON"); + } } steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_BOMBARDIER, STEAM_STAT_INT, 1); } diff --git a/src/book.cpp b/src/book.cpp index 6f92b27dd..abfa80891 100644 --- a/src/book.cpp +++ b/src/book.cpp @@ -358,6 +358,7 @@ void BookParser_t::writeCompiledBooks() std::string inputPath = outputdir; inputPath.append(PHYSFS_getDirSeparator()); std::string fileName = "books/compiled_books.json"; + inputPath.append(fileName); File* fp = FileIO::open(inputPath.c_str(), "rb"); rapidjson::Document d; @@ -598,7 +599,7 @@ void BookParser_t::createBook(std::string filename) const int MAX_PARSE_CHARACTERS = 65535; Field* tmpField = new Field(MAX_PARSE_CHARACTERS); tmpField->setSize(SDL_Rect{ 0, 0, Player::BookGUI_t::BOOK_PAGE_WIDTH, Player::BookGUI_t::BOOK_PAGE_HEIGHT }); - tmpField->setFont("fonts/pixel_maz.ttf#16"); + tmpField->setFont("fonts/pixel_maz.ttf#32"); tmpField->setText(pageText.c_str()); tmpField->reflowTextToFit(0); @@ -620,7 +621,8 @@ void BookParser_t::createBook(std::string filename) firstIteration = false; pageText += token; tmpField->setText(pageText.c_str()); - if ( auto getText = Text::get(tmpField->getText(), tmpField->getFont()) ) + if ( auto getText = Text::get(tmpField->getText(), tmpField->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { int textHeight = getText->getHeight(); if ( textHeight > tmpField->getSize().h ) @@ -694,7 +696,8 @@ void BookParser_t::createBook(std::string filename) // } // } // tmpField.setText(pageText.c_str()); - // if ( auto getText = Text::get(tmpField.getText(), tmpField.getFont()) ) + // if ( auto getText = Text::get(tmpField.getText(), tmpField.getFont(), + // makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) // { // if ( getText->getHeight() > tmpField.getSize().h - tmpField.getSize().y ) // { diff --git a/src/buttons.cpp b/src/buttons.cpp index ca3c69fe2..4abc19913 100644 --- a/src/buttons.cpp +++ b/src/buttons.cpp @@ -2280,6 +2280,20 @@ void buttonSpriteProperties(button_t* my) suby2 = yres / 2 + 60; strcpy(subtext, "Player Spawn Properties:"); break; + case 24: // statue + snprintf(spriteProperties[0], 4, "%d", static_cast(selectedEntity[0]->statueDir)); + snprintf(spriteProperties[1], 32, "%d", static_cast(selectedEntity[0]->statueId)); + inputstr = spriteProperties[0]; + cursorflash = ticks; + menuVisible = 0; + subwindow = 1; + newwindow = 29; + subx1 = xres / 2 - 170; + subx2 = xres / 2 + 170; + suby1 = yres / 2 - 60; + suby2 = yres / 2 + 60; + strcpy(subtext, "Statue Properties:"); + break; default: strcpy(message, "No properties available for current sprite."); messagetime = 60; @@ -3355,6 +3369,10 @@ void buttonSpritePropertiesConfirm(button_t* my) case 23: // player spawn selectedEntity[0]->playerStartDir = (Sint32)atoi(spriteProperties[0]); break; + case 24: // statue + selectedEntity[0]->statueDir = (Sint32)atoi(spriteProperties[0]); + selectedEntity[0]->statueId = (Sint32)atoi(spriteProperties[1]); + break; default: break; } diff --git a/src/draw.cpp b/src/draw.cpp index 8f4d5803c..3a587113a 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -491,7 +491,7 @@ void drawImageRotatedAlpha( SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos, re } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); glColor4f(1, 1, 1, alpha / 255.1); glBegin(GL_QUADS); glTexCoord2f(1.0 * ((real_t)src->x / image->w), 1.0 * ((real_t)src->y / image->h)); @@ -540,7 +540,7 @@ void drawImageColor( SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos, Uint32 co } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); real_t r = ((Uint8)(color >> mainsurface->format->Rshift)) / 255.f; real_t g = ((Uint8)(color >> mainsurface->format->Gshift)) / 255.f; real_t b = ((Uint8)(color >> mainsurface->format->Bshift)) / 255.f; @@ -594,7 +594,7 @@ void drawImageAlpha( SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos, Uint8 alp } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); glColor4f(1, 1, 1, alpha / 255.1); glPushMatrix(); glBegin(GL_QUADS); @@ -644,7 +644,7 @@ void drawImage( SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos ) } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); glColor4f(1, 1, 1, 1); glPushMatrix(); glBegin(GL_QUADS); @@ -694,7 +694,7 @@ void drawImageRing(SDL_Surface* image, SDL_Rect* src, int radius, int thickness, } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); glColor4f(1, 1, 1, alpha / 255.f); glPushMatrix(); @@ -819,7 +819,7 @@ void drawImageScaled( SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos ) } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); glColor4f(1, 1, 1, 1); glPushMatrix(); glBegin(GL_QUADS); @@ -887,7 +887,7 @@ void drawImageScaledPartial(SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos, fl } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); glColor4f(1, 1, 1, 1); glPushMatrix(); glBegin(GL_QUADS); @@ -950,7 +950,7 @@ void drawImageScaledColor(SDL_Surface* image, SDL_Rect* src, SDL_Rect* pos, Uint } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); real_t r = ((Uint8)(color >> mainsurface->format->Rshift)) / 255.f; real_t g = ((Uint8)(color >> mainsurface->format->Gshift)) / 255.f; real_t b = ((Uint8)(color >> mainsurface->format->Bshift)) / 255.f; @@ -1046,7 +1046,7 @@ void drawImageFancy( SDL_Surface* image, Uint32 color, real_t angle, SDL_Rect* s } // draw a textured quad - glBindTexture(GL_TEXTURE_2D, texid[image->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)image->userdata]); real_t r = ((Uint8)(color >> mainsurface->format->Rshift)) / 255.f; real_t g = ((Uint8)(color >> mainsurface->format->Gshift)) / 255.f; real_t b = ((Uint8)(color >> mainsurface->format->Bshift)) / 255.f; @@ -1572,6 +1572,7 @@ void drawEntities3D(view_t* camera, int mode) } } +#ifndef EDITOR for ( int i = 0; i < MAXPLAYERS; ++i ) { for ( auto& enemybar : enemyHPDamageBarHandler[i].HPBars ) @@ -1581,6 +1582,7 @@ void drawEntities3D(view_t* camera, int mode) spritesToDraw.push_back(std::make_tuple(camDist, &enemybar, SPRITE_HPBAR)); } } +#endif std::sort(spritesToDraw.begin(), spritesToDraw.end(), [](const std::tuple& lhs, const std::tuple& rhs) { @@ -1616,8 +1618,10 @@ void drawEntities3D(view_t* camera, int mode) } else if ( std::get<2>(distSpriteType) == SpriteTypes::SPRITE_HPBAR ) { +#ifndef EDITOR auto enemybar = (std::pair*)std::get<1>(distSpriteType); glDrawEnemyBarSprite(camera, mode, &enemybar->second, false); +#endif } } @@ -2392,7 +2396,7 @@ void drawWindowFancy(int x1, int y1, int x2, int y2) glVertex2f(x2 - 1, yres - y1 - 1); glEnd(); glColor3f(.75, .75, .75); - glBindTexture(GL_TEXTURE_2D, texid[fancyWindow_bmp->refcount]); // wood texture + glBindTexture(GL_TEXTURE_2D, texid[(long int)fancyWindow_bmp->userdata]); // wood texture glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(x1 + 2, yres - y1 - 2); @@ -2544,7 +2548,7 @@ SDL_Rect ttfPrintTextColor( TTF_Font* font, int x, int y, Uint32 color, bool out SDL_FreeSurface(textSurf); // load the text outline surface as a GL texture allsurfaces[imgref] = surf; - allsurfaces[imgref]->refcount = imgref; + allsurfaces[imgref]->userdata = (void*)imgref; glLoadTexture(allsurfaces[imgref], imgref); imgref++; // store the surface in the text surface cache diff --git a/src/editor.cpp b/src/editor.cpp index 1d8feccc2..dfc3a5f70 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -73,10 +73,6 @@ void actHudArrowModel(Entity* my) {} // dummy for draw.cpp void actLeftHandMagic(Entity* my) {} // dummy for draw.cpp void actRightHandMagic(Entity* my) {} // dummy for draw.cpp void messagePlayer(int player, char const * const message, ...) {} // dummy -void updateLoadingScreen(real_t progress) {} // dummy -void doLoadingScreen() {} -void destroyLoadingScreen() {} -void createLoadingScreen(real_t progress) {} map_t copymap; @@ -317,6 +313,12 @@ char playerSpawnPropertyNames[1][35] = "Spawn Facing Direction (-1 - 7)" }; +char statuePropertyNames[2][16] = +{ + "Direction (0-3)", + "Statue ID", +}; + const char* playerClassLangEntry(int classnum, int playernum) { if ( classnum >= CLASS_BARBARIAN && classnum <= CLASS_JOKER ) @@ -7457,6 +7459,113 @@ int main(int argc, char** argv) } } } + else if ( newwindow == 29 ) + { + if ( selectedEntity[0] != nullptr ) + { + int numProperties = sizeof(statuePropertyNames) / sizeof(statuePropertyNames[0]); //find number of entries in property list + const int lenProperties = sizeof(statuePropertyNames[0]) / sizeof(char); //find length of entry in property list + int spacing = 36; // 36 px between each item in the list. + int inputFieldHeader_y = suby1 + 28; // 28 px spacing from subwindow start. + int inputField_x = subx1 + 8; // 8px spacing from subwindow start. + int inputField_y = inputFieldHeader_y + 16; + int inputFieldWidth = 64; // width of the text field + int inputFieldFeedback_x = inputField_x + inputFieldWidth + 8; + char tmpPropertyName[lenProperties] = ""; + Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0); + Uint32 colorRandom = SDL_MapRGB(mainsurface->format, 0, 168, 255); + Uint32 colorError = SDL_MapRGB(mainsurface->format, 255, 0, 0); + + for ( int i = 0; i < numProperties; i++ ) + { + int propertyInt = atoi(spriteProperties[i]); + + strcpy(tmpPropertyName, statuePropertyNames[i]); + inputFieldHeader_y = suby1 + 28 + i * spacing; + inputField_y = inputFieldHeader_y + 16; + // box outlines then text + if ( i == 1 ) + { + inputFieldWidth = 80; // width of the text field + } + else + { + inputFieldWidth = 64; // width of the text field + } + drawDepressed(inputField_x - 4, inputField_y - 4, inputField_x - 4 + inputFieldWidth, inputField_y + 16 - 4); + // print values on top of boxes + printText(font8x8_bmp, inputField_x, suby1 + 44 + i * spacing, spriteProperties[i]); + printText(font8x8_bmp, inputField_x, inputFieldHeader_y, tmpPropertyName); + + if ( errorArr[i] != 1 ) + { + if ( i == 0 ) + { + if ( propertyInt > 3 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + char tmpStr[32] = ""; + switch ( propertyInt ) + { + case 0: + strcpy(tmpStr, "East"); + break; + case 1: + strcpy(tmpStr, "South"); + break; + case 2: + strcpy(tmpStr, "West"); + break; + case 3: + strcpy(tmpStr, "North"); + break; + default: + break; + } + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr); + } + } + else if ( i == 1 ) + { + if ( propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + } + else + { + // enter other row entries here + } + } + + if ( errorMessage ) + { + if ( errorArr[i] == 1 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, colorError, "Invalid ID!"); + } + } + } + + propertyPageTextAndInput(numProperties, inputFieldWidth); + + if ( editproperty < numProperties ) // edit + { + if ( !SDL_IsTextInputActive() ) + { + SDL_StartTextInput(); + inputstr = spriteProperties[0]; + } + + // set the maximum length allowed for user input + inputlen = 10; + propertyPageCursorFlash(spacing); + } + } + } else if ( newwindow == 25 ) { //if ( selectedEntity[0] != nullptr ) diff --git a/src/entity.cpp b/src/entity.cpp index 80a9c5de7..9a440c89e 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -301,6 +301,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli interactedByMonster(skill[47]), highlightForUI(fskill[29]), highlightForUIGlow(fskill[28]), + grayscaleGLRender(fskill[27]), soundSourceFired(skill[0]), soundSourceToPlay(skill[1]), soundSourceVolume(skill[2]), @@ -339,7 +340,10 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli worldTooltipInit(skill[3]), worldTooltipFadeDelay(skill[4]), worldTooltipIgnoreDrawing(skill[5]), - worldTooltipRequiresButtonHeld(skill[6]) + worldTooltipRequiresButtonHeld(skill[6]), + statueInit(skill[0]), + statueDir(skill[1]), + statueId(skill[3]) { int c; // add the entity to the entity list @@ -19057,6 +19061,11 @@ bool Entity::bEntityHighlightedForPlayer(const int player) const { return false; } + if ( (behavior == &actPlayer || behavior == &actPlayerLimb) + && StatueManager.activeEditing && highlightForUI > 0.001 ) + { + return true; + } if ( behavior == &actMonster || behavior == &actPlayer ) { return false; diff --git a/src/entity.hpp b/src/entity.hpp index 52c28d9b4..970dfccb3 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -219,6 +219,7 @@ class Entity Sint32& interactedByMonster; //skill[47] for use with monsterAllyInteractTarget real_t& highlightForUI; //fskill[29] for highlighting interactibles real_t& highlightForUIGlow; //fskill[28] for highlighting animation + real_t& grayscaleGLRender; //fskill[27] for grayscale rendering //--PUBLIC PLAYER SKILLS-- Sint32& playerLevelEntrySpeech; //skill[18] @@ -504,6 +505,11 @@ class Entity Sint32& worldTooltipIgnoreDrawing; //skill[5] Sint32& worldTooltipRequiresButtonHeld; //skill[6] + //--STATUES-- + Sint32& statueInit; //skill[0] + Sint32& statueDir; //skill[1] + Sint32& statueId; //skill[3] + void pedestalOrbInit(); // init orb properties // a pointer to the entity's location in a list (ie the map list of entities) @@ -1033,7 +1039,7 @@ void actTextSource(Entity* my); static const int NUM_ITEM_STRINGS = 290; static const int NUM_ITEM_STRINGS_BY_TYPE = 129; -static const int NUM_EDITOR_SPRITES = 168; +static const int NUM_EDITOR_SPRITES = 170; static const int NUM_EDITOR_TILES = 350; // furniture types. diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index e8c399654..d7ba2dd9c 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -275,6 +275,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli interactedByMonster(skill[47]), highlightForUI(fskill[29]), highlightForUIGlow(fskill[28]), + grayscaleGLRender(fskill[27]), soundSourceFired(skill[0]), soundSourceToPlay(skill[1]), soundSourceVolume(skill[2]), @@ -310,7 +311,10 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli worldTooltipInit(skill[3]), worldTooltipFadeDelay(skill[4]), worldTooltipIgnoreDrawing(skill[5]), - worldTooltipRequiresButtonHeld(skill[6]) + worldTooltipRequiresButtonHeld(skill[6]), + statueInit(skill[0]), + statueDir(skill[1]), + statueId(skill[3]) { int c; // add the entity to the entity list @@ -462,6 +466,12 @@ void actSpriteWorldTooltip(Entity* my) return; } +void actDamageGib(Entity* my) +{ + // dummy function + return; +} + void actSpriteWorldTooltip(Entity* my); void actGoldBag(Entity* my) diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index b7bd6b3a4..96ce98630 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -142,7 +142,9 @@ int checkSpriteType(Sint32 sprite) return 22; case 1: return 23; - break; + case 169: + // statue + return 24; default: return 0; break; @@ -889,7 +891,9 @@ char spriteEditorNameStrings[NUM_EDITOR_SPRITES][64] = "SPELLBOT", "DUMMYBOT", "GYROBOT", - "UNUSED" + "UNUSED", + "STATUE ANIMATOR", + "STATUE" }; char monsterEditorNameStrings[NUMMONSTERS][16] = @@ -1787,6 +1791,21 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->playerStartDir = 0; } } + else if ( spriteType == 24 ) // statue + { + if ( entityToCopy != nullptr ) + { + // copy old entity attributes to newly created. + entityNew->statueDir = entityToCopy->statueDir; + entityNew->statueId = entityToCopy->statueId; + } + else + { + // set default new entity attributes. + entityNew->statueDir = 0; + entityNew->statueId = 0; + } + } if ( entityToCopy != nullptr ) { diff --git a/src/files.cpp b/src/files.cpp index fde37aea8..683854417 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -484,6 +484,7 @@ void glLoadTexture(SDL_Surface* image, int texnum) glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texid[texnum]); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + //glPixelStorei(GL_UNPACK_ROW_LENGTH, (image->pitch / 4)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -603,7 +604,7 @@ SDL_Surface* loadImage(char const * const filename) // load the new surface as a GL texture allsurfaces[imgref] = newSurface; - allsurfaces[imgref]->refcount = imgref + 1; + allsurfaces[imgref]->userdata = (void *)((long int)imgref); glLoadTexture(allsurfaces[imgref], imgref); // free the translated surface @@ -1179,6 +1180,10 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea fp->read(&entity->playerStartDir, sizeof(Sint32), 1); } break; + case 24: + fp->read(&entity->statueDir, sizeof(Sint32), 1); + fp->read(&entity->statueId, sizeof(Sint32), 1); + break; default: break; } @@ -1587,6 +1592,10 @@ int saveMap(const char* filename2) case 23: fp->write(&entity->playerStartDir, sizeof(Sint32), 1); break; + case 24: + fp->write(&entity->statueDir, sizeof(Sint32), 1); + fp->write(&entity->statueId, sizeof(Sint32), 1); + break; default: break; } @@ -2044,6 +2053,14 @@ bool physfsModelIndexUpdate(int &start, int &end, bool freePreviousModels) { SDL_glDeleteBuffers(1, &polymodels[c].colors_shifted); } + if ( polymodels[c].grayscale_colors ) + { + SDL_glDeleteVertexArrays(1, &polymodels[c].grayscale_colors); + } + if ( polymodels[c].grayscale_colors_shifted ) + { + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors_shifted); + } } } diff --git a/src/game.cpp b/src/game.cpp index 91eff26c2..fb85622a9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2850,6 +2850,7 @@ void handleEvents(void) inputs.updateAllMouse(); } + Input::lastInputOfAnyKind = ""; for (auto& input : Input::inputs) { input.updateReleasedBindings(); input.update(); @@ -3188,7 +3189,7 @@ void handleEvents(void) { printlog("(Device %d successfully initialized as game controller.)\n", id); inputs.addControllerIDToNextAvailableInput(id); - Input::gameControllers.emplace(id, const_cast(controller.getControllerDevice())); + Input::gameControllers[id]= const_cast(controller.getControllerDevice()); for (int c = 0; c < 4; ++c) { Input::inputs[c].refresh(); } @@ -3417,16 +3418,6 @@ void handleEvents(void) printlog("critical error! Attempting to abort safely...\n"); mainloop = 0; } - if (zbuffer != NULL) - { - free(zbuffer); - } - zbuffer = (real_t*)malloc(sizeof(real_t) * xres * yres); - if (clickmap != NULL) - { - free(clickmap); - } - clickmap = (Entity**)malloc(sizeof(Entity*)*xres * yres); } break; /*case SDL_CONTROLLERAXISMOTION: @@ -4102,6 +4093,10 @@ void ingameHud() { players[player]->bookGUI.closeBookGUI(); } + if ( players[player]->skillSheet.bSkillSheetOpen ) + { + players[player]->skillSheet.closeSkillSheet(); + } gui_clickdrag[player] = false; //Just a catchall to make sure that any ongoing GUI dragging ends when the GUI is closed. @@ -4147,6 +4142,7 @@ void ingameHud() players[player]->inventoryUI.updateInventoryItemTooltip(); players[player]->hotbar.processHotbar(); players[player]->inventoryUI.processInventory(); + players[player]->skillSheet.processSkillSheet(); players[player]->inventoryUI.updateCursor(); players[player]->hotbar.updateCursor(); players[player]->hud.updateCursor(); @@ -4155,12 +4151,15 @@ void ingameHud() continue; } //drawSkillsSheet(player); - if ( !nohud ) + if ( !gamePaused ) { - drawStatusNew(player); + if ( !nohud ) + { + drawStatusNew(player); + } + drawSustainedSpells(player); + updateAppraisalItemBox(player); } - drawSustainedSpells(player); - updateAppraisalItemBox(player); // inventory and stats if ( players[player]->shootmode == false ) @@ -5122,7 +5121,8 @@ int main(int argc, char** argv) inputs.controllerClearInput(clientnum, INJOY_MENU_NEXT); inputs.controllerClearInput(clientnum, INJOY_MENU_CANCEL); fadealpha = 255; -#if (!defined STEAMWORKS && !defined USE_EOS && !defined NINTENDO) + // Yeah we're just not going to do the "Please don't pirate us" message anymore +#if (0) introstage = 0; fadeout = false; fadefinished = false; @@ -5398,7 +5398,7 @@ int main(int argc, char** argv) if (newui) { - MainMenu::doMainMenu(); + MainMenu::doMainMenu(!intro); } else { @@ -5758,25 +5758,13 @@ int main(int argc, char** argv) doFrames(); - if ( !gamePaused ) + if ( /*newui*/ 0 ) { - if ( /*newui*/ 0 ) - { - newIngameHud(); - } - else - { - ingameHud(); - } + newIngameHud(); } - else if ( !multiplayer ) + else { - // darken the rest of the screen - src.x = 0; - src.y = 0; - src.w = mainsurface->w; - src.h = mainsurface->h; - drawRect(&src, SDL_MapRGB(mainsurface->format, 0, 0, 0), 127); + ingameHud(); } if ( gamePaused ) @@ -5784,7 +5772,7 @@ int main(int argc, char** argv) // handle menu if (newui) { - MainMenu::doMainMenu(); + MainMenu::doMainMenu(!intro); } else { @@ -5793,6 +5781,8 @@ int main(int argc, char** argv) } else { + MainMenu::destroyMainMenu(); + // draw subwindow if ( !movie ) { diff --git a/src/game.hpp b/src/game.hpp index 82439132f..98a8004bb 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -220,6 +220,8 @@ void actLiquid(Entity* my); void actEmpty(Entity* my); void actFurniture(Entity* my); void actMCaxe(Entity* my); +void actStatueAnimator(Entity* my); +void actStatue(Entity* my); void actDoorFrame(Entity* my); void actDeathCam(Entity* my); void actPlayerLimb(Entity* my); diff --git a/src/init.cpp b/src/init.cpp index a2dd89a55..fd789dc47 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -136,6 +136,7 @@ int initApp(char const * const title, int fullscreen) PHYSFS_mkdir("logfiles"); PHYSFS_mkdir("data"); PHYSFS_mkdir("data/custom-monsters"); + PHYSFS_mkdir("data/statues"); #ifdef NINTENDO PHYSFS_mkdir("mods"); std::string path = outputdir; @@ -395,7 +396,9 @@ int initApp(char const * const title, int fullscreen) } // init new ui engine +#ifndef EDITOR Frame::guiInit(); +#endif // cache language entries bool cacheText = false; @@ -981,8 +984,8 @@ void generatePolyModels(int start, int end, bool forceCacheRebuild) { FileIO::close(model_cache); } + return; } - return; } } @@ -1941,12 +1944,20 @@ void generateVBOs(int start, int end) std::unique_ptr color_shifted_buffers(new GLuint[count]); SDL_glGenBuffers(count, color_shifted_buffers.get()); + std::unique_ptr grayscale_color_buffers(new GLuint[count]); + SDL_glGenBuffers(count, grayscale_color_buffers.get()); + + std::unique_ptr grayscale_color_shifted_buffers(new GLuint[count]); + SDL_glGenBuffers(count, grayscale_color_shifted_buffers.get()); + for ( int c = start; c < end; ++c ) { polymodel_t *model = &polymodels[c]; std::unique_ptr points(new GLfloat[9 * model->numfaces]); std::unique_ptr colors(new GLfloat[9 * model->numfaces]); std::unique_ptr colors_shifted(new GLfloat[9 * model->numfaces]); + std::unique_ptr grayscale_colors(new GLfloat[9 * model->numfaces]); + std::unique_ptr grayscale_colors_shifted(new GLfloat[9 * model->numfaces]); for ( int i = 0; i < model->numfaces; i++ ) { const polytriangle_t *face = &model->faces[i]; @@ -1966,12 +1977,23 @@ void generateVBOs(int start, int end) colors_shifted[data_index] = face->b / 255.f; colors_shifted[data_index + 1] = face->r / 255.f; colors_shifted[data_index + 2] = face->g / 255.f; + + real_t grayscaleFactor = (face->r + face->g + face->b) / 3.0; + grayscale_colors[data_index] = grayscaleFactor / 255.f; + grayscale_colors[data_index + 1] = grayscaleFactor / 255.f; + grayscale_colors[data_index + 2] = grayscaleFactor / 255.f; + + grayscale_colors_shifted[data_index] = grayscaleFactor / 255.f; + grayscale_colors_shifted[data_index + 1] = grayscaleFactor / 255.f; + grayscale_colors_shifted[data_index + 2] = grayscaleFactor / 255.f; } } model->va = vas[c - start]; model->vbo = vbos[c - start]; model->colors = color_buffers[c - start]; model->colors_shifted = color_shifted_buffers[c - start]; + model->grayscale_colors = grayscale_color_buffers[c - start]; + model->grayscale_colors_shifted = grayscale_color_shifted_buffers[c - start]; SDL_glBindVertexArray(model->va); // vertex data @@ -1986,6 +2008,14 @@ void generateVBOs(int start, int end) // shifted color data SDL_glBindBuffer(GL_ARRAY_BUFFER, model->colors_shifted); SDL_glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9 * model->numfaces, colors_shifted.get(), GL_STATIC_DRAW); // Set the size and data of our VBO and set it to STATIC_DRAW + + // grayscale color data + SDL_glBindBuffer(GL_ARRAY_BUFFER, model->grayscale_colors); + SDL_glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9 * model->numfaces, grayscale_colors.get(), GL_STATIC_DRAW); // Set the size and data of our VBO and set it to STATIC_DRAW + + // grayscale shifted color data + SDL_glBindBuffer(GL_ARRAY_BUFFER, model->grayscale_colors_shifted); + SDL_glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9 * model->numfaces, grayscale_colors_shifted.get(), GL_STATIC_DRAW); // Set the size and data of our VBO and set it to STATIC_DRAW } } @@ -2041,7 +2071,9 @@ int deinitApp() Text::dumpCache(); Image::dumpCache(); Font::dumpCache(); +#ifndef EDITOR Frame::guiDestroy(); +#endif printlog("freeing map data...\n"); if ( map.entities != NULL ) @@ -2174,6 +2206,18 @@ int deinitApp() { SDL_glDeleteVertexArrays(1, &polymodels[c].va); } + if ( polymodels[c].colors_shifted ) + { + SDL_glDeleteBuffers(1, &polymodels[c].colors_shifted); + } + if ( polymodels[c].grayscale_colors ) + { + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors); + } + if ( polymodels[c].grayscale_colors_shifted ) + { + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors_shifted); + } } } free(polymodels); @@ -2609,9 +2653,15 @@ bool initVideo() -------------------------------------------------------------------------------*/ -bool changeVideoMode() +bool changeVideoMode(int new_xres, int new_yres) { - printlog("changing video mode.\n"); + if (new_xres) { + xres = new_xres; + } + if (new_yres) { + yres = new_yres; + } + printlog("changing video mode (%d x %d).\n", xres, yres); #ifdef PANDORA GO_InitFBO(); #else @@ -2621,7 +2671,9 @@ bool changeVideoMode() glDeleteTextures(MAXTEXTURES, texid); // destroy gui fbo +#ifndef EDITOR Frame::fboDestroy(); +#endif // delete vertex data if ( !disablevbos ) @@ -2631,6 +2683,9 @@ bool changeVideoMode() SDL_glDeleteBuffers(1, &polymodels[c].vbo); SDL_glDeleteBuffers(1, &polymodels[c].colors); SDL_glDeleteVertexArrays(1, &polymodels[c].va); + SDL_glDeleteBuffers(1, &polymodels[c].colors_shifted); + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors); + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors_shifted); } } @@ -2689,9 +2744,23 @@ bool changeVideoMode() #endif // !EDITOR // create new frame fbo +#ifndef EDITOR Frame::fboInit(); +#endif #endif + + if ( zbuffer != NULL ) + { + free(zbuffer); + } + zbuffer = (real_t*) malloc(sizeof(real_t) * xres * yres); + if ( clickmap != NULL ) + { + free(clickmap); + } + clickmap = (Entity**) malloc(sizeof(Entity*)*xres * yres); + // success return true; } diff --git a/src/init.hpp b/src/init.hpp index c035d2a3b..d7759a11e 100644 --- a/src/init.hpp +++ b/src/init.hpp @@ -13,7 +13,7 @@ int initApp(char const * const title, int fullscreen); int deinitApp(); bool initVideo(); -bool changeVideoMode(); +bool changeVideoMode(int new_xres = 0, int new_yres = 0); void generatePolyModels(int start, int end, bool forceCacheRebuild); void generateVBOs(int start, int end); int loadLanguage(char const * const lang); diff --git a/src/init_game.cpp b/src/init_game.cpp index e035aece3..2d48ff2ad 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -282,7 +282,6 @@ int initGame() } } FileIO::close(fp); - bookParser_t.createBooks(false); setupSpells(); #ifdef NINTENDO @@ -302,6 +301,7 @@ int initGame() ItemTooltips.readTooltipsFromFile(); loadHUDSettingsJSON(); + Player::SkillSheet_t::loadSkillSheetJSON(); updateLoadingScreen(94); @@ -466,6 +466,7 @@ int initGame() int result = loading_task.get(); if (result == 0) { + bookParser_t.createBooks(false); Text::dumpCache(); // createBooks makes some invalid Text() surfaces, this cleans them up for re-rendering. gameModeManager.Tutorial.init(); diff --git a/src/input.cpp b/src/input.cpp index c52ec3d08..271de7bc8 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1,5 +1,12 @@ +#include "main.hpp" #include "input.hpp" +#ifdef EDITOR +#ifndef TICKS_PER_SECOND +#define TICKS_PER_SECOND 50 +#endif +#else #include "player.hpp" +#endif #include @@ -92,6 +99,48 @@ void Input::defaultBindings() { inputs[c].bind("HotbarSlot9", "9"); inputs[c].bind("HotbarSlot10", "0"); } +#ifndef NINTENDO + inputs[0].bind("MenuUp", "Up"); + inputs[0].bind("MenuLeft", "Left"); + inputs[0].bind("MenuRight", "Right"); + inputs[0].bind("MenuDown", "Down"); + inputs[0].bind("MenuConfirm", "Space"); + inputs[0].bind("MenuCancel", "Escape"); + inputs[0].bind("MenuAlt1", "Left Shift"); + inputs[0].bind("MenuAlt2", "Left Alt"); + inputs[0].bind("MenuStart", "Return"); + inputs[0].bind("MenuSelect", "Backspace"); + inputs[0].bind("MenuPageLeft", "["); + inputs[0].bind("MenuPageRight", "]"); +#endif +} + +void Input::clearDefaultBindings() { + // This is used to disable UI bindings while the player is rebinding keys + // in the game settings. Maybe it will be used for other things though... + for (int c = 0; c < MAXPLAYERS; ++c) { + inputs[c].bind("MenuTab", ""); + inputs[c].bind("MenuUp", ""); + inputs[c].bind("MenuLeft", ""); + inputs[c].bind("MenuRight", ""); + inputs[c].bind("MenuDown", ""); + inputs[c].bind("MenuConfirm", ""); + inputs[c].bind("MenuCancel", ""); + inputs[c].bind("MenuAlt1", ""); + inputs[c].bind("MenuAlt2", ""); + inputs[c].bind("MenuStart", ""); + inputs[c].bind("MenuSelect", ""); + inputs[c].bind("MenuPageLeft", ""); + inputs[c].bind("MenuPageRight", ""); + inputs[c].bind("AltMenuUp", ""); + inputs[c].bind("AltMenuLeft", ""); + inputs[c].bind("AltMenuRight", ""); + inputs[c].bind("AltMenuDown", ""); + inputs[c].bind("MenuScrollUp", ""); + inputs[c].bind("MenuScrollLeft", ""); + inputs[c].bind("MenuScrollRight", ""); + inputs[c].bind("MenuScrollDown", ""); + } } float Input::analog(const char* binding) const { @@ -101,6 +150,7 @@ float Input::analog(const char* binding) const { bool Input::binary(const char* binding) const { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -108,11 +158,13 @@ bool Input::binary(const char* binding) const { return false; } } +#endif return b != bindings.end() ? (*b).second.binary : false; } bool Input::binaryToggle(const char* binding) const { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -120,11 +172,13 @@ bool Input::binaryToggle(const char* binding) const { return false; } } +#endif return b != bindings.end() ? (*b).second.binary && !(*b).second.consumed : false; } bool Input::analogToggle(const char* binding) const { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -132,11 +186,13 @@ bool Input::analogToggle(const char* binding) const { return false; } } +#endif return b != bindings.end() ? (*b).second.analog > analogToggleThreshold && !(*b).second.analogConsumed : false; } bool Input::binaryReleaseToggle(const char* binding) const { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -144,11 +200,13 @@ bool Input::binaryReleaseToggle(const char* binding) const { return false; } } +#endif return b != bindings.end() ? (*b).second.binaryRelease && !(*b).second.binaryReleaseConsumed : false; } bool Input::consumeAnalogToggle(const char* binding) { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -156,6 +214,7 @@ bool Input::consumeAnalogToggle(const char* binding) { return false; } } +#endif if ( b != bindings.end() && (*b).second.analog > analogToggleThreshold && !(*b).second.analogConsumed ) { (*b).second.analogConsumed = true; return true; @@ -167,6 +226,7 @@ bool Input::consumeAnalogToggle(const char* binding) { bool Input::consumeBinaryToggle(const char* binding) { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -174,6 +234,7 @@ bool Input::consumeBinaryToggle(const char* binding) { return false; } } +#endif if (b != bindings.end() && (*b).second.binary && !(*b).second.consumed) { (*b).second.consumed = true; if ( (*b).second.type == binding_t::bindtype_t::MOUSE_BUTTON @@ -190,6 +251,7 @@ bool Input::consumeBinaryToggle(const char* binding) { bool Input::consumeBinaryReleaseToggle(const char* binding) { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -197,6 +259,7 @@ bool Input::consumeBinaryReleaseToggle(const char* binding) { return false; } } +#endif if ( b != bindings.end() && (*b).second.binaryRelease && !(*b).second.binaryReleaseConsumed ) { (*b).second.binaryReleaseConsumed = true; return true; @@ -208,6 +271,7 @@ bool Input::consumeBinaryReleaseToggle(const char* binding) { bool Input::binaryHeldToggle(const char* binding) const { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -215,6 +279,7 @@ bool Input::binaryHeldToggle(const char* binding) const { return false; } } +#endif return b != bindings.end() ? ((*b).second.binary && !(*b).second.consumed && (ticks - (*b).second.binaryHeldTicks) > BUTTON_HELD_TICKS) : false; @@ -222,6 +287,7 @@ bool Input::binaryHeldToggle(const char* binding) const { bool Input::analogHeldToggle(const char* binding) const { auto b = bindings.find(binding); +#ifndef EDITOR if ( b != bindings.end() ) { if ( (*b).second.type == binding_t::bindtype_t::KEYBOARD && ::inputs.bPlayerUsingKeyboardControl(player) == false ) @@ -229,6 +295,7 @@ bool Input::analogHeldToggle(const char* binding) const { return false; } } +#endif return b != bindings.end() ? ((*b).second.analog > analogToggleThreshold && !(*b).second.analogConsumed && (ticks - (*b).second.analogHeldTicks) > BUTTON_HELD_TICKS) : false; @@ -330,6 +397,22 @@ std::string Input::getGlyphPathForInput(binding_t binding) const } #endif } + else if ( binding.type == binding_t::bindtype_t::CONTROLLER_AXIS ) + { + switch ( binding.padAxis ) + { + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX: + return rootPath + "G_Switch_LStick00.png"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY: + return rootPath + "G_Switch_LStick00.png"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX: + return rootPath + "G_Switch_LStick00.png"; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY: + return rootPath + "G_Switch_LStick00.png"; + default: + return ""; + } + } else if ( binding.type == binding_t::bindtype_t::KEYBOARD ) { return ""; diff --git a/src/input.hpp b/src/input.hpp index 061ebb18f..f36d01970 100644 --- a/src/input.hpp +++ b/src/input.hpp @@ -35,6 +35,9 @@ class Input { //! set default bindings for all players static void defaultBindings(); + //! clear default bindings + static void clearDefaultBindings(); + //! input mapping struct binding_t { std::string input = ""; diff --git a/src/interface/bookgui.cpp b/src/interface/bookgui.cpp index 64c6e6bd1..51dbe16d3 100644 --- a/src/interface/bookgui.cpp +++ b/src/interface/bookgui.cpp @@ -65,7 +65,7 @@ void Player::BookGUI_t::createBookGUI() auto nextPage = bookBackground->addFrame("next page mouse boundary"); nextPage->setSize(SDL_Rect{ width / 2, bgImg->pos.y, width, height }); - std::string promptFont = "fonts/pixel_maz.ttf#16#2"; + std::string promptFont = "fonts/pixel_maz.ttf#32#2"; auto promptBack = bookBackground->addField("prompt back txt", 16); promptBack->setSize(SDL_Rect{ bgImg->pos.x + bgImg->pos.w - promptWidth - 16, // lower right corner bgImg->pos.y + bgImg->pos.h, promptWidth, promptHeight }); @@ -73,6 +73,7 @@ void Player::BookGUI_t::createBookGUI() promptBack->setHJustify(Field::justify_t::RIGHT); promptBack->setVJustify(Field::justify_t::CENTER); promptBack->setText(language[4053]); + promptBack->setColor(makeColor(201, 162, 100, 255)); auto promptBackImg = bookBackground->addImage(SDL_Rect{0, 0, 0, 0}, 0xFFFFFFFF, "", "prompt back img"); @@ -85,6 +86,7 @@ void Player::BookGUI_t::createBookGUI() promptNextPage->setHJustify(Field::justify_t::RIGHT); promptNextPage->setVJustify(Field::justify_t::CENTER); promptNextPage->setText(language[4054]); + promptNextPage->setColor(makeColor(201, 162, 100, 255)); auto promptNextPageImg = bookBackground->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "prompt next img"); @@ -97,12 +99,13 @@ void Player::BookGUI_t::createBookGUI() promptPrevPage->setHJustify(Field::justify_t::LEFT); promptPrevPage->setVJustify(Field::justify_t::CENTER); promptPrevPage->setText(language[4055]); + promptPrevPage->setColor(makeColor(201, 162, 100, 255)); auto promptPrevPageImg = bookBackground->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "prompt prev img"); promptPrevPageImg->disabled = true; - std::string bookFont = "fonts/pixel_maz.ttf#16"; + std::string bookFont = "fonts/pixel_maz.ttf#30"; Field* bookLeftColumnText = bookBackground->addField("left column text", 1024); bookLeftColumnText->setText("Nothing"); const int pageWidth = BOOK_PAGE_WIDTH; @@ -114,7 +117,7 @@ void Player::BookGUI_t::createBookGUI() bookLeftColumnText->setHJustify(Field::justify_t::LEFT); bookLeftColumnText->setVJustify(Field::justify_t::TOP); bookLeftColumnText->setColor(SDL_MapRGBA(mainsurface->format, 0, 0, 0, 255)); - //bookLeftColumnText->setColor(SDL_MapRGBA(mainsurface->format, 67, 195, 157, 255)); + //bookLeftColumnText->setColor(makeColor(201, 162, 100, 255)); //bookBackground->addImage(bookLeftColumnText->getSize(), 0xFFFFFFFF, "images/system/white.png", "debug img"); @@ -200,7 +203,7 @@ void Player::BookGUI_t::updateBookGUI() bookSize.y = -bookSize.h + bookFadeInAnimationY * (baseY + bookSize.h); innerFrame->setSize(bookSize); - bool drawGlyphs = !::inputs.getVirtualMouse(player.playernum)->draw_cursor; + bool drawGlyphs = ::inputs.getVirtualMouse(player.playernum)->lastMovementFromController; int numPages = allBooks[bookIndex].formattedPages.size(); bool canAdvanceNextPage = (currentBookPage + 2 < numPages); bool canAdvancePrevPage = (currentBookPage - 2 >= 0); @@ -212,7 +215,8 @@ void Player::BookGUI_t::updateBookGUI() auto promptImg = innerFrame->findImage("prompt back img"); promptImg->disabled = !drawGlyphs; SDL_Rect glyphPos = promptImg->pos; - if ( auto textGet = Text::get(promptBack->getText(), promptBack->getFont()) ) + if ( auto textGet = Text::get(promptBack->getText(), promptBack->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { SDL_Rect textPos = promptBack->getSize(); textPos.w = textGet->getWidth(); @@ -239,7 +243,8 @@ void Player::BookGUI_t::updateBookGUI() promptImg->disabled = promptNext->isDisabled(); SDL_Rect glyphPos = promptImg->pos; SDL_Rect textPos = promptNext->getSize(); - if ( auto textGet = Text::get(promptNext->getText(), promptNext->getFont()) ) + if ( auto textGet = Text::get(promptNext->getText(), promptNext->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { textPos.w = textGet->getWidth(); } @@ -264,7 +269,8 @@ void Player::BookGUI_t::updateBookGUI() promptImg->disabled = promptPrev->isDisabled(); SDL_Rect glyphPos = promptImg->pos; SDL_Rect textPos = promptPrev->getSize(); - if ( auto textGet = Text::get(promptPrev->getText(), promptPrev->getFont()) ) + if ( auto textGet = Text::get(promptPrev->getText(), promptPrev->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { textPos.w = textGet->getWidth(); } @@ -432,7 +438,8 @@ void Player::BookGUI_t::updateBookGUI() // firstIteration = false; // pageText += token; // leftColumn->setText(pageText.c_str()); - // if ( auto getText = Text::get(leftColumn->getText(), leftColumn->getFont()) ) + // if ( auto getText = Text::get(leftColumn->getText(), leftColumn->getFont(), + // makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) // { // int textHeight = getText->getHeight(); // if ( textHeight > leftColumn->getSize().h ) diff --git a/src/interface/clickdescription.cpp b/src/interface/clickdescription.cpp index 4804ab450..36fb5eaa3 100644 --- a/src/interface/clickdescription.cpp +++ b/src/interface/clickdescription.cpp @@ -93,6 +93,11 @@ void clickDescription(int player, Entity* entity) { return; //Click falls inside the right sidebar. } + + if ( players[player]->skillSheet.bSkillSheetOpen ) + { + return; + } //int x = std::max(character_bmp->w, xres/2-inventory_bmp->w/2); //if (mouseInBounds(x,x+inventory_bmp->w,0,inventory_bmp->h)) //return NULL; diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index df42c3038..7ffcb92ca 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -495,16 +495,6 @@ void consoleCommand(char const * const command_str) printlog("critical error! Attempting to abort safely...\n"); mainloop = 0; } - if ( zbuffer != NULL ) - { - free(zbuffer); - } - zbuffer = (real_t*) malloc(sizeof(real_t) * xres * yres); - if ( clickmap != NULL ) - { - free(clickmap); - } - clickmap = (Entity**) malloc(sizeof(Entity*)*xres * yres); } } else if ( !strncmp(command_str, "/rscale", 7) ) @@ -1377,6 +1367,14 @@ void consoleCommand(char const * const command_str) { SDL_glDeleteBuffers(1, &polymodels[c].colors_shifted); } + if ( polymodels[c].grayscale_colors ) + { + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors); + } + if ( polymodels[c].grayscale_colors_shifted ) + { + SDL_glDeleteBuffers(1, &polymodels[c].grayscale_colors_shifted); + } } models[c] = loadVoxel(name2); } @@ -2710,7 +2708,7 @@ void consoleCommand(char const * const command_str) { networkTickrate = atoi(&command_str[10]); networkTickrate = std::max(1, networkTickrate); - messagePlayer(clientnum, "Set tickrate to %d, network processing allowed %3.0f percent of frame limit interval. Default value 2.", + messagePlayer(clientnum, "Set tickrate to %d, network processing allowed %3.0f percent of frame limit interval. Default value 2.", networkTickrate, 100.f / networkTickrate); } else if ( !strncmp(command_str, "/disablenetcodefpslimit", 23) ) @@ -2744,7 +2742,7 @@ void consoleCommand(char const * const command_str) messagePlayer(clientnum, language[284]); return; } - + Uint32 newseed = atoi(&command_str[12]); forceMapSeed = newseed; messagePlayer(clientnum, "Set next map seed to: %d", forceMapSeed); @@ -3451,11 +3449,25 @@ void consoleCommand(char const * const command_str) loadHUDSettingsJSON(); messagePlayer(clientnum, "Reloaded HUD_settings.json"); } + else if ( !strncmp(command_str, "/loadskillsheet", 15) ) + { + Player::SkillSheet_t::loadSkillSheetJSON(); + messagePlayer(clientnum, "Reloaded skillsheet_entries.json"); + } else if ( !strncmp(command_str, "/usepaperdollmovement", 21) ) { restrictPaperDollMovement = !restrictPaperDollMovement; messagePlayer(clientnum, "Set restrictPaperDollMovement to %d", restrictPaperDollMovement); } + else if ( !strncmp(command_str, "/exportstatue", 13) ) + { + StatueManager.exportActive = true; + } + else if ( !strncmp(command_str, "/importstatue ", 14) ) + { + int index = atoi(&command_str[14]); + StatueManager.readStatueFromFile(index); + } else { messagePlayer(clientnum, language[305], command_str); diff --git a/src/interface/drawstatus.cpp b/src/interface/drawstatus.cpp index 4918dc9f3..d6be8ae11 100644 --- a/src/interface/drawstatus.cpp +++ b/src/interface/drawstatus.cpp @@ -21,6 +21,7 @@ #include "../player.hpp" #include "interface.hpp" #include "../colors.hpp" +#include "../mod_tools.hpp" //Sint32 enemy_hp = 0, enemy_maxhp = 0, enemy_oldhp = 0; //Uint32 enemy_timer = 0, enemy_lastuid = 0; @@ -1784,7 +1785,7 @@ void drawStatus(int player) { // if hotbar_numkey_quick_add is enabled, then the number keys won't do the default equip function // skips equipping items if the mouse is in the hotbar or inventory area. otherwise the below code runs. - if ( inputs.bPlayerUsingKeyboardControl(player) ) + if ( inputs.bPlayerUsingKeyboardControl(player) && !StatueManager.activeEditing ) { if ( keystatus[SDL_SCANCODE_1] ) { diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index dbfb66f6f..f8e6317f7 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -30,6 +30,7 @@ #include "../scores.hpp" #include "../scrolls.hpp" #include "../lobbies.hpp" +#include "../ui/GameUI.hpp" Uint32 svFlags = 30; Uint32 settings_svFlags = svFlags; @@ -1586,6 +1587,7 @@ bool Player::GUI_t::bActiveModuleHasNoCursor() switch ( activeModule ) { case MODULE_BOOK_VIEW: + case MODULE_SKILLS_LIST: return true; default: break; @@ -8617,8 +8619,60 @@ void EnemyHPDamageBarHandler::addEnemyToList(Sint32 HP, Sint32 maxHP, Sint32 old details->animator.animateTicks = ticks; details->animator.damageTaken = std::max(-1, oldHP - HP); - spawnDamageGib(uidToEntity(uid), details->animator.damageTaken); + Entity* entity = uidToEntity(uid); + spawnDamageGib(entity, details->animator.damageTaken); lastEnemyUid = uid; + + if ( entity ) + { + details->worldX = entity->x; + details->worldY = entity->y; + if ( entity->behavior == &actDoor && entity->flags[PASSABLE] ) + { + if ( entity->doorStartAng == 0 ) + { + details->worldY -= 5; + } + else + { + details->worldX -= 5; + } + } + details->worldZ = entity->z + enemyBarSettings.getHeightOffset(entity); + details->screenDistance = enemyBarSettings.getScreenDistanceOffset(entity); + } + if ( entity && (entity->behavior == &actPlayer || entity->behavior == &actMonster) ) + { + if ( Stat* stat = entity->getStats() ) + { + details->enemy_statusEffects1 = 0; + details->enemy_statusEffects2 = 0; + details->enemy_statusEffectsLowDuration1 = 0; + details->enemy_statusEffectsLowDuration2 = 0; + for ( int i = 0; i < NUMEFFECTS; ++i ) + { + if ( stat->EFFECTS[i] ) + { + if ( i < 32 ) + { + details->enemy_statusEffects1 |= (1 << i); + if ( stat->EFFECTS_TIMERS[i] > 0 && stat->EFFECTS_TIMERS[i] < 5 * TICKS_PER_SECOND ) + { + details->enemy_statusEffectsLowDuration1 |= (1 << i); + } + } + else if ( i < 64 ) + { + details->enemy_statusEffects2 |= (1 << (i - 32)); + if ( stat->EFFECTS_TIMERS[i] > 0 && stat->EFFECTS_TIMERS[i] < 5 * TICKS_PER_SECOND ) + { + details->enemy_statusEffectsLowDuration2 |= (1 << i); + } + } + } + } + } + } } SDL_Rect getRectForSkillIcon(const int skill) diff --git a/src/interface/interface.hpp b/src/interface/interface.hpp index 53c7842f6..fede9624a 100644 --- a/src/interface/interface.hpp +++ b/src/interface/interface.hpp @@ -78,6 +78,10 @@ class EnemyHPDamageBarHandler Uint32 enemy_timer = 0; Uint32 enemy_bar_color = 0; Uint32 enemy_uid = 0; + Uint32 enemy_statusEffects1 = 0; + Uint32 enemy_statusEffects2 = 0; + Uint32 enemy_statusEffectsLowDuration1 = 0; + Uint32 enemy_statusEffectsLowDuration2 = 0; bool lowPriorityTick = false; bool shouldDisplay = true; bool hasDistanceCheck = false; @@ -114,6 +118,10 @@ class EnemyHPDamageBarHandler delete worldTexture; worldTexture = nullptr; } + if ( worldSurfaceSpriteStatusEffects ) { + SDL_FreeSurface(worldSurfaceSpriteStatusEffects); + worldSurfaceSpriteStatusEffects = nullptr; + } if ( worldSurfaceSprite ) { SDL_FreeSurface(worldSurfaceSprite); worldSurfaceSprite = nullptr; @@ -126,6 +134,8 @@ class EnemyHPDamageBarHandler real_t screenDistance = 0.0; TempTexture* worldTexture = nullptr; SDL_Surface* worldSurfaceSprite = nullptr; + SDL_Surface* worldSurfaceSpriteStatusEffects = nullptr; + SDL_Surface* blitEnemyBarStatusEffects(const int player); }; Uint32 enemy_bar_client_color = 0; @@ -295,6 +305,7 @@ void updateMagicGUI(); extern SDL_Surface* identifyGUI_img; void drawSustainedSpells(const int player); //Draws an icon for every sustained spell. +SDL_Surface* getStatusEffectSprite(Entity* entity, Stat* stat, const int effect, const int player); enum GUICurrentType { diff --git a/src/interface/magicgui.cpp b/src/interface/magicgui.cpp index 80a828d10..1092d4d90 100644 --- a/src/interface/magicgui.cpp +++ b/src/interface/magicgui.cpp @@ -197,6 +197,124 @@ // } //} +SDL_Surface* getStatusEffectSprite(Entity* entity, Stat* stat, const int effect, const int player) +{ + node_t* effectImageNode = nullptr; + SDL_Surface** sprite = nullptr; + + switch ( effect ) + { + case EFF_SLOW: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_SLOW); + break; + case EFF_BLEEDING: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_BLEED); + break; + case EFF_ASLEEP: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_SLEEP); + break; + case EFF_CONFUSED: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_CONFUSE); + break; + case EFF_PACIFY: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_CHARM_MONSTER); + break; + case EFF_FEAR: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_FEAR); + break; + case EFF_WEBBED: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_SPRAY_WEB); + break; + case EFF_MAGICAMPLIFY: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_AMPLIFY_MAGIC); + break; + case EFF_TROLLS_BLOOD: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_TROLLS_BLOOD); + break; + case EFF_FLUTTER: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_FLUTTER); + break; + case EFF_FAST: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_SPEED); + break; + case EFF_SHAPESHIFT: + if ( entity && entity->behavior == &actPlayer ) + { + switch ( entity->effectShapeshift ) + { + case RAT: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_RAT_FORM); + break; + case TROLL: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_TROLL_FORM); + break; + case SPIDER: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_SPIDER_FORM); + break; + case CREATURE_IMP: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_IMP_FORM); + break; + default: + break; + } + } + break; + case EFF_VAMPIRICAURA: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_VAMPIRIC_AURA); + break; + case EFF_PARALYZED: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_LIGHTNING); + break; + case EFF_DRUNK: + if ( effect_drunk_bmp ) + { + sprite = &effect_drunk_bmp; + } + break; + case EFF_POLYMORPH: + if ( effect_polymorph_bmp ) + { + sprite = &effect_polymorph_bmp; + } + break; + case EFF_WITHDRAWAL: + if ( effect_hungover_bmp ) + { + sprite = &effect_hungover_bmp; + } + break; + case EFF_POTION_STR: + sprite = &str_bmp64u; + break; + case EFF_LEVITATING: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_LEVITATION); + break; + case EFF_INVISIBLE: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_INVISIBILITY); + break; + case EFF_SHADOW_TAGGED: + effectImageNode = list_Node(&items[SPELL_ITEM].surfaces, SPELL_SHADOW_TAG); + break; + default: + effectImageNode = nullptr; + break; + } + + if ( effectImageNode || sprite ) + { + if ( !sprite ) + { + sprite = (SDL_Surface**)effectImageNode->element; + } + } + + if ( sprite ) + { + return *sprite; + } + return nullptr; +} + void drawSustainedSpells(const int player) { SDL_Surface** sprite; diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index fe062ec78..f8415a930 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -2257,7 +2257,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) snprintf(buf, sizeof(buf), "%s %s (%+d)", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(), item->getName(), item->beatitude); } txtHeader->setText(buf); - Text* textGet = Text::get(txtHeader->getText(), txtHeader->getFont()); + Text* textGet = Text::get(txtHeader->getText(), txtHeader->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); if ( textGet ) { textx = textGet->getWidth(); @@ -2324,13 +2325,15 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) unsigned int newWidth = 0; for ( auto& str : tokens ) { - if ( Text* textGet = Text::get(str.c_str(), txtHeader->getFont()) ) + if ( Text* textGet = Text::get(str.c_str(), txtHeader->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { newWidth = std::max(newWidth, textGet->getWidth()); } } - if ( Text* textGet = Text::get(txtHeader->getText(), txtHeader->getFont()) ) + if ( Text* textGet = Text::get(txtHeader->getText(), txtHeader->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { textx = newWidth; texty = textGet->getHeight(); @@ -2344,7 +2347,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) } } - //if ( Text* textGet = Text::get(txtHeader->getText(), txtHeader->getFont()) ) + //if ( Text* textGet = Text::get(txtHeader->getText(), txtHeader->getFont(), + // makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) //{ //} @@ -3083,7 +3087,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) int numLines = txtPrimaryValue->getNumTextLines(); if ( numLines > 1 ) { - auto textGet = Text::get(txtPrimaryValue->getText(), txtPrimaryValue->getFont()); + auto textGet = Text::get(txtPrimaryValue->getText(), txtPrimaryValue->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); if ( textGet ) { imgPrimaryIcon->pos.y += pady * (std::max(0, numLines - 1)); // for each line > 1 add padding @@ -3126,7 +3131,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) int numLines = txtSecondaryValue->getNumTextLines(); if ( numLines > 1 ) { - auto textGet = Text::get(txtSecondaryValue->getText(), txtSecondaryValue->getFont()); + auto textGet = Text::get(txtSecondaryValue->getText(), txtSecondaryValue->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); if ( textGet ) { imgSecondaryIcon->pos.y += pady * (std::max(0, numLines - 1)); // for each line > 1 add padding @@ -3164,7 +3170,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) int numLines = txtThirdValue->getNumTextLines(); if ( numLines > 1 ) { - auto textGet = Text::get(txtThirdValue->getText(), txtThirdValue->getFont()); + auto textGet = Text::get(txtThirdValue->getText(), txtThirdValue->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); if ( textGet ) { imgThirdIcon->pos.y += pady * (std::max(0, numLines - 1)); // for each line > 1 add padding @@ -3257,7 +3264,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) const int charHeight = 13; //Font::get(txtDescription->getFont())->sizeText("_", nullptr, &charHeight); -- this produces 13px @ 12 point font, used above - /*auto textGet = Text::get(txtDescription->getText(), txtDescription->getFont()); + /*auto textGet = Text::get(txtDescription->getText(), txtDescription->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); if ( textGet ) { auto tmp = textGet->getWidth(); @@ -3614,7 +3622,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) auto& txt = pair.second; if ( !txt->isDisabled() ) { - if ( auto textGet = Text::get(txt->getText(), txt->getFont()) ) + if ( auto textGet = Text::get(txt->getText(), txt->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { if ( txt->getHJustify() == Field::justify_t::RIGHT ) { @@ -3646,7 +3655,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y) auto& txt = pair.second; if ( !txt->isDisabled() ) { - if ( auto textGet = Text::get(txt->getText(), txt->getFont()) ) + if ( auto textGet = Text::get(txt->getText(), txt->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { if ( txt->getHJustify() == Field::justify_t::LEFT ) { diff --git a/src/main.hpp b/src/main.hpp index 65a64260d..3de94c3c0 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -514,6 +514,8 @@ typedef struct polymodel_t GLuint vbo; GLuint colors; GLuint colors_shifted; + GLuint grayscale_colors; + GLuint grayscale_colors_shifted; GLuint va; } polymodel_t; diff --git a/src/maps.cpp b/src/maps.cpp index e1d05e0b8..a05b84f0b 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -5646,6 +5646,29 @@ void assignActions(map_t* map) item = nullptr; } break; + case 168: + //Statue Animator + entity->sizex = 4; + entity->sizey = 4; + entity->x += 8; + entity->y += 8; + entity->z = 3.5; + entity->behavior = &actStatueAnimator; + entity->sprite = 995; + entity->skill[0] = 0; + entity->flags[BRIGHT] = true; + break; + case 169: + //Statue + entity->sizex = 4; + entity->sizey = 4; + entity->x += 8; + entity->y += 8; + entity->z = 3.5; + entity->behavior = &actStatue; + entity->sprite = 995; + entity->yaw = entity->statueDir * PI / 2; + break; default: break; } diff --git a/src/menu.cpp b/src/menu.cpp index 591b8b7b3..e969b3fa4 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -168,7 +168,6 @@ int serialVerifyWindow = 0; bool scoreDisplayMultiplayer = false; int settings_xres, settings_yres; -typedef std::tuple resolution; std::list resolutions; Uint32 settings_fov; Uint32 settings_fps; @@ -12123,7 +12122,7 @@ void openGameoverWindow() } // get -void getResolutionList() +void getResolutionList(std::list& resolutions) { // for now just use the resolution modes on the first // display. @@ -12145,9 +12144,13 @@ void getResolutionList() } } - // Sort by total number of pixels + // Sort first by xres and then by yres resolutions.sort([](resolution a, resolution b) { - return std::get<0>(a) * std::get<1>(a) > std::get<0>(b) * std::get<1>(b); + if (std::get<0>(a) == std::get<0>(b)) { + return std::get<1>(a) > std::get<1>(b); + } else { + return std::get<0>(a) > std::get<0>(b); + } }); resolutions.unique(); @@ -12262,7 +12265,7 @@ void openSettingsWindow() button_t* button; int c; - getResolutionList(); + getResolutionList(resolutions); // set the "settings" variables settings_xres = xres; @@ -14204,16 +14207,6 @@ void applySettings() printlog("critical error! Attempting to abort safely...\n"); mainloop = 0; } - if ( zbuffer != NULL ) - { - free(zbuffer); - } - zbuffer = (real_t*) malloc(sizeof(real_t) * xres * yres); - if ( clickmap != NULL ) - { - free(clickmap); - } - clickmap = (Entity**) malloc(sizeof(Entity*)*xres * yres); } // set audio options sfxvolume = settings_sfxvolume; diff --git a/src/menu.hpp b/src/menu.hpp index 7ad8beb0d..a1938671e 100644 --- a/src/menu.hpp +++ b/src/menu.hpp @@ -283,6 +283,8 @@ extern Sint32 oldXres; extern Sint32 oldYres; extern button_t* revertResolutionButton; +typedef std::tuple resolution; +void getResolutionList(std::list&); void applySettings(); void openConfirmResolutionWindow(); void buttonAcceptResolution(button_t* my); diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 2d2ccb2f4..7002c2665 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -24,6 +24,7 @@ ItemTooltips_t ItemTooltips; #ifndef NINTENDO IRCHandler_t IRCHandler; #endif // !NINTENDO +StatueManager_t StatueManager; const std::vector MonsterStatCustomManager::itemStatusStrings = { @@ -3280,4 +3281,191 @@ void ItemTooltips_t::stripOutPositiveNegativeItemDetails(std::string& str, std:: index = std::distance(str.begin(), it); } } -#endif // !EDITOR \ No newline at end of file +#endif // !EDITOR + +Uint32 StatueManager_t::statueId = 0; +int StatueManager_t::processStatueExport() +{ + if ( !exportActive ) + { + return 0; + } + + Entity* player = uidToEntity(editingPlayerUid); + + if ( exportRotations >= 4 || !player ) + { + if ( player ) // save the file. + { + std::string outputPath = PHYSFS_getRealDir("/data/statues"); + outputPath.append(PHYSFS_getDirSeparator()); + std::string fileName = "data/statues/" + exportFileName; + outputPath.append(fileName.c_str()); + + exportRotations = 0; + exportFileName = ""; + exportActive = false; + + File* fp = FileIO::open(outputPath.c_str(), "wb"); + if ( !fp ) + { + return 0; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + exportDocument.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + return 2; // success + } + else + { + exportRotations = 0; + exportFileName = ""; + exportActive = false; + return 0; + } + } + + bool newDocument = false; + + if ( exportFileName == "" ) // find a new filename + { + int filenum = 0; + std::string testPath = "/data/statues/statue" + std::to_string(filenum) + ".json"; + while ( PHYSFS_getRealDir(testPath.c_str()) != nullptr && filenum < 1000 ) + { + ++filenum; + testPath = "/data/statues/statue" + std::to_string(filenum) + ".json"; + } + exportFileName = "statue" + std::to_string(filenum) + ".json"; + newDocument = true; + } + + if ( newDocument ) + { + if ( exportDocument.IsObject() ) + { + exportDocument.RemoveAllMembers(); + } + exportDocument.SetObject(); + CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(1)); + CustomHelpers::addMemberToRoot(exportDocument, "statue_id", rapidjson::Value(rand())); + CustomHelpers::addMemberToRoot(exportDocument, "height_offset", rapidjson::Value(0)); + rapidjson::Value limbsObject(rapidjson::kObjectType); + CustomHelpers::addMemberToRoot(exportDocument, "limbs", limbsObject); + } + + rapidjson::Value limbsArray(rapidjson::kArrayType); + + std::vector allLimbs; + allLimbs.push_back(player); + + for ( auto& bodypart : player->bodyparts ) + { + allLimbs.push_back(bodypart); + } + + int index = 0; + for ( auto& limb : allLimbs ) + { + if ( limb->flags[INVISIBLE] ) + { + continue; + } + rapidjson::Value limbsObj(rapidjson::kObjectType); + + if ( index != 0 ) + { + limbsObj.AddMember("x", rapidjson::Value(player->x - limb->x), exportDocument.GetAllocator()); + limbsObj.AddMember("y", rapidjson::Value(player->y - limb->y), exportDocument.GetAllocator()); + limbsObj.AddMember("z", rapidjson::Value(limb->z), exportDocument.GetAllocator()); + } + else + { + limbsObj.AddMember("x", rapidjson::Value(0), exportDocument.GetAllocator()); + limbsObj.AddMember("y", rapidjson::Value(0), exportDocument.GetAllocator()); + limbsObj.AddMember("z", rapidjson::Value(limb->z), exportDocument.GetAllocator()); + } + limbsObj.AddMember("pitch", rapidjson::Value(limb->pitch), exportDocument.GetAllocator()); + limbsObj.AddMember("roll", rapidjson::Value(limb->roll), exportDocument.GetAllocator()); + limbsObj.AddMember("yaw", rapidjson::Value(limb->yaw), exportDocument.GetAllocator()); + limbsObj.AddMember("focalx", rapidjson::Value(limb->focalx), exportDocument.GetAllocator()); + limbsObj.AddMember("focaly", rapidjson::Value(limb->focaly), exportDocument.GetAllocator()); + limbsObj.AddMember("focalz", rapidjson::Value(limb->focalz), exportDocument.GetAllocator()); + limbsObj.AddMember("sprite", rapidjson::Value(limb->sprite), exportDocument.GetAllocator()); + limbsArray.PushBack(limbsObj, exportDocument.GetAllocator()); + + ++index; + } + + CustomHelpers::addMemberToSubkey(exportDocument, "limbs", directionKeys[exportRotations], limbsArray); + ++exportRotations; + return 1; +} + +void StatueManager_t::readStatueFromFile(int index) +{ + std::string fileName = "/data/statues/statue" + std::to_string(index) + ".json"; + if ( PHYSFS_getRealDir(fileName.c_str()) ) + { + std::string inputPath = PHYSFS_getRealDir(fileName.c_str()); + inputPath.append(fileName); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf)); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.HasMember("version") || !d.HasMember("limbs") || !d.HasMember("statue_id") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + int version = d["version"].GetInt(); + Uint32 statueId = d["statue_id"].GetUint(); + auto findStatue = allStatues.find(statueId); + if ( findStatue != allStatues.end() ) + { + allStatues.erase(findStatue); + } + allStatues.insert(std::make_pair(statueId, Statue_t())); + for ( rapidjson::Value::ConstMemberIterator limb_itr = d["limbs"].MemberBegin(); limb_itr != d["limbs"].MemberEnd(); ++limb_itr ) + { + auto& statue = allStatues[statueId]; + if ( d.HasMember("height_offset") ) + { + statue.heightOffset = d["height_offset"].GetDouble(); + } + for ( rapidjson::Value::ConstValueIterator dir_itr = limb_itr->value.Begin(); dir_itr != limb_itr->value.End(); ++dir_itr ) + { + const rapidjson::Value& attributes = *dir_itr; + std::string direction = limb_itr->name.GetString(); + auto& limbVector = statue.limbs[direction]; + limbVector.push_back(Statue_t::StatueLimb_t()); + auto& limb = limbVector[limbVector.size() - 1]; + limb.x = attributes["x"].GetDouble(); + limb.y = attributes["y"].GetDouble(); + limb.z = attributes["z"].GetDouble(); + limb.focalx = attributes["focalx"].GetDouble(); + limb.focaly = attributes["focaly"].GetDouble(); + limb.focalz = attributes["focalz"].GetDouble(); + limb.pitch = attributes["pitch"].GetDouble(); + limb.roll = attributes["roll"].GetDouble(); + limb.yaw = attributes["yaw"].GetDouble(); + limb.sprite = attributes["sprite"].GetInt(); + } + } + + printlog("[JSON]: Successfully read json file %s", inputPath.c_str()); + } +} \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 88adc28e0..66fe82fd9 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2753,4 +2753,54 @@ class ItemTooltips_t void stripOutPositiveNegativeItemDetails(std::string& str, std::string& positiveValues, std::string& negativeValues); void stripOutHighlightBracketText(std::string& str, std::string& bracketText); }; -extern ItemTooltips_t ItemTooltips; \ No newline at end of file +extern ItemTooltips_t ItemTooltips; + +class StatueManager_t +{ +public: + StatueManager_t() {}; + ~StatueManager_t() {}; + int processStatueExport(); + + bool activeEditing = false; + Uint32 lastEntityUnderMouse = 0; + Uint32 editingPlayerUid = 0; + real_t statueEditorHeightOffset = 0.0; + bool drawGreyscale = false; + void readStatueFromFile(int index); + static Uint32 statueId; + std::string exportFileName = ""; + int exportRotations = 0; + bool exportActive = false; + rapidjson::Document exportDocument; + + class Statue_t + { + Uint32 id; + public: + struct StatueLimb_t + { + real_t x; + real_t y; + real_t z; + real_t pitch; + real_t roll; + real_t yaw; + real_t focalx; + real_t focaly; + real_t focalz; + Sint32 sprite; + bool visible = true; + }; + std::map> limbs; + Statue_t() { + id = statueId; + ++statueId; + } + real_t heightOffset = 0.0; + }; + + const std::vector directionKeys{ "east", "south", "west", "north" }; + std::map allStatues; +}; +extern StatueManager_t StatueManager; \ No newline at end of file diff --git a/src/opengl.cpp b/src/opengl.cpp index 9eeeec77b..813e59f62 100644 --- a/src/opengl.cpp +++ b/src/opengl.cpp @@ -525,6 +525,14 @@ void glDrawVoxel(view_t* camera, Entity* entity, int mode) } } + bool doGrayScale = false; + real_t grayScaleFactor = 0.0; + if ( entity->grayscaleGLRender > 0.001 ) + { + doGrayScale = true; + grayScaleFactor = entity->grayscaleGLRender; + } + // get shade factor if (!entity->flags[BRIGHT]) { @@ -716,22 +724,27 @@ void glDrawVoxel(view_t* camera, Entity* entity, int mode) { if ( mode == REALCOLORS ) { + Uint8 r, g, b; + r = polymodels[modelindex].faces[index].r; + b = polymodels[modelindex].faces[index].g; + g = polymodels[modelindex].faces[index].b; + if ( entity->flags[USERFLAG2] ) { if ( entity->behavior == &actMonster && (entity->isPlayerHeadSprite() || entity->sprite == 467 || !monsterChangesColorWhenAlly(nullptr, entity)) ) { // dont invert human heads, or automaton heads. - glColor3f((polymodels[modelindex].faces[index].r / 255.f)*s, (polymodels[modelindex].faces[index].g / 255.f)*s, (polymodels[modelindex].faces[index].b / 255.f)*s ); + glColor3f((r / 255.f)*s, (g / 255.f)*s, (b / 255.f)*s ); } else { - glColor3f((polymodels[modelindex].faces[index].b / 255.f)*s, (polymodels[modelindex].faces[index].r / 255.f)*s, (polymodels[modelindex].faces[index].g / 255.f)*s); + glColor3f((b / 255.f)*s, (r / 255.f)*s, (g / 255.f)*s); } } else { - glColor3f((polymodels[modelindex].faces[index].b / 255.f)*s, (polymodels[modelindex].faces[index].r / 255.f)*s, (polymodels[modelindex].faces[index].g / 255.f)*s ); + glColor3f((b / 255.f)*s, (r / 255.f)*s, (g / 255.f)*s ); } } else @@ -764,16 +777,37 @@ void glDrawVoxel(view_t* camera, Entity* entity, int mode) if ( entity->behavior == &actMonster && (entity->isPlayerHeadSprite() || entity->sprite == 467 || !monsterChangesColorWhenAlly(nullptr, entity)) ) { - SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].colors); + if ( doGrayScale ) + { + SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].grayscale_colors); + } + else + { + SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].colors); + } } else { - SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].colors_shifted); + if ( doGrayScale ) + { + SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].grayscale_colors_shifted); + } + else + { + SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].colors_shifted); + } } } else { - SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].colors); + if ( doGrayScale ) + { + SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].grayscale_colors); + } + else + { + SDL_glBindBuffer(GL_ARRAY_BUFFER, polymodels[modelindex].colors); + } } glColorPointer(3, GL_FLOAT, 0, 0); GLfloat params_col[4] = { static_cast(s), static_cast(s), static_cast(s), 1.f }; @@ -844,14 +878,14 @@ void glDrawVoxel(view_t* camera, Entity* entity, int mode) SDL_Surface* glTextSurface(std::string text, GLuint* outTextId) { SDL_Surface* image = sprites[0]; - GLuint textureId = texid[sprites[0]->refcount]; + GLuint textureId = texid[(long int)sprites[0]->userdata]; char textToRetrieve[128]; strncpy(textToRetrieve, text.c_str(), 127); textToRetrieve[std::min(static_cast(strlen(text.c_str())), 127)] = '\0'; if ( (image = ttfTextHashRetrieve(ttfTextHash, textToRetrieve, ttf12, true)) != NULL ) { - textureId = texid[image->refcount]; + textureId = texid[(long int)image->userdata]; } else { @@ -876,7 +910,7 @@ SDL_Surface* glTextSurface(std::string text, GLuint* outTextId) SDL_FreeSurface(textSurf); // load the text outline surface as a GL texture allsurfaces[imgref] = image; - allsurfaces[imgref]->refcount = imgref; + allsurfaces[imgref]->userdata = (void *)((long int)imgref); glLoadTexture(allsurfaces[imgref], imgref); imgref++; // store the surface in the text surface cache @@ -884,7 +918,7 @@ SDL_Surface* glTextSurface(std::string text, GLuint* outTextId) { printlog("warning: failed to store text outline surface with imgref %d\n", imgref - 1); } - textureId = texid[image->refcount]; + textureId = texid[(long int)image->userdata]; } if ( outTextId ) { @@ -1060,18 +1094,21 @@ bool glDrawEnemyBarSprite(view_t* camera, int mode, void* enemyHPBarDetails, boo // //drawRect(&resPos, 0xFFFFFF00, 255); //} + int drawOffsetY = (enemybar->worldSurfaceSpriteStatusEffects ? -enemybar->worldSurfaceSpriteStatusEffects->h / 2 : 0); + drawOffsetY += enemybar->glWorldOffsetY; + if ( !doVisibilityCheckOnly ) { // draw quad glBegin(GL_QUADS); glTexCoord2f(0, 0); - glVertex3f(enemybar->screenDistance, GLfloat(sprite->h / 2) - enemybar->glWorldOffsetY, GLfloat(sprite->w / 2)); + glVertex3f(enemybar->screenDistance, GLfloat(sprite->h / 2) - drawOffsetY, GLfloat(sprite->w / 2)); glTexCoord2f(0, 1); - glVertex3f(enemybar->screenDistance, GLfloat(-sprite->h / 2) - enemybar->glWorldOffsetY, GLfloat(sprite->w / 2)); + glVertex3f(enemybar->screenDistance, GLfloat(-sprite->h / 2) - drawOffsetY, GLfloat(sprite->w / 2)); glTexCoord2f(1, 1); - glVertex3f(enemybar->screenDistance, GLfloat(-sprite->h / 2) - enemybar->glWorldOffsetY, GLfloat(-sprite->w / 2)); + glVertex3f(enemybar->screenDistance, GLfloat(-sprite->h / 2) - drawOffsetY, GLfloat(-sprite->w / 2)); glTexCoord2f(1, 0); - glVertex3f(enemybar->screenDistance, GLfloat(sprite->h / 2) - enemybar->glWorldOffsetY, GLfloat(-sprite->w / 2)); + glVertex3f(enemybar->screenDistance, GLfloat(sprite->h / 2) - drawOffsetY, GLfloat(-sprite->w / 2)); glEnd(); } glDepthRange(0, 1); @@ -1499,7 +1536,7 @@ void glDrawSprite(view_t* camera, Entity* entity, int mode) if ( mode == REALCOLORS ) { - glBindTexture(GL_TEXTURE_2D, texid[sprite->refcount]); + glBindTexture(GL_TEXTURE_2D, texid[(long int)sprite->userdata]); } else { @@ -1589,7 +1626,8 @@ void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int return; } - auto rendered_text = Text::get(text.c_str(), "fonts/pixel_maz.ttf#16#2"); + auto rendered_text = Text::get(text.c_str(), "fonts/pixel_maz.ttf#32#2", + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); auto textureId = rendered_text->getTexID(); // setup projection @@ -1832,7 +1870,7 @@ void glDrawWorld(view_t* camera, int mode) // first (higher) sky layer glColor4f(1.f, 1.f, 1.f, .5); - glBindTexture(GL_TEXTURE_2D, texid[tiles[cloudtile]->refcount]); // sky tile + glBindTexture(GL_TEXTURE_2D, texid[(long int)tiles[cloudtile]->userdata]); // sky tile glBegin( GL_QUADS ); glTexCoord2f((real_t)(ticks % 60) / 60, (real_t)(ticks % 60) / 60); glVertex3f(-CLIPFAR * 16, 64, -CLIPFAR * 16); @@ -1849,7 +1887,7 @@ void glDrawWorld(view_t* camera, int mode) // second (closer) sky layer glColor4f(1.f, 1.f, 1.f, .5); - glBindTexture(GL_TEXTURE_2D, texid[tiles[cloudtile]->refcount]); // sky tile + glBindTexture(GL_TEXTURE_2D, texid[(long int)tiles[cloudtile]->userdata]); // sky tile glBegin( GL_QUADS ); glTexCoord2f((real_t)(ticks % 240) / 240, (real_t)(ticks % 240) / 240); glVertex3f(-CLIPFAR * 16, 32, -CLIPFAR * 16); @@ -1922,12 +1960,12 @@ void glDrawWorld(view_t* camera, int mode) { if ( map.tiles[index] < 0 || map.tiles[index] >= numtiles ) { - new_tex = texid[sprites[0]->refcount]; + new_tex = texid[(long int)sprites[0]->userdata]; //glBindTexture(GL_TEXTURE_2D, texid[sprites[0]->refcount]); } else { - new_tex = texid[tiles[map.tiles[index]]->refcount]; + new_tex = texid[(long int)tiles[map.tiles[index]]->userdata]; //glBindTexture(GL_TEXTURE_2D, texid[tiles[map.tiles[index]]->refcount]); } } @@ -2250,7 +2288,7 @@ void glDrawWorld(view_t* camera, int mode) // bind texture if ( mode == REALCOLORS ) { - new_tex = texid[tiles[mapceilingtile]->refcount]; + new_tex = texid[(long int)tiles[mapceilingtile]->userdata]; //glBindTexture(GL_TEXTURE_2D, texid[tiles[50]->refcount]); // rock tile if (cur_tex!=new_tex) { diff --git a/src/player.cpp b/src/player.cpp index a4866bb53..a31243d27 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1261,6 +1261,11 @@ void initGameControllers() { printlog("(Device %d successfully initialized as game controller.)\n", c); inputs.setControllerID(c, controller_itr->getID()); + Input::gameControllers[controller_itr->getID()] = + const_cast(controller_itr->getControllerDevice()); + for ( int c = 0; c < 4; ++c ) { + Input::inputs[c].refresh(); + } found = true; controller_itr = std::next(controller_itr); @@ -1608,6 +1613,7 @@ Player::Player(int in_playernum, bool in_local_host) : hud(*this), magic(*this), characterSheet(*this), + skillSheet(*this), movement(*this), messageZone(*this), worldUI(*this), diff --git a/src/player.hpp b/src/player.hpp index 8c51b15f0..9d012345d 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -963,6 +963,90 @@ class Player void updateCharacterInfo(); } characterSheet; + class SkillSheet_t + { + Player& player; + public: + SkillSheet_t(Player& p) : player(p) + {}; + ~SkillSheet_t() {}; + + Frame* skillFrame = nullptr; + int selectedSkill = 0; + int highlightedSkill = 0; + real_t skillsFadeInAnimationY = 0.0; + bool bSkillSheetOpen = false; + Uint32 openTick = 0; + bool bSkillSheetEntryLoaded = false; + real_t scrollPercent = 0.0; + real_t scrollInertia = 0.0; + int skillSlideDirection = 0; + real_t skillSlideAmount = 0.0; + bool bUseCompactSkillsView = false; + static real_t windowHeightScaleX; + static real_t windowHeightScaleY; + static real_t windowCompactHeightScaleX; + static real_t windowCompactHeightScaleY; + + static struct SkillSheetData_t + { + Uint32 defaultTextColor = 0xFFFFFFFF; + Uint32 noviceTextColor = 0xFFFFFFFF; + Uint32 expertTextColor = 0xFFFFFFFF; + Uint32 legendTextColor = 0xFFFFFFFF; + struct SkillEntry_t + { + SkillEntry_t() {}; + ~SkillEntry_t() {}; + std::string name; + int skillId = -1; + std::string skillIconPath; + std::string skillIconPathLegend; + std::string statIconPath; + std::string description; + std::string legendaryDescription; + int effectStartOffsetX = 72; + int effectBackgroundOffsetX = 8; + int effectBackgroundWidth = 80; + struct SkillEffect_t + { + SkillEffect_t() {}; + ~SkillEffect_t() {}; + std::string tag; + std::string title; + std::string rawValue; + std::string value; + real_t marquee = 0.0; + Uint32 marqueeTicks = 0; + bool marqueeCompleted = false; + int effectUpdatedAtSkillLevel = -1; + }; + std::vector effects; + }; + std::vector skillEntries; + std::string iconBgPathDefault = ""; + std::string iconBgPathNovice = ""; + std::string iconBgPathExpert = ""; + std::string iconBgPathLegend = ""; + std::string iconBgSelectedPathDefault = ""; + std::string iconBgSelectedPathNovice = ""; + std::string iconBgSelectedPathExpert = ""; + std::string iconBgSelectedPathLegend = ""; + std::string highlightSkillImg = ""; + std::string selectSkillImg = ""; + std::string highlightSkillImg_Right = ""; + std::string selectSkillImg_Right = ""; + } skillSheetData; + + void selectSkill(int skill); + void createSkillSheet(); + void processSkillSheet(); + void closeSkillSheet(); + void openSkillSheet(); + void resetSkillDisplay(); + static void loadSkillSheetJSON(); + } skillSheet; + class HUD_t { Player& player; diff --git a/src/savepng.cpp b/src/savepng.cpp index 395a90a81..f6df3a0db 100644 --- a/src/savepng.cpp +++ b/src/savepng.cpp @@ -61,7 +61,7 @@ SDL_Surface* SDL_PNGFormatAlpha(SDL_Surface* src) /* NO-OP for images < 32bpp and 32bpp images that already have Alpha channel */ if (src->format->BitsPerPixel <= 24 || src->format->Amask) { - src->refcount++; + src->userdata = (void *)((long int)src->userdata + 1); return src; } diff --git a/src/ui/Button.cpp b/src/ui/Button.cpp index 977ea5166..7d77cfe6d 100644 --- a/src/ui/Button.cpp +++ b/src/ui/Button.cpp @@ -59,7 +59,7 @@ static char* tokenize(char* str, const char* const delimiters) { } } -void Button::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) { +void Button::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) const { if (invisible) { return; } @@ -148,7 +148,8 @@ void Button::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets); + void draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) const; //! handles button clicks, etc. //! @param _size size and position of button's parent frame diff --git a/src/ui/Field.cpp b/src/ui/Field.cpp index f2522be8e..089d83a9e 100644 --- a/src/ui/Field.cpp +++ b/src/ui/Field.cpp @@ -13,12 +13,18 @@ Field::Field(const int _textLen) { textlen = std::max(_textLen, 0); text = new char[textlen + 1]; + color = makeColor(255, 255, 255, 255); + textColor = makeColor(255, 255, 255, 255); + outlineColor = makeColor(0, 0, 0, 255); memset(text, 0, textlen + 1); } Field::Field(const char* _text) { textlen = strlen(_text); text = new char[textlen + 1]; + color = makeColor(255, 255, 255, 255); + textColor = makeColor(255, 255, 255, 255); + outlineColor = makeColor(0, 0, 0, 255); setText(_text); } @@ -100,7 +106,7 @@ char* Field::tokenize(char* str, const char* const delimiters) { } } -void Field::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) { +void Field::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) const { if ( invisible || isDisabled() ) { return; } @@ -140,7 +146,7 @@ void Field::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectorheight(false) + actualFont->getOutline() * 2; + int fullH = lines * (actualFont->height(false) + actualFont->getOutline() * 2); char* buf = (char*)malloc(textlen + 1); memcpy(buf, text, textlen + 1); @@ -164,7 +170,7 @@ void Field::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector(parent)->getOpacity() < 100.0 ) { @@ -406,7 +412,8 @@ std::unordered_map reflowTextLine(std::string& input, int w { // this is probably OK } - else if ( getText = Text::get(std::string(result[currentLine] + " " + token).c_str(), font) ) + else if ( getText = Text::get(std::string(result[currentLine] + " " + token).c_str(), font, + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { if ( getText->getWidth() > width ) { @@ -447,12 +454,41 @@ std::unordered_map reflowTextLine(std::string& input, int w return result; } +std::string Field::getLongestLine() +{ + if ( text == nullptr || textlen <= 1 ) { + return ""; + } + if ( getNumTextLines() <= 1 ) + { + return text; + } + char* nexttoken; + char* token = text; + std::string originalText = text; + std::string longestLine = ""; + int longestLineWidth = 0; + do { + nexttoken = tokenize(token, "\n"); + if ( auto getText = Text::get(token, font.c_str(), textColor, outlineColor) ) + { + if ( getText->getWidth() > longestLineWidth ) + { + longestLineWidth = getText->getWidth(); + longestLine = token; + } + } + } while ( (token = nexttoken) != NULL ); + setText(originalText.c_str()); // make sure to replace the original text field, as tokenize will modify it + return longestLine; +} + int Field::getLastLineThatFitsWithinHeight() { if ( text == nullptr || textlen <= 1 ) { return -1; } - if ( auto getText = Text::get(text, font.c_str()) ) + if ( auto getText = Text::get(text, font.c_str(), textColor, outlineColor) ) { if ( getText->getHeight() <= getSize().h/* - getSize().y*/ ) { @@ -468,6 +504,7 @@ int Field::getLastLineThatFitsWithinHeight() int lineNumber = 0; char* nexttoken; char* token = text; + std::string originalText = text; do { nexttoken = tokenize(token, "\n"); if ( !allLines.empty() ) @@ -475,17 +512,18 @@ int Field::getLastLineThatFitsWithinHeight() allLines.push_back('\n'); } allLines += token[0]; - if ( auto getText = Text::get(allLines.c_str(), font.c_str()) ) + if ( auto getText = Text::get(allLines.c_str(), font.c_str(), textColor, outlineColor) ) { if ( getText->getHeight() > getSize().h ) { // doesn't fit, return the last line number. + setText(originalText.c_str()); // make sure to replace the original text field, as tokenize will modify it return lineNumber; } } ++lineNumber; } while ( (token = nexttoken) != NULL ); - + setText(originalText.c_str()); // make sure to replace the original text field, as tokenize will modify it return -1; } @@ -494,7 +532,7 @@ void Field::reflowTextToFit(const int characterOffset) { return; } - if ( auto getText = Text::get(text, font.c_str()) ) + if ( auto getText = Text::get(text, font.c_str(), textColor, outlineColor) ) { if ( getText->getWidth() <= (getSize().w) ) { @@ -541,7 +579,7 @@ void Field::reflowTextToFit(const int characterOffset) { int charWidth = 0; actualFont->sizeText("_", &charWidth, nullptr); - //if ( auto textGet = Text::get("_", font.c_str()) ) + //if ( auto textGet = Text::get("_", font.c_str(), textColor, outlineColor) ) //{ // charWidth = textGet->getWidth(); //} diff --git a/src/ui/Field.hpp b/src/ui/Field.hpp index 7466dc64c..584931970 100644 --- a/src/ui/Field.hpp +++ b/src/ui/Field.hpp @@ -53,7 +53,7 @@ class Field : public Widget { //! @param _size size and position of field's parent frame //! @param _actualSize offset into the parent frame space (scroll) //! @param selectedWidgets the currently selected widgets, if any - void draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets); + void draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) const; //! handles clicks, etc. //! @param _size size and position of field's parent frame @@ -65,10 +65,18 @@ class Field : public Widget { //! gets the physical screen-space x/y (not relative to current parent - but to the absolute root) SDL_Rect getAbsoluteSize() const; + //! gets the last line number that fits within the field y + height (to check if overflowing container) + int getLastLineThatFitsWithinHeight(); + + //! gets longest line of field, measured by actual text width + std::string getLongestLine(); + virtual type_t getType() const override { return WIDGET_FIELD; } const char* getText() const { return text; } const char* getFont() const { return font.c_str(); } const Uint32 getColor() const { return color; } + const Uint32 getTextColor() const { return textColor; } + const Uint32 getOutlineColor() const { return outlineColor; } const SDL_Rect getSize() const { return size; } const int getHJustify() const { return static_cast(hjustify); } const int getVJustify() const { return static_cast(vjustify); } @@ -83,6 +91,8 @@ class Field : public Widget { void setPos(const int x, const int y) { size.x = x; size.y = y; } void setSize(const SDL_Rect _size) { size = _size; } void setColor(const Uint32 _color) { color = _color; } + void setTextColor(const Uint32 _color) { textColor = _color; } + void setOutlineColor(const Uint32 _color) { outlineColor = _color; } void setEditable(const bool _editable) { editable = _editable; } void setNumbersOnly(const bool _numbersOnly) { numbersOnly = _numbersOnly; } void setJustify(const int _justify) { hjustify = vjustify = static_cast(_justify); } @@ -93,7 +103,6 @@ class Field : public Widget { void setFont(const char* _font) { font = _font; } void setGuide(const char* _guide) { guide = _guide; } void reflowTextToFit(const int characterOffset); - int getLastLineThatFitsWithinHeight(); void setOntop(const bool _ontop) { ontop = _ontop; } static char* tokenize(char* str, const char* const delimiters); @@ -102,7 +111,9 @@ class Field : public Widget { std::string guide; //!< string to use as a descriptive guide for the field (eg "Enter character's name"); char* text = nullptr; //!< internal text buffer size_t textlen = 0; //!< length of internal text buffer - Uint32 color = 0xFFFFFFFF; //!< text color + Uint32 color; //!< color mixed w/ final rendered text + Uint32 textColor; //!< text color + Uint32 outlineColor; //!< outline color SDL_Rect size; //!< size of the field in pixels justify_t hjustify = LEFT; //!< horizontal text justification justify_t vjustify = TOP; //!< vertical text justification diff --git a/src/ui/Font.cpp b/src/ui/Font.cpp index 86decee52..fb4b1e512 100644 --- a/src/ui/Font.cpp +++ b/src/ui/Font.cpp @@ -28,7 +28,7 @@ Font::Font(const char* _name) { printlog("failed to load '%s': %s", path.c_str(), TTF_GetError()); return; } - TTF_SetFontHinting(font, TTF_HINTING_MONO); + //TTF_SetFontHinting(font, TTF_HINTING_MONO); TTF_SetFontKerning(font, 1); } diff --git a/src/ui/Frame.cpp b/src/ui/Frame.cpp index 34873a11d..afdbebf7f 100644 --- a/src/ui/Frame.cpp +++ b/src/ui/Frame.cpp @@ -151,7 +151,7 @@ Frame::~Frame() { clear(); } -void Frame::draw() { +void Frame::draw() const { SDL_glBindFramebuffer(GL_FRAMEBUFFER, gui_fbo); SDL_glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gui_fbo_color, 0); SDL_glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gui_fbo_depth, 0); @@ -161,7 +161,7 @@ void Frame::draw() { //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); SDL_glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); auto _actualSize = allowScrolling ? actualSize : SDL_Rect{0, 0, size.w, size.h}; - std::vector selectedWidgets; + std::vector selectedWidgets; findSelectedWidgets(selectedWidgets); Frame::draw(size, _actualSize, selectedWidgets); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -191,12 +191,7 @@ void Frame::draw() { glColor4f(1.f, 1.f, 1.f, 1.f); } -void Frame::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) { - if ( parent && inheritParentFrameOpacity ) - { - setOpacity(static_cast(parent)->getOpacity()); - } - +void Frame::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) const { if (disabled || invisible) return; @@ -267,11 +262,15 @@ void Frame::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectorgetHeight(); SDL_Rect pos; - pos.x = _size.x + border + listOffset.x - scroll.x; + switch (justify) { + case justify_t::LEFT: pos.x = _size.x + border + listOffset.x - scroll.x; break; + case justify_t::CENTER: pos.x = _size.x + (_size.w - textSizeW) / 2 + listOffset.x - scroll.x; break; + case justify_t::RIGHT: pos.x = _size.x + _size.w - textSizeW - border + listOffset.x - scroll.x; break; + } pos.y = _size.y + border + listOffset.y + i * entrySize - scroll.y; pos.w = textSizeW; pos.h = textSizeH; @@ -485,7 +489,8 @@ void Frame::draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vectorgetName()); + Text* text = Text::get(tooltip, font->getName(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); SDL_Rect src; src.x = mousex + 20; src.y = mousey; @@ -539,11 +544,9 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: return result; } -#ifndef EDITOR // bad editor no cookie - if ( players[getOwner()]->shootmode ) { - return result; + if ( parent && inheritParentFrameOpacity ) { + setOpacity(static_cast(parent)->getOpacity()); } -#endif // warning: overloading member variable! SDL_Rect actualSize = allowScrolling ? this->actualSize : SDL_Rect{0, 0, size.w, size.h}; @@ -580,11 +583,15 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: Sint32 mousey = (::mousey / (float)yres) * (float)Frame::virtualScreenY; Sint32 omousex = (::omousex / (float)xres) * (float)Frame::virtualScreenX; Sint32 omousey = (::omousey / (float)yres) * (float)Frame::virtualScreenY; + Sint32 mousexrel = (::mousexrel / (float)xres) * (float)Frame::virtualScreenX; + Sint32 mouseyrel = (::mouseyrel / (float)yres) * (float)Frame::virtualScreenY; #else Sint32 mousex = (inputs.getMouse(owner, Inputs::X) / (float)xres) * (float)Frame::virtualScreenX; Sint32 mousey = (inputs.getMouse(owner, Inputs::Y) / (float)yres) * (float)Frame::virtualScreenY; Sint32 omousex = (inputs.getMouse(owner, Inputs::OX) / (float)xres) * (float)Frame::virtualScreenX; Sint32 omousey = (inputs.getMouse(owner, Inputs::OY) / (float)yres) * (float)Frame::virtualScreenY; + Sint32 mousexrel = (inputs.getMouse(owner, Inputs::XREL) / (float)xres) * (float)Frame::virtualScreenX; + Sint32 mouseyrel = (inputs.getMouse(owner, Inputs::YREL) / (float)yres) * (float)Frame::virtualScreenY; #endif Input& input = Input::inputs[owner]; @@ -631,9 +638,6 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: search->select(); } } - if (dropDown) { - toBeDeleted = true; - } } // choose a selection @@ -664,17 +668,36 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } } + // process frames + { + for (int i = frames.size() - 1; i >= 0; --i) { + Frame* frame = frames[i]; + result_t frameResult = frame->process(_size, actualSize, selectedWidgets, usable); + usable = result.usable = frameResult.usable; + if (!frameResult.removed) { + if (frameResult.tooltip != nullptr) { + result = frameResult; + } + } else { + delete frame; + frames.erase(frames.begin() + i); + } + } + } + // scroll with right stick - if (allowScrolling && allowScrollBinds) { + if (usable && allowScrolling && allowScrollBinds) { Input& input = Input::inputs[owner]; // x scroll if (this->actualSize.w > size.w) { if (input.binary("MenuScrollRight")) { this->actualSize.x += std::min(this->actualSize.x + 5, this->actualSize.w - _size.w); + usable = result.usable = false; } else if (input.binary("MenuScrollLeft")) { this->actualSize.x -= std::max(this->actualSize.x - 5, 0); + usable = result.usable = false; } } @@ -682,61 +705,56 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: if (this->actualSize.h > size.h) { if (input.binary("MenuScrollDown")) { this->actualSize.y = std::min(this->actualSize.y + 5, this->actualSize.h - _size.h); + usable = result.usable = false; } else if (input.binary("MenuScrollUp")) { this->actualSize.y = std::max(this->actualSize.y - 5, 0); + usable = result.usable = false; } } } // scroll with mouse wheel - if (parent != nullptr && !hollow && rectContainsPoint(fullSize, omousex, omousey) && usable && allowScrolling && allowScrollBinds) { - // x scroll with mouse wheel - if (this->actualSize.w > size.w) { - if (this->actualSize.h <= size.h) { - if (mousestatus[SDL_BUTTON_WHEELDOWN]) { - mousestatus[SDL_BUTTON_WHEELDOWN] = 0; - this->actualSize.x += std::min(entrySize * 4, size.w); - usable = result.usable = false; - } else if (mousestatus[SDL_BUTTON_WHEELUP]) { - mousestatus[SDL_BUTTON_WHEELUP] = 0; - this->actualSize.x -= std::min(entrySize * 4, size.w); - usable = result.usable = false; - } - } + if (parent != nullptr && !hollow && rectContainsPoint(fullSize, omousex, omousey) && usable) { + bool mwheeldown = false; + bool mwheelup = false; + if (mousestatus[SDL_BUTTON_WHEELDOWN]) { + mousestatus[SDL_BUTTON_WHEELDOWN] = 0; + mwheeldown = true; } - - // y scroll with mouse wheel - if (this->actualSize.h > size.h) { - if (mousestatus[SDL_BUTTON_WHEELDOWN]) { - mousestatus[SDL_BUTTON_WHEELDOWN] = 0; - this->actualSize.y += std::min(entrySize * 4, size.h); - usable = result.usable = false; - } else if (mousestatus[SDL_BUTTON_WHEELUP]) { - mousestatus[SDL_BUTTON_WHEELUP] = 0; - this->actualSize.y -= std::min(entrySize * 4, size.h); - usable = result.usable = false; - } + if (mousestatus[SDL_BUTTON_WHEELUP]) { + mousestatus[SDL_BUTTON_WHEELUP] = 0; + mwheelup = true; } + if (allowScrolling && allowScrollBinds) { + if (mwheeldown || mwheelup) { + usable = result.usable = false; - // bound - this->actualSize.x = std::min(std::max(0, this->actualSize.x), std::max(0, this->actualSize.w - size.w)); - this->actualSize.y = std::min(std::max(0, this->actualSize.y), std::max(0, this->actualSize.h - size.h)); - } + // x scroll with mouse wheel + if (this->actualSize.w > size.w) { + if (this->actualSize.h <= size.h) { + if (mwheeldown) { + this->actualSize.x += std::min(entrySize, size.w); + } + if (mwheelup) { + this->actualSize.x -= std::min(entrySize, size.w); + } + this->actualSize.x = std::min(std::max(0, this->actualSize.x), + std::max(0, this->actualSize.w - size.w)); + } + } - // process frames - { - for (int i = frames.size() - 1; i >= 0; --i) { - Frame* frame = frames[i]; - result_t frameResult = frame->process(_size, actualSize, selectedWidgets, usable); - usable = result.usable = frameResult.usable; - if (!frameResult.removed) { - if (frameResult.tooltip != nullptr) { - result = frameResult; + // y scroll with mouse wheel + if (this->actualSize.h > size.h) { + if (mwheeldown) { + this->actualSize.y += std::min(entrySize, size.h); + } + if (mwheelup) { + this->actualSize.y -= std::min(entrySize, size.h); + } + this->actualSize.y = std::min(std::max(0, this->actualSize.y), + std::max(0, this->actualSize.h - size.h)); } - } else { - delete frame; - frames.erase(frames.begin() + i); } } } @@ -793,7 +811,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: ticks = -1; // hack to fix sliders in drop downs } else if (rectContainsPoint(sliderRect, omousex, omousey)) { if (mousestatus[SDL_BUTTON_LEFT]) { - this->actualSize.x += omousex < handleRect.x ? -std::min(entrySize * 4, size.w) : std::min(entrySize * 4, size.w); + this->actualSize.x += omousex < handleRect.x ? -std::min(entrySize, size.w) : std::min(entrySize, size.w); this->actualSize.x = std::min(std::max(0, this->actualSize.x), std::max(0, this->actualSize.w - size.w)); mousestatus[SDL_BUTTON_LEFT] = 0; } @@ -843,7 +861,7 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: ticks = -1; // hack to fix sliders in drop downs } else if (rectContainsPoint(sliderRect, omousex, omousey)) { if (mousestatus[SDL_BUTTON_LEFT]) { - this->actualSize.y += omousey < handleRect.y ? -std::min(entrySize * 4, size.h) : std::min(entrySize * 4, size.h); + this->actualSize.y += omousey < handleRect.y ? -std::min(entrySize, size.h) : std::min(entrySize, size.h); this->actualSize.y = std::min(std::max(0, this->actualSize.y), std::max(0, this->actualSize.h - size.h)); mousestatus[SDL_BUTTON_LEFT] = 0; } @@ -866,6 +884,9 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: if (usable && buttonResult.highlighted) { result.highlightTime = buttonResult.highlightTime; result.tooltip = buttonResult.tooltip; + if (mousexrel || mouseyrel) { + button->select(); + } if (buttonResult.clicked) { button->activate(); } @@ -893,6 +914,9 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: if (usable && sliderResult.highlighted) { result.highlightTime = sliderResult.highlightTime; result.tooltip = sliderResult.tooltip; + if (mousexrel || mouseyrel) { + slider->select(); + } if (sliderResult.clicked) { slider->fireCallback(); } @@ -919,14 +943,18 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: } SDL_Rect entryRect; - entryRect.x = _size.x + border - actualSize.x; entryRect.w = _size.w - border * 2; - entryRect.y = _size.y + border + i * entrySize - actualSize.y; entryRect.h = entrySize; + entryRect.x = _size.x + border - actualSize.x + listOffset.x; entryRect.w = _size.w - border * 2; + entryRect.y = _size.y + border + i * entrySize - actualSize.y + listOffset.y; entryRect.h = entrySize; if (rectContainsPoint(_size, omousex, omousey) && rectContainsPoint(entryRect, omousex, omousey)) { result.highlightTime = entry->highlightTime; result.tooltip = entry->tooltip.c_str(); + if (mousexrel || mouseyrel) { + selection = i; + } if (mousestatus[SDL_BUTTON_LEFT]) { if (!entry->pressed) { + mousestatus[SDL_BUTTON_LEFT] = 0; entry->pressed = true; activateEntry(*entry); } @@ -963,8 +991,13 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, const std:: Field::result_t fieldResult = field->process(_size, actualSize, usable); if (usable) { - if (field->isSelected() && fieldResult.highlighted) { - result.usable = usable = false; + if (fieldResult.highlighted) { + if (mousexrel || mouseyrel) { + field->select(); + } + if (field->isSelected()) { + result.usable = usable = false; + } } } @@ -1259,7 +1292,6 @@ void Frame::resizeForEntries() { entrySize = _font->height(); entrySize += entrySize / 2; } - actualSize.w = size.w; actualSize.h = (Uint32)list.size() * entrySize; actualSize.y = std::min(std::max(0, actualSize.y), std::max(0, actualSize.h - size.h)); } @@ -1440,28 +1472,24 @@ void Frame::enableScroll(bool enabled) { allowScrolling = enabled; } -void Frame::scrollToSelection() { - if (selection == -1) { +void Frame::scrollToSelection(bool scroll_to_top) { + if (selection < 0 || selection >= list.size()) { return; } - int index = 0; - for (auto entry : list) { - if (entry == list[selection]) { - break; - } - ++index; - } int entrySize = 20; Font* _font = Font::get(font.c_str()); if (_font != nullptr) { entrySize = _font->height(); entrySize += entrySize / 2; } - if (actualSize.y > index * entrySize) { + const int index = selection; + if (scroll_to_top || actualSize.y > index * entrySize) { actualSize.y = index * entrySize; + actualSize.y = std::min(std::max(0, actualSize.y), std::max(0, actualSize.h - size.h)); } if (actualSize.y + size.h < (index + 1) * entrySize) { actualSize.y = (index + 1) * entrySize - size.h; + actualSize.y = std::min(std::max(0, actualSize.y), std::max(0, actualSize.h - size.h)); } } @@ -1475,6 +1503,9 @@ void Frame::activateEntry(entry_t& entry) { (*entry.click)(entry); } } + if (dropDown) { + toBeDeleted = true; + } } void createTestUI() { @@ -1586,7 +1617,7 @@ void createTestUI() { } } -void Frame::drawImage(image_t* image, const SDL_Rect& _size, const SDL_Rect& scroll) { +void Frame::drawImage(const image_t* image, const SDL_Rect& _size, const SDL_Rect& scroll) const { assert(image); const Image* actualImage = Image::get(image->path.c_str()); if (actualImage) { diff --git a/src/ui/Frame.hpp b/src/ui/Frame.hpp index b36d9e566..b876dea0b 100644 --- a/src/ui/Frame.hpp +++ b/src/ui/Frame.hpp @@ -36,6 +36,14 @@ class Frame : public Widget { BORDER_MAX }; + //! list text justification + enum justify_t { + LEFT, + RIGHT, + CENTER, + JUSTIFY_TYPE_LENGTH + }; + //! frame image struct image_t { std::string name; @@ -112,7 +120,7 @@ class Frame : public Widget { static void fboDestroy(); //! draws the frame and all of its subelements - void draw(); + void draw() const; //! handle clicks and other events //! @return compiled results of frame processing @@ -252,7 +260,11 @@ class Frame : public Widget { //! @param image the image to draw //! @param _size the size of the rectangle to clip against //! @param scroll the amount by which to offset the image in x/y - void drawImage(image_t* image, const SDL_Rect& _size, const SDL_Rect& scroll); + void drawImage(const image_t* image, const SDL_Rect& _size, const SDL_Rect& scroll) const; + + //! scroll to the current list entry selection in the frame + //! @param scroll_to_top if true, scroll the selection to the very top of the frame + void scrollToSelection(bool scroll_to_top = false); virtual type_t getType() const override { return WIDGET_FRAME; } const char* getFont() const { return font.c_str(); } @@ -276,6 +288,7 @@ class Frame : public Widget { int getSelection() const { return selection; } real_t getOpacity() const { return opacity; } const bool getInheritParentFrameOpacity() const { return inheritParentFrameOpacity; } + justify_t getJustify() const { return justify; } void setFont(const char* _font) { font = _font; } void setBorder(const int _border) { border = _border; } @@ -294,6 +307,7 @@ class Frame : public Widget { void setListOffset(SDL_Rect _size) { listOffset = _size; } void setInheritParentFrameOpacity(const bool _inherit) { inheritParentFrameOpacity = _inherit; } void setOpacity(const real_t _opacity) { opacity = _opacity; } + void setListJustify(justify_t _justify) { justify = _justify; } private: Uint32 ticks = 0; //!< number of engine ticks this frame has persisted @@ -320,6 +334,7 @@ class Frame : public Widget { SDL_Rect listOffset{0, 0, 0, 0}; //!< frame list offset in x, y real_t opacity = 100.0; //!< opacity multiplier of elements within this frame (image/fields etc) bool inheritParentFrameOpacity = true; //!< if true, uses parent frame opacity + justify_t justify = justify_t::LEFT; //!< frame list horizontal justification std::vector frames; std::vector buttons; @@ -328,9 +343,6 @@ class Frame : public Widget { std::vector sliders; std::vector list; - //! scroll to the current list entry selection in the frame - void scrollToSelection(); - //! activate the given list entry //! @param entry the entry to activate void activateEntry(entry_t& entry); @@ -339,7 +351,7 @@ class Frame : public Widget { //! @param _size real position of the frame onscreen //! @param _actualSize offset into the frame space (scroll) //! @param selectedWidgets the currently selected widgets, if any - void draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets); + void draw(SDL_Rect _size, SDL_Rect _actualSize, const std::vector& selectedWidgets) const; //! handle clicks and other events //! @param _size real position of the frame onscreen diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 29bc2b9ac..a64762ddb 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -6,6 +6,7 @@ #include "Field.hpp" #include "Button.hpp" #include "Text.hpp" +#include "Slider.hpp" #include "../main.hpp" #include "../game.hpp" @@ -22,7 +23,7 @@ #include -bool newui = false; +bool newui = true; int selectedCursorOpacity = 255; int oldSelectedCursorOpacity = 255; int hotbarSlotOpacity = 255; @@ -40,8 +41,9 @@ struct CustomColors_t Uint32 characterSheetGreen = 0xFFFFFFFF; Uint32 characterSheetRed = 0xFFFFFFFF; } hudColors; +EnemyBarSettings_t enemyBarSettings; -std::string getEnemyBarSpriteName(Entity* entity) +std::string EnemyBarSettings_t::getEnemyBarSpriteName(Entity* entity) { if ( !entity ) { return "default"; } @@ -77,21 +79,86 @@ std::string getEnemyBarSpriteName(Entity* entity) return "default"; } -struct EnemyBarSettings_t +enum ImageIndexes9x9 : int { - std::unordered_map heightOffsets; - std::unordered_map screenDistanceOffsets; - float getHeightOffset(Entity* entity) - { - if ( !entity ) { return 0.f; } - return heightOffsets[getEnemyBarSpriteName(entity)]; - } - float getScreenDistanceOffset(Entity* entity) + TOP_LEFT, + TOP_RIGHT, + TOP, + MIDDLE_LEFT, + MIDDLE_RIGHT, + MIDDLE, + BOTTOM_LEFT, + BOTTOM_RIGHT, + BOTTOM +}; + +const std::vector skillsheetEffectBackgroundImages = +{ + "9x9 bg top left", + "9x9 bg top right", + "9x9 bg top middle", + "9x9 bg left", + "9x9 bg right", + "9x9 bg middle", + "9x9 bg bottom left", + "9x9 bg bottom right", + "9x9 bg bottom middle" +}; + +void imageSetWidthHeight9x9(Frame* container, const std::vector& imgNames) +{ + for ( auto& img : imgNames ) { - if ( !entity ) { return 0.f; } - return screenDistanceOffsets[getEnemyBarSpriteName(entity)]; + if ( auto i = container->findImage(img.c_str()) ) + { + if ( auto imgGet = Image::get(i->path.c_str()) ) + { + i->pos.w = imgGet->getWidth(); + i->pos.h = imgGet->getHeight(); + } + } } -} enemyBarSettings; +} + +// for 9x9 images stretched to fit a container +void imageResizeToContainer9x9(Frame* container, SDL_Rect dimensionsToFill, const std::vector& imgNames) +{ + assert(imgNames.size() == 9); + // adjust inner background image elements + auto tl = container->findImage(imgNames[TOP_LEFT].c_str()); + tl->pos.x = dimensionsToFill.x; + tl->pos.y = dimensionsToFill.y; + auto tr = container->findImage(imgNames[TOP_RIGHT].c_str()); + tr->pos.x = dimensionsToFill.w - tr->pos.w; + tr->pos.y = dimensionsToFill.y; + auto tm = container->findImage(imgNames[TOP].c_str()); + tm->pos.x = tl->pos.x + tl->pos.w; + tm->pos.y = dimensionsToFill.y; + tm->pos.w = dimensionsToFill.w - tr->pos.w - tl->pos.w; + auto bl = container->findImage(imgNames[BOTTOM_LEFT].c_str()); + bl->pos.x = dimensionsToFill.x; + bl->pos.y = dimensionsToFill.h - bl->pos.h; + auto br = container->findImage(imgNames[BOTTOM_RIGHT].c_str()); + br->pos.x = dimensionsToFill.w - br->pos.w; + br->pos.y = bl->pos.y; + auto bm = container->findImage(imgNames[BOTTOM].c_str()); + bm->pos.x = bl->pos.x + bl->pos.w; + bm->pos.w = dimensionsToFill.w - bl->pos.w - br->pos.w; + bm->pos.y = bl->pos.y; + auto ml = container->findImage(imgNames[MIDDLE_LEFT].c_str()); + ml->pos.x = dimensionsToFill.x; + ml->pos.y = tl->pos.y + tl->pos.h; + ml->pos.h = dimensionsToFill.h - dimensionsToFill.y - bl->pos.h - tl->pos.h; + auto mr = container->findImage(imgNames[MIDDLE_RIGHT].c_str()); + mr->pos.x = dimensionsToFill.w - mr->pos.w; + mr->pos.y = ml->pos.y; + mr->pos.h = ml->pos.h; + auto mm = container->findImage(imgNames[MIDDLE].c_str()); + mm->pos.x = ml->pos.x + ml->pos.w; + mm->pos.y = ml->pos.y; + mm->pos.w = dimensionsToFill.w - ml->pos.w - mr->pos.w; + mm->pos.h = ml->pos.h; +} void createHPMPBars(const int player) { @@ -145,7 +212,7 @@ void createHPMPBars(const int player) auto endCap = foregroundFrame->addImage(SDL_Rect{ pos.w - endCapWidth, 0, endCapWidth, barTotalHeight }, 0xFFFFFFFF, "images/ui/HUD/hpmpbars/HUD_Bars_EndCap_00.png", "hp img endcap"); - auto font = "fonts/pixel_maz.ttf#16#2"; + auto font = "fonts/pixel_maz.ttf#32#2"; auto hptext = foregroundFrame->addField("hp text", 16); hptext->setText("0"); hptext->setSize(numbase->pos); @@ -200,7 +267,7 @@ void createHPMPBars(const int player) auto endCap = foregroundFrame->addImage(SDL_Rect{ pos.w - endCapWidth, 0, endCapWidth, barTotalHeight }, 0xFFFFFFFF, "images/ui/HUD/hpmpbars/HUD_Bars_EndCap_00.png", "mp img endcap"); - auto font = "fonts/pixel_maz.ttf#16#2"; + auto font = "fonts/pixel_maz.ttf#32#2"; auto mptext = foregroundFrame->addField("mp text", 16); mptext->setText("0"); mptext->setSize(numbase->pos); @@ -344,7 +411,7 @@ void createXPBar(const int player) auto endCapRight = hud_t.xpFrame->addImage(endCapPos, 0xFFFFFFFF, "images/ui/HUD/xpbar/HUD_Bars_ExpCap2_00.png", "xp img endcap right"); const int textWidth = 40; - auto font = "fonts/pixel_maz.ttf#16#2"; + auto font = "fonts/pixel_maz.ttf#32#2"; auto textStatic = hud_t.xpFrame->addField("xp text static", 16); textStatic->setText("/100"); textStatic->setSize(SDL_Rect{ pos.w / 2 - 4, 0, textWidth, pos.h }); // x - 4 to center the slash @@ -385,7 +452,7 @@ void createHotbar(const int player) glyph->disabled = true; } - auto font = "fonts/pixel_maz.ttf#18#2"; + auto font = "fonts/pixel_maz.ttf#32#2"; for ( int i = 0; i < NUM_HOTBAR_SLOTS; ++i ) { @@ -406,7 +473,7 @@ void createHotbar(const int player) snprintf(numStr, sizeof(numStr), "%d", i + 1); auto text = slot->addField("slot num text", 4); text->setText(numStr); - text->setSize(SDL_Rect{ 0, -8, slotPos.w, slotPos.h }); + text->setSize(SDL_Rect{ 0, -4, slotPos.w, slotPos.h }); text->setFont(font); text->setVJustify(Field::justify_t::TOP); text->setHJustify(Field::justify_t::LEFT); @@ -468,7 +535,7 @@ void createHotbar(const int player) auto text = highlightFrame->addField("slot num text", 4); text->setText(""); - text->setSize(SDL_Rect{ 0, -8, slotPos.w, slotPos.h }); + text->setSize(SDL_Rect{ 0, -4, slotPos.w, slotPos.h }); text->setFont(font); text->setVJustify(Field::justify_t::TOP); text->setHJustify(Field::justify_t::LEFT); @@ -491,7 +558,7 @@ void Player::HUD_t::processHUD() players[player.playernum]->camera_virtualWidth(), players[player.playernum]->camera_virtualHeight() }); - if ( nohud || !players[player.playernum]->isLocalPlayer() ) + if ( gamePaused || nohud || !players[player.playernum]->isLocalPlayer() ) { // hide hudFrame->setDisabled(true); @@ -585,6 +652,11 @@ void Player::MessageZone_t::processChatbox() players[player.playernum]->camera_virtualHeight() }; Frame* messageBoxFrame = chatFrame->findFrame("message box"); + if (gamePaused) { + messageBoxFrame->setDisabled(true); + } else { + messageBoxFrame->setDisabled(false); + } SDL_Rect messageBoxSize = messageBoxFrame->getSize(); if ( player.shootmode && messageBoxSize.x == chatboxTopAlignedPos.x ) { @@ -653,7 +725,8 @@ void Player::MessageZone_t::processChatbox() //current->requiresResize = false; } - Text* textGet = Text::get(entry->getText(), entry->getFont()); + Text* textGet = Text::get(entry->getText(), entry->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); if ( !messageDrawDescending ) { currentY -= textGet->getHeight(); @@ -741,7 +814,7 @@ void Player::CharacterSheet_t::createCharacterSheet() fullscreenBg->addImage(SDL_Rect{ 0, 0, fullscreenBg->getSize().w, fullscreenBg->getSize().h }, 0xFFFFFFFF, "images/ui/CharSheet/HUD_CharSheet_Window_00.png", "bg image"); - const char* titleFont = "fonts/pixel_maz.ttf#14#2"; + const char* titleFont = "fonts/pixel_maz.ttf#32#2"; auto characterSheetTitleText = fullscreenBg->addField("character sheet title text", 32); characterSheetTitleText->setFont(titleFont); characterSheetTitleText->setSize(SDL_Rect{ 6, 120, 202, 32 }); @@ -752,7 +825,7 @@ void Player::CharacterSheet_t::createCharacterSheet() // log / map buttons { - const char* buttonFont = "fonts/pixel_maz.ttf#14#2"; + const char* buttonFont = "fonts/pixel_maz.ttf#32#2"; SDL_Rect buttonFramePos{ leftAlignX + 9, 6, 196, 82 }; auto buttonFrame = sheetFrame->addFrame("log map buttons"); buttonFrame->setSize(buttonFramePos); @@ -778,7 +851,7 @@ void Player::CharacterSheet_t::createCharacterSheet() // game timer { - const char* timerFont = "fonts/pixel_maz.ttf#14#2"; + const char* timerFont = "fonts/pixel_maz.ttf#32#2"; Uint32 timerTextColor = SDL_MapRGBA(mainsurface->format, 188, 154, 114, 255); Frame* timerFrame = sheetFrame->addFrame("game timer"); @@ -798,7 +871,7 @@ void Player::CharacterSheet_t::createCharacterSheet() // skills button { - const char* skillsFont = "fonts/pixel_maz.ttf#14#2"; + const char* skillsFont = "fonts/pixel_maz.ttf#32#2"; Frame* skillsFrame = sheetFrame->addFrame("skills button"); skillsFrame->setSize(SDL_Rect{ leftAlignX + 14, 270, 186, 42 }); auto skillsImg = skillsFrame->addImage(SDL_Rect{0, 0, skillsFrame->getSize().w, skillsFrame->getSize().h}, @@ -812,7 +885,7 @@ void Player::CharacterSheet_t::createCharacterSheet() } Frame* characterFrame = sheetFrame->addFrame("character info"); - const char* infoFont = "fonts/pixel_maz.ttf#14#2"; + const char* infoFont = "fonts/pixel_maz.ttf#32#2"; characterFrame->setSize(SDL_Rect{ leftAlignX, 150, bgWidth, 116}); const int infoTextHeight = 18; Uint32 infoTextColor = SDL_MapRGBA(mainsurface->format, 188, 154, 114, 255); @@ -892,7 +965,7 @@ void Player::CharacterSheet_t::createCharacterSheet() SDL_Rect textPos{ headingLeftX, iconPos.y, 40, iconPos.h }; Uint32 statTextColor = hudColors.characterSheetNeutral; - const char* statFont = "fonts/pixel_maz.ttf#14#2"; + const char* statFont = "fonts/pixel_maz.ttf#32#2"; textPos.y = iconPos.y + 1; statsFrame->addImage(iconPos, 0xFFFFFFFF, "images/ui/CharSheet/HUD_CharSheet_STR_00.png", "str icon"); { @@ -1076,7 +1149,7 @@ void Player::CharacterSheet_t::createCharacterSheet() SDL_Rect textPos{ headingLeftX, iconPos.y, 80, iconPos.h }; Uint32 statTextColor = hudColors.characterSheetNeutral; - const char* attributeFont = "fonts/pixel_maz.ttf#14#2"; + const char* attributeFont = "fonts/pixel_maz.ttf#32#2"; textPos.y = iconPos.y + 1; attributesFrame->addImage(iconPos, 0xFFFFFFFF, "images/ui/CharSheet/HUD_CharSheet_ATT_00.png", "atk icon"); { @@ -1614,7 +1687,6 @@ void Player::CharacterSheet_t::updateAttributes() real_t resistance = 0.0; if ( players[player.playernum]->entity ) { - players[player.playernum]->entity; resistance = 100 - 100 / (players[player.playernum]->entity->getMagicResistance() + 1); } snprintf(buf, sizeof(buf), "%.f%%", resistance); @@ -1782,7 +1854,7 @@ void Player::Hotbar_t::processHotbar() players[player.playernum]->camera_virtualWidth(), players[player.playernum]->camera_virtualHeight() }); - if ( nohud || !players[player.playernum]->isLocalPlayer() ) + if ( gamePaused || nohud || !players[player.playernum]->isLocalPlayer() ) { // hide hotbarFrame->setDisabled(true); @@ -2157,7 +2229,7 @@ void createPlayerInventorySlotFrameElements(Frame* slotFrame) unusableFrame->addImage(coloredBackgroundPos, SDL_MapRGBA(mainsurface->format, 64, 64, 64, 144), "images/system/white.png", "unusable item bg"); - static const char* qtyfont = "fonts/pixel_maz.ttf#14#2"; + static const char* qtyfont = "fonts/pixel_maz.ttf#32#2"; auto quantityFrame = slotFrame->addFrame("quantity frame"); quantityFrame->setSize(slotSize); quantityFrame->setHollow(true); @@ -2788,10 +2860,12 @@ void createInventoryTooltipFrame(const int player) interactFrame->addImage(SDL_Rect{ 6, optionHeight, interactWidth, 76 }, hudColors.itemContextMenuOptionSelectedImg, "images/system/whitecurve.png", "interact selected highlight"); + const char* interactFont = "fonts/pixel_maz.ttf#32#2"; + auto interactText = interactFrame->addField("interact text", 32); interactText->setText(language[4040]); interactText->setSize(SDL_Rect{ 0, 2, 0, topBackgroundHeight }); - interactText->setFont("fonts/pixel_maz.ttf#14#2"); + interactText->setFont(interactFont); interactText->setHJustify(Field::justify_t::CENTER); interactText->setVJustify(Field::justify_t::CENTER); interactText->setColor(hudColors.itemContextMenuHeadingText); @@ -2817,7 +2891,7 @@ void createInventoryTooltipFrame(const int player) textAlignX, textAlignY, textWidth, textHeight }); - interactText->setFont("fonts/pixel_maz.ttf#14#2"); + interactText->setFont(interactFont); interactText->setHJustify(Field::justify_t::LEFT); interactText->setVJustify(Field::justify_t::CENTER); interactText->setColor(textColor); @@ -2835,7 +2909,7 @@ void createInventoryTooltipFrame(const int player) textAlignX, textAlignY, textWidth, textHeight }); - interactText->setFont("fonts/pixel_maz.ttf#14#2"); + interactText->setFont(interactFont); interactText->setHJustify(Field::justify_t::LEFT); interactText->setVJustify(Field::justify_t::CENTER); interactText->setColor(textColor); @@ -2853,7 +2927,7 @@ void createInventoryTooltipFrame(const int player) textAlignX, textAlignY, textWidth, textHeight }); - interactText->setFont("fonts/pixel_maz.ttf#14#2"); + interactText->setFont(interactFont); interactText->setHJustify(Field::justify_t::LEFT); interactText->setVJustify(Field::justify_t::CENTER); interactText->setColor(textColor); @@ -2871,7 +2945,7 @@ void createInventoryTooltipFrame(const int player) textAlignX, textAlignY, textWidth, textHeight }); - interactText->setFont("fonts/pixel_maz.ttf#14#2"); + interactText->setFont(interactFont); interactText->setHJustify(Field::justify_t::LEFT); interactText->setVJustify(Field::justify_t::CENTER); interactText->setColor(textColor); @@ -2889,7 +2963,7 @@ void createInventoryTooltipFrame(const int player) textAlignX, textAlignY, textWidth, textHeight }); - interactText->setFont("fonts/pixel_maz.ttf#14#2"); + interactText->setFont(interactFont); interactText->setHJustify(Field::justify_t::LEFT); interactText->setVJustify(Field::justify_t::CENTER); interactText->setColor(textColor); @@ -2951,7 +3025,7 @@ void createInventoryTooltipFrame(const int player) const int textHeight = glyphSize + 8; Uint32 promptTextColor = SDL_MapRGBA(mainsurface->format, 188, 154, 114, 255); - + const char * promptFont = "fonts/pixel_maz.ttf#32#2"; int textAlignY = interactGlyph1->pos.y - 4; int textAlignXRightJustify = interactGlyph1->pos.x - 6 - textWidth; auto promptText = promptFrame->addField("txt 1", 32); @@ -2960,7 +3034,7 @@ void createInventoryTooltipFrame(const int player) textAlignXRightJustify, textAlignY, textWidth, textHeight }); - promptText->setFont("fonts/pixel_maz.ttf#14#2"); + promptText->setFont(promptFont); promptText->setHJustify(Field::justify_t::RIGHT); promptText->setVJustify(Field::justify_t::CENTER); promptText->setColor(promptTextColor); @@ -2973,7 +3047,7 @@ void createInventoryTooltipFrame(const int player) textAlignXRightJustify, textAlignY, textWidth, textHeight }); - promptText->setFont("fonts/pixel_maz.ttf#14#2"); + promptText->setFont(promptFont); promptText->setHJustify(Field::justify_t::RIGHT); promptText->setVJustify(Field::justify_t::CENTER); promptText->setColor(promptTextColor); @@ -2986,7 +3060,7 @@ void createInventoryTooltipFrame(const int player) textAlignXLeftJustify, textAlignY, textWidth, textHeight }); - promptText->setFont("fonts/pixel_maz.ttf#14#2"); + promptText->setFont(promptFont); promptText->setHJustify(Field::justify_t::LEFT); promptText->setVJustify(Field::justify_t::CENTER); promptText->setColor(promptTextColor); @@ -2999,7 +3073,7 @@ void createInventoryTooltipFrame(const int player) textAlignXLeftJustify, textAlignY, textWidth, textHeight }); - promptText->setFont("fonts/pixel_maz.ttf#14#2"); + promptText->setFont(promptFont); promptText->setHJustify(Field::justify_t::LEFT); promptText->setVJustify(Field::justify_t::CENTER); promptText->setColor(promptTextColor); @@ -3042,7 +3116,8 @@ void drawCharacterPreview(const int player, SDL_Rect pos) camera_charsheet.vang = PI / 20; camera_charsheet.winx = pos.x; - camera_charsheet.winy = pos.y; + // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 + camera_charsheet.winy = pos.y + (yres - Frame::virtualScreenY); //camera_charsheet.winx = x1 + 8; //camera_charsheet.winy = y1 + 8; @@ -3127,6 +3202,223 @@ void drawCharacterPreview(const int player, SDL_Rect pos) fov = ofov; } +Player::SkillSheet_t::SkillSheetData_t Player::SkillSheet_t::skillSheetData; +void Player::SkillSheet_t::loadSkillSheetJSON() +{ + if ( !PHYSFS_getRealDir("/data/skillsheet_entries.json") ) + { + printlog("[JSON]: Error: Could not find file: data/skillsheet_entries.json"); + } + else + { + std::string inputPath = PHYSFS_getRealDir("/data/skillsheet_entries.json"); + inputPath.append("/data/skillsheet_entries.json"); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not open json file %s", inputPath.c_str()); + } + else + { + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf)); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.HasMember("version") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + } + else + { + if ( d.HasMember("skills") ) + { + auto& allEntries = skillSheetData.skillEntries; + allEntries.clear(); + for ( rapidjson::Value::ConstValueIterator itr = d["skills"].Begin(); + itr != d["skills"].End(); ++itr ) + { + allEntries.push_back(SkillSheetData_t::SkillEntry_t()); + auto& entry = allEntries[allEntries.size() - 1]; + if ( (*itr).HasMember("name") ) + { + entry.name = (*itr)["name"].GetString(); + } + if ( (*itr).HasMember("id") ) + { + entry.skillId = (*itr)["id"].GetInt(); + } + if ( (*itr).HasMember("icon_base_path") ) + { + entry.skillIconPath = (*itr)["icon_base_path"].GetString(); + } + if ( (*itr).HasMember("icon_legend_path") ) + { + entry.skillIconPathLegend = (*itr)["icon_legend_path"].GetString(); + } + if ( (*itr).HasMember("icon_stat_path") ) + { + entry.statIconPath = (*itr)["icon_stat_path"].GetString(); + } + if ( (*itr).HasMember("description") ) + { + entry.description = (*itr)["description"].GetString(); + } + if ( (*itr).HasMember("legend_text") ) + { + entry.legendaryDescription = (*itr)["legend_text"].GetString(); + } + if ( (*itr).HasMember("effects") ) + { + for ( rapidjson::Value::ConstValueIterator eff_itr = (*itr)["effects"].Begin(); eff_itr != (*itr)["effects"].End(); ++eff_itr ) + { + entry.effects.push_back(SkillSheetData_t::SkillEntry_t::SkillEffect_t()); + auto& effect = entry.effects[entry.effects.size() - 1]; + effect.tag = (*eff_itr)["tag"].GetString(); + effect.title = (*eff_itr)["title"].GetString(); + effect.rawValue = (*eff_itr)["value"].GetString(); + } + } + if ( (*itr).HasMember("effects_position") ) + { + for ( rapidjson::Value::ConstMemberIterator effPos_itr = (*itr)["effects_position"].MemberBegin(); + effPos_itr != (*itr)["effects_position"].MemberEnd(); ++effPos_itr ) + { + std::string memberName = effPos_itr->name.GetString(); + if ( memberName == "effect_start_offset_x" ) + { + entry.effectStartOffsetX = effPos_itr->value.GetInt(); + } + else if ( memberName == "effect_background_offset_x" ) + { + entry.effectBackgroundOffsetX = effPos_itr->value.GetInt(); + } + else if ( memberName == "effect_background_width" ) + { + entry.effectBackgroundWidth = effPos_itr->value.GetInt(); + } + } + } + } + } + if ( d.HasMember("window_scaling") ) + { + if ( d["window_scaling"].HasMember("standard_scale_modifier_x") ) + { + windowHeightScaleX = d["window_scaling"]["standard_scale_modifier_x"].GetDouble(); + } + if ( d["window_scaling"].HasMember("standard_scale_modifier_y") ) + { + windowHeightScaleY = d["window_scaling"]["standard_scale_modifier_y"].GetDouble(); + } + if ( d["window_scaling"].HasMember("compact_scale_modifier_x") ) + { + windowCompactHeightScaleX = d["window_scaling"]["compact_scale_modifier_x"].GetDouble(); + } + if ( d["window_scaling"].HasMember("compact_scale_modifier_y") ) + { + windowCompactHeightScaleY = d["window_scaling"]["compact_scale_modifier_y"].GetDouble(); + } + } + if ( d.HasMember("skill_select_images") ) + { + if ( d["skill_select_images"].HasMember("highlight_left") ) + { + skillSheetData.highlightSkillImg = d["skill_select_images"]["highlight_left"].GetString(); + } + if ( d["skill_select_images"].HasMember("selected_left") ) + { + skillSheetData.selectSkillImg = d["skill_select_images"]["selected_left"].GetString(); + } + if ( d["skill_select_images"].HasMember("highlight_right") ) + { + skillSheetData.highlightSkillImg_Right = d["skill_select_images"]["highlight_right"].GetString(); + } + if ( d["skill_select_images"].HasMember("selected_right") ) + { + skillSheetData.selectSkillImg_Right = d["skill_select_images"]["selected_right"].GetString(); + } + } + if ( d.HasMember("skill_background_icons") ) + { + if ( d["skill_background_icons"].HasMember("default") ) + { + skillSheetData.iconBgPathDefault = d["skill_background_icons"]["default"].GetString(); + } + if ( d["skill_background_icons"].HasMember("default_selected") ) + { + skillSheetData.iconBgSelectedPathDefault = d["skill_background_icons"]["default_selected"].GetString(); + } + if ( d["skill_background_icons"].HasMember("novice") ) + { + skillSheetData.iconBgPathNovice = d["skill_background_icons"]["novice"].GetString(); + } + if ( d["skill_background_icons"].HasMember("novice_selected") ) + { + skillSheetData.iconBgSelectedPathNovice = d["skill_background_icons"]["novice_selected"].GetString(); + } + if ( d["skill_background_icons"].HasMember("expert") ) + { + skillSheetData.iconBgPathExpert = d["skill_background_icons"]["expert"].GetString(); + } + if ( d["skill_background_icons"].HasMember("expert_selected") ) + { + skillSheetData.iconBgSelectedPathExpert = d["skill_background_icons"]["expert_selected"].GetString(); + } + if ( d["skill_background_icons"].HasMember("legend") ) + { + skillSheetData.iconBgPathLegend = d["skill_background_icons"]["legend"].GetString(); + } + if ( d["skill_background_icons"].HasMember("legend_selected") ) + { + skillSheetData.iconBgSelectedPathLegend = d["skill_background_icons"]["legend_selected"].GetString(); + } + } + if ( d.HasMember("colors") ) + { + if ( d["colors"].HasMember("default") ) + { + skillSheetData.defaultTextColor = SDL_MapRGBA(mainsurface->format, + d["colors"]["default"]["r"].GetInt(), + d["colors"]["default"]["g"].GetInt(), + d["colors"]["default"]["b"].GetInt(), + d["colors"]["default"]["a"].GetInt()); + } + if ( d["colors"].HasMember("novice") ) + { + skillSheetData.noviceTextColor = SDL_MapRGBA(mainsurface->format, + d["colors"]["novice"]["r"].GetInt(), + d["colors"]["novice"]["g"].GetInt(), + d["colors"]["novice"]["b"].GetInt(), + d["colors"]["novice"]["a"].GetInt()); + } + if ( d["colors"].HasMember("expert") ) + { + skillSheetData.expertTextColor = SDL_MapRGBA(mainsurface->format, + d["colors"]["expert"]["r"].GetInt(), + d["colors"]["expert"]["g"].GetInt(), + d["colors"]["expert"]["b"].GetInt(), + d["colors"]["expert"]["a"].GetInt()); + } + if ( d["colors"].HasMember("legend") ) + { + skillSheetData.legendTextColor = SDL_MapRGBA(mainsurface->format, + d["colors"]["legend"]["r"].GetInt(), + d["colors"]["legend"]["g"].GetInt(), + d["colors"]["legend"]["b"].GetInt(), + d["colors"]["legend"]["a"].GetInt()); + } + } + printlog("[JSON]: Successfully read json file %s", inputPath.c_str()); + } + } + } +} + void loadHUDSettingsJSON() { if ( !PHYSFS_getRealDir("/data/HUD_settings.json") ) @@ -3419,12 +3711,12 @@ void createPlayerInventory(const int player) charSize.w -= 2 * (inventorySlotSize + baseSlotOffsetX + 4); charFrame->setSize(charSize); - charFrame->setDrawCallback([](Widget& widget, SDL_Rect pos) { + charFrame->setDrawCallback([](const Widget& widget, SDL_Rect pos) { drawCharacterPreview(widget.getOwner(), pos); }); - //charFrame->addImage(SDL_Rect{ 0, 0, charSize.w, charSize.h }, - // SDL_MapRGBA(mainsurface->format, 255, 255, 255, 255), - // "images/system/white.png", "inventory character preview bg"); + /*charFrame->addImage(SDL_Rect{ 0, 0, charSize.w, charSize.h }, + SDL_MapRGBA(mainsurface->format, 255, 255, 255, 255), + "images/system/white.png", "inventory character preview bg");*/ } /*auto selectedFrame = dollSlotsFrame->addFrame("paperdoll selected item"); @@ -3617,7 +3909,8 @@ void Player::Inventory_t::updateItemContextMenu() if ( auto interactText = interactFrame->findField("interact text") ) { interactText->setColor(hudColors.itemContextMenuHeadingText); - if ( auto textGet = Text::get(interactText->getText(), interactText->getFont()) ) + if ( auto textGet = Text::get(interactText->getText(), interactText->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { maxWidth = textGet->getWidth(); } @@ -3666,7 +3959,8 @@ void Player::Inventory_t::updateItemContextMenu() }*/ txt->setText(getContextMenuLangEntry(player.playernum, promptType, *item)); - if ( auto textGet = Text::get(txt->getText(), txt->getFont()) ) + if ( auto textGet = Text::get(txt->getText(), txt->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) { maxWidth = std::max(textGet->getWidth(), maxWidth); @@ -4768,7 +5062,7 @@ void Player::HUD_t::updateXPBar() xpProgressEndCap->pos.x = xpProgress->pos.x + xpProgress->pos.w; } -SDL_Surface* blitEnemyBar(const int player) +SDL_Surface* blitEnemyBar(const int player, SDL_Surface* statusEffectSprite) { Frame* frame = players[player]->hud.enemyBarFrame; if ( !frame || !players[player]->isLocalPlayer() ) @@ -4790,7 +5084,15 @@ SDL_Surface* blitEnemyBar(const int player) auto skullFrame = frame->findFrame("skull frame"); int totalWidth = baseBg->pos.x + baseBg->pos.w + baseEndCap->pos.w; - SDL_Surface* sprite = SDL_CreateRGBSurface(0, totalWidth, frame->getSize().h, 32, + int totalHeight = frame->getSize().h; + int statusEffectOffsetY = 0; + if ( statusEffectSprite ) + { + statusEffectOffsetY = statusEffectSprite->h; + totalHeight += statusEffectOffsetY; + } + + SDL_Surface* sprite = SDL_CreateRGBSurface(0, totalWidth, totalHeight, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); for ( auto& img : frame->getImages() ) { @@ -4800,6 +5102,7 @@ SDL_Surface* blitEnemyBar(const int player) SDL_SetSurfaceAlphaMod(srcSurf, a * frameOpacity); SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE); SDL_Rect pos = img->pos; + pos.y += statusEffectOffsetY; SDL_BlitScaled(srcSurf, nullptr, sprite, &pos); } for ( auto& img : dmgFrame->getImages() ) @@ -4812,6 +5115,7 @@ SDL_Surface* blitEnemyBar(const int player) SDL_Rect pos = img->pos; pos.x += dmgFrame->getSize().x; pos.y += dmgFrame->getSize().y; + pos.y += statusEffectOffsetY; SDL_BlitScaled(srcSurf, nullptr, sprite, &pos); } for ( auto& img : foregroundFrame->getImages() ) @@ -4824,6 +5128,7 @@ SDL_Surface* blitEnemyBar(const int player) SDL_Rect pos = img->pos; pos.x += foregroundFrame->getSize().x; pos.y += foregroundFrame->getSize().y; + pos.y += statusEffectOffsetY; SDL_BlitScaled(srcSurf, nullptr, sprite, &pos); } for ( auto& img : skullFrame->getImages() ) @@ -4835,22 +5140,141 @@ SDL_Surface* blitEnemyBar(const int player) SDL_Rect pos = img->pos; pos.x += skullFrame->getSize().x; pos.y += skullFrame->getSize().y; + pos.y += statusEffectOffsetY; SDL_BlitScaled(srcSurf, nullptr, sprite, &pos); } for ( auto& txt : frame->getFields() ) { - auto textGet = Text::get(txt->getText(), txt->getFont()); + auto textGet = Text::get(txt->getText(), txt->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); SDL_Surface* txtSurf = const_cast(textGet->getSurf()); SDL_Rect pos; pos.w = textGet->getWidth(); pos.h = textGet->getHeight(); pos.x = sprite->w / 2 - pos.w / 2; - pos.y = sprite->h / 2 - pos.h / 2; + pos.y = frame->getSize().h / 2 - pos.h / 2; + pos.y += statusEffectOffsetY; Uint8 r, g, b, a; SDL_GetRGBA(txt->getColor(), mainsurface->format, &r, &g, &b, &a); SDL_SetSurfaceAlphaMod(txtSurf, a * frameOpacity); SDL_BlitSurface(txtSurf, nullptr, sprite, &pos); } + if ( statusEffectSprite ) + { + SDL_Rect pos{0, 0, statusEffectSprite->w, statusEffectSprite->h}; + SDL_BlitSurface(statusEffectSprite, nullptr, sprite, &pos); + } + return sprite; +} + +SDL_Surface* EnemyHPDamageBarHandler::EnemyHPDetails::blitEnemyBarStatusEffects(const int player) +{ + Entity* entity = uidToEntity(enemy_uid); + if ( entity && (entity->behavior != &actPlayer && entity->behavior != &actMonster) ) + { + return nullptr; + } + Frame* frame = players[player]->hud.enemyBarFrame; + if ( !frame || !players[player]->isLocalPlayer() ) + { + return nullptr; + } + if ( enemy_statusEffects1 == 0 && enemy_statusEffects2 == 0 ) + { + return nullptr; + } + auto baseBg = frame->findImage("base img"); + auto baseEndCap = frame->findImage("base img endcap"); + real_t frameOpacity = frame->getOpacity() / 100.0; + const int maxWidth = baseBg->pos.x + baseBg->pos.w + baseEndCap->pos.w; + const int iconHeight = 32; + const int iconWidth = 32; + + SDL_Surface* sprite = SDL_CreateRGBSurface(0, maxWidth, iconHeight + 2, 32, + 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + + int playernum = -1; + if ( entity && entity->behavior == &actPlayer ) + { + playernum = entity->skill[2]; + } + + int currentX = (maxWidth / 2); + bool anyStatusEffect = false; + + std::vector> statusEffectIcons; + if ( enemy_statusEffects1 != 0 ) + { + for ( int i = 0; i < 32; ++i ) + { + if ( (enemy_statusEffects1 & (1 << i)) != 0 ) + { + if ( SDL_Surface* srcSurf = getStatusEffectSprite(entity, + (entity ? entity->getStats() : nullptr), i, playernum) ) + { + bool blinking = false; + if ( (enemy_statusEffectsLowDuration1 & (1 << i)) != 0 ) + { + blinking = true; + } + statusEffectIcons.push_back(std::make_pair(srcSurf, blinking)); + } + } + } + } + if ( enemy_statusEffects2 != 0 ) + { + for ( int i = 0; i < 32; ++i ) + { + if ( (enemy_statusEffects2 & (1 << i)) != 0 ) + { + if ( SDL_Surface* srcSurf = getStatusEffectSprite(entity, + (entity ? entity->getStats() : nullptr), i + 32, playernum) ) + { + bool blinking = false; + if ( (enemy_statusEffectsLowDuration2 & (1 << i)) != 0 ) + { + blinking = true; + } + statusEffectIcons.push_back(std::make_pair(srcSurf, blinking)); + } + } + } + } + + const int numIcons = statusEffectIcons.size(); + const int iconTotalWidth = iconWidth + 2; + if ( numIcons % 2 == 1 ) // odd numbered + { + currentX -= ((iconTotalWidth) * (numIcons / 2)) + (iconTotalWidth / 2); + } + else + { + currentX -= ((iconTotalWidth) * (numIcons / 2)); + } + + for ( auto& icon : statusEffectIcons ) + { + anyStatusEffect = true; + int tickModifier = ticks % 25; + real_t alpha = 255 * frameOpacity; + if ( tickModifier >= 12 && icon.second ) + { + alpha = 0; + } + SDL_SetSurfaceAlphaMod(icon.first, alpha); + //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE); + SDL_Rect pos{ currentX, 0, iconWidth, iconHeight }; + SDL_BlitScaled(icon.first, nullptr, sprite, &pos); + currentX += (iconWidth + 2); + } + + if ( !anyStatusEffect ) + { + SDL_FreeSurface(sprite); + sprite = nullptr; + } + return sprite; } @@ -5198,17 +5622,19 @@ void Player::HUD_t::updateEnemyBar2(Frame* whichFrame, void* enemyHPDetails) if ( damageNumber >= 0 ) { char buf[128] = ""; - itoa(damageNumber, buf, 10); + snprintf(buf, sizeof(buf), "%d", damageNumber); dmgText->setText(buf); dmgText->setDisabled(false); SDL_Rect txtPos = dmgText->getSize(); SDL_Rect barPos = foregroundFrame->getAbsoluteSize(); //txtPos.x = barPos.x + baseBg->pos.x/* + baseBg->pos.w*/; //txtPos.y = barPos.y - 30; + auto text = Text::get(dmgText->getText(), dmgText->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); txtPos.x = 30 + player.camera_virtualWidth() / 2; txtPos.y = -50 + player.camera_virtualHeight() / 2; - txtPos.w = Text::get(dmgText->getText(), dmgText->getFont())->getWidth(); - txtPos.h = Text::get(dmgText->getText(), dmgText->getFont())->getHeight(); + txtPos.w = text->getWidth(); + txtPos.h = text->getHeight(); dmgText->setSize(txtPos); hudDamageTextVelocityX = 1.0; hudDamageTextVelocityY = 3.0; @@ -5249,8 +5675,14 @@ void Player::HUD_t::updateEnemyBar2(Frame* whichFrame, void* enemyHPDetails) SDL_FreeSurface(enemyDetails->worldSurfaceSprite); enemyDetails->worldSurfaceSprite = nullptr; } + if ( enemyDetails->worldSurfaceSpriteStatusEffects ) + { + SDL_FreeSurface(enemyDetails->worldSurfaceSpriteStatusEffects); + enemyDetails->worldSurfaceSpriteStatusEffects = nullptr; + } - enemyDetails->worldSurfaceSprite = blitEnemyBar(player.playernum); + enemyDetails->worldSurfaceSpriteStatusEffects = enemyDetails->blitEnemyBarStatusEffects(player.playernum); + enemyDetails->worldSurfaceSprite = blitEnemyBar(player.playernum, enemyDetails->worldSurfaceSpriteStatusEffects); enemyDetails->worldTexture = new TempTexture(); enemyDetails->worldTexture->load(enemyDetails->worldSurfaceSprite, false, true); @@ -5567,17 +5999,19 @@ void Player::HUD_t::updateEnemyBar(Frame* whichFrame) if ( damageNumber >= 0 ) { char buf[128] = ""; - itoa(damageNumber, buf, 10); + snprintf(buf, sizeof(buf), "%d", damageNumber); dmgText->setText(buf); dmgText->setDisabled(false); SDL_Rect txtPos = dmgText->getSize(); SDL_Rect barPos = foregroundFrame->getAbsoluteSize(); //txtPos.x = barPos.x + baseBg->pos.x/* + baseBg->pos.w*/; //txtPos.y = barPos.y - 30; + auto text = Text::get(dmgText->getText(), dmgText->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)); txtPos.x = 30 + player.camera_virtualWidth() / 2; txtPos.y = -50 + player.camera_virtualHeight() / 2; - txtPos.w = Text::get(dmgText->getText(), dmgText->getFont())->getWidth(); - txtPos.h = Text::get(dmgText->getText(), dmgText->getFont())->getHeight(); + txtPos.w = text->getWidth(); + txtPos.h = text->getHeight(); dmgText->setSize(txtPos); hudDamageTextVelocityX = 1.0; hudDamageTextVelocityY = 3.0; @@ -5628,7 +6062,7 @@ void Player::HUD_t::updateEnemyBar(Frame* whichFrame) SDL_FreeSurface(enemyDetails->worldSurfaceSprite); enemyDetails->worldSurfaceSprite = nullptr; } - enemyDetails->worldSurfaceSprite = blitEnemyBar(player.playernum); + enemyDetails->worldSurfaceSprite = blitEnemyBar(player.playernum, enemyDetails->worldSurfaceSpriteStatusEffects); enemyDetails->worldTexture = new TempTexture(); enemyDetails->worldTexture->load(enemyDetails->worldSurfaceSprite, false, true); } @@ -5954,24 +6388,24 @@ void Player::Hotbar_t::updateHotbar() if ( !player.shootmode ) { - if ( Input::inputs[player.shootmode].binaryToggle("HotbarFacebarCancel") ) + if ( Input::inputs[player.playernum].binaryToggle("HotbarFacebarCancel") ) { - Input::inputs[player.shootmode].consumeBinaryToggle("HotbarFacebarCancel"); + Input::inputs[player.playernum].consumeBinaryToggle("HotbarFacebarCancel"); } - if ( Input::inputs[player.shootmode].binaryToggle("HotbarFacebarLeft") ) + if ( Input::inputs[player.playernum].binaryToggle("HotbarFacebarLeft") ) { - Input::inputs[player.shootmode].consumeBinaryToggle("HotbarFacebarLeft"); - Input::inputs[player.shootmode].consumeBinaryReleaseToggle("HotbarFacebarLeft"); + Input::inputs[player.playernum].consumeBinaryToggle("HotbarFacebarLeft"); + Input::inputs[player.playernum].consumeBinaryReleaseToggle("HotbarFacebarLeft"); } - if ( Input::inputs[player.shootmode].binaryToggle("HotbarFacebarUp") ) + if ( Input::inputs[player.playernum].binaryToggle("HotbarFacebarUp") ) { - Input::inputs[player.shootmode].consumeBinaryToggle("HotbarFacebarUp"); - Input::inputs[player.shootmode].consumeBinaryReleaseToggle("HotbarFacebarUp"); + Input::inputs[player.playernum].consumeBinaryToggle("HotbarFacebarUp"); + Input::inputs[player.playernum].consumeBinaryReleaseToggle("HotbarFacebarUp"); } - if ( Input::inputs[player.shootmode].binaryToggle("HotbarFacebarRight") ) + if ( Input::inputs[player.playernum].binaryToggle("HotbarFacebarRight") ) { - Input::inputs[player.shootmode].consumeBinaryToggle("HotbarFacebarRight"); - Input::inputs[player.shootmode].consumeBinaryReleaseToggle("HotbarFacebarRight"); + Input::inputs[player.playernum].consumeBinaryToggle("HotbarFacebarRight"); + Input::inputs[player.playernum].consumeBinaryReleaseToggle("HotbarFacebarRight"); } faceMenuButtonHeld = FaceMenuGroup::GROUP_NONE; } @@ -6335,4 +6769,2326 @@ void doFrames() { (void)gui->process(); gui->draw(); } +} + +real_t Player::SkillSheet_t::windowCompactHeightScaleX = 0.0; +real_t Player::SkillSheet_t::windowCompactHeightScaleY = 0.0; +real_t Player::SkillSheet_t::windowHeightScaleX = 0.0; +real_t Player::SkillSheet_t::windowHeightScaleY = 0.0; + +void Player::SkillSheet_t::createSkillSheet() +{ + if ( skillFrame ) + { + return; + } + + char name[32]; + snprintf(name, sizeof(name), "player skills %d", player.playernum); + Frame* frame = gui->addFrame(name); + skillFrame = frame; + frame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(), + players[player.playernum]->camera_virtualy1(), + players[player.playernum]->camera_virtualWidth(), + players[player.playernum]->camera_virtualHeight() }); + frame->setHollow(true); + frame->setBorder(0); + frame->setOwner(player.playernum); + frame->setInheritParentFrameOpacity(false); + + auto fade = skillFrame->addImage( + SDL_Rect{ 0, 0, skillFrame->getSize().w, skillFrame->getSize().h }, + 0, "images/system/white.png", "fade img"); + Uint8 r, g, b, a; + SDL_GetRGBA(fade->color, mainsurface->format, &r, &g, &b, &a); + a = 0; + fade->color = SDL_MapRGBA(mainsurface->format, r, g, b, a); + + Frame* skillBackground = frame->addFrame("skills frame"); + const int width = 684; + const int height = 404; + skillBackground->setSize(SDL_Rect{ frame->getSize().x, frame->getSize().y, width, height }); + /*auto bgImg = skillBackground->addImage(SDL_Rect{ 0, 0, width, height }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_Window_02.png", "skills img");*/ + + Frame* allSkillEntriesLeft = skillBackground->addFrame("skill entries frame left"); + Frame* allSkillEntriesRight = skillBackground->addFrame("skill entries frame right"); + //allSkillEntries->setSize(SDL_Rect{ 0, 0, skillBackground->getSize().w, skillBackground->getSize().h }); + + SDL_Rect allSkillEntriesPosLeft{ 0, 0, 182, skillBackground->getSize().h }; + SDL_Rect allSkillEntriesPosRight{ skillBackground->getSize().w - 182, 0, 182, skillBackground->getSize().h }; + allSkillEntriesLeft->setSize(allSkillEntriesPosLeft); + allSkillEntriesRight->setSize(allSkillEntriesPosRight); + + allSkillEntriesLeft->addImage(SDL_Rect{ 0, 12, 182, 376 }, + 0xFFFFFFFF, "images/ui/SkillSheet/UI_Skills_Window_Left_03.png", "bg wing left"); + allSkillEntriesRight->addImage(SDL_Rect{ 0, 12, 182, 376 }, + 0xFFFFFFFF, "images/ui/SkillSheet/UI_Skills_Window_Right_03.png", "bg wing right"); + + auto skillBackgroundImagesFrame = skillBackground->addFrame("skills bg images"); + skillBackgroundImagesFrame->setSize(SDL_Rect{ 0, 0, skillBackground->getSize().w, skillBackground->getSize().h }); + { + Uint32 color = makeColor(255, 255, 255, 255); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_TL_04.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_TR_04.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_T_04.png", skillsheetEffectBackgroundImages[TOP].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_L_03.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_R_03.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + makeColor(0, 0, 0, 255), "images/system/white.png", skillsheetEffectBackgroundImages[MIDDLE].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_BL_03.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_BR_03.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str()); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_Window_B_03.png", skillsheetEffectBackgroundImages[BOTTOM].c_str()); + imageSetWidthHeight9x9(skillBackgroundImagesFrame, skillsheetEffectBackgroundImages); + + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 78, 18 }, + color, "images/ui/SkillSheet/UI_Skills_Window_Flourish_T.png", "flourish top"); + skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 34, 14 }, + color, "images/ui/SkillSheet/UI_Skills_Window_Flourish_B.png", "flourish bottom"); + } + + const int skillEntryStartY = 38; + SDL_Rect skillEntryPos{ 0, skillEntryStartY, 182, 40 }; + SDL_Rect skillSelectorPos{ 0, 2, 146, 32 }; + const char* boldFont = "fonts/pixel_maz_multiline.ttf#16#2"; + const char* numberFont = "fonts/pixelmix.ttf#16"; + const char* titleFont = "fonts/pixel_maz_multiline.ttf#24#2"; + const char* descFont = "fonts/pixel_maz_multiline.ttf#16#2"; + for ( int i = 0; i < 8; ++i ) + { + char skillname[32]; + snprintf(skillname, sizeof(skillname), "skill %d", i); + Frame* entry = allSkillEntriesRight->addFrame(skillname); + entry->setSize(skillEntryPos); + entry->addImage(skillSelectorPos, 0xFFFFFFFF, "images/ui/SkillSheet/UI_Skills_SkillSelector_00.png", "selector img"); + SDL_Rect imgBgPos{ skillEntryPos.w - 36, 0, 36, 36 }; + entry->addImage(imgBgPos, 0xFFFFFFFF, "images/ui/SkillSheet/UI_Skills_Icons_BG00_00.png", "skill icon bg"); + SDL_Rect imgFgPos{ imgBgPos.x + 6, imgBgPos.y + 6, 24, 24 }; + entry->addImage(imgFgPos, 0xFFFFFFFF, "", "skill icon fg"); + SDL_Rect statPos{ 10, 6, 24, 24 }; + auto statIcon = entry->addImage(statPos, 0xFFFFFFFF, "", "stat icon"); + if ( i < skillSheetData.skillEntries.size() ) + { + statIcon->path = skillSheetData.skillEntries[i].statIconPath; + } + skillEntryPos.y += skillEntryPos.h; + + SDL_Rect profNamePos{ statPos.x + statPos.w - 32, 4, 98, 28 }; + Field* profName = entry->addField("skill name", 64); + profName->setSize(profNamePos); + profName->setHJustify(Field::justify_t::RIGHT); + profName->setVJustify(Field::justify_t::CENTER); + profName->setFont(boldFont); + profName->setColor(skillSheetData.defaultTextColor); + if ( i < skillSheetData.skillEntries.size() ) + { + profName->setText(skillSheetData.skillEntries[i].name.c_str()); + } + else + { + profName->setText("Default"); + } + + SDL_Rect profLevelPos{ profNamePos.x + profNamePos.w + 8 - 2, profNamePos.y, 34, 28 }; + Field* profLevel = entry->addField("skill level", 16); + profLevel->setSize(profLevelPos); + profLevel->setHJustify(Field::justify_t::RIGHT); + profLevel->setVJustify(Field::justify_t::CENTER); + profLevel->setFont(numberFont); + profLevel->setColor(skillSheetData.defaultTextColor); + profLevel->setText("0"); + } + + skillEntryPos.x = 0; + skillEntryPos.y = skillEntryStartY; + skillSelectorPos.x = 36; + + for ( int i = 8; i < 16; ++i ) + { + char skillname[32]; + snprintf(skillname, sizeof(skillname), "skill %d", i); + Frame* entry = allSkillEntriesLeft->addFrame(skillname); + entry->setSize(skillEntryPos); + entry->addImage(skillSelectorPos, 0xFFFFFFFF, "images/ui/SkillSheet/UI_Skills_SkillSelectorR_00.png", "selector img"); + SDL_Rect imgBgPos{ 0, 0, 36, 36 }; + entry->addImage(imgBgPos, 0xFFFFFFFF, "images/ui/SkillSheet/UI_Skills_Icons_BG00_00.png", "skill icon bg"); + SDL_Rect imgFgPos{ imgBgPos.x + 6, imgBgPos.y + 6, 24, 24 }; + entry->addImage(imgFgPos, 0xFFFFFFFF, "images/ui/SkillSheet/icons/Alchemy01.png", "skill icon fg"); + SDL_Rect statPos{ skillEntryPos.w - 10 - 24, 6, 24, 24 }; + auto statIcon = entry->addImage(statPos, 0xFFFFFFFF, "", "stat icon"); + if ( i < skillSheetData.skillEntries.size() ) + { + statIcon->path = skillSheetData.skillEntries[i].statIconPath; + } + skillEntryPos.y += skillEntryPos.h; + + SDL_Rect profNamePos{ statPos.x - 98 + 32, 4, 98, 28 }; + Field* profName = entry->addField("skill name", 64); + profName->setSize(profNamePos); + profName->setHJustify(Field::justify_t::LEFT); + profName->setVJustify(Field::justify_t::CENTER); + profName->setFont(boldFont); + profName->setColor(skillSheetData.defaultTextColor); + if ( i < skillSheetData.skillEntries.size() ) + { + profName->setText(skillSheetData.skillEntries[i].name.c_str()); + } + else + { + profName->setText("Default"); + } + + SDL_Rect profLevelPos{ profNamePos.x - 12 - 32, profNamePos.y, 34, 28 }; + Field* profLevel = entry->addField("skill level", 16); + profLevel->setSize(profLevelPos); + profLevel->setHJustify(Field::justify_t::RIGHT); + profLevel->setVJustify(Field::justify_t::CENTER); + profLevel->setFont(numberFont); + profLevel->setColor(skillSheetData.defaultTextColor); + profLevel->setText("0"); + } + + SDL_Rect skillTitlePos{ skillBackground->getSize().w / 2 - 320 / 2, 14, 320, 40 }; + auto skillTitleTxt = skillBackground->addField("skill title txt", 64); + skillTitleTxt->setHJustify(Field::justify_t::CENTER); + skillTitleTxt->setVJustify(Field::justify_t::CENTER); + skillTitleTxt->setText(""); + skillTitleTxt->setSize(skillTitlePos); + skillTitleTxt->setFont(titleFont); + skillTitleTxt->setOntop(true); + skillTitleTxt->setColor(makeColor(201, 162, 100, 255)); + + SDL_Rect descPos{ 0, 54, 320, 324 }; + descPos.x = skillBackground->getSize().w / 2 - descPos.w / 2; + auto skillDescriptionFrame = skillBackground->addFrame("skill desc frame"); + skillDescriptionFrame->setSize(descPos); + + /*auto debugRect = skillDescriptionFrame->addImage(SDL_Rect{ 0, 0, descPos.w, descPos.h }, 0xFFFFFFFF, + "images/system/white.png", "")*/; + + auto slider = skillDescriptionFrame->addSlider("skill slider"); + slider->setMinValue(0); + slider->setMaxValue(100); + slider->setValue(0); + SDL_Rect sliderPos{ descPos.w - 34, 4, 30, descPos.h - 8 }; + slider->setRailSize(SDL_Rect{ sliderPos }); + slider->setHandleSize(SDL_Rect{ 0, 0, 34, 34 }); + slider->setOrientation(Slider::SLIDER_VERTICAL); + //slider->setCallback(callback); + slider->setColor(makeColor(127, 127, 127, 255)); + slider->setHighlightColor(makeColor(255, 255, 255, 255)); + slider->setHandleImage("images/ui/Main Menus/Settings/Settings_Slider_Boulder00.png"); + slider->setRailImage("images/ui/Main Menus/Settings/Settings_Slider_Backing00.png"); + + Font* actualFont = Font::get(descFont); + int fontHeight; + actualFont->sizeText("_", nullptr, &fontHeight); + + auto scrollAreaOuterFrame = skillDescriptionFrame->addFrame("scroll area outer frame"); + scrollAreaOuterFrame->setSize(SDL_Rect{ 16, 4, sliderPos.x - 4 - 16, descPos.h - 8 }); + auto scrollAreaFrame = scrollAreaOuterFrame->addFrame("skill scroll area"); + scrollAreaFrame->setSize(SDL_Rect{ 0, 0, scrollAreaOuterFrame->getSize().w, 1000 }); + + SDL_Rect txtPos{ 0, 0, scrollAreaFrame->getSize().w, scrollAreaFrame->getSize().h }; + { + auto skillLvlHeaderTxt = scrollAreaFrame->addField("skill lvl header txt", 128); + skillLvlHeaderTxt->setFont(descFont); + skillLvlHeaderTxt->setSize(txtPos); + skillLvlHeaderTxt->setText("Skill Level:"); + + auto skillLvlHeaderVal = scrollAreaFrame->addField("skill lvl header val", 128); + skillLvlHeaderVal->setFont(descFont); + skillLvlHeaderVal->setSize(txtPos); + skillLvlHeaderVal->setText(""); + skillLvlHeaderVal->setHJustify(Field::justify_t::RIGHT); + + txtPos.y += fontHeight; + } + { + const int iconSize = 24; + auto statIcon = scrollAreaFrame->addImage( + SDL_Rect{ txtPos.x + txtPos.w - iconSize, txtPos.y, iconSize, iconSize }, + 0xFFFFFFFF, "images/ui/CharSheet/HUD_CharSheet_DEX_00.png", "stat icon"); + + txtPos.y += std::max(0, iconSize - fontHeight); + auto statHeaderTxt = scrollAreaFrame->addField("stat header txt", 128); + statHeaderTxt->setFont(descFont); + statHeaderTxt->setSize(txtPos); + statHeaderTxt->setText("Associated Stat:"); + + auto statTypeTxt = scrollAreaFrame->addField("stat type txt", 128); + statTypeTxt->setFont(descFont); + txtPos.w -= iconSize + 4; + statTypeTxt->setSize(txtPos); + txtPos.w += iconSize + 4; + statTypeTxt->setText(""); + statTypeTxt->setHJustify(Field::justify_t::RIGHT); + txtPos.y += fontHeight; + txtPos.y += fontHeight / 2; + } + { + auto skillDescriptionTxt = scrollAreaFrame->addField("skill desc txt", 1024); + skillDescriptionTxt->setFont(descFont); + skillDescriptionTxt->setSize(txtPos); + skillDescriptionTxt->setText(""); + //skillDescriptionTxt->setColor(makeColor(201, 162, 100, 255)); + skillDescriptionTxt->setHJustify(Field::justify_t::CENTER); + skillDescriptionTxt->setOntop(true); + + auto skillDescriptionBgFrame = scrollAreaFrame->addFrame("skill desc bg frame"); + { + //Uint32 color = makeColor(22, 24, 29, 255); + Uint32 color = makeColor(255, 255, 255, 128); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_TL_00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_TR_00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_T_00.png", skillsheetEffectBackgroundImages[TOP].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_ML_00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_MR_00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png", skillsheetEffectBackgroundImages[MIDDLE].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_BL_00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_BR_00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str()); + skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_LegendBox_B_00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str()); + imageSetWidthHeight9x9(skillDescriptionBgFrame, skillsheetEffectBackgroundImages); + } + + + int txtHeight = skillDescriptionTxt->getNumTextLines() * actualFont->height(true); + + txtPos.y += txtHeight; + txtPos.y += fontHeight / 2; + + } + { + char effectFrameName[64] = ""; + char effectFieldName[64] = ""; + char effectFieldVal[64] = ""; + char effectBgName[64]; + int effectXOffset = 72; // tmp paramters - configured in skillsheet json + int effectBackgroundXOffset = 8; + int effectBackgroundWidth = 80; + for ( int i = 0; i < 10; ++i ) + { + snprintf(effectFrameName, sizeof(effectFrameName), "effect %d frame", i); + auto effectFrame = scrollAreaFrame->addFrame(effectFrameName); + effectFrame->setSize(SDL_Rect{ txtPos.x, txtPos.y - 2, txtPos.w, fontHeight + 8 }); + effectFrame->addImage( + SDL_Rect{ 0, 0, effectFrame->getSize().w, effectFrame->getSize().h - 4 }, + makeColor(101, 87, 67, 255), "images/system/white.png", "effect frame bg highlight"); + effectFrame->addImage( + SDL_Rect{ 0, 0, effectFrame->getSize().w, effectFrame->getSize().h - 4 }, + makeColor(101, 33, 33, 28), "images/system/white.png", "effect frame bg tmp"); + int valueX = effectFrame->getSize().w - effectXOffset; + + auto valBgImgFrame = effectFrame->addFrame("effect val bg frame"); + valBgImgFrame->setSize(SDL_Rect{ valueX - effectBackgroundXOffset, 0, effectBackgroundWidth, effectFrame->getSize().h - 4}); + { + Uint32 color = makeColor(51, 33, 26, 255); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_TL00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_TR00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_T00.png", skillsheetEffectBackgroundImages[TOP].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_L00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_R00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_M00.png", skillsheetEffectBackgroundImages[MIDDLE].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_BL00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_BR00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str()); + valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 }, + color, "images/ui/SkillSheet/UI_Skills_EffectBG_B00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str()); + + imageSetWidthHeight9x9(valBgImgFrame, skillsheetEffectBackgroundImages); + } + + auto effectTxtFrame = effectFrame->addFrame("effect txt frame"); + effectTxtFrame->setSize(SDL_Rect{ 0, 0, effectFrame->getSize().w - effectXOffset - effectBackgroundXOffset, effectFrame->getSize().h - 4 }); + auto effectTxt = effectTxtFrame->addField("effect txt", 128); + effectTxt->setFont(descFont); + effectTxt->setSize(SDL_Rect{0, 0, 1000, effectTxtFrame->getSize().h}); // large 1000px to handle large text length marquee + effectTxt->setVJustify(Field::justify_t::CENTER); + effectTxt->setText(""); + effectTxt->setColor(makeColor(201, 162, 100, 255)); + + auto effectValFrame = effectFrame->addFrame("effect val frame"); + effectValFrame->setSize(SDL_Rect{ valueX, 0, effectXOffset, effectFrame->getSize().h - 4 }); + auto effectVal = effectValFrame->addField("effect val", 128); + effectVal->setFont(descFont); + effectVal->setSize(SDL_Rect{ 0, 0, 1000, effectValFrame->getSize().h }); // large 1000px to handle large text length marquee + effectVal->setVJustify(Field::justify_t::CENTER); + effectVal->setText(""); + effectVal->setColor(makeColor(201, 162, 100, 255)); + + //effectFrame->setDisabled(true); + + txtPos.y += effectFrame->getSize().h; + } + } + { + auto legendDivImg = scrollAreaFrame->addImage(SDL_Rect{ 0, txtPos.y, 212, 28 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_Separator_00.png", "legend div"); + legendDivImg->pos.x = scrollAreaFrame->getSize().w / 2 - legendDivImg->pos.w / 2; + auto legendDivTxt = scrollAreaFrame->addField("legend div text", 128); + legendDivTxt->setText(language[4056]); + SDL_Rect legendDivTxtPos = legendDivImg->pos; + legendDivTxt->setSize(legendDivTxtPos); + legendDivTxt->setFont(descFont); + legendDivTxt->setHJustify(Field::justify_t::CENTER); + + auto legendFrame = scrollAreaFrame->addFrame("legend frame"); + SDL_Rect legendPos{ 0, txtPos.y, txtPos.w, 100 }; + legendFrame->setSize(legendPos); + auto tl = legendFrame->addImage(SDL_Rect{ 0, 0, 18, 18 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_TL_00.png", "top left img"); + auto tm = legendFrame->addImage(SDL_Rect{ 18, 0, legendPos.w - 18 * 2, 18 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_T_00.png", "top img"); + auto tr = legendFrame->addImage(SDL_Rect{ legendPos.w - 18, 0, 18, 18 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_TR_00.png", "top right img"); + + auto ml = legendFrame->addImage(SDL_Rect{ 0, 18, 18, legendPos.h - 18 * 2 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_ML_00.png", "middle left img"); + auto mm = legendFrame->addImage(SDL_Rect{ 18, 18, legendPos.w - 18 * 2, ml->pos.h }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png", "middle img"); + auto mr = legendFrame->addImage(SDL_Rect{ legendPos.w - 18, 18, 18, ml->pos.h }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_MR_00.png", "middle right img"); + + auto bl = legendFrame->addImage(SDL_Rect{ 0, ml->pos.y + ml->pos.h, 18, 18 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_BL_00.png", "bottom left img"); + auto bm = legendFrame->addImage(SDL_Rect{ 18, bl->pos.y, legendPos.w - 18 * 2, 18 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_B_00.png", "bottom img"); + auto br = legendFrame->addImage(SDL_Rect{ legendPos.w - 18, bl->pos.y, 18, 18 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendBox_BR_00.png", "bottom right img"); + + auto backerImg = scrollAreaFrame->addImage(SDL_Rect{ 0, 0, 154, 12 }, 0xFFFFFFFF, + "images/ui/SkillSheet/UI_Skills_LegendTextBacker_00.png", "legend txt backer img"); + backerImg->disabled = true; + + auto legendText = legendFrame->addField("legend text", 256); + legendText->setText(""); + legendText->setSize(mm->pos); + legendText->setFont(descFont); + legendText->setHJustify(Field::justify_t::CENTER); + } + + std::string promptFont = "fonts/pixel_maz.ttf#32#2"; + const int promptWidth = 60; + const int promptHeight = 27; + auto promptBack = frame->addField("prompt back txt", 16); + promptBack->setSize(SDL_Rect{ frame->getSize().w - promptWidth - 16, // lower right corner + 0, promptWidth, promptHeight }); + promptBack->setFont(promptFont.c_str()); + promptBack->setHJustify(Field::justify_t::RIGHT); + promptBack->setVJustify(Field::justify_t::CENTER); + promptBack->setText(language[4053]); + //promptBack->setOntop(true); + promptBack->setColor(makeColor(201, 162, 100, 255)); + + auto promptBackImg = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, + "", "prompt back img"); + promptBackImg->disabled = true; + //promptBackImg->ontop = true; + + auto promptScroll = frame->addField("prompt scroll txt", 16); + promptScroll->setSize(SDL_Rect{ frame->getSize().w - promptWidth - 16, // lower right corner + 0, promptWidth, promptHeight }); + promptScroll->setFont(promptFont.c_str()); + promptScroll->setHJustify(Field::justify_t::LEFT); + promptScroll->setVJustify(Field::justify_t::CENTER); + promptScroll->setText(language[4062]); + //promptScroll->setOntop(true); + promptScroll->setColor(makeColor(201, 162, 100, 255)); + + auto promptScrollImg = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, + "", "prompt scroll img"); + promptScrollImg->disabled = true; + promptScrollImg->ontop = true; +} + +void Player::SkillSheet_t::resetSkillDisplay() +{ + bSkillSheetEntryLoaded = false; + scrollInertia = 0.0; + + for ( auto& skillEntry : skillSheetData.skillEntries ) + { + for ( auto& skillEffect : skillEntry.effects ) + { + skillEffect.marqueeTicks = 0; + skillEffect.marquee = 0.0; + } + } +} + +void Player::SkillSheet_t::closeSkillSheet() +{ + bSkillSheetOpen = false; + if ( skillFrame ) + { + skillFrame->setDisabled(true); + } + resetSkillDisplay(); +} + +void Player::SkillSheet_t::openSkillSheet() +{ + players[player.playernum]->openStatusScreen(GUI_MODE_INVENTORY, + INVENTORY_MODE_ITEM, player.GUI.MODULE_SKILLS_LIST); // Reset the GUI to the inventory. + bSkillSheetOpen = true; + openTick = ticks; + scrollPercent = 0.0; + if ( skillFrame ) + { + auto innerFrame = skillFrame->findFrame("skills frame"); + auto skillDescriptionFrame = innerFrame->findFrame("skill desc frame"); + auto slider = skillDescriptionFrame->findSlider("skill slider"); + slider->setValue(0.0); + } + resetSkillDisplay(); + if ( selectedSkill < 0 ) + { + selectSkill(0); + } + if ( ::inputs.getVirtualMouse(player.playernum)->lastMovementFromController ) + { + highlightedSkill = selectedSkill; + } +} + +std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& tag, std::string& rawValue) +{ + char buf[128] = ""; + if ( !players[playernum] ) { return ""; } + Entity* player = players[playernum]->entity; + real_t val = 0.0; + real_t val2 = 0.0; + if ( proficiency == PRO_STEALTH ) + { + if ( tag == "STEALTH_VIS_REDUCTION" ) + { + val = stats[playernum]->PROFICIENCIES[proficiency] * 2 * 100 / 512.f; // % visibility reduction + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "STEALTH_SNEAK_VIS" ) + { + val = (2 + (stats[playernum]->PROFICIENCIES[proficiency] / 40)); // night vision when sneaking + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "STEALTH_BACKSTAB" ) + { + val = (stats[playernum]->PROFICIENCIES[proficiency] / 20 + 2) * 2; // backstab dmg + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val *= 2; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "STEALTH_CURRENT_VIS" ) + { + if ( player ) + { + val = player->entityLightAfterReductions(*stats[playernum], nullptr); + val = std::max(1, (static_cast(val / 32.0))); // general visibility + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + } + return buf; + } + else if ( proficiency == PRO_RANGED ) + { + if ( tag == "RANGED_DMG_RANGE" ) + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 2.f; // lowest damage roll + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "RANGED_DEGRADE_CHANCE" ) + { + val = 50 + static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20) * 10; + if ( stats[playernum]->type == GOBLIN ) + { + val += 20; + if ( stats[playernum]->PROFICIENCIES[proficiency] < SKILL_LEVEL_LEGENDARY ) + { + val = std::min(val, 90.0); + } + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "RANGED_THROWN_DMG" ) + { + int skillLVL = stats[playernum]->PROFICIENCIES[proficiency] / 20; // thrown dmg bonus + val = 100 * thrownDamageSkillMultipliers[std::min(skillLVL, 5)]; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "RANGED_PIERCE_CHANCE" ) + { + if ( player ) + { + val = std::min(std::max(player->getPER() / 2, 0), 50); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + } + return buf; + } + else if ( proficiency == PRO_SHIELD ) + { + if ( tag == "BLOCK_AC_INCREASE" ) + { + val = 5 + static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 5); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "BLOCK_DEGRADE_NORMAL_CHANCE" ) + { + val = 25 + (stats[playernum]->type == GOBLIN ? 10 : 0); // degrade > 0 dmg taken + val += (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 10)); + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), val); + } + else if ( tag == "BLOCK_DEGRADE_DEFENDING_CHANCE" ) + { + val = 10 + (stats[playernum]->type == GOBLIN ? 10 : 0); // degrade on 0 dmg + val += (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 10)); + if ( svFlags & SV_FLAG_HARDCORE ) + { + val = 40 + (stats[playernum]->type == GOBLIN ? 10 : 0); + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), val); + } + return buf; + } + else if ( proficiency == PRO_UNARMED ) + { + if ( tag == "UNARMED_DMG_RANGE" ) + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 2.f; // lowest damage roll + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "UNARMED_BONUS_DMG" ) + { + val = static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "GLOVE_DEGRADE_CHANCE" ) + { + val = 100 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "GLOVE_DEGRADE0_CHANCE" ) + { + val = 8 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "UNARMED_KNOCKBACK_DIST" ) + { + val = static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20) * 20; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_SWORD ) + { + if ( tag == "SWORD_DMG_RANGE" ) + { + if ( proficiency == PRO_POLEARM ) + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 3.f; // lowest damage roll + } + else + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 2.f; // lowest damage roll + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "SWORD_DEGRADE_CHANCE" ) + { + val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg + val += (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20)) * 10; + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "SWORD_DEGRADE0_CHANCE" ) + { + val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg + val += static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20); + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_POLEARM ) + { + if ( tag == "POLEARM_DMG_RANGE" ) + { + if ( proficiency == PRO_POLEARM ) + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 3.f; // lowest damage roll + } + else + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 2.f; // lowest damage roll + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "POLEARM_DEGRADE_CHANCE" ) + { + val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg + val += (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20)) * 10; + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "POLEARM_DEGRADE0_CHANCE" ) + { + val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg + val += static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20); + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_AXE ) + { + if ( tag == "AXE_DMG_RANGE" ) + { + if ( proficiency == PRO_POLEARM ) + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 3.f; // lowest damage roll + } + else + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 2.f; // lowest damage roll + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "AXE_DEGRADE_CHANCE" ) + { + val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg + val += (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20)) * 10; + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "AXE_DEGRADE0_CHANCE" ) + { + val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg + val += static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20); + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_MACE ) + { + if ( tag == "MACE_DMG_RANGE" ) + { + if ( proficiency == PRO_POLEARM ) + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 3.f; // lowest damage roll + } + else + { + val = 100 - (100 - stats[playernum]->PROFICIENCIES[proficiency]) / 2.f; // lowest damage roll + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "MACE_DEGRADE_CHANCE" ) + { + val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg + val += (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20)) * 10; + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "MACE_DEGRADE0_CHANCE" ) + { + val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg + val += static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20); + if ( svFlags & SV_FLAG_HARDCORE ) + { + val *= 2; + } + if ( skillCapstoneUnlocked(playernum, proficiency) ) + { + val = 0.0; + } + if ( val > 0.0001 ) + { + val = 100 / val; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_SWIMMING ) + { + if ( tag == "SWIM_SPEED" ) + { + val = (((stats[playernum]->PROFICIENCIES[proficiency] / 100.f) * 50.f) + 50); // water movement speed + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_LEADERSHIP ) + { + if ( tag == "LEADER_MAX_FOLLOWERS" ) + { + val = std::min(8, std::max(4, 2 * (stats[playernum]->PROFICIENCIES[proficiency] / 20))); // max followers + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "LEADER_FOLLOWER_SPEED" ) + { + val = 1 + (stats[playernum]->PROFICIENCIES[proficiency] / 20); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "LEADER_CHARM_MONSTER" ) + { + val = 80 + ((statGetCHR(stats[playernum], player) + stats[playernum]->PROFICIENCIES[proficiency]) / 20) * 10; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "LIST_LEADER_AVAILABLE_FOLLOWERS" ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), " - TODO\n - TODO\n - TODO"); + } + return buf; + } + else if ( proficiency == PRO_TRADING ) + { + if ( tag == "TRADING_BUY_PRICE" ) + { + val = 1 / ((50 + stats[playernum]->PROFICIENCIES[proficiency]) / 150.f); // buy value + snprintf(buf, sizeof(buf), rawValue.c_str(), val); + } + else if ( tag == "TRADING_SELL_PRICE" ) + { + val = (50 + stats[playernum]->PROFICIENCIES[proficiency]) / 150.f; // sell value + snprintf(buf, sizeof(buf), rawValue.c_str(), val); + } + return buf; + } + else if ( proficiency == PRO_APPRAISAL ) + { + if ( tag == "APPRAISE_GOLD_SPEED" ) + { + val = (60.f / (stats[playernum]->PROFICIENCIES[proficiency] + 1)) / (TICKS_PER_SECOND); // appraisal time per gold value + snprintf(buf, sizeof(buf), rawValue.c_str(), val); + } + else if ( tag == "APPRAISE_MAX_GOLD_VALUE" ) + { + val = 10 * (stats[playernum]->PROFICIENCIES[proficiency] + (statGetPER(stats[playernum], player) * 5)); // max gold value can appraise + if ( val < 0.1 ) + { + val = 9; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "APPRAISE_WORTHLESS_GLASS" ) + { + if ( (stats[playernum]->PROFICIENCIES[proficiency] + (statGetPER(stats[playernum], player) * 5)) >= 100 ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), language[1314]); // yes + } + else + { + snprintf(buf, sizeof(buf), rawValue.c_str(), language[1315]); // no + } + } + return buf; + } + else if ( proficiency == PRO_LOCKPICKING ) + { + Sint32 PER = statGetPER(stats[playernum], player); + if ( tag == "TINKERING_LOCKPICK_CHESTS_DOORS" ) + { + val = stats[playernum]->PROFICIENCIES[proficiency] / 2.f; // lockpick chests/doors + if ( stats[playernum]->PROFICIENCIES[proficiency] == SKILL_LEVEL_LEGENDARY ) + { + val = 100.f; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "TINKERING_SCRAP_CHESTS" ) + { + val = std::min(100.f, stats[playernum]->PROFICIENCIES[proficiency] + 50.f); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "TINKERING_SCRAP_AUTOMATONS" ) + { + if ( stats[playernum]->PROFICIENCIES[proficiency] >= SKILL_LEVEL_EXPERT ) + { + val = 100.f; // lockpick automatons + } + else + { + val = (100 - 100 / (static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20 + 1))); // lockpick automatons + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "TINKERING_DISARM_ARROWS" ) + { + val = (100 - 100 / (std::max(1, static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 10)))); // disarm arrow traps + if ( stats[playernum]->PROFICIENCIES[proficiency] < SKILL_LEVEL_BASIC ) + { + val = 0.f; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "TINKERING_KIT_SCRAP_BONUS" ) + { + // bonus scrapping chances. + int skillLVL = std::min(5, static_cast((stats[playernum]->PROFICIENCIES[proficiency] + PER) / 20)); + skillLVL = std::max(0, skillLVL); + switch ( skillLVL ) + { + case 5: + val = 150.f; + break; + case 4: + val = 125.f; + break; + case 3: + val = 50.f; + break; + case 2: + val = 25.f; + break; + case 1: + val = 12.5; + break; + default: + val = 0.f; + break; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "TINKERING_KIT_REPAIR_ITEM" ) + { + std::string canRepairItems = language[4057]; // none + if ( (stats[playernum]->PROFICIENCIES[proficiency] + PER + (stats[playernum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_LEGENDARY ) + { + canRepairItems = language[4058]; // all + } + else if ( (stats[playernum]->PROFICIENCIES[proficiency] + PER + (stats[playernum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_MASTER ) + { + canRepairItems = language[4059]; // 2/0 + } + else if ( (stats[playernum]->PROFICIENCIES[proficiency] + PER + (stats[playernum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_EXPERT ) + { + canRepairItems = language[4060]; // 1/0 + } + snprintf(buf, sizeof(buf), rawValue.c_str(), canRepairItems.c_str()); + } + else if ( tag == "TINKERING_MAX_ALLIES" ) + { + val = maximumTinkeringBotsCanBeDeployed(stats[playernum]); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + return buf; + } + else if ( proficiency == PRO_ALCHEMY ) + { + if ( tag == "ALCHEMY_POTION_EFFECT_DMG" ) + { + int skillLVL = stats[playernum]->PROFICIENCIES[proficiency] / 20; + val = 100 * potionDamageSkillMultipliers[std::min(skillLVL, 5)]; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "ALCHEMY_THROWN_IMPACT_DMG" ) + { + int skillLVL = stats[playernum]->PROFICIENCIES[proficiency] / 20; + val = 100 * potionDamageSkillMultipliers[std::min(skillLVL, 5)]; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "ALCHEMY_DUPLICATION_CHANCE" ) + { + val = 50.f + static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20) * 10; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "ALCHEMY_EMPTY_BOTTLE_CONSUME" ) + { + val = std::min(80, (60 + static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20) * 10)); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "ALCHEMY_EMPTY_BOTTLE_BREW" ) + { + val = 50.f + static_cast(stats[playernum]->PROFICIENCIES[proficiency] / 20) * 5; + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "ALCHEMY_LEARNT_INGREDIENTS" ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), " - TODO\n - TODO\n - TODO"); + } + return buf; + } + else if ( proficiency == PRO_SPELLCASTING ) + { + if ( tag == "CASTING_MP_REGEN" ) + { + if ( player ) + { + val = player->getManaRegenInterval(*(stats[playernum])) / (TICKS_PER_SECOND * 1.f); + } + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "CASTING_BEGINNER" ) + { + if ( isSpellcasterBeginner(playernum, player) ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), language[1314]); // yes + } + else + { + snprintf(buf, sizeof(buf), rawValue.c_str(), language[1315]); // no + } + } + else if ( tag == "CASTING_SPELLBOOK_FUMBLE" ) + { + int skillLVL = std::min(std::max(0, stats[playernum]->PROFICIENCIES[proficiency] + statGetINT(stats[playernum], player)), 100); + skillLVL /= 20; + std::string tierName = language[4061]; + tierName += " "; + if ( skillLVL == 0 ) + { + tierName += "I"; + } + else if ( skillLVL == 1 ) + { + tierName += "II"; + } + else if ( skillLVL == 2 ) + { + tierName += "III"; + } + else if ( skillLVL == 3 ) + { + tierName += "IV"; + } + else if ( skillLVL == 4 ) + { + tierName += "V"; + } + else if ( skillLVL >= 5 ) + { + tierName += "VI"; + } + snprintf(buf, sizeof(buf), rawValue.c_str(), tierName.c_str()); + } + return buf; + } + else if ( proficiency == PRO_MAGIC ) + { + /*if ( tag == "CASTING_MP_REGEN" ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "CASTING_BEGINNER" ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } + else if ( tag == "CASTING_SPELLBOOK_FUMBLE" ) + { + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + }*/ + } + + return ""; +} + +void Player::SkillSheet_t::selectSkill(int skill) +{ + selectedSkill = skill; + bSkillSheetEntryLoaded = false; + openTick = ticks; + resetSkillDisplay(); +} + +void Player::SkillSheet_t::processSkillSheet() +{ + if ( !skillFrame ) + { + createSkillSheet(); + } + + if ( keystatus[SDL_SCANCODE_K] ) + { + if ( !bSkillSheetOpen ) + { + openSkillSheet(); + } + else + { + closeSkillSheet(); + } + keystatus[SDL_SCANCODE_K] = 0; + } + + if ( !bSkillSheetOpen ) + { + skillFrame->setDisabled(true); + + auto innerFrame = skillFrame->findFrame("skills frame"); + SDL_Rect pos = innerFrame->getSize(); + skillsFadeInAnimationY = 0.0; + pos.y = -pos.h; + innerFrame->setSize(pos); + + auto fade = skillFrame->findImage("fade img"); + Uint8 r, g, b, a; + SDL_GetRGBA(fade->color, mainsurface->format, &r, &g, &b, &a); + a = 0; + fade->color = SDL_MapRGBA(mainsurface->format, r, g, b, a); + return; + } + + skillFrame->setDisabled(false); + skillFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(), + players[player.playernum]->camera_virtualy1(), + players[player.playernum]->camera_virtualWidth(), + players[player.playernum]->camera_virtualHeight() }); + + bool oldCompactViewVal = bUseCompactSkillsView; + if ( splitscreen && players[player.playernum]->camera_virtualHeight() < Frame::virtualScreenY * .8 ) + { + // use compact view. + bUseCompactSkillsView = true; + } + else + { + bUseCompactSkillsView = false; + } + if ( oldCompactViewVal != bUseCompactSkillsView && bUseCompactSkillsView ) + { + skillSlideAmount = 1.0; + skillSlideDirection = 1; + } + if ( !bUseCompactSkillsView ) + { + skillSlideAmount = 0.0; + skillSlideDirection = 0; + } + + auto innerFrame = skillFrame->findFrame("skills frame"); + SDL_Rect sheetSize = innerFrame->getSize(); + Frame* allSkillEntriesLeft = innerFrame->findFrame("skill entries frame left"); + Frame* allSkillEntriesRight = innerFrame->findFrame("skill entries frame right"); + auto leftWingImg = allSkillEntriesLeft->findImage("bg wing left"); + auto rightWingImg = allSkillEntriesRight->findImage("bg wing right"); + auto skillDescriptionFrame = innerFrame->findFrame("skill desc frame"); + auto bgImgFrame = innerFrame->findFrame("skills bg images"); + auto scrollAreaOuterFrame = skillDescriptionFrame->findFrame("scroll area outer frame"); + auto scrollArea = scrollAreaOuterFrame->findFrame("skill scroll area"); + SDL_Rect scrollOuterFramePos = scrollAreaOuterFrame->getSize(); + SDL_Rect scrollAreaPos = scrollArea->getSize(); + + real_t slideTravelDistance = 52; + int compactViewWidthOffset = (skillSlideDirection != 0 ? 104: 0); + { + // dynamic width/height adjustments of outer containers + sheetSize.h = std::max(0, std::min(skillFrame->getSize().h - 8, + (int)(404 + (bUseCompactSkillsView ? windowCompactHeightScaleY : windowHeightScaleY) * 80))); + sheetSize.w = std::max(0, std::min(skillFrame->getSize().w - 8, + (int)(684 + (bUseCompactSkillsView ? windowCompactHeightScaleX : windowHeightScaleX) * 80))); + innerFrame->setSize(sheetSize); + + SDL_Rect skillDescPos = skillDescriptionFrame->getSize(); + skillDescPos.h = innerFrame->getSize().h - skillDescPos.y - 16; + skillDescPos.w = innerFrame->getSize().w + compactViewWidthOffset - allSkillEntriesLeft->getSize().w - allSkillEntriesRight->getSize().w; + skillDescPos.x = innerFrame->getSize().w / 2 - skillDescPos.w / 2; + if ( skillSlideDirection != 0 ) + { + skillDescPos.x += (skillSlideAmount) * slideTravelDistance; + } + skillDescriptionFrame->setSize(skillDescPos); + + scrollOuterFramePos.h = skillDescPos.h - 4; + + auto leftWingPos = allSkillEntriesLeft->getSize(); + auto rightWingPos = allSkillEntriesRight->getSize(); + + leftWingPos.x = 0; + rightWingPos.x = innerFrame->getSize().w - rightWingPos.w; + leftWingPos.y = std::max(0, innerFrame->getSize().h / 2 - leftWingPos.h / 2); + rightWingPos.y = std::max(0, innerFrame->getSize().h / 2 - rightWingPos.h / 2); + allSkillEntriesLeft->setSize(leftWingPos); + allSkillEntriesRight->setSize(rightWingPos); + + if ( bUseCompactSkillsView ) + { + leftWingImg->path = "images/ui/SkillSheet/UI_Skills_Window_LeftCompact_03.png"; + rightWingImg->path = "images/ui/SkillSheet/UI_Skills_Window_RightCompact_03.png"; + } + else + { + leftWingImg->path = "images/ui/SkillSheet/UI_Skills_Window_Left_03.png"; + rightWingImg->path = "images/ui/SkillSheet/UI_Skills_Window_Right_03.png"; + } + leftWingImg->pos.h = Image::get(leftWingImg->path.c_str())->getHeight(); + rightWingImg->pos.h = Image::get(rightWingImg->path.c_str())->getHeight(); + } + { + // dynamic main panel width/height background image adjustment + SDL_Rect bgImgFramePos = bgImgFrame->getSize(); + int backgroundWidth = skillDescriptionFrame->getSize().w; + int backgroundHeight = innerFrame->getSize().h; + + auto flourishTop = bgImgFrame->findImage("flourish top"); + flourishTop->pos.x = bgImgFramePos.w / 2 - flourishTop->pos.w / 2; + auto flourishBottom = bgImgFrame->findImage("flourish bottom"); + flourishBottom->pos.x = bgImgFramePos.w / 2 - flourishBottom->pos.w / 2; + flourishBottom->pos.y = backgroundHeight - flourishBottom->pos.h; + + bgImgFramePos.x = innerFrame->getSize().w / 2 - backgroundWidth / 2; + if ( skillSlideDirection != 0 ) + { + bgImgFramePos.x += (skillSlideAmount) * slideTravelDistance; + } + bgImgFramePos.y = 0; + bgImgFramePos.w = backgroundWidth; + bgImgFramePos.h = backgroundHeight; + bgImgFrame->setSize(bgImgFramePos); + + imageResizeToContainer9x9(bgImgFrame, SDL_Rect{0, 12, backgroundWidth, backgroundHeight - 6 }, skillsheetEffectBackgroundImages); + } + sheetSize.x = skillFrame->getSize().w / 2 - sheetSize.w / 2; + + const real_t fpsScale = (50.f / std::max(1U, fpsLimit)); // ported from 50Hz + real_t setpointDiffX = fpsScale * std::max(.01, (1.0 - skillsFadeInAnimationY)) / 5.0; + skillsFadeInAnimationY += setpointDiffX; + skillsFadeInAnimationY = std::min(1.0, skillsFadeInAnimationY); + + auto fade = skillFrame->findImage("fade img"); + Uint8 r, g, b, a; + SDL_GetRGBA(fade->color, mainsurface->format, &r, &g, &b, &a); + a = 128 * skillsFadeInAnimationY; + fade->color = SDL_MapRGBA(mainsurface->format, r, g, b, a); + + int baseY = (skillFrame->getSize().h / 2 - sheetSize.h / 2); + sheetSize.y = -sheetSize.h + skillsFadeInAnimationY * (baseY + sheetSize.h); + innerFrame->setSize(sheetSize); + + auto slider = skillDescriptionFrame->findSlider("skill slider"); + bool sliderDisabled = slider->isDisabled(); + + auto titleText = innerFrame->findField("skill title txt"); + SDL_Rect titleTextPos = titleText->getSize(); + titleTextPos.x = skillDescriptionFrame->getSize().x + skillDescriptionFrame->getSize().w / 2 - titleTextPos.w / 2; + titleText->setSize(titleTextPos); + + if ( keystatus[SDL_SCANCODE_F1] ) + { + if ( keystatus[SDL_SCANCODE_LSHIFT] ) + { + windowHeightScaleX = std::max(windowHeightScaleX - .01, -1.0); + } + else + { + windowHeightScaleX = std::min(windowHeightScaleX + .01, 1.0); + } + } + if ( keystatus[SDL_SCANCODE_F2] ) + { + if ( keystatus[SDL_SCANCODE_LSHIFT] ) + { + windowHeightScaleY = std::max(windowHeightScaleY - .01, -1.0); + } + else + { + windowHeightScaleY = std::min(windowHeightScaleY + .01, 1.0); + } + } + if ( keystatus[SDL_SCANCODE_F3] ) + { + keystatus[SDL_SCANCODE_F3] = 0; + if ( keystatus[SDL_SCANCODE_LCTRL] ) + { + //bUseCompactSkillsView = !bUseCompactSkillsView; + } + else if ( keystatus[SDL_SCANCODE_LSHIFT] ) + { + skillSlideDirection = 0; + } + else + { + skillSlideDirection = (skillSlideDirection != 1) ? 1 : -1; + //skillSlideAmount = 0.0; + } + } + if ( skillSlideDirection != 0 ) + { + const real_t fpsScale = (144.0 / std::max(1U, fpsLimit)); // ported from 144Hz + real_t setpointDiff = std::max(0.1, 1.0 - abs(skillSlideAmount)); + skillSlideAmount += fpsScale * (setpointDiff / 5.0) * skillSlideDirection; + if ( skillSlideAmount < -1.0 ) + { + skillSlideAmount = -1.0; + } + if ( skillSlideAmount > 1.0 ) + { + skillSlideAmount = 1.0; + } + } + + int lowestSkillEntryY = 0; + bool dpad_moved = false; + if ( ::inputs.getVirtualMouse(player.playernum)->draw_cursor ) + { + highlightedSkill = -1; // if using mouse, clear out the highlighted skill data to be updated below + } + int defaultHighlightedSkill = 0; + if ( selectedSkill >= 0 && selectedSkill < NUMPROFICIENCIES ) + { + defaultHighlightedSkill = selectedSkill; + } + bool closeSheetAction = false; + if ( player.GUI.activeModule == player.GUI.MODULE_SKILLS_LIST ) + { + if ( Input::inputs[player.playernum].binaryToggle("MenuUp") ) + { + dpad_moved = true; + Input::inputs[player.playernum].consumeBinaryToggle("MenuUp"); + if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES ) + { + highlightedSkill = defaultHighlightedSkill; + } + else if ( highlightedSkill < NUMPROFICIENCIES / 2 ) + { + --highlightedSkill; + if ( highlightedSkill < 0 ) + { + highlightedSkill = (NUMPROFICIENCIES / 2) - 1; + } + } + else + { + --highlightedSkill; + if ( highlightedSkill < NUMPROFICIENCIES / 2 ) + { + highlightedSkill = NUMPROFICIENCIES - 1; + } + } + if ( selectedSkill != highlightedSkill ) + { + selectSkill(highlightedSkill); + } + } + if ( Input::inputs[player.playernum].binaryToggle("MenuDown") ) + { + dpad_moved = true; + Input::inputs[player.playernum].consumeBinaryToggle("MenuDown"); + if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES ) + { + highlightedSkill = defaultHighlightedSkill; + } + else if ( highlightedSkill < (NUMPROFICIENCIES / 2) ) + { + ++highlightedSkill; + if ( highlightedSkill >= NUMPROFICIENCIES / 2 ) + { + highlightedSkill = 0; + } + } + else + { + ++highlightedSkill; + if ( highlightedSkill >= NUMPROFICIENCIES ) + { + highlightedSkill = (NUMPROFICIENCIES / 2); + } + } + if ( selectedSkill != highlightedSkill ) + { + selectSkill(highlightedSkill); + } + } + if ( Input::inputs[player.playernum].binaryToggle("MenuLeft") ) + { + dpad_moved = true; + Input::inputs[player.playernum].consumeBinaryToggle("MenuLeft"); + if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES ) + { + highlightedSkill = defaultHighlightedSkill; + } + else if ( highlightedSkill < NUMPROFICIENCIES / 2 ) + { + highlightedSkill += NUMPROFICIENCIES / 2; + } + else + { + highlightedSkill -= NUMPROFICIENCIES / 2; + } + if ( selectedSkill != highlightedSkill ) + { + selectSkill(highlightedSkill); + } + } + if ( Input::inputs[player.playernum].binaryToggle("MenuRight") ) + { + dpad_moved = true; + Input::inputs[player.playernum].consumeBinaryToggle("MenuRight"); + if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES ) + { + highlightedSkill = defaultHighlightedSkill; + } + else if ( highlightedSkill < NUMPROFICIENCIES / 2 ) + { + highlightedSkill += NUMPROFICIENCIES / 2; + } + else + { + highlightedSkill -= NUMPROFICIENCIES / 2; + } + if ( selectedSkill != highlightedSkill ) + { + selectSkill(highlightedSkill); + } + } + + if ( dpad_moved ) + { + inputs.getVirtualMouse(player.playernum)->draw_cursor = false; + } + if ( Input::inputs[player.playernum].binaryToggle("MenuCancel") ) + { + Input::inputs[player.playernum].consumeBinaryToggle("MenuCancel"); + closeSheetAction = true; + } + } + bool mouseClickedOutOfBounds = false; + if ( inputs.bPlayerUsingKeyboardControl(player.playernum) && inputs.bMouseLeft(player.playernum) ) + { + mouseClickedOutOfBounds = true; + } + + if ( stats[player.playernum] && skillSheetData.skillEntries.size() > 0 ) + { + bool skillDescAreaCapturesMouse = bgImgFrame->capturesMouse(); + if ( skillDescAreaCapturesMouse ) { mouseClickedOutOfBounds = false; } + const int skillEntryStartY = bUseCompactSkillsView ? 28 : 38; + const int entryHeight = bUseCompactSkillsView ? 36 : 40; + SDL_Rect entryResizePos{ 0, skillEntryStartY, 0, entryHeight }; + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + char skillname[32]; + snprintf(skillname, sizeof(skillname), "skill %d", i); + Frame* allSkillEntries = allSkillEntriesRight; + if ( i >= 8 ) + { + allSkillEntries = allSkillEntriesLeft; + if ( i == 8 ) + { + entryResizePos.y = skillEntryStartY; + } + } + auto entry = allSkillEntries->findFrame(skillname); + + if ( i >= skillSheetData.skillEntries.size() ) + { + entry->setDisabled(true); + break; + } + entry->setDisabled(false); + SDL_Rect entryPos = entry->getSize(); + entryPos.y = entryResizePos.y; + entryPos.h = entryResizePos.h; + entry->setSize(entryPos); + entryResizePos.y += entryResizePos.h; + lowestSkillEntryY = std::max(lowestSkillEntryY, entryPos.y + entryPos.h); + + int proficiency = skillSheetData.skillEntries[i].skillId; + + auto skillLevel = entry->findField("skill level"); + char skillLevelText[32]; + snprintf(skillLevelText, sizeof(skillLevelText), "%d", stats[player.playernum]->PROFICIENCIES[proficiency]); + skillLevel->setText(skillLevelText); + + auto skillIconBg = entry->findImage("skill icon bg"); + auto skillIconFg = entry->findImage("skill icon fg"); + skillIconFg->path = skillSheetData.skillEntries[i].skillIconPath; + + auto statIcon = entry->findImage("stat icon"); + statIcon->disabled = true; + + auto selectorIcon = entry->findImage("selector img"); + selectorIcon->disabled = true; + + if ( entry->capturesMouse() ) + { + mouseClickedOutOfBounds = false; + if ( ::inputs.getVirtualMouse(player.playernum)->draw_cursor + && entry->capturesMouse() && !skillDescAreaCapturesMouse ) + { + highlightedSkill = i; + if ( skillSlideDirection != 0 ) + { + if ( highlightedSkill >= 8 ) + { + skillSlideDirection = 1; + } + else + { + skillSlideDirection = -1; + } + } + if ( inputs.bMouseLeft(player.playernum) ) + { + selectSkill(i); + inputs.mouseClearLeft(player.playernum); + } + } + } + + if ( selectedSkill == i || highlightedSkill == i ) + { + selectorIcon->disabled = false; + if ( selectedSkill == i && highlightedSkill == i ) + { + selectorIcon->path = (i < 8) ? "images/ui/SkillSheet/UI_Skills_SkillSelector100_01.png" : "images/ui/SkillSheet/UI_Skills_SkillSelector100R_01.png"; + } + else if ( highlightedSkill == i ) + { + selectorIcon->path = (i < 8) ? skillSheetData.highlightSkillImg : skillSheetData.highlightSkillImg_Right; + } + else if ( selectedSkill == i ) + { + selectorIcon->path = (i < 8) ? skillSheetData.selectSkillImg : skillSheetData.selectSkillImg_Right; + } + } + + if ( stats[player.playernum]->PROFICIENCIES[proficiency] >= SKILL_LEVEL_LEGENDARY ) + { + skillIconFg->path = skillSheetData.skillEntries[i].skillIconPathLegend; + skillLevel->setColor(skillSheetData.legendTextColor); + if ( selectedSkill == i ) + { + skillIconBg->path = skillSheetData.iconBgSelectedPathLegend; + } + else + { + skillIconBg->path = skillSheetData.iconBgPathLegend; + } + } + else if ( stats[player.playernum]->PROFICIENCIES[proficiency] >= SKILL_LEVEL_EXPERT ) + { + skillLevel->setColor(skillSheetData.expertTextColor); + if ( selectedSkill == i ) + { + skillIconBg->path = skillSheetData.iconBgSelectedPathExpert; + } + else + { + skillIconBg->path = skillSheetData.iconBgPathExpert; + } + } + else if ( stats[player.playernum]->PROFICIENCIES[proficiency] >= SKILL_LEVEL_BASIC ) + { + skillLevel->setColor(skillSheetData.noviceTextColor); + if ( selectedSkill == i ) + { + skillIconBg->path = skillSheetData.iconBgSelectedPathNovice; + } + else + { + skillIconBg->path = skillSheetData.iconBgPathNovice; + } + } + else + { + skillLevel->setColor(skillSheetData.defaultTextColor); + if ( selectedSkill == i ) + { + skillIconBg->path = skillSheetData.iconBgSelectedPathDefault; + } + else + { + skillIconBg->path = skillSheetData.iconBgPathDefault; + } + } + //statIcon->path = skillIconFg->path; + //skillIconFg->path = ""; + } + + int lowestY = 0; + + if ( keystatus[SDL_SCANCODE_J] ) + { + keystatus[SDL_SCANCODE_J] = 0; + slider->setDisabled(!slider->isDisabled()); + } + SDL_Rect sliderPos = slider->getRailSize(); + sliderPos.x = skillDescriptionFrame->getSize().w - 34; + sliderPos.h = skillDescriptionFrame->getSize().h - 8; + slider->setRailSize(sliderPos); + + int sliderOffsetW = 0; + if ( slider->isDisabled() ) + { + int diff = (scrollOuterFramePos.w - (skillDescriptionFrame->getSize().w - 32)); + sliderOffsetW = -diff; + } + else + { + int diff = (scrollOuterFramePos.w - (slider->getRailSize().x - 4 - 16)); + sliderOffsetW = -diff; + } + if ( sliderOffsetW != 0 ) + { + bSkillSheetEntryLoaded = false; + } + + scrollOuterFramePos.w += sliderOffsetW; + scrollAreaOuterFrame->setSize(scrollOuterFramePos); + scrollAreaPos.w = scrollOuterFramePos.w; + scrollArea->setSize(scrollAreaPos); + + if ( slider->isDisabled() ) + { + scrollInertia = 0.0; + } + + if ( selectedSkill >= 0 && selectedSkill < skillSheetData.skillEntries.size() ) + { + auto skillLvlHeaderVal = scrollArea->findField("skill lvl header val"); + char skillLvl[128] = ""; + int proficiency = skillSheetData.skillEntries[selectedSkill].skillId; + int proficiencyValue = stats[player.playernum]->PROFICIENCIES[proficiency]; + std::string skillLvlTitle = ""; + if ( proficiencyValue >= SKILL_LEVEL_LEGENDARY ) + { + skillLvlTitle = language[369]; + } + else if ( proficiencyValue >= SKILL_LEVEL_MASTER ) + { + skillLvlTitle = language[368]; + } + else if ( proficiencyValue >= SKILL_LEVEL_EXPERT ) + { + skillLvlTitle = language[367]; + } + else if ( proficiencyValue >= SKILL_LEVEL_SKILLED ) + { + skillLvlTitle = language[366]; + } + else if ( proficiencyValue >= SKILL_LEVEL_BASIC ) + { + skillLvlTitle = language[365]; + } + else if ( proficiencyValue >= SKILL_LEVEL_NOVICE ) + { + skillLvlTitle = language[364]; + } + else + { + skillLvlTitle = language[363]; + } + skillLvlTitle.erase(std::remove(skillLvlTitle.begin(), skillLvlTitle.end(), ' '), skillLvlTitle.end()); // trim whitespace + snprintf(skillLvl, sizeof(skillLvl), "%s (%d)", skillLvlTitle.c_str(), stats[player.playernum]->PROFICIENCIES[proficiency]); + skillLvlHeaderVal->setText(skillLvl); + + SDL_Rect skillLvlHeaderValPos = skillLvlHeaderVal->getSize(); + skillLvlHeaderValPos.x += sliderOffsetW; + skillLvlHeaderVal->setSize(skillLvlHeaderValPos); + + if ( !bSkillSheetEntryLoaded ) + { + auto skillTitleTxt = innerFrame->findField("skill title txt"); + skillTitleTxt->setText(skillSheetData.skillEntries[selectedSkill].name.c_str()); + + auto statTypeTxt = scrollArea->findField("stat type txt"); + auto statIcon = scrollArea->findImage("stat icon"); + statIcon->path = skillSheetData.skillEntries[selectedSkill].statIconPath; + switch ( getStatForProficiency(proficiency) ) + { + case STAT_STR: + statTypeTxt->setText("STR"); + break; + case STAT_DEX: + statTypeTxt->setText("DEX"); + break; + case STAT_CON: + statTypeTxt->setText("CON"); + break; + case STAT_INT: + statTypeTxt->setText("INT"); + break; + case STAT_PER: + statTypeTxt->setText("PER"); + break; + case STAT_CHR: + statTypeTxt->setText("CHR"); + break; + default: + break; + } + + SDL_Rect statTypeTxtPos = statTypeTxt->getSize(); + statTypeTxtPos.x += sliderOffsetW; + statTypeTxt->setSize(statTypeTxtPos); + statIcon->pos.x += sliderOffsetW; + } + + auto skillDescriptionTxt = scrollArea->findField("skill desc txt"); + auto skillDescriptionBgFrame = scrollArea->findFrame("skill desc bg frame"); + int moveEffectsOffsetY = 0; + Font* actualFont = Font::get(skillDescriptionTxt->getFont()); + if ( !bSkillSheetEntryLoaded ) + { + SDL_Rect skillDescriptionTxtPos = skillDescriptionTxt->getSize(); + skillDescriptionTxtPos.w = scrollAreaPos.w; + skillDescriptionTxt->setSize(skillDescriptionTxtPos); + + int txtHeightOld = skillDescriptionTxt->getNumTextLines() * actualFont->height(true); + skillDescriptionTxt->setText(skillSheetData.skillEntries[selectedSkill].description.c_str()); + skillDescriptionTxt->reflowTextToFit(0); + int txtHeightNew = skillDescriptionTxt->getNumTextLines() * actualFont->height(true); + + if ( txtHeightNew != txtHeightOld ) + { + moveEffectsOffsetY = (txtHeightNew - txtHeightOld); + } + skillDescriptionBgFrame->setSize(SDL_Rect{skillDescriptionTxtPos.x, skillDescriptionTxtPos.y - 4, + skillDescriptionTxtPos.w, txtHeightNew + 8 }); + imageResizeToContainer9x9(skillDescriptionBgFrame, + SDL_Rect{ 0, 0, skillDescriptionBgFrame->getSize().w, skillDescriptionBgFrame->getSize().h }, skillsheetEffectBackgroundImages); + } + + lowestY = std::max(lowestY, skillDescriptionTxt->getSize().y + skillDescriptionTxt->getNumTextLines() * actualFont->height(true)); + + int previousEffectFrameHeight = 0; + for ( int eff = 0; eff < 10; ++eff ) + { + char effectFrameName[64] = ""; + snprintf(effectFrameName, sizeof(effectFrameName), "effect %d frame", eff); + auto effectFrame = scrollArea->findFrame(effectFrameName); + if ( !effectFrame ) { continue; } + + effectFrame->setDisabled(true); + SDL_Rect effectFramePos = effectFrame->getSize(); + effectFramePos.w = scrollAreaPos.w; + + if ( moveEffectsOffsetY != 0 ) + { + effectFramePos.y += moveEffectsOffsetY; + } + effectFrame->setSize(effectFramePos); + + if ( eff < skillSheetData.skillEntries[selectedSkill].effects.size() ) + { + effectFrame->setDisabled(false); + + auto& effect_t = skillSheetData.skillEntries[selectedSkill].effects[eff]; + auto effectTxtFrame = effectFrame->findFrame("effect txt frame"); + auto effectTxt = effectTxtFrame->findField("effect txt"); + auto effectValFrame = effectFrame->findFrame("effect val frame"); + auto effectVal = effectValFrame->findField("effect val"); + auto effectBgImgFrame = effectFrame->findFrame("effect val bg frame"); + { + // adjust position to match width of container + SDL_Rect effectTxtFramePos = effectTxtFrame->getSize(); + SDL_Rect effectValFramePos = effectValFrame->getSize(); + SDL_Rect effectBgImgFramePos = effectBgImgFrame->getSize(); + const auto& effectStartOffsetX = skillSheetData.skillEntries[selectedSkill].effectStartOffsetX; + const auto& effectBackgroundOffsetX = skillSheetData.skillEntries[selectedSkill].effectBackgroundOffsetX; + const auto& effectBackgroundWidth = skillSheetData.skillEntries[selectedSkill].effectBackgroundWidth; + effectTxtFramePos.w = effectFrame->getSize().w - effectStartOffsetX - effectBackgroundOffsetX; + effectValFramePos.x = effectFrame->getSize().w - effectStartOffsetX; + effectValFramePos.w = effectStartOffsetX; + effectBgImgFramePos.x = effectFrame->getSize().w - effectStartOffsetX - effectBackgroundOffsetX; + effectBgImgFramePos.w = effectBackgroundWidth; + effectTxtFrame->setSize(effectTxtFramePos); + effectValFrame->setSize(effectValFramePos); + effectBgImgFrame->setSize(effectBgImgFramePos); + } + + effectTxt->setText(effect_t.title.c_str()); + if ( effect_t.effectUpdatedAtSkillLevel != stats[player.playernum]->PROFICIENCIES[proficiency] + || effect_t.value == "" ) + { + effect_t.effectUpdatedAtSkillLevel = stats[player.playernum]->PROFICIENCIES[proficiency]; + effect_t.value = formatSkillSheetEffects(player.playernum, proficiency, effect_t.tag, effect_t.rawValue); + } + effectVal->setText(effect_t.value.c_str()); + + Font* effectTxtFont = Font::get(effectTxt->getFont()); + int fontHeight; + effectTxtFont->sizeText("_", nullptr, &fontHeight); + int numEffectLines = effectTxt->getNumTextLines(); + effectFramePos = effectFrame->getSize(); + if ( numEffectLines > 1 ) + { + effectFramePos.h = (fontHeight * numEffectLines) + 8; + } + else + { + effectFramePos.h = fontHeight + 8; + } + if ( eff > 0 ) + { + effectFramePos.y = previousEffectFrameHeight; // don't adjust first effect frame y pos + } + effectFrame->setSize(effectFramePos); + + { + // adjust position to match height of container + SDL_Rect effectTxtFramePos = effectTxtFrame->getSize(); + SDL_Rect effectValFramePos = effectValFrame->getSize(); + SDL_Rect effectBgImgFramePos = effectBgImgFrame->getSize(); + const int containerHeight = effectFramePos.h - 4; + effectTxtFramePos.h = containerHeight; + effectValFramePos.h = containerHeight; + effectTxtFrame->setSize(effectTxtFramePos); + effectValFrame->setSize(effectValFramePos); + + SDL_Rect effectTxtPos = effectTxt->getSize(); + effectTxtPos.h = containerHeight; + effectTxt->setSize(effectTxtPos); + SDL_Rect effectValPos = effectVal->getSize(); + effectValPos.h = containerHeight; + effectVal->setSize(effectValPos); + + if ( numEffectLines > 1 ) + { + effectBgImgFramePos.h = (fontHeight + 8) * effectVal->getNumTextLines(); + effectBgImgFramePos.y = (containerHeight / 2 - effectBgImgFramePos.h / 2); + } + else + { + effectBgImgFramePos.y = 0; + effectBgImgFramePos.h = containerHeight; + } + effectBgImgFrame->setSize(effectBgImgFramePos); + + auto effectFrameBgImg = effectFrame->findImage("effect frame bg highlight"); + effectFrameBgImg->pos = SDL_Rect{ 0, effectFrame->getSize().h - 2, effectFrame->getSize().w, 1 }; + + auto effectFrameBgImgTmp = effectFrame->findImage("effect frame bg tmp"); + effectFrameBgImgTmp->pos = SDL_Rect{ 0, 0, effectFrame->getSize().w, effectFrame->getSize().h }; + } + + { + // adjust inner background image elements + imageResizeToContainer9x9(effectBgImgFrame, + SDL_Rect{ 0, 0, effectBgImgFrame->getSize().w, effectBgImgFrame->getSize().h }, skillsheetEffectBackgroundImages); + } + + lowestY = std::max(lowestY, effectFrame->getSize().y + effectFrame->getSize().h); + + auto textGetTitle = Text::get(effectTxt->getText(), effectTxt->getFont(), + effectTxt->getTextColor(), effectTxt->getOutlineColor()); + int titleWidth = textGetTitle->getWidth(); + if ( numEffectLines > 1 ) + { + auto textGetTitle = Text::get(effectTxt->getLongestLine().c_str(), effectTxt->getFont(), + effectTxt->getTextColor(), effectTxt->getOutlineColor()); + titleWidth = textGetTitle->getWidth(); + } + auto textGetValue = Text::get(effectVal->getText(), effectVal->getFont(), + effectVal->getTextColor(), effectVal->getOutlineColor()); + int valueWidth = textGetValue->getWidth(); + + // check marquee if needed + if ( ticks - openTick > TICKS_PER_SECOND * 2 ) + { + bool doMarquee = false; + doMarquee = doMarquee || (titleWidth > (effectTxt->getSize().x + effectTxtFrame->getSize().w)); + doMarquee = doMarquee || (valueWidth > (effectVal->getSize().x + effectValFrame->getSize().w)); + + if ( doMarquee ) + { + const real_t fpsScale = (60.f / std::max(1U, fpsLimit)); // ported from 60Hz + effect_t.marquee += (.005 * fpsScale); + //effect_t.marquee = std::min(1.0, effect_t.marquee); + + /*if ( effect_t.marqueeTicks == 0 && effect_t.marquee >= 1.0 ) + { + effect_t.marqueeTicks = ticks; + }*/ + /*if ( effect_t.marqueeTicks > 0 && (ticks - effect_t.marqueeTicks > TICKS_PER_SECOND * 2) ) + { + effect_t.marqueeTicks = 0; + effect_t.marquee = 0.0; + }*/ + } + } + SDL_Rect posTitle = effectTxt->getSize(); + int scrollTitleLength = titleWidth - effectTxtFrame->getSize().w; + if ( titleWidth <= effectTxtFrame->getSize().w ) + { + scrollTitleLength = 0; + posTitle.x = 0; + effect_t.marqueeCompleted = false; + effect_t.marquee = 0.0; + effect_t.marqueeTicks = 0; + } + else + { + posTitle.x = std::max((int)(-effect_t.marquee * 100), -scrollTitleLength); + if ( posTitle.x == -scrollTitleLength ) + { + if ( !effect_t.marqueeCompleted ) + { + effect_t.marqueeTicks = ticks; + } + effect_t.marqueeCompleted = true; + } + else + { + effect_t.marqueeCompleted = false; + } + } + //posTitle.x = -scrollTitleLength * effect_t.marquee; + effectTxt->setSize(posTitle); + + SDL_Rect posValue = effectVal->getSize(); + int scrollValueLength = valueWidth - effectValFrame->getSize().w; + if ( valueWidth <= effectValFrame->getSize().w ) + { + scrollValueLength = 0; + } + posValue.x = -scrollValueLength * effect_t.marquee; + effectVal->setSize(posValue); + } + + previousEffectFrameHeight = effectFrame->getSize().y + effectFrame->getSize().h; + } + + Uint32 lastMarqueeTick = 0; + bool allMarqueeCompleted = true; + for ( auto& effect_t : skillSheetData.skillEntries[selectedSkill].effects ) + { + if ( effect_t.marquee > 0.0 ) + { + if ( !effect_t.marqueeCompleted ) + { + allMarqueeCompleted = false; + } + lastMarqueeTick = std::max(effect_t.marqueeTicks, lastMarqueeTick); + } + } + if ( allMarqueeCompleted && lastMarqueeTick > 0 && ((ticks - lastMarqueeTick) > 2 * TICKS_PER_SECOND) ) + { + for ( auto& effect_t : skillSheetData.skillEntries[selectedSkill].effects ) + { + effect_t.marquee = 0.0; + effect_t.marqueeCompleted = false; + effect_t.marqueeTicks = 0; + } + openTick = (ticks > TICKS_PER_SECOND) ? (ticks - TICKS_PER_SECOND) : ticks; + } + + Uint32 legendGoldColor = makeColor(230, 183, 20, 255); + Uint32 legendRegularColor = makeColor(201, 162, 100, 255); + + // legend panel + auto legendDivImg = scrollArea->findImage("legend div"); + legendDivImg->pos.x = scrollArea->getSize().w / 2 - legendDivImg->pos.w / 2; + legendDivImg->pos.y = lowestY + 8; + auto legendDivTxt = scrollArea->findField("legend div text"); + SDL_Rect legendDivTxtPos = legendDivTxt->getSize(); + legendDivTxtPos.x = legendDivImg->pos.x; + legendDivTxtPos.w = legendDivImg->pos.w; + legendDivTxtPos.y = legendDivImg->pos.y + legendDivImg->pos.h - 16; + legendDivTxtPos.h = actualFont->height(true); + legendDivTxt->setSize(legendDivTxtPos); + lowestY = legendDivTxtPos.y + legendDivTxtPos.h; + + auto legendBackerImg = scrollArea->findImage("legend txt backer img"); + legendBackerImg->pos.x = scrollArea->getSize().w / 2 - legendBackerImg->pos.w / 2; + legendBackerImg->pos.y = legendDivTxtPos.y + 6; + legendBackerImg->disabled = true; + if ( proficiencyValue >= SKILL_LEVEL_LEGENDARY ) + { + legendBackerImg->disabled = false; + } + + auto legendFrame = scrollArea->findFrame("legend frame"); + SDL_Rect legendPos = legendFrame->getSize(); + legendPos.y = lowestY; + legendPos.w = scrollArea->getSize().w; + legendFrame->setSize(legendPos); + + auto tl = legendFrame->findImage("top left img"); + auto tm = legendFrame->findImage("top img"); + auto tr = legendFrame->findImage("top right img"); + tm->pos.w = legendPos.w - tl->pos.w - tr->pos.w; + tr->pos.x = legendPos.w - tr->pos.w; + + auto legendText = legendFrame->findField("legend text"); + legendText->setText(skillSheetData.skillEntries[selectedSkill].legendaryDescription.c_str()); + SDL_Rect legendTextPos = legendText->getSize(); + legendTextPos.w = tm->pos.w; + legendText->setSize(legendTextPos); + legendText->reflowTextToFit(0); + legendTextPos.h = legendText->getNumTextLines() * actualFont->height(true); + legendTextPos.y = tm->pos.y + tm->pos.h / 2; + legendText->setSize(legendTextPos); + + auto ml = legendFrame->findImage("middle left img"); + ml->pos.h = legendTextPos.h - tm->pos.h; + auto mm = legendFrame->findImage("middle img"); + mm->pos.h = ml->pos.h; + auto mr = legendFrame->findImage("middle right img"); + mr->pos.h = ml->pos.h; + mm->pos.w = legendPos.w - ml->pos.w - mr->pos.w; + mr->pos.x = legendPos.w - mr->pos.w; + + auto bl = legendFrame->findImage("bottom left img"); + bl->pos.y = ml->pos.y + ml->pos.h; + auto bm = legendFrame->findImage("bottom img"); + bm->pos.y = bl->pos.y; + auto br = legendFrame->findImage("bottom right img"); + br->pos.y = bl->pos.y; + bm->pos.w = legendPos.w - bl->pos.w - br->pos.w; + br->pos.x = legendPos.w - br->pos.w; + + if ( proficiencyValue < SKILL_LEVEL_LEGENDARY ) + { + legendDivTxt->setColor(legendRegularColor); + legendText->setColor(legendRegularColor); + tl->path = "images/ui/SkillSheet/UI_Skills_LegendBox_TL_00.png"; + tm->path = "images/ui/SkillSheet/UI_Skills_LegendBox_T_00.png"; + tr->path = "images/ui/SkillSheet/UI_Skills_LegendBox_TR_00.png"; + + ml->path = "images/ui/SkillSheet/UI_Skills_LegendBox_ML_00.png"; + mm->path = "images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png"; + mr->path = "images/ui/SkillSheet/UI_Skills_LegendBox_MR_00.png"; + + bl->path = "images/ui/SkillSheet/UI_Skills_LegendBox_BL_00.png"; + bm->path = "images/ui/SkillSheet/UI_Skills_LegendBox_B_00.png"; + br->path = "images/ui/SkillSheet/UI_Skills_LegendBox_BR_00.png"; + } + else + { + legendDivTxt->setColor(legendGoldColor); + legendText->setColor(legendGoldColor); + tl->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_TL_00.png"; + tm->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_T_00.png"; + tr->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_TR_00.png"; + + ml->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_ML_00.png"; + mm->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_M_00.png"; + mr->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_MR_00.png"; + + bl->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_BL_00.png"; + bm->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_B_00.png"; + br->path = "images/ui/SkillSheet/UI_Skills_LegendBox100_BR_00.png"; + } + + legendPos.h = bl->pos.y + bl->pos.h; + legendFrame->setSize(legendPos); + + lowestY = legendPos.y + legendPos.h; + if ( lowestY < scrollAreaOuterFrame->getSize().h ) + { + // shift the legend items as far to the bottom as possible. + int offset = scrollAreaOuterFrame->getSize().h - lowestY; + legendDivImg->pos.y += offset; + legendBackerImg->pos.y += offset; + legendDivTxt->setPos(legendDivTxt->getSize().x, legendDivTxt->getSize().y + offset); + legendFrame->setPos(legendFrame->getSize().x, legendFrame->getSize().y + offset); + } + //lowestY += 4; // small buffer after legend box + } + + if ( !slider->isDisabled() ) + { + if ( inputs.bPlayerUsingKeyboardControl(player.playernum) ) + { + if ( mousestatus[SDL_BUTTON_WHEELDOWN] ) + { + mousestatus[SDL_BUTTON_WHEELDOWN] = 0; + scrollInertia = std::min(scrollInertia + .05, .15); + } + if ( mousestatus[SDL_BUTTON_WHEELUP] ) + { + mousestatus[SDL_BUTTON_WHEELUP] = 0; + scrollInertia = std::max(scrollInertia - .05, -.15); + } + } + if ( Input::inputs[player.playernum].analog("MenuScrollDown") ) + { + scrollInertia = 0.0; + real_t delta = Input::inputs[player.playernum].analog("MenuScrollDown"); + scrollPercent = std::min(1.0, scrollPercent + .05 * (60.f / std::max(1U, fpsLimit)) * delta); + slider->setValue(scrollPercent * 100); + } + else if ( Input::inputs[player.playernum].analog("MenuScrollUp") ) + { + scrollInertia = 0.0; + real_t delta = Input::inputs[player.playernum].analog("MenuScrollUp"); + scrollPercent = std::max(0.0, scrollPercent -.05 * (60.f / std::max(1U, fpsLimit) * delta)); + slider->setValue(scrollPercent * 100); + } + } + + if ( abs(scrollInertia) > 0.0 ) + { + scrollInertia *= .9; + if ( abs(scrollInertia) < .01 ) + { + scrollInertia = 0.0; + } + scrollPercent = std::min(1.0, std::max(scrollPercent + scrollInertia, 0.00)); + if ( scrollPercent >= 1.0 || scrollPercent <= 0.0 ) + { + scrollInertia = 0.0; + } + slider->setValue(scrollPercent * 100); + } + + scrollPercent = slider->getValue() / 100.0; + scrollAreaPos = scrollArea->getSize(); + if ( lowestY > scrollAreaOuterFrame->getSize().h ) + { + scrollAreaPos.y = -(lowestY - scrollAreaOuterFrame->getSize().h) * scrollPercent; + slider->setDisabled(false); + } + else + { + scrollAreaPos.y = 0; + slider->setDisabled(true); + } + scrollArea->setSize(scrollAreaPos); + } + + bool drawGlyphs = ::inputs.getVirtualMouse(player.playernum)->lastMovementFromController; + if ( auto promptBack = skillFrame->findField("prompt back txt") ) + { + promptBack->setDisabled(!drawGlyphs); + promptBack->setText(language[4053]); + auto promptImg = skillFrame->findImage("prompt back img"); + promptImg->disabled = !drawGlyphs; + SDL_Rect glyphPos = promptImg->pos; + if ( auto textGet = Text::get(promptBack->getText(), promptBack->getFont(), + promptBack->getTextColor(), promptBack->getOutlineColor()) ) + { + SDL_Rect textPos = promptBack->getSize(); + textPos.w = textGet->getWidth(); + textPos.x = innerFrame->getSize().x + innerFrame->getSize().w - textPos.w - 16; + textPos.y = innerFrame->getSize().y + allSkillEntriesLeft->getSize().y + lowestSkillEntryY + 4; + if ( !bUseCompactSkillsView ) + { + textPos.y -= 4; + } + promptBack->setSize(textPos); + glyphPos.x = promptBack->getSize().x + promptBack->getSize().w - textGet->getWidth() - 4; + } + promptImg->path = Input::inputs[player.playernum].getGlyphPathForInput("MenuCancel"); + Image* glyphImage = Image::get(promptImg->path.c_str()); + if ( glyphImage ) + { + glyphPos.w = glyphImage->getWidth(); + glyphPos.h = glyphImage->getHeight(); + glyphPos.x -= glyphPos.w; + glyphPos.y = promptBack->getSize().y + promptBack->getSize().h / 2 - glyphPos.h / 2; + promptImg->pos = glyphPos; + } + } + if ( auto promptScroll = skillFrame->findField("prompt scroll txt") ) + { + promptScroll->setDisabled(!drawGlyphs); + if ( bUseCompactSkillsView ) + { + promptScroll->setText(language[4063]); + } + else + { + promptScroll->setText(language[4062]); + } + auto promptImg = skillFrame->findImage("prompt scroll img"); + promptImg->disabled = !drawGlyphs; + SDL_Rect glyphPos = promptImg->pos; + if ( auto textGet = Text::get(promptScroll->getText(), promptScroll->getFont(), + promptScroll->getTextColor(), promptScroll->getOutlineColor()) ) + { + SDL_Rect textPos = promptScroll->getSize(); + textPos.w = textGet->getWidth(); + if ( bUseCompactSkillsView ) + { + textPos.x = innerFrame->getSize().x + 16; + textPos.y = innerFrame->getSize().y + allSkillEntriesLeft->getSize().y + lowestSkillEntryY + 4; + } + else + { + textPos.x = innerFrame->getSize().x + innerFrame->getSize().w / 2 - textPos.w / 2; + textPos.y = innerFrame->getSize().y + innerFrame->getSize().h - 8; + } + promptScroll->setSize(textPos); + } + promptImg->path = Input::inputs[player.playernum].getGlyphPathForInput("MenuScrollDown"); + Image* glyphImage = Image::get(promptImg->path.c_str()); + if ( glyphImage ) + { + SDL_Rect textPos = promptScroll->getSize(); + glyphPos.w = glyphImage->getWidth(); + glyphPos.h = glyphImage->getHeight(); + glyphPos.x = textPos.x; + if ( !bUseCompactSkillsView ) // centred + { + glyphPos.x -= glyphPos.w / 2 + 2; + } + glyphPos.y = promptScroll->getSize().y + promptScroll->getSize().h / 2 - glyphPos.h / 2; + promptImg->pos = glyphPos; + + textPos.x = glyphPos.x + glyphPos.w + 4; + promptScroll->setSize(textPos); + } + } + + if ( closeSheetAction || mouseClickedOutOfBounds ) + { + closeSkillSheet(); + return; + } + + if ( sliderDisabled != slider->isDisabled() ) + { + // rerun this function + processSkillSheet(); + bSkillSheetEntryLoaded = false; + } + else + { + bSkillSheetEntryLoaded = true; + } + } \ No newline at end of file diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index 6568c10f9..33eb4b67c 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -12,7 +12,24 @@ bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy); void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void loadHUDSettingsJSON(); -SDL_Surface* blitEnemyBar(const int player); +SDL_Surface* blitEnemyBar(const int player, SDL_Surface* statusEffectSprite); +struct EnemyBarSettings_t +{ + std::unordered_map heightOffsets; + std::unordered_map screenDistanceOffsets; + std::string getEnemyBarSpriteName(Entity* entity); + float getHeightOffset(Entity* entity) + { + if ( !entity ) { return 0.f; } + return heightOffsets[getEnemyBarSpriteName(entity)]; + } + float getScreenDistanceOffset(Entity* entity) + { + if ( !entity ) { return 0.f; } + return screenDistanceOffsets[getEnemyBarSpriteName(entity)]; + } +}; +extern EnemyBarSettings_t enemyBarSettings; // if true, use the new user interface extern bool newui; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 33238ecb0..89f95eacf 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -6,6 +6,7 @@ #include "Slider.hpp" #include "Text.hpp" +#include "../init.hpp" #include "../net.hpp" #include "../player.hpp" #include "../stat.hpp" @@ -43,9 +44,10 @@ namespace MainMenu { static const char* bigfont_outline = "fonts/pixelmix.ttf#16#2"; static const char* bigfont_no_outline = "fonts/pixelmix.ttf#16#0"; - static const char* smallfont_outline = "fonts/pixel_maz.ttf#14#2"; - static const char* smallfont_no_outline = "fonts/pixel_maz.ttf#14#0"; - static const char* menu_option_font = "fonts/pixel_maz.ttf#24#2"; + static const char* smallfont_outline = "fonts/pixel_maz.ttf#32#2"; + static const char* smallfont_no_outline = "fonts/pixel_maz.ttf#32#0"; + static const char* menu_option_font = "fonts/pixel_maz.ttf#48#2"; + static const char* banner_font = "fonts/pixel_maz.ttf#64#2"; static inline void soundToggleMenu() { playSound(500, 48); @@ -107,21 +109,23 @@ namespace MainMenu { } // update cursor position - auto cursor = main_menu_frame->findImage("cursor"); - if (cursor) { - cursor->disabled = !buttonSelected; - int diff = main_menu_cursor_y - cursor->pos.y; - if (diff > 0) { - diff = std::max(1, diff / 2); - } else if (diff < 0) { - diff = std::min(-1, diff / 2); + if (main_menu_frame) { + auto cursor = main_menu_frame->findImage("cursor"); + if (cursor) { + cursor->disabled = !buttonSelected; + int diff = main_menu_cursor_y - cursor->pos.y; + if (diff > 0) { + diff = std::max(1, diff / 2); + } else if (diff < 0) { + diff = std::min(-1, diff / 2); + } + cursor->pos = SDL_Rect{ + main_menu_cursor_x + (int)(sinf(main_menu_cursor_bob) * 16.f) - 16, + diff + cursor->pos.y, + 37 * 2, + 23 * 2 + }; } - cursor->pos = SDL_Rect{ - main_menu_cursor_x + (int)(sinf(main_menu_cursor_bob) * 16.f) - 16, - diff + cursor->pos.y, - 37 * 2, - 23 * 2 - }; } } @@ -167,6 +171,59 @@ namespace MainMenu { } } + static void updateSettingSelection(Frame& frame) { + auto& images = frame.getImages(); + for (auto image : images) { + if (image->path == "images/ui/Main Menus/Settings/Settings_Left_BackingSelect00.png") { + image->path = "images/ui/Main Menus/Settings/Settings_Left_Backing00.png"; + } + if (image->path == "images/ui/Main Menus/Settings/GenericWindow/Settings_Left_BackingSelect_Short00.png") { + image->path = "images/ui/Main Menus/Settings/GenericWindow/Settings_Left_Backing_Short00.png"; + } + } + auto selectedWidget = frame.findSelectedWidget(0); + if (selectedWidget) { + std::string setting; + auto name = std::string(selectedWidget->getName()); + if (selectedWidget->getType() == Widget::WIDGET_SLIDER) { + setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_slider") - 1) - (sizeof("setting_") - 1)); + } else if (selectedWidget->getType() == Widget::WIDGET_BUTTON) { + auto button = static_cast(selectedWidget); + auto customize = "images/ui/Main Menus/Settings/Settings_Button_Customize00.png"; + auto binding = "images/ui/Main Menus/Settings/GenericWindow/UI_MM14_ButtonChoosing00.png"; + auto dropdown = "images/ui/Main Menus/Settings/Settings_Drop_ScrollBG02.png"; + if (strcmp(button->getBackground(), customize) == 0) { + setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_customize_button") - 1) - (sizeof("setting_") - 1)); + } else if (strcmp(button->getBackground(), binding) == 0) { + setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_binding_button") - 1) - (sizeof("setting_") - 1)); + } else if (strcmp(button->getBackground(), dropdown) == 0) { + setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_dropdown_button") - 1) - (sizeof("setting_") - 1)); + } else { + setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_button") - 1) - (sizeof("setting_") - 1)); + } + } + if (!setting.empty()) { + auto image = frame.findImage((std::string("setting_") + setting + std::string("_image")).c_str()); + if (image && image->path == "images/ui/Main Menus/Settings/Settings_Left_Backing00.png") { + image->path = "images/ui/Main Menus/Settings/Settings_Left_BackingSelect00.png"; + } + else if (image && image->path == "images/ui/Main Menus/Settings/GenericWindow/Settings_Left_Backing_Short00.png") { + image->path = "images/ui/Main Menus/Settings/GenericWindow/Settings_Left_BackingSelect_Short00.png"; + } + auto field = frame.findField((std::string("setting_") + setting + std::string("_field")).c_str()); + if (field) { + static Widget* current_selected_widget = nullptr; + if (current_selected_widget != selectedWidget) { + current_selected_widget = selectedWidget; + auto settings = static_cast(frame.getParent()); + auto tooltip = settings->findField("tooltip"); assert(tooltip); + tooltip->setText(field->getGuide()); + } + } + } + } + } + static Button* createBackWidget(Frame* parent, void (*callback)(Button&)) { auto back = parent->addFrame("back"); back->setSize(SDL_Rect{5, 5, 66, 36}); @@ -268,6 +325,110 @@ namespace MainMenu { return InventorySorting(); } +/******************************************************************************/ + + inline void Bindings::save() { + FileHelper::writeObject("config/bindings.json", EFileFormat::Json, *this); + } + + inline Bindings Bindings::load() { + Bindings bindings; + bool result = FileHelper::readObject("config/bindings.json", bindings); + return result ? bindings : reset(); + } + + inline Bindings Bindings::reset() { + Bindings bindings; + for (int c = 0; c < 4; ++c) { + bindings.devices[c] = c; + + bindings.kb_mouse_bindings[c].emplace("Move Forward", "W"); + bindings.kb_mouse_bindings[c].emplace("Move Left", "A"); + bindings.kb_mouse_bindings[c].emplace("Move Backward", "S"); + bindings.kb_mouse_bindings[c].emplace("Move Right", "D"); + bindings.kb_mouse_bindings[c].emplace("Turn Left", "Left"); + bindings.kb_mouse_bindings[c].emplace("Turn Right", "Right"); + bindings.kb_mouse_bindings[c].emplace("Look Up", "Up"); + bindings.kb_mouse_bindings[c].emplace("Look Down", "Down"); + bindings.kb_mouse_bindings[c].emplace("Chat", "Return"); + bindings.kb_mouse_bindings[c].emplace("Console Command", "/"); + bindings.kb_mouse_bindings[c].emplace("Character Status", "Tab"); + bindings.kb_mouse_bindings[c].emplace("Spell List", "M"); + bindings.kb_mouse_bindings[c].emplace("Cast Spell", "F"); + bindings.kb_mouse_bindings[c].emplace("Block", "Space"); + bindings.kb_mouse_bindings[c].emplace("Sneak", "Shift"); + bindings.kb_mouse_bindings[c].emplace("Attack", "Mouse1"); + bindings.kb_mouse_bindings[c].emplace("Use", "Mouse3"); + bindings.kb_mouse_bindings[c].emplace("Autosort Inventory", "Y"); + bindings.kb_mouse_bindings[c].emplace("Command NPC", "C"); + bindings.kb_mouse_bindings[c].emplace("Show NPC Commands", "X"); + bindings.kb_mouse_bindings[c].emplace("Cycle NPCs", "Z"); + bindings.kb_mouse_bindings[c].emplace("Hotbar Scroll Left", "["); + bindings.kb_mouse_bindings[c].emplace("Hotbar Scroll Right", "]"); + bindings.kb_mouse_bindings[c].emplace("Hotbar Select", "\\"); + + bindings.gamepad_bindings[c].emplace("Move Forward", "StickLeftY-"); + bindings.gamepad_bindings[c].emplace("Move Left", "StickLeftX-"); + bindings.gamepad_bindings[c].emplace("Move Backward", "StickLeftY+"); + bindings.gamepad_bindings[c].emplace("Move Right", "StickLeftX+"); + bindings.gamepad_bindings[c].emplace("Turn Left", "StickRightX-"); + bindings.gamepad_bindings[c].emplace("Turn Right", "StickRightX+"); + bindings.gamepad_bindings[c].emplace("Look Up", "StickRightY-"); + bindings.gamepad_bindings[c].emplace("Look Down", "StickRightY+"); + bindings.gamepad_bindings[c].emplace("Character Status", "ButtonSelect"); + bindings.gamepad_bindings[c].emplace("Cast Spell", "RightBumper"); + bindings.gamepad_bindings[c].emplace("Block", "LeftTrigger"); + bindings.gamepad_bindings[c].emplace("Sneak", "LeftTrigger"); + bindings.gamepad_bindings[c].emplace("Attack", "RightTrigger"); + bindings.gamepad_bindings[c].emplace("Use", "ButtonA"); + bindings.gamepad_bindings[c].emplace("Command NPC", "DpadY-"); + bindings.gamepad_bindings[c].emplace("Show NPC Commands", "DpadX+"); + bindings.gamepad_bindings[c].emplace("Cycle NPCs", "DpadX-"); + bindings.gamepad_bindings[c].emplace("Hotbar Scroll Left", "ButtonLeftBumper"); + bindings.gamepad_bindings[c].emplace("Hotbar Scroll Right", "ButtonRightBumper"); + bindings.gamepad_bindings[c].emplace("Hotbar Select", "ButtonY"); + } + return bindings; + } + +/******************************************************************************/ + + inline void Minimap::save() { + minimapTransparencyForeground = 100 - foreground_opacity; + minimapTransparencyBackground = 100 - background_opacity; + minimapScale = map_scale; + minimapObjectZoom = icon_scale; + } + + inline Minimap Minimap::load() { + Minimap minimap; + minimap.foreground_opacity = 100 - minimapTransparencyForeground; + minimap.background_opacity = 100 - minimapTransparencyBackground; + minimap.map_scale = minimapScale; + minimap.icon_scale = minimapObjectZoom; + return minimap; + } + + inline Minimap Minimap::reset() { + return Minimap(); + } + +/******************************************************************************/ + + inline void Messages::save() { + FileHelper::writeObject("config/messages.json", EFileFormat::Json, *this); + } + + inline Messages Messages::load() { + Messages messages; + bool result = FileHelper::readObject("config/messages.json", messages); + return result ? messages : reset(); + } + + inline Messages Messages::reset() { + return Messages(); + } + /******************************************************************************/ static const char* intro_text = @@ -428,6 +589,8 @@ namespace MainMenu { Customize = 2, BooleanWithCustomize = 3, Dropdown = 4, + Binding = 5, + //Field = 6, }; Type type; const char* name; @@ -437,7 +600,9 @@ namespace MainMenu { auto_hotbar_new_items = allSettings.add_items_to_hotbar_enabled; allSettings.inventory_sorting.save(); right_click_protect = !allSettings.use_on_release_enabled; + allSettings.minimap.save(); disable_messages = !allSettings.show_messages_enabled; + allSettings.show_messages.save(); hide_playertags = !allSettings.show_player_nametags_enabled; nohud = !allSettings.show_hud_enabled; broadcast = !allSettings.show_ip_address_enabled; @@ -447,11 +612,7 @@ namespace MainMenu { shaking = allSettings.shaking_enabled; bobbing = allSettings.bobbing_enabled; flickerLights = allSettings.light_flicker_enabled; - xres = allSettings.resolution_x; - yres = allSettings.resolution_y; - verticalSync = allSettings.vsync_enabled; vertical_splitscreen = allSettings.vertical_split_enabled; - vidgamma = allSettings.gamma / 100.f; fov = allSettings.fov; fpsLimit = allSettings.fps; master_volume = allSettings.master_volume; @@ -462,6 +623,7 @@ namespace MainMenu { minimapPingMute = !allSettings.minimap_pings_enabled; mute_player_monster_sounds = !allSettings.player_monster_sounds_enabled; mute_audio_on_focus_lost = !allSettings.out_of_focus_audio_enabled; + allSettings.bindings.save(); hotbar_numkey_quick_add = allSettings.numkeys_in_inventory_enabled; mousespeed = allSettings.mouse_sensitivity; reversemouse = allSettings.reverse_mouse_enabled; @@ -479,6 +641,31 @@ namespace MainMenu { svFlags = allSettings.extra_life_enabled ? svFlags | SV_FLAG_LIFESAVING : svFlags & ~(SV_FLAG_LIFESAVING); svFlags = allSettings.cheats_enabled ? svFlags | SV_FLAG_CHEATS : svFlags & ~(SV_FLAG_CHEATS); + // change video mode + switch (allSettings.window_mode) { + case 0: + fullscreen = false; + borderless = false; + break; + case 1: + fullscreen = true; + borderless = false; + break; + case 2: + fullscreen = true; + borderless = true; + break; + default: + assert("Unknown video mode" && 0); + break; + } + vidgamma = allSettings.gamma / 100.f; + verticalSync = allSettings.vsync_enabled; + if ( !changeVideoMode(allSettings.resolution_x, allSettings.resolution_y) ) { + printlog("critical error! Attempting to abort safely...\n"); + mainloop = 0; + } + // transmit server flags if ( !intro && multiplayer == SERVER ) { strcpy((char*)net_packet->data, "SVFL"); @@ -517,7 +704,9 @@ namespace MainMenu { allSettings.add_items_to_hotbar_enabled = true; allSettings.inventory_sorting = InventorySorting::reset(); allSettings.use_on_release_enabled = true; + allSettings.minimap.reset(); allSettings.show_messages_enabled = true; + allSettings.show_messages.reset(); allSettings.show_player_nametags_enabled = true; allSettings.show_hud_enabled = true; allSettings.show_ip_address_enabled = true; @@ -527,6 +716,7 @@ namespace MainMenu { allSettings.shaking_enabled = true; allSettings.bobbing_enabled = true; allSettings.light_flicker_enabled = true; + allSettings.window_mode = 0; allSettings.resolution_x = 1280; allSettings.resolution_y = 720; allSettings.vsync_enabled = true; @@ -542,6 +732,7 @@ namespace MainMenu { allSettings.minimap_pings_enabled = true; allSettings.player_monster_sounds_enabled = true; allSettings.out_of_focus_audio_enabled = true; + allSettings.bindings.reset(); allSettings.numkeys_in_inventory_enabled = true; allSettings.mouse_sensitivity = 32.f; allSettings.reverse_mouse_enabled = false; @@ -560,7 +751,7 @@ namespace MainMenu { allSettings.cheats_enabled = false; } - void settingsCustomizeInventorySorting(Button&); + static void settingsCustomizeInventorySorting(Button&); static void inventorySortingDefaults(Button& button) { soundActivate(); @@ -604,7 +795,7 @@ namespace MainMenu { allSettings.inventory_sorting.save(); } - void settingsCustomizeInventorySorting(Button& button) { + static void settingsCustomizeInventorySorting(Button& button) { soundActivate(); auto window = main_menu_frame->addFrame("inventory_sorting_window"); @@ -867,9 +1058,13 @@ namespace MainMenu { auto number = std::string(slider->getName()).substr(sizeof("sort_slider") - 1); int c = atoi(number.c_str()); auto icon = window->findImage((std::string("sort_slider_img") + std::to_string(c)).c_str()); assert(icon); - icon->disabled = false; - icon->pos.x = slider->getHandleSize().x; - icon->pos.y = slider->getHandleSize().y; + int x = slider->getHandleSize().x; + int y = slider->getHandleSize().y; + if (x || y) { + icon->disabled = false; + icon->pos.x = x; + icon->pos.y = y; + } if (slider->isSelected()) { slider->setHandleImage("images/ui/Main Menus/Settings/AutoSort/AutoSort_SliderBox_BackBrown00.png"); } else { @@ -909,11 +1104,160 @@ namespace MainMenu { ); } - static int settingsAddSubHeader(Frame& frame, int y, const char* name, const char* text) { + static void settingsOpenDropdown(Button& button, const char* name, bool small_dropdown, void(*entry_func)(Frame::entry_t&)) { + std::string dropdown_name = "setting_" + std::string(name) + "_dropdown"; + auto frame = static_cast(button.getParent()); + auto dropdown = frame->addFrame(dropdown_name.c_str()); assert(dropdown); + dropdown->setSize(SDL_Rect{ + button.getSize().x, + button.getSize().y, + 174, + small_dropdown ? 181 : 362 + }); + dropdown->setActualSize(SDL_Rect{0, 0, dropdown->getSize().w, dropdown->getSize().h}); + dropdown->setColor(0); + dropdown->setBorder(0); + dropdown->setDropDown(true); + + auto background = dropdown->addImage( + dropdown->getActualSize(), + 0xffffffff, + small_dropdown ? + "images/ui/Main Menus/Settings/Settings_Drop_ScrollBG01.png" : + "images/ui/Main Menus/Settings/Settings_Drop_ScrollBG00.png", + "background" + ); + + int border = 4; + auto dropdown_list = dropdown->addFrame("list"); + dropdown_list->setSize(SDL_Rect{0, border, dropdown->getSize().w, dropdown->getSize().h - border*2}); + dropdown_list->setActualSize(SDL_Rect{0, 0, dropdown_list->getSize().w, dropdown_list->getSize().h}); + dropdown_list->setColor(0); + dropdown_list->setBorder(0); + dropdown_list->setFont(bigfont_outline); + dropdown_list->setListOffset(SDL_Rect{0, 11, 0, 0}); + dropdown_list->setListJustify(Frame::justify_t::CENTER); + dropdown_list->setScrollBarsEnabled(false); + dropdown_list->select(); + dropdown_list->activate(); + + for (int i = 0;; ++i) { + auto str = std::string("__") + std::to_string(i); + auto find = button.getWidgetActions().find(str); + if (find != button.getWidgetActions().end()) { + auto entry_name = find->second.c_str(); + auto entry = dropdown_list->addEntry(entry_name, false); + entry->text = entry_name; + entry->click = entry_func; + entry->ctrlClick = entry_func; + dropdown_list->resizeForEntries(); + auto size = dropdown_list->getActualSize(); + size.h += 14; + dropdown_list->setActualSize(size); + if (strcmp(button.getText(), entry_name) == 0) { + dropdown_list->setSelection(i); + } + } + else { + auto str = std::string("~__") + std::to_string(i); + auto find = button.getWidgetActions().find(str); + if (find != button.getWidgetActions().end()) { + auto entry_name = find->second.c_str(); + auto entry = dropdown_list->addEntry(entry_name, false); + entry->text = entry_name; + entry->click = [](Frame::entry_t&){soundError();}; + entry->ctrlClick = entry->click; + entry->color = makeColor(127, 127, 127, 255); + dropdown_list->resizeForEntries(); + auto size = dropdown_list->getActualSize(); + size.h += 14; + dropdown_list->setActualSize(size); + if (strcmp(button.getText(), entry_name) == 0) { + dropdown_list->setSelection(i); + } + } else { + break; + } + } + } + dropdown_list->scrollToSelection(true); + + auto selection = dropdown_list->addImage( + SDL_Rect{8, 0, 158, 30}, + 0xffffffff, + "images/ui/Main Menus/Settings/Settings_Drop_SelectBacking00.png", + "selection" + ); + + dropdown_list->setTickCallback([](Widget& widget){ + Frame* dropdown_list = static_cast(&widget); assert(dropdown_list); + auto selection = dropdown_list->findImage("selection"); assert(selection); + if (dropdown_list->getSelection() >= 0 && dropdown_list->getSelection() < dropdown_list->getEntries().size()) { + selection->disabled = false; + int entrySize = 0; + Font* _font = Font::get(bigfont_outline); + if (_font != nullptr) { + entrySize = _font->height(); + entrySize += entrySize / 2; + } + selection->pos.y = dropdown_list->getSelection() * entrySize + 8; + } else { + selection->disabled = true; + } + }); + } + + static void settingsResolution(Button& button) { + settingsOpenDropdown(button, "resolution", false, [](Frame::entry_t& entry){ + soundActivate(); + int new_xres, new_yres; + sscanf(entry.name.c_str(), "%d x %d", &new_xres, &new_yres); + allSettings.resolution_x = new_xres; + allSettings.resolution_y = new_yres; + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); + auto button = settings_subwindow->findButton("setting_resolution_dropdown_button"); assert(button); + auto dropdown = settings_subwindow->findFrame("setting_resolution_dropdown"); assert(dropdown); + button->setText(entry.name.c_str()); + dropdown->removeSelf(); + button->select(); + }); + } + + static void settingsWindowMode(Button& button) { + settingsOpenDropdown(button, "window_mode", true, [](Frame::entry_t& entry){ + soundActivate(); + do { + if (entry.name == "Windowed") { + allSettings.window_mode = 0; + break; + } + if (entry.name == "Fullscreen") { + allSettings.window_mode = 1; + break; + } + if (entry.name == "Borderless") { + allSettings.window_mode = 2; + break; + } + } while (0); + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); + auto button = settings_subwindow->findButton("setting_window_mode_dropdown_button"); assert(button); + auto dropdown = settings_subwindow->findFrame("setting_window_mode_dropdown"); assert(dropdown); + button->setText(entry.name.c_str()); + dropdown->removeSelf(); + button->select(); + }); + } + + static int settingsAddSubHeader(Frame& frame, int y, const char* name, const char* text, bool generic_window = false) { std::string fullname = std::string("subheader_") + name; auto image = frame.addImage( - SDL_Rect{0, y, 1080, 42}, + SDL_Rect{0, y, frame.getSize().w, 42}, 0xffffffff, + generic_window? + "images/ui/Main Menus/Settings/GenericWindow/UI_MM14_HeaderBacking00.png": "images/ui/Main Menus/Settings/Settings_SubHeading_Backing00.png", (fullname + "_image").c_str() ); @@ -922,16 +1266,17 @@ namespace MainMenu { field->setFont(bigfont_outline); field->setText(text); field->setJustify(Field::justify_t::CENTER); - Text* text_image = Text::get(text, field->getFont()); + Text* text_image = Text::get(field->getText(), field->getFont(), + field->getTextColor(), field->getOutlineColor()); int w = text_image->getWidth(); auto fleur_left = frame.addImage( - SDL_Rect{ (1080 - w) / 2 - 26 - 8, y + 6, 26, 30 }, + SDL_Rect{ (image->pos.w - w) / 2 - 26 - 8, y + 6, 26, 30 }, 0xffffffff, "images/ui/Main Menus/Settings/Settings_SubHeading_Fleur00.png", (fullname + "_fleur_left").c_str() ); auto fleur_right = frame.addImage( - SDL_Rect{ (1080 + w) / 2 + 8, y + 6, 26, 30 }, + SDL_Rect{ (image->pos.w + w) / 2 + 8, y + 6, 26, 30 }, 0xffffffff, "images/ui/Main Menus/Settings/Settings_SubHeading_Fleur00.png", (fullname + "_fleur_right").c_str() @@ -939,11 +1284,20 @@ namespace MainMenu { return image->pos.h + 6; } - static int settingsAddOption(Frame& frame, int y, const char* name, const char* text, const char* tip) { + static int settingsAddOption( + Frame& frame, + int y, + const char* name, + const char* text, + const char* tip, + bool _short = false + ) { std::string fullname = std::string("setting_") + name; auto image = frame.addImage( - SDL_Rect{0, y, 382, 52}, + SDL_Rect{0, y, _short ? 278 : 382, 52}, 0xffffffff, + _short ? + "images/ui/Main Menus/Settings/GenericWindow/Settings_Left_Backing_Short00.png": "images/ui/Main Menus/Settings/Settings_Left_Backing00.png", (fullname + "_image").c_str() ); @@ -958,6 +1312,48 @@ namespace MainMenu { return size.h + 10; } + static int settingsAddBinding( + Frame& frame, + int y, + int player_index, + const char* binding, + const char* tip, + void (*callback)(Button&)) + { + std::string fullname = std::string("setting_") + binding; + int result = settingsAddOption(frame, y, binding, binding, tip); + auto button = frame.addButton((fullname + "_binding_button").c_str()); + button->setSize(SDL_Rect{ + 390, + y + 4, + 158, + 44}); + button->setFont(smallfont_outline); + auto device = allSettings.bindings.devices[player_index]; + auto& bindings = + device == 0 ? allSettings.bindings.kb_mouse_bindings[player_index]: + device >= 1 && device <= 4 ? allSettings.bindings.gamepad_bindings[player_index]: + allSettings.bindings.joystick_bindings[player_index]; + auto find = bindings.find(binding); + if (find != bindings.end()) { + button->setText(find->second.c_str()); + } else { + button->setText("[unbound]"); + } + button->setJustify(Button::justify_t::CENTER); + button->setCallback(callback); + button->setBackground("images/ui/Main Menus/Settings/Settings_Button_Customize00.png"); + button->setHighlightColor(makeColor(255,255,255,255)); + button->setColor(makeColor(127,127,127,255)); + button->setTextHighlightColor(makeColor(255,255,255,255)); + button->setTextColor(makeColor(127,127,127,255)); + button->setWidgetSearchParent(frame.getParent()->getName()); + button->setWidgetBack("discard_and_exit"); + button->addWidgetAction("MenuAlt1", "restore_defaults"); + button->addWidgetAction("MenuStart", "confirm_and_exit"); + return result; + } + static int settingsAddBooleanOption( Frame& frame, int y, @@ -987,6 +1383,7 @@ namespace MainMenu { button->setColor(makeColor(127,127,127,255)); button->setTextHighlightColor(makeColor(255,255,255,255)); button->setTextColor(makeColor(127,127,127,255)); + button->setWidgetSearchParent(frame.getParent()->getName()); button->setWidgetBack("discard_and_exit"); button->setWidgetPageLeft("tab_left"); button->setWidgetPageRight("tab_right"); @@ -1022,6 +1419,7 @@ namespace MainMenu { button->setColor(makeColor(127,127,127,255)); button->setTextHighlightColor(makeColor(255,255,255,255)); button->setTextColor(makeColor(127,127,127,255)); + button->setWidgetSearchParent(frame.getParent()->getName()); button->setWidgetLeft((fullname + "_button").c_str()); button->setWidgetBack("discard_and_exit"); button->setWidgetPageLeft("tab_left"); @@ -1029,6 +1427,7 @@ namespace MainMenu { button->addWidgetAction("MenuAlt1", "restore_defaults"); button->addWidgetAction("MenuStart", "confirm_and_exit"); auto boolean = frame.findButton((fullname + "_button").c_str()); assert(boolean); + boolean->setWidgetSearchParent(frame.getParent()->getName()); boolean->setWidgetRight((fullname + "_customize_button").c_str()); boolean->setWidgetBack("discard_and_exit"); boolean->setWidgetPageLeft("tab_left"); @@ -1063,6 +1462,7 @@ namespace MainMenu { button->setColor(makeColor(127,127,127,255)); button->setTextHighlightColor(makeColor(255,255,255,255)); button->setTextColor(makeColor(127,127,127,255)); + button->setWidgetSearchParent(frame.getParent()->getName()); button->setWidgetBack("discard_and_exit"); button->setWidgetPageLeft("tab_left"); button->setWidgetPageRight("tab_right"); @@ -1078,30 +1478,41 @@ namespace MainMenu { const char* text, const char* tip, const std::vector& items, - void (*callback)(Button&)) + const char* selected, + void (*callback)(Button&), + const std::set& grayed_items = {}) { std::string fullname = std::string("setting_") + name; int result = settingsAddOption(frame, y, name, text, tip); - auto button = frame.addButton((fullname + "_dropdown").c_str()); + auto button = frame.addButton((fullname + "_dropdown_button").c_str()); button->setSize(SDL_Rect{ 390, - y + 4, - 158, - 44}); - button->setFont(smallfont_outline); - button->setText(items[0]); + y, + 174, + 52}); + button->setFont(bigfont_outline); + button->setText(selected); button->setJustify(Button::justify_t::CENTER); button->setCallback(callback); - button->setBackground("images/ui/Main Menus/Settings/Settings_Button_Customize00.png"); + button->setBackground("images/ui/Main Menus/Settings/Settings_Drop_ScrollBG02.png"); + button->setBackgroundHighlighted("images/ui/Main Menus/Settings/Settings_Drop_ScrollBG02_Highlighted.png"); button->setHighlightColor(makeColor(255,255,255,255)); button->setColor(makeColor(127,127,127,255)); button->setTextHighlightColor(makeColor(255,255,255,255)); button->setTextColor(makeColor(127,127,127,255)); + button->setWidgetSearchParent(frame.getParent()->getName()); button->setWidgetBack("discard_and_exit"); button->setWidgetPageLeft("tab_left"); button->setWidgetPageRight("tab_right"); button->addWidgetAction("MenuAlt1", "restore_defaults"); button->addWidgetAction("MenuStart", "confirm_and_exit"); + for (int i = 0; i < items.size(); ++i) { + if (grayed_items.find(i) == grayed_items.end()) { + button->addWidgetAction((std::string("__") + std::to_string(i)).c_str(), items[i]); + } else { + button->addWidgetAction((std::string("~__") + std::to_string(i)).c_str(), items[i]); + } + } return result; } @@ -1115,12 +1526,13 @@ namespace MainMenu { float minValue, float maxValue, bool percent, - void (*callback)(Slider&)) + void (*callback)(Slider&), + bool _short = false) { std::string fullname = std::string("setting_") + name; - int result = settingsAddOption(frame, y, name, text, tip); + int result = settingsAddOption(frame, y, name, text, tip, _short); auto box = frame.addImage( - SDL_Rect{402, y + 4, 132, 44}, + SDL_Rect{_short ? 298 : 402, y + 4, 132, 44}, 0xffffffff, "images/ui/Main Menus/Settings/Settings_Value_Backing00.png", (fullname + "_box").c_str() @@ -1151,16 +1563,23 @@ namespace MainMenu { }); } auto slider = frame.addSlider((fullname + "_slider").c_str()); + slider->setOrientation(Slider::orientation_t::SLIDER_HORIZONTAL); slider->setMinValue(minValue); slider->setMaxValue(maxValue); + slider->setBorder(16); slider->setValue(value); - slider->setRailSize(SDL_Rect{field->getSize().x + field->getSize().w + 32, y + 14, 450, 24}); + slider->setRailSize(SDL_Rect{field->getSize().x + field->getSize().w + 32, y + 14, _short ? 282 : 450, 24}); slider->setHandleSize(SDL_Rect{0, 0, 52, 42}); slider->setCallback(callback); slider->setColor(makeColor(127,127,127,255)); slider->setHighlightColor(makeColor(255,255,255,255)); slider->setHandleImage("images/ui/Main Menus/Settings/Settings_ValueSlider_Slide00.png"); - slider->setRailImage("images/ui/Main Menus/Settings/Settings_ValueSlider_Backing00.png"); + if (_short) { + slider->setRailImage("images/ui/Main Menus/Settings/GenericWindow/Settings_ValueSlider_Backing_Short00.png"); + } else { + slider->setRailImage("images/ui/Main Menus/Settings/Settings_ValueSlider_Backing00.png"); + } + slider->setWidgetSearchParent(frame.getParent()->getName()); slider->setWidgetBack("discard_and_exit"); slider->setWidgetPageLeft("tab_left"); slider->setWidgetPageRight("tab_right"); @@ -1190,40 +1609,7 @@ namespace MainMenu { settings_subwindow->setBorder(0); settings_subwindow->setTickCallback([](Widget& widget){ auto frame = static_cast(&widget); - auto& images = frame->getImages(); - for (auto image : images) { - if (image->path == "images/ui/Main Menus/Settings/Settings_Left_BackingSelect00.png") { - image->path = "images/ui/Main Menus/Settings/Settings_Left_Backing00.png"; - } - } - auto selectedWidget = widget.findSelectedWidget(0); - if (selectedWidget) { - std::string setting; - auto name = std::string(selectedWidget->getName()); - if (selectedWidget->getType() == Widget::WIDGET_SLIDER) { - setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_slider") - 1) - (sizeof("setting_") - 1)); - } else if (selectedWidget->getType() == Widget::WIDGET_BUTTON) { - auto button = static_cast(selectedWidget); - auto customize = "images/ui/Main Menus/Settings/Settings_Button_Customize00.png"; - if (strcmp(button->getBackground(), customize) == 0) { - setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_customize_button") - 1) - (sizeof("setting_") - 1)); - } else { - setting = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_button") - 1) - (sizeof("setting_") - 1)); - } - } - if (!setting.empty()) { - auto image = frame->findImage((std::string("setting_") + setting + std::string("_image")).c_str()); - if (image) { - image->path = "images/ui/Main Menus/Settings/Settings_Left_BackingSelect00.png"; - } - auto field = frame->findField((std::string("setting_") + setting + std::string("_field")).c_str()); - if (field) { - auto settings = static_cast(frame->getParent()); - auto tooltip = settings->findField("tooltip"); assert(tooltip); - tooltip->setText(field->getGuide()); - } - } - } + updateSettingSelection(*frame); updateSliderArrows(*frame); }); auto rock_background = settings_subwindow->addImage( @@ -1234,8 +1620,9 @@ namespace MainMenu { ); rock_background->tiled = true; auto slider = settings_subwindow->addSlider("scroll_slider"); + slider->setBorder(24); slider->setOrientation(Slider::SLIDER_VERTICAL); - slider->setRailSize(SDL_Rect{1038, 16, 30, 440}); + slider->setRailSize(SDL_Rect{1040, 8, 30, 440}); slider->setRailImage("images/ui/Main Menus/Settings/Settings_Slider_Backing00.png"); slider->setHandleSize(SDL_Rect{0, 0, 34, 34}); slider->setHandleImage("images/ui/Main Menus/Settings/Settings_Slider_Boulder00.png"); @@ -1245,8 +1632,9 @@ namespace MainMenu { actualSize.y = slider.getValue(); frame->setActualSize(actualSize); auto railSize = slider.getRailSize(); - railSize.y = 16 + actualSize.y; + railSize.y = 8 + actualSize.y; slider.setRailSize(railSize); + slider.updateHandlePosition(); }); slider->setTickCallback([](Widget& widget){ Slider* slider = static_cast(&widget); @@ -1254,8 +1642,9 @@ namespace MainMenu { auto actualSize = frame->getActualSize(); slider->setValue(actualSize.y); auto railSize = slider->getRailSize(); - railSize.y = 16 + actualSize.y; + railSize.y = 8 + actualSize.y; slider->setRailSize(railSize); + slider->updateHandlePosition(); }); auto sliderLeft = settings_subwindow->addImage( SDL_Rect{0, 0, 30, 44}, @@ -1294,10 +1683,16 @@ namespace MainMenu { return std::make_pair( std::string("setting_") + std::string(setting.name) + std::string("_button"), std::string("setting_") + std::string(setting.name) + std::string("_customize_button")); - default: + case Setting::Type::Dropdown: return std::make_pair( - std::string("setting_") + std::string(setting.name) + std::string("_dropdown"), + std::string("setting_") + std::string(setting.name) + std::string("_dropdown_button"), std::string("")); + case Setting::Type::Binding: + return std::make_pair( + std::string("setting_") + std::string(setting.name) + std::string("_binding_button"), + std::string("")); + default: + return std::make_pair(std::string(""), std::string("")); } } @@ -1308,14 +1703,15 @@ namespace MainMenu { } static void settingsSubwindowFinalize(Frame& frame, int y) { - const int height = std::max(224 * 2, y); - frame.setActualSize(SDL_Rect{0, 0, 547 * 2, height}); + auto size = frame.getActualSize(); + const int height = std::max(size.h, y); + frame.setActualSize(SDL_Rect{0, 0, size.w, height}); auto rock_background = frame.findImage("background"); assert(rock_background); rock_background->pos = frame.getActualSize(); auto slider = frame.findSlider("scroll_slider"); assert(slider); slider->setValue(0.f); slider->setMinValue(0.f); - slider->setMaxValue(height - 224 * 2); + slider->setMaxValue(height - size.h); } static void hookSettingToSetting(Frame& frame, const Setting& setting1, const Setting& setting2) { @@ -1349,19 +1745,615 @@ namespace MainMenu { } } + static Frame* settingsGenericWindow( + const char* name, + const char* title, + void (*defaults_callback)(Button&), + void (*discard_callback)(Button&), + void (*confirm_callback)(Button&)) + { + auto dimmer = main_menu_frame->addFrame("dimmer"); + dimmer->setSize(SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}); + dimmer->setActualSize(dimmer->getSize()); + dimmer->setColor(makeColor(0, 0, 0, 63)); + dimmer->setBorder(0); + + auto window = dimmer->addFrame(name); + window->setSize(SDL_Rect{ + (Frame::virtualScreenX - 826) / 2, + (Frame::virtualScreenY - 718) / 2, + 826, + 718}); + window->setActualSize(SDL_Rect{0, 0, 826, 718}); + window->setBorder(0); + window->setColor(0); + + auto tooltip = window->addField("tooltip", 256); + tooltip->setSize(SDL_Rect{30, 566, 766, 54}); + tooltip->setFont(smallfont_no_outline); + tooltip->setJustify(Field::justify_t::CENTER); + tooltip->setText(""); + + auto background = window->addImage( + window->getActualSize(), + 0xffffffff, + "images/ui/Main Menus/Settings/GenericWindow/UI_MM14_Window00.png", + "background" + ); + + auto timber = window->addImage( + window->getActualSize(), + 0xffffffff, + "images/ui/Main Menus/Settings/GenericWindow/UI_MM14_Window01.png", + "timber" + ); + timber->ontop = true; + + auto banner = window->addField("title", 64); + banner->setSize(SDL_Rect{246, 22, 338, 24}); + banner->setFont(banner_font); + banner->setText(title); + banner->setJustify(Field::justify_t::CENTER); + + auto subwindow = window->addFrame("subwindow"); + subwindow->setSize(SDL_Rect{30, 64, 766, 502}); + subwindow->setActualSize(SDL_Rect{0, 0, 766, 502}); + subwindow->setScrollBarsEnabled(false); + subwindow->setBorder(0); + subwindow->setColor(0); + subwindow->setTickCallback([](Widget& widget){ + auto frame = static_cast(&widget); + updateSettingSelection(*frame); + updateSliderArrows(*frame); + }); + + auto rocks = subwindow->addImage( + subwindow->getActualSize(), + makeColor(127, 127, 127, 251), + "images/ui/Main Menus/Settings/GenericWindow/UI_MM14_Rocks00.png", + "background" + ); + rocks->tiled = true; + + auto slider = subwindow->addSlider("scroll_slider"); + slider->setBorder(24); + slider->setOrientation(Slider::SLIDER_VERTICAL); + slider->setRailSize(SDL_Rect{724, 8, 30, 486}); + slider->setRailImage("images/ui/Main Menus/Settings/GenericWindow/UI_MM14_ScrollBar00.png"); + slider->setHandleSize(SDL_Rect{0, 0, 34, 34}); + slider->setHandleImage("images/ui/Main Menus/Settings/GenericWindow/UI_MM14_ScrollBoulder00.png"); + slider->setCallback([](Slider& slider){ + Frame* frame = static_cast(slider.getParent()); + auto actualSize = frame->getActualSize(); + actualSize.y = slider.getValue(); + frame->setActualSize(actualSize); + auto railSize = slider.getRailSize(); + railSize.y = 8 + actualSize.y; + slider.setRailSize(railSize); + slider.updateHandlePosition(); + }); + slider->setTickCallback([](Widget& widget){ + Slider* slider = static_cast(&widget); + Frame* frame = static_cast(slider->getParent()); + auto actualSize = frame->getActualSize(); + slider->setValue(actualSize.y); + auto railSize = slider->getRailSize(); + railSize.y = 8 + actualSize.y; + slider->setRailSize(railSize); + slider->updateHandlePosition(); + }); + + auto sliderLeft = subwindow->addImage( + SDL_Rect{0, 0, 30, 44}, + 0xffffffff, + "images/ui/Main Menus/Settings/AutoSort/AutoSort_SliderBox_Left00.png", + "slider_left" + ); + sliderLeft->disabled = true; + sliderLeft->ontop = true; + + auto sliderRight = subwindow->addImage( + SDL_Rect{0, 0, 30, 44}, + 0xffffffff, + "images/ui/Main Menus/Settings/AutoSort/AutoSort_SliderBox_Right00.png", + "slider_right" + ); + sliderRight->disabled = true; + sliderRight->ontop = true; + + auto defaults = window->addButton("restore_defaults"); + defaults->setBackground("images/ui/Main Menus/Settings/GenericWindow/UI_MM14_ButtonStandard00.png"); + defaults->setColor(makeColor(127, 127, 127, 255)); + defaults->setHighlightColor(makeColor(255, 255, 255, 255)); + defaults->setTextColor(makeColor(127, 127, 127, 255)); + defaults->setTextHighlightColor(makeColor(255, 255, 255, 255)); + defaults->setSize(SDL_Rect{156, 630, 164, 62}); + defaults->setText("Restore\nDefaults"); + defaults->setFont(smallfont_outline); + defaults->setWidgetSearchParent(name); + defaults->setWidgetBack("discard_and_exit"); + defaults->addWidgetAction("MenuStart", "confirm_and_exit"); + defaults->addWidgetAction("MenuAlt1", "restore_defaults"); + defaults->setWidgetRight("discard_and_exit"); + defaults->setCallback(defaults_callback); + + auto discard = window->addButton("discard_and_exit"); + discard->setBackground("images/ui/Main Menus/Settings/GenericWindow/UI_MM14_ButtonStandard00.png"); + discard->setColor(makeColor(127, 127, 127, 255)); + discard->setHighlightColor(makeColor(255, 255, 255, 255)); + discard->setTextColor(makeColor(127, 127, 127, 255)); + discard->setTextHighlightColor(makeColor(255, 255, 255, 255)); + discard->setText("Discard\n& Exit"); + discard->setFont(smallfont_outline); + discard->setSize(SDL_Rect{ + (window->getActualSize().w - 164) / 2, + 630, + 164, + 62} + ); + discard->setCallback(discard_callback); + discard->setWidgetSearchParent(name); + discard->setWidgetBack("discard_and_exit"); + discard->addWidgetAction("MenuStart", "confirm_and_exit"); + discard->addWidgetAction("MenuAlt1", "restore_defaults"); + discard->setWidgetLeft("restore_defaults"); + discard->setWidgetRight("confirm_and_exit"); + + auto confirm = window->addButton("confirm_and_exit"); + confirm->setBackground("images/ui/Main Menus/Settings/GenericWindow/UI_MM14_ButtonStandard00.png"); + confirm->setColor(makeColor(127, 127, 127, 255)); + confirm->setHighlightColor(makeColor(255, 255, 255, 255)); + confirm->setTextColor(makeColor(127, 127, 127, 255)); + confirm->setTextHighlightColor(makeColor(255, 255, 255, 255)); + confirm->setText("Confirm\n& Exit"); + confirm->setFont(smallfont_outline); + confirm->setSize(SDL_Rect{504, 630, 164, 62}); + confirm->setCallback(confirm_callback); + confirm->setWidgetSearchParent(name); + confirm->setWidgetBack("discard_and_exit"); + confirm->addWidgetAction("MenuStart", "confirm_and_exit"); + confirm->addWidgetAction("MenuAlt1", "restore_defaults"); + confirm->setWidgetLeft("discard_and_exit"); + confirm->select(); + + return window; + } + + static void settingsMinimap(Button& button) { + soundActivate(); + auto window = settingsGenericWindow("minimap", "MINIMAP", + [](Button& button){ // restore defaults + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.minimap = Minimap::reset(); + settingsMinimap(button); + }, + [](Button& button){ // discard & exit + soundCancel(); + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.minimap.load(); + }, + [](Button& button){ // confirm & exit + soundActivate(); + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.minimap.save(); + }); + assert(window); + auto subwindow = window->findFrame("subwindow"); assert(subwindow); + int y = 0; + + y += settingsAddSubHeader(*subwindow, y, "scale_header", "Scale", true); + + y += settingsAddSlider(*subwindow, y, "map_scale", "Map scale", + "Scale the map to be larger or smaller.", + 100, 100, 200, true, nullptr, true); + + y += settingsAddSlider(*subwindow, y, "icon_scale", "Icon scale", + "Scale the size of icons on the map (such as players and allies)", + 100, 50, 200, true, nullptr, true); + + y += settingsAddSubHeader(*subwindow, y, "transparency_header", "Transparency", true); + + y += settingsAddSlider(*subwindow, y, "foreground_opacity", "Foreground opacity", + "Set the opacity of the minimap's foreground.", + 100, 0, 100, true, nullptr, true); + + y += settingsAddSlider(*subwindow, y, "background_opacity", "Background opacity", + "Set the opacity of the minimap's background.", + 100, 0, 100, true, nullptr, true); + + hookSettings(*subwindow, + {{Setting::Type::Slider, "map_scale"}, + {Setting::Type::Slider, "icon_scale"}, + {Setting::Type::Slider, "foreground_opacity"}, + {Setting::Type::Slider, "background_opacity"}, + }); + settingsSubwindowFinalize(*subwindow, y); + settingsSelect(*subwindow, {Setting::Type::Slider, "map_scale"}); + } + + static void settingsMessages(Button& button) { + soundActivate(); + auto window = settingsGenericWindow("messages", "MESSAGES", + [](Button& button){ // restore defaults + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.show_messages = Messages::reset(); + settingsMessages(button); + }, + [](Button& button){ // discard & exit + soundCancel(); + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.show_messages.load(); + }, + [](Button& button){ // confirm & exit + soundActivate(); + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.show_messages.save(); + }); + assert(window); + auto subwindow = window->findFrame("subwindow"); assert(subwindow); + int y = 0; + + y += settingsAddSubHeader(*subwindow, y, "categories_header", "Categories", true); + + // TODO bind these to actual settings + + y += settingsAddBooleanOption(*subwindow, y, "messages_combat", "Combat messages", + "Enable report of damage received or given in combat.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_status", "Status messages", + "Enable report of player character status changes and other passive effects.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_inventory", "Inventory messages", + "Enable report of inventory and item appraisal messages.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_equipment", "Equipment messages", + "Enable report of player equipment changes.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_world", "World messages", + "Enable report of diegetic messages, such as speech and text.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_chat", "Player chat", + "Enable multiplayer chat.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_progression", "Progression messages", + "Enable report of player character progression messages (ie level-ups).", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_interaction", "Interaction messages", + "Enable report of player interactions with the world.", + true, nullptr); + + y += settingsAddBooleanOption(*subwindow, y, "messages_inspection", "Inspection messages", + "Enable player inspections of world objects.", + true, nullptr); + + hookSettings(*subwindow, + {{Setting::Type::Boolean, "messages_combat"}, + {Setting::Type::Boolean, "messages_status"}, + {Setting::Type::Boolean, "messages_inventory"}, + {Setting::Type::Boolean, "messages_equipment"}, + {Setting::Type::Boolean, "messages_world"}, + {Setting::Type::Boolean, "messages_chat"}, + {Setting::Type::Boolean, "messages_progression"}, + {Setting::Type::Boolean, "messages_interaction"}, + {Setting::Type::Boolean, "messages_inspection"}, + }); + settingsSubwindowFinalize(*subwindow, y); + settingsSelect(*subwindow, {Setting::Type::Boolean, "messages_combat"}); + } + + static const char* getDeviceNameForIndex(int index) { + switch (index) { + case 0: return "KB & Mouse"; + case 1: return "Gamepad 1"; + case 2: return "Gamepad 2"; + case 3: return "Gamepad 3"; + case 4: return "Gamepad 4"; + case 5: return "Joystick 1"; + case 6: return "Joystick 2"; + case 7: return "Joystick 3"; + case 8: return "Joystick 4"; + default: return "Unknown"; + } + } + + static int getDeviceIndexForName(const char* name) { + if (strcmp(name, "KB & Mouse") == 0) { return 0; } + if (strcmp(name, "Gamepad 1") == 0) { return 1; } + if (strcmp(name, "Gamepad 2") == 0) { return 2; } + if (strcmp(name, "Gamepad 3") == 0) { return 3; } + if (strcmp(name, "Gamepad 4") == 0) { return 4; } + if (strcmp(name, "Joystick 1") == 0) { return 5; } + if (strcmp(name, "Joystick 2") == 0) { return 6; } + if (strcmp(name, "Joystick 3") == 0) { return 7; } + if (strcmp(name, "Joystick 4") == 0) { return 8; } + else { return -1; } + } + + static bool settingsBind(int player_index, int device_index, const char* binding, const char* input) { + assert(binding); + auto& bindings = + device_index == 0 ? allSettings.bindings.kb_mouse_bindings[player_index]: + device_index >= 1 && device_index <= 4 ? allSettings.bindings.gamepad_bindings[player_index]: + allSettings.bindings.joystick_bindings[player_index]; + if (input == nullptr) { + bindings.erase(binding); + return true; + } else { + std::string input_to_store; + if (device_index >= 1 && device_index <= 4 && strncmp(input, "Pad", 3) == 0) { + input_to_store = input + 4; + } + else if (device_index >= 5 && device_index <= 8 && strncmp(input, "Joy", 3) == 0) { + input_to_store = input + 4; + } + else if (device_index == 0 && strncmp(input, "Pad", 3) && strncmp(input, "Joy", 3)) { + input_to_store = input; + } + if (input_to_store.empty()) { + return false; + } else { + bindings.insert_or_assign(binding, input_to_store.c_str()); + return true; + } + } + } + + static void settingsBindings(int player_index, Setting setting_to_select) { + soundActivate(); + auto window = settingsGenericWindow("bindings", "BINDINGS", + [](Button& button){ // restore defaults + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.bindings = Bindings::reset(); + settingsBindings(0, {Setting::Type::Dropdown, "player_dropdown_button"}); + }, + [](Button& button){ // discard & exit + soundCancel(); + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.bindings.load(); + }, + [](Button& button){ // confirm & exit + soundActivate(); + auto parent = static_cast(button.getParent()); assert(parent); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + allSettings.bindings.save(); + }); + assert(window); + auto subwindow = window->findFrame("subwindow"); assert(subwindow); + int y = 0; + + static const std::vector bindings = { + {Setting::Type::Binding, "Move Forward"}, + {Setting::Type::Binding, "Move Left"}, + {Setting::Type::Binding, "Move Backward"}, + {Setting::Type::Binding, "Move Right"}, + {Setting::Type::Binding, "Turn Left"}, + {Setting::Type::Binding, "Turn Right"}, + {Setting::Type::Binding, "Look Up"}, + {Setting::Type::Binding, "Look Down"}, + {Setting::Type::Binding, "Chat"}, + {Setting::Type::Binding, "Console Command"}, + {Setting::Type::Binding, "Character Status"}, + {Setting::Type::Binding, "Spell List"}, + {Setting::Type::Binding, "Cast Spell"}, + {Setting::Type::Binding, "Block"}, + {Setting::Type::Binding, "Sneak"}, + {Setting::Type::Binding, "Attack"}, + {Setting::Type::Binding, "Use"}, + {Setting::Type::Binding, "Autosort Inventory"}, + {Setting::Type::Binding, "Command NPC"}, + {Setting::Type::Binding, "Show NPC Commands"}, + {Setting::Type::Binding, "Cycle NPCs"}, + {Setting::Type::Binding, "Hotbar Scroll Left"}, + {Setting::Type::Binding, "Hotbar Scroll Right"}, + {Setting::Type::Binding, "Hotbar Select"}, + }; + + static bool bind_mode; + static Button* bound_button = nullptr; + static std::string bound_binding = ""; + static std::string bound_input = ""; + static int bound_player; + static int bound_device; + bound_player = player_index; + bound_device = allSettings.bindings.devices[bound_player]; + bind_mode = false; + + y += settingsAddSubHeader(*subwindow, y, "bindings_header", "Profiles", true); + + std::string player_str = "Player " + std::to_string(player_index + 1); + y += settingsAddDropdown(*subwindow, y, "player_dropdown_button", "Player", + "Select the player whose controls you wish to customize.", + {"Player 1", "Player 2", "Player 3", "Player 4"}, player_str.c_str(), + [](Button& button){ + soundActivate(); + settingsOpenDropdown(button, "player_dropdown", true, + [](Frame::entry_t& entry){ + soundActivate(); + auto parent = main_menu_frame->findFrame("bindings"); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + int player_index = (int)(entry.name.back() - '1'); + settingsBindings(player_index, {Setting::Type::Dropdown, "player_dropdown_button"}); + }); + }); + + std::set locked_controllers; + for (int i = 1 + Input::gameControllers.size(); i < 5; ++i) { + locked_controllers.emplace(i); + } + for (int i = 5 + Input::joysticks.size(); i < 9; ++i) { + locked_controllers.emplace(i); + } + + std::vector devices; + devices.reserve(9); + for (int i = 0; i < 9; ++i) { + devices.push_back(getDeviceNameForIndex(i)); + } + + y += settingsAddDropdown(*subwindow, y, "device_dropdown_button", "Device", + "Select a controller for the given player.", devices, getDeviceNameForIndex(allSettings.bindings.devices[player_index]), + [](Button& button){ + soundActivate(); + settingsOpenDropdown(button, "device_dropdown", false, + [](Frame::entry_t& entry){ + soundActivate(); + auto parent = main_menu_frame->findFrame("bindings"); + auto parent_background = static_cast(parent->getParent()); assert(parent_background); + parent_background->removeSelf(); + int device_index = getDeviceIndexForName(entry.text.c_str()); + allSettings.bindings.devices[bound_player] = device_index; + settingsBindings(bound_player, {Setting::Type::Dropdown, "device_dropdown_button"}); + }); + }, locked_controllers); + + y += settingsAddSubHeader(*subwindow, y, "bindings_header", "Bindings", true); + + for (auto& binding : bindings) { + char tip[256]; + snprintf(tip, sizeof(tip), "Bind an input device to %s", binding.name); + y += settingsAddBinding(*subwindow, y, player_index, binding.name, tip, + [](Button& button){ + soundToggle(); + auto& name = std::string(button.getName()); + bind_mode = true; + bound_button = &button; + bound_input = button.getText(); + bound_binding = name.substr(sizeof("setting_") - 1, name.size() - (sizeof("_binding_button") - 1) - (sizeof("setting_") - 1)); + button.setText(". . ."); + auto subwindow = static_cast(button.getParent()); assert(subwindow); + auto settings = static_cast(subwindow->getParent()); assert(settings); + auto tooltip = settings->findField("tooltip"); assert(tooltip); + char buf[256]; + snprintf(buf, sizeof(buf), + "Binding \"%s\". Press ESC to cancel or DEL to delete the binding.\n" + "The next input you activate will be bound to this action.", + bound_binding.c_str()); + tooltip->setText(buf); + Input::clearDefaultBindings(); + for (auto button : subwindow->getButtons()) { + button->setDisabled(true); + } + + auto bindings = main_menu_frame->findFrame("bindings"); assert(bindings); + auto confirm = bindings->findButton("confirm_and_exit"); assert(confirm); + auto discard = bindings->findButton("discard_and_exit"); assert(discard); + auto defaults = bindings->findButton("restore_defaults"); assert(defaults); + confirm->setDisabled(true); + discard->setDisabled(true); + defaults->setDisabled(true); + }); + } + + window->setTickCallback([](Widget&){ + if (bind_mode) { + if (bound_button && !Input::lastInputOfAnyKind.empty()) { + auto bindings = main_menu_frame->findFrame("bindings"); assert(bindings); + auto tooltip = bindings->findField("tooltip"); assert(tooltip); + if (Input::lastInputOfAnyKind == "Escape") { + bound_button->setText(bound_input.c_str()); + char buf[256]; + snprintf(buf, sizeof(buf), "Cancelled rebinding \"%s\"", bound_binding.c_str()); + tooltip->setText(buf); + Input::keys[SDL_SCANCODE_ESCAPE] = 0; + } else if (Input::lastInputOfAnyKind == "Delete") { + (void)settingsBind(bound_player, bound_device, bound_binding.c_str(), nullptr); + bound_button->setText("[unbound]"); + char buf[256]; + snprintf(buf, sizeof(buf), "Deleted \"%s\" binding.", bound_binding.c_str()); + tooltip->setText(buf); + } else { + bool result = settingsBind(bound_player, bound_device, bound_binding.c_str(), Input::lastInputOfAnyKind.c_str()); + if (!result) { + goto bind_failed; + } + auto begin = Input::lastInputOfAnyKind.substr(0, 3); + std:: string newinput = begin == "Pad" || begin == "Joy" ? + Input::lastInputOfAnyKind.substr(4) : Input::lastInputOfAnyKind; + bound_button->setText(newinput.c_str()); + char buf[256]; + snprintf(buf, sizeof(buf), "Bound \"%s\" to \"%s\"", bound_binding.c_str(), newinput.c_str()); + tooltip->setText(buf); + } + bound_button = nullptr; + bind_failed: + // fixes a bug where these are not released after being used + Input::mouseButtons[SDL_BUTTON_WHEELDOWN] = 0; + Input::mouseButtons[SDL_BUTTON_WHEELUP] = 0; + } + else if (!bound_button && + !Input::mouseButtons[SDL_BUTTON_LEFT] && + !Input::mouseButtons[SDL_BUTTON_WHEELDOWN] && + !Input::mouseButtons[SDL_BUTTON_WHEELUP] && + !Input::keys[SDL_SCANCODE_SPACE]) { + auto bindings = main_menu_frame->findFrame("bindings"); assert(bindings); + auto confirm = bindings->findButton("confirm_and_exit"); assert(confirm); + auto discard = bindings->findButton("discard_and_exit"); assert(discard); + auto defaults = bindings->findButton("restore_defaults"); assert(defaults); + confirm->setDisabled(false); + discard->setDisabled(false); + defaults->setDisabled(false); + + auto subwindow = bindings->findFrame("subwindow"); assert(subwindow); + for (auto button : subwindow->getButtons()) { + button->setDisabled(false); + } + + Input::defaultBindings(); + bound_binding = ""; + bind_mode = false; + } + } + }); + + hookSettings(*subwindow, + {{Setting::Type::Dropdown, "player_dropdown_button"}, + {Setting::Type::Dropdown, "device_dropdown_button"}, + bindings[0], + }); + hookSettings(*subwindow, bindings); + settingsSubwindowFinalize(*subwindow, y); + settingsSelect(*subwindow, setting_to_select); + } + void settingsUI(Button& button) { Frame* settings_subwindow; if ((settings_subwindow = settingsSubwindowSetup(button)) == nullptr) { - settingsSelect(*settings_subwindow, {Setting::Type::BooleanWithCustomize, "add_items_to_hotbar"}); + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); + settingsSelect(*settings_subwindow, {Setting::Type::Boolean, "add_items_to_hotbar"}); return; } int y = 0; y += settingsAddSubHeader(*settings_subwindow, y, "inventory", "Inventory Options"); - y += settingsAddBooleanWithCustomizeOption(*settings_subwindow, y, "add_items_to_hotbar", "Add Items to Hotbar", + y += settingsAddBooleanOption(*settings_subwindow, y, "add_items_to_hotbar", "Add Items to Hotbar", "Automatically fill the hotbar with recently collected items.", - allSettings.add_items_to_hotbar_enabled, [](Button& button){soundToggle(); allSettings.add_items_to_hotbar_enabled = button.isPressed();}, - settingsCustomizeInventorySorting); + allSettings.add_items_to_hotbar_enabled, [](Button& button){soundToggle(); allSettings.add_items_to_hotbar_enabled = button.isPressed();}); y += settingsAddCustomize(*settings_subwindow, y, "inventory_sorting", "Inventory Sorting", "Customize the way items are automatically sorted in your inventory.", settingsCustomizeInventorySorting); @@ -1374,11 +2366,11 @@ namespace MainMenu { y += settingsAddSubHeader(*settings_subwindow, y, "hud", "HUD Options"); y += settingsAddCustomize(*settings_subwindow, y, "minimap_settings", "Minimap Settings", "Customize the appearance of the in-game minimap.", - nullptr); + [](Button& button){allSettings.minimap = Minimap::load(); settingsMinimap(button);}); y += settingsAddBooleanWithCustomizeOption(*settings_subwindow, y, "show_messages", "Show Messages", "Customize which messages will be logged to the player, if any.", allSettings.show_messages_enabled, [](Button& button){soundToggle(); allSettings.show_messages_enabled = button.isPressed();}, - nullptr); + [](Button& button){allSettings.show_messages = Messages::load(); settingsMessages(button);}); y += settingsAddBooleanOption(*settings_subwindow, y, "show_player_nametags", "Show Player Nametags", "Display the name of each player character above their avatar.", allSettings.show_player_nametags_enabled, [](Button& button){soundToggle(); allSettings.show_player_nametags_enabled = button.isPressed();}); @@ -1393,7 +2385,7 @@ namespace MainMenu { #ifndef NINTENDO hookSettings(*settings_subwindow, - {{Setting::Type::BooleanWithCustomize, "add_items_to_hotbar"}, + {{Setting::Type::Boolean, "add_items_to_hotbar"}, {Setting::Type::Customize, "inventory_sorting"}, {Setting::Type::Boolean, "use_on_release"}, {Setting::Type::Customize, "minimap_settings"}, @@ -1403,7 +2395,7 @@ namespace MainMenu { {Setting::Type::Boolean, "show_ip_address"}}); #else hookSettings(*settings_subwindow, - {{Setting::Type::BooleanWithCustomize, "add_items_to_hotbar"}, + {{Setting::Type::Boolean, "add_items_to_hotbar"}, {Setting::Type::Customize, "inventory_sorting"}, {Setting::Type::Customize, "minimap_settings"}, {Setting::Type::BooleanWithCustomize, "show_messages"}, @@ -1412,12 +2404,14 @@ namespace MainMenu { #endif settingsSubwindowFinalize(*settings_subwindow, y); - settingsSelect(*settings_subwindow, {Setting::Type::BooleanWithCustomize, "add_items_to_hotbar"}); + settingsSelect(*settings_subwindow, {Setting::Type::Boolean, "add_items_to_hotbar"}); } void settingsVideo(Button& button) { Frame* settings_subwindow; if ((settings_subwindow = settingsSubwindowSetup(button)) == nullptr) { + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); settingsSelect(*settings_subwindow, {Setting::Type::Boolean, "content_control"}); return; } @@ -1445,14 +2439,39 @@ namespace MainMenu { "Toggle the flickering appearance of torches and other light fixtures in the game world.", allSettings.light_flicker_enabled, [](Button& button){soundToggle(); allSettings.light_flicker_enabled = button.isPressed();}); + int selected_res = 0; + std::list resolutions; + getResolutionList(resolutions); + std::vector resolutions_formatted; + std::vector resolutions_formatted_ptrs; + resolutions_formatted.reserve(resolutions.size()); + resolutions_formatted_ptrs.reserve(resolutions.size()); + + int index; + std::list::iterator it; + for (index = 0, it = resolutions.begin(); it != resolutions.end(); ++it, ++index) { + auto& res = *it; + const int x = std::get<0>(res); + const int y = std::get<1>(res); + char buf[32]; + snprintf(buf, sizeof(buf), "%d x %d", x, y); + resolutions_formatted.push_back(std::string(buf)); + resolutions_formatted_ptrs.push_back(resolutions_formatted.back().c_str()); + if (allSettings.resolution_x == x && allSettings.resolution_y == y) { + selected_res = index; + } + } + + const char* selected_mode = fullscreen ? (borderless ? "Borderless" : "Fullscreen") : "Windowed"; + y += settingsAddSubHeader(*settings_subwindow, y, "display", "Display"); #ifndef NINTENDO y += settingsAddDropdown(*settings_subwindow, y, "resolution", "Resolution", "Change the current window resolution.", - {"1280 x 720", "1920 x 1080"}, - nullptr); + resolutions_formatted_ptrs, resolutions_formatted_ptrs[selected_res], + settingsResolution); y += settingsAddDropdown(*settings_subwindow, y, "window_mode", "Window Mode", "Change the current display mode.", - {"Fullscreen", "Borderless", "Windowed"}, - nullptr); + {"Windowed", "Fullscreen", "Borderless"}, selected_mode, + settingsWindowMode); y += settingsAddBooleanOption(*settings_subwindow, y, "vsync", "Vertical Sync", "Prevent screen-tearing by locking the game's refresh rate to the current display.", allSettings.vsync_enabled, [](Button& button){soundToggle(); allSettings.vsync_enabled = button.isPressed();}); @@ -1507,6 +2526,8 @@ namespace MainMenu { void settingsAudio(Button& button) { Frame* settings_subwindow; if ((settings_subwindow = settingsSubwindowSetup(button)) == nullptr) { + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); settingsSelect(*settings_subwindow, {Setting::Type::Slider, "master_volume"}); return; } @@ -1570,6 +2591,8 @@ namespace MainMenu { void settingsControls(Button& button) { Frame* settings_subwindow; if ((settings_subwindow = settingsSubwindowSetup(button)) == nullptr) { + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); settingsSelect(*settings_subwindow, {Setting::Type::Customize, "bindings"}); return; } @@ -1579,7 +2602,7 @@ namespace MainMenu { y += settingsAddSubHeader(*settings_subwindow, y, "general", "General Settings"); y += settingsAddCustomize(*settings_subwindow, y, "bindings", "Bindings", "Modify controls for mouse, keyboard, gamepads, and other peripherals.", - nullptr); + [](Button&){allSettings.bindings = Bindings::load(); settingsBindings(0, {Setting::Type::Dropdown, "player_dropdown_button"});}); y += settingsAddSubHeader(*settings_subwindow, y, "mouse_and_keyboard", "Mouse & Keyboard"); y += settingsAddBooleanOption(*settings_subwindow, y, "numkeys_in_inventory", "Number Keys in Inventory", @@ -1601,6 +2624,7 @@ namespace MainMenu { #ifdef NINTENDO y += settingsAddSubHeader(*settings_subwindow, y, "gamepad", "Controller Settings"); + // TODO y += settingsAddCustomize(*settings_subwindow, y, "bindings", "Bindings", "Modify controller bindings.", nullptr); @@ -1638,6 +2662,8 @@ namespace MainMenu { void settingsGame(Button& button) { Frame* settings_subwindow; if ((settings_subwindow = settingsSubwindowSetup(button)) == nullptr) { + auto settings = main_menu_frame->findFrame("settings"); assert(settings); + auto settings_subwindow = settings->findFrame("settings_subwindow"); assert(settings_subwindow); settingsSelect(*settings_subwindow, {Setting::Type::Boolean, "classic_mode"}); return; } @@ -1744,7 +2770,7 @@ namespace MainMenu { back_button->setSize(SDL_Rect{Frame::virtualScreenX - 400, Frame::virtualScreenY - 70, 380, 50}); back_button->setCallback([](Button& b){ destroyMainMenu(); - createMainMenu(); + createMainMenu(false); mainHallOfRecords(b); auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); auto credits = buttons->findButton("CREDITS"); assert(credits); @@ -1785,7 +2811,7 @@ namespace MainMenu { text1->setColor(makeColor(255, 191, 32, 255)); text1->setHJustify(Field::justify_t::CENTER); text1->setVJustify(Field::justify_t::TOP); - text1->setSize(SDL_Rect{0, Frame::virtualScreenY, Frame::virtualScreenX, font->height() * 81}); + text1->setSize(SDL_Rect{0, Frame::virtualScreenY, Frame::virtualScreenX, font->height() * 82}); text1->setText( u8"Project lead, programming, and design\n" u8" \n" @@ -1836,7 +2862,7 @@ namespace MainMenu { text2->setColor(0xffffffff); text2->setHJustify(Field::justify_t::CENTER); text2->setVJustify(Field::justify_t::TOP); - text2->setSize(SDL_Rect{0, Frame::virtualScreenY, Frame::virtualScreenX, font->height() * 81}); + text2->setSize(SDL_Rect{0, Frame::virtualScreenY, Frame::virtualScreenX, font->height() * 82}); text2->setText( u8" \n" u8"Sheridan Rathbun\n" @@ -1929,7 +2955,7 @@ namespace MainMenu { {"PLAY MODDED GAME", mainPlayModdedGame}, {"HALL OF RECORDS", mainHallOfRecords}, {"SETTINGS", mainSettings}, - {"QUIT", mainQuit} + {"QUIT", mainQuitToDesktop} }; #endif const int num_options = sizeof(options) / sizeof(options[0]); @@ -3211,6 +4237,7 @@ namespace MainMenu { slider->setRailImage("images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_00.png"); slider->setHandleImage("images/ui/Main Menus/Play/PlayerCreation/ClassSelection/ClassSelect_ScrollBar_SliderB_00.png"); slider->setOrientation(Slider::orientation_t::SLIDER_VERTICAL); + slider->setBorder(24); slider->setMinValue(0.f); slider->setMaxValue(subframe->getActualSize().h - 258); slider->setCallback([](Slider& slider){ @@ -3662,8 +4689,6 @@ namespace MainMenu { "backdrop" ); - static const char* banner_font = "fonts/pixel_maz.ttf#36#2"; - auto banner = card->addField("invite_banner", 64); banner->setText((std::string("PLAYER ") + std::to_string(index + 1)).c_str()); banner->setFont(banner_font); @@ -3715,8 +4740,6 @@ namespace MainMenu { "backdrop" ); - static const char* banner_font = "fonts/pixel_maz.ttf#36#2"; - auto banner = card->addField("invite_banner", 64); banner->setText("INVITE"); banner->setFont(banner_font); @@ -3753,7 +4776,7 @@ namespace MainMenu { auto back_button = createBackWidget(lobby, [](Button&){ soundCancel(); destroyMainMenu(); - createMainMenu(); + createMainMenu(false); }); auto back_frame = back_button->getParent(); @@ -3793,7 +4816,13 @@ namespace MainMenu { /******************************************************************************/ static void createPlayWindow() { - auto window = main_menu_frame->addFrame("play_game_window"); + auto dimmer = main_menu_frame->addFrame("dimmer"); + dimmer->setSize(SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}); + dimmer->setActualSize(dimmer->getSize()); + dimmer->setColor(makeColor(0, 0, 0, 63)); + dimmer->setBorder(0); + + auto window = dimmer->addFrame("play_game_window"); window->setSize(SDL_Rect{ (Frame::virtualScreenX - 218 * 2) / 2, (Frame::virtualScreenY - 130 * 2) / 2, @@ -3844,6 +4873,7 @@ namespace MainMenu { soundCancel(); auto frame = static_cast(button.getParent()); frame = static_cast(frame->getParent()); + frame = static_cast(frame->getParent()); frame->removeSelf(); assert(main_menu_frame); auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); @@ -3909,10 +4939,17 @@ namespace MainMenu { // remove "Play Game" window auto frame = static_cast(button.getParent()); + frame = static_cast(frame->getParent()); frame->removeSelf(); + auto dimmer = main_menu_frame->addFrame("dimmer"); + dimmer->setSize(SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}); + dimmer->setActualSize(dimmer->getSize()); + dimmer->setColor(makeColor(0, 0, 0, 63)); + dimmer->setBorder(0); + // create "Local or Network" window - auto window = main_menu_frame->addFrame("local_or_network_window"); + auto window = dimmer->addFrame("local_or_network_window"); window->setSize(SDL_Rect{ (Frame::virtualScreenX - 436) / 2, (Frame::virtualScreenY - 240) / 2, @@ -3939,6 +4976,7 @@ namespace MainMenu { soundCancel(); auto frame = static_cast(button.getParent()); frame = static_cast(frame->getParent()); + frame = static_cast(frame->getParent()); frame->removeSelf(); createPlayWindow(); }); @@ -4108,7 +5146,9 @@ namespace MainMenu { allSettings.add_items_to_hotbar_enabled = auto_hotbar_new_items; allSettings.inventory_sorting = InventorySorting::load(); allSettings.use_on_release_enabled = !right_click_protect; + allSettings.minimap = Minimap::load(); allSettings.show_messages_enabled = !disable_messages; + allSettings.show_messages = Messages::load(); allSettings.show_player_nametags_enabled = !hide_playertags; allSettings.show_hud_enabled = !nohud; allSettings.show_ip_address_enabled = !broadcast; @@ -4118,6 +5158,7 @@ namespace MainMenu { allSettings.shaking_enabled = shaking; allSettings.bobbing_enabled = bobbing; allSettings.light_flicker_enabled = flickerLights; + allSettings.window_mode = fullscreen ? (borderless ? 2 : 1) : 0; allSettings.resolution_x = xres; allSettings.resolution_y = yres; allSettings.vsync_enabled = verticalSync; @@ -4133,6 +5174,7 @@ namespace MainMenu { allSettings.minimap_pings_enabled = !minimapPingMute; allSettings.player_monster_sounds_enabled = !mute_player_monster_sounds; allSettings.out_of_focus_audio_enabled = !mute_audio_on_focus_lost; + allSettings.bindings = Bindings::load(); allSettings.numkeys_in_inventory_enabled = hotbar_numkey_quick_add; allSettings.mouse_sensitivity = mousespeed; allSettings.reverse_mouse_enabled = reversemouse; @@ -4150,7 +5192,13 @@ namespace MainMenu { allSettings.extra_life_enabled = svFlags & SV_FLAG_LIFESAVING; allSettings.cheats_enabled = svFlags & SV_FLAG_CHEATS; - auto settings = main_menu_frame->addFrame("settings"); + auto dimmer = main_menu_frame->addFrame("dimmer"); + dimmer->setSize(SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}); + dimmer->setActualSize(dimmer->getSize()); + dimmer->setColor(makeColor(0, 0, 0, 63)); + dimmer->setBorder(0); + + auto settings = dimmer->addFrame("settings"); settings->setSize(SDL_Rect{(Frame::virtualScreenX - 1126) / 2, (Frame::virtualScreenY - 718) / 2, 1126, 718}); settings->setActualSize(SDL_Rect{0, 0, settings->getSize().w, settings->getSize().h}); settings->setColor(0); @@ -4176,12 +5224,15 @@ namespace MainMenu { settings->setTickCallback([](Widget& widget){ auto settings = static_cast(&widget); - const char* tabs[] = { + std::vector tabs = { "UI", "Video", "Audio", - "Controls" + "Controls", }; + if (!intro) { + tabs.push_back("Game"); + } for (auto name : tabs) { auto button = settings->findButton(name); if (button) { @@ -4194,10 +5245,8 @@ namespace MainMenu { } }); - static const char* pixel_maz_outline = "fonts/pixel_maz.ttf#36#2"; - auto window_title = settings->addField("window_title", 64); - window_title->setFont(pixel_maz_outline); + window_title->setFont(banner_font); window_title->setSize(SDL_Rect{394, 26, 338, 24}); window_title->setJustify(Field::justify_t::CENTER); window_title->setText("SETTINGS"); @@ -4206,23 +5255,28 @@ namespace MainMenu { const char* name; void (*callback)(Button&); }; - Option tabs[] = { + std::vector