From 75a1edcda25b35e8cf083ec1a5a6ba9700711892 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 17 Jul 2023 15:28:11 +0100 Subject: [PATCH] layout unlock status and strength of club stats --- samples/golf/src/golf/Clubs.cpp | 31 ++++ samples/golf/src/golf/Clubs.hpp | 16 ++ samples/golf/src/golf/LeaderboardState.cpp | 12 ++ samples/golf/src/golf/StatsState.cpp | 194 ++++++++++++++++++++- 4 files changed, 246 insertions(+), 7 deletions(-) diff --git a/samples/golf/src/golf/Clubs.cpp b/samples/golf/src/golf/Clubs.cpp index 3b6d30e69..fbb6fb207 100644 --- a/samples/golf/src/golf/Clubs.cpp +++ b/samples/golf/src/golf/Clubs.cpp @@ -120,6 +120,37 @@ std::string Club::getName(bool imperial, float distanceToPin) const } } +std::string Club::getDistanceLabel(bool imperial, std::int32_t level) const +{ + CRO_ASSERT(level > -1 && level < 3, ""); + + auto t = getTargetAtLevel(level); + if (imperial) + { + if (getPower(/*distanceToPin*/10.f, imperial) > 10.f) + { + auto dist = static_cast(t * ToYards); + return m_name + std::to_string(dist) + "y"; + } + else + { + auto dist = static_cast(std::round(t * ToFeet)); + return std::to_string(dist) + "\'"; + } + } + else + { + if (t < 1.f) + { + t *= 100.f; + auto dist = static_cast(t); + return std::to_string(dist) + "cm"; + } + auto dist = static_cast(t); + return std::to_string(dist) + "m"; + } +} + float Club::getPower(float distanceToPin, bool imperial) const { if (m_id == ClubID::Putter) diff --git a/samples/golf/src/golf/Clubs.hpp b/samples/golf/src/golf/Clubs.hpp index daa4af40f..81175a034 100644 --- a/samples/golf/src/golf/Clubs.hpp +++ b/samples/golf/src/golf/Clubs.hpp @@ -60,6 +60,19 @@ struct ClubID final FiveWood, FourIron, SixIron, SevenIron, NineIron }; + static inline std::int32_t getUnlockLevel(std::int32_t id) + { + for (auto i = 0; i < LockedSet.size(); ++i) + { + if (LockedSet[i] == id) + { + return (i + 1) * 5; + } + } + + return 0; + } + static constexpr std::int32_t DefaultSet = Flags[Driver] | Flags[ThreeWood] | Flags[FiveIron] | Flags[EightIron] | Flags[PitchWedge] | Flags[GapWedge] | @@ -78,6 +91,8 @@ class Club final std::string getLabel() const { return m_name; } //doesn't include distance + std::string getDistanceLabel(bool imperial, std::int32_t level) const; + float getPower(float distanceToPin, bool imperial) const; float getAngle() const { return m_angle; } @@ -92,6 +107,7 @@ class Club final float getSideSpinMultiplier() const { return m_sidespin; } float getTopSpinMultiplier() const { return m_topspin; } + float getSpinInfluence() const { return (m_topspin + m_sidespin) / 2.f; } //an average just used for printing stats static std::int32_t getClubLevel(); //0-2 for range static void setClubLevel(std::int32_t); diff --git a/samples/golf/src/golf/LeaderboardState.cpp b/samples/golf/src/golf/LeaderboardState.cpp index 6c4c6144a..a020de5d0 100644 --- a/samples/golf/src/golf/LeaderboardState.cpp +++ b/samples/golf/src/golf/LeaderboardState.cpp @@ -353,6 +353,13 @@ void LeaderboardState::buildScene() Social::refreshHallOfFame(m_courseStrings[m_displayContext.courseIndex].first); refreshDisplay(); + + if (m_sharedData.baseState == StateID::Clubhouse) + { + //ideally we always want to set this but returning to lobby and + //fining the correct tag is a faff. I might do this one day :) + Social::setStatus(Social::InfoID::Menu, { "Viewing Leaderboards" }); + } } break; case RootCallbackData::FadeOut: @@ -363,6 +370,11 @@ void LeaderboardState::buildScene() requestStackPop(); state = RootCallbackData::FadeIn; + + if (m_sharedData.baseState == StateID::Clubhouse) + { + Social::setStatus(Social::InfoID::Menu, { "Clubhouse" }); + } } break; } diff --git a/samples/golf/src/golf/StatsState.cpp b/samples/golf/src/golf/StatsState.cpp index ea100a808..2f0c9eccf 100644 --- a/samples/golf/src/golf/StatsState.cpp +++ b/samples/golf/src/golf/StatsState.cpp @@ -35,8 +35,11 @@ source distribution. #include "GameConsts.hpp" #include "TextAnimCallback.hpp" #include "MessageIDs.hpp" +#include "Clubs.hpp" #include "../GolfGame.hpp" +#include + #include #include #include @@ -66,6 +69,7 @@ source distribution. #include +#include #include namespace @@ -188,7 +192,7 @@ void StatsState::render() void StatsState::buildScene() { auto& mb = getContext().appInstance.getMessageBus(); - m_scene.addSystem(mb);// ->setActiveControllerID(m_sharedData.inputBinding.controllerID); + m_scene.addSystem(mb); m_scene.addSystem(mb); m_scene.addSystem(mb); m_scene.addSystem(mb); @@ -239,6 +243,8 @@ void StatsState::buildScene() m_scene.setSystemActive(true); m_audioEnts[AudioID::Accept].getComponent().play(); + + Social::setStatus(Social::InfoID::Menu, { "Browsing Stats" }); } break; case RootCallbackData::FadeOut: @@ -249,6 +255,8 @@ void StatsState::buildScene() requestStackPop(); state = RootCallbackData::FadeIn; + + Social::setStatus(Social::InfoID::Menu, { "Clubhouse" }); } break; } @@ -325,8 +333,6 @@ void StatsState::buildScene() }); - - //const float bgWidth = bounds.width; const float stride = 102.f; auto sprite = spriteSheet.getSprite("button_highlight"); bounds = sprite.getTextureBounds(); @@ -420,6 +426,152 @@ void StatsState::createClubStatsTab(cro::Entity parent, const cro::SpriteSheet& entity.addComponent() = spriteSheet.getSprite("club_stats"); entity.getComponent().move(glm::vec2(-entity.getComponent().getTextureBounds().width / 2.f, 0.f)); m_tabNodes[TabID::ClubStats].getComponent().addChild(entity.getComponent()); + + const auto& font = m_sharedData.sharedResources->fonts.get(FontID::Info); + + constexpr glm::vec2 BasePos(76.f, 302.f); + constexpr glm::vec2 StatSpacing(160.f, 64.f); + constexpr std::int32_t RowCount = 4; + constexpr float MaxBarWidth = StatSpacing.x - 56.f; + constexpr float BarHeight = 10.f; + constexpr std::array BarColours = + { + cro::Colour(0xb83530ff), //red + cro::Colour(0x467e3eff), //green + cro::Colour(0x6eb39dff), //blue + cro::Colour(0xf2cf5cff) //yellow + }; + struct ColourID final + { + enum + { + Novice, Expert, Pro, Spin + }; + }; + + auto playerLevel = Social::getLevel(); + auto clubFlags = Social::getUnlockStatus(Social::UnlockType::Club); + + const auto createStat = [&](std::int32_t clubID) + { + glm::vec2 statPos = BasePos; + statPos.x += StatSpacing.x * (clubID / RowCount); + statPos.y -= StatSpacing.y * (clubID % RowCount); + + //TODO check if this club is unlocked for current user + //else mark at which level is unlocked + //max range level 0,1,2 and spin influence + auto statNode = m_scene.createEntity(); + statNode.addComponent().setPosition(statPos); + m_tabNodes[TabID::ClubStats].getComponent().addChild(statNode.getComponent()); + + //TODO we need to be able to refresh these labels if the user switch + //to imperial measurements + cro::String label = Clubs[clubID].getLabel() + "\n"; + label += Clubs[clubID].getDistanceLabel(m_sharedData.imperialMeasurements, 0) + "\n"; + label += Clubs[clubID].getDistanceLabel(m_sharedData.imperialMeasurements, 1) + "\n"; + label += Clubs[clubID].getDistanceLabel(m_sharedData.imperialMeasurements, 2) + "\n"; + + std::stringstream ss; + ss.precision(2); + ss << Clubs[clubID].getTopSpinMultiplier() << "/" << Clubs[clubID].getSideSpinMultiplier(); + label += ss.str(); + + auto labelEnt = m_scene.createEntity(); + labelEnt.addComponent(); + labelEnt.addComponent(); + labelEnt.addComponent(font).setString(label); + labelEnt.getComponent().setCharacterSize(InfoTextSize); + labelEnt.getComponent().setFillColour(TextNormalColour); + labelEnt.getComponent().setVerticalSpacing(-1.f); + labelEnt.getComponent().setAlignment(cro::Text::Alignment::Right); + statNode.getComponent().addChild(labelEnt.getComponent()); + + auto unlockLevel = ClubID::getUnlockLevel(clubID); + + label = ((clubFlags & ClubID::Flags[clubID]) == 0) ? "Level " + std::to_string(unlockLevel) : "|"; + label += "\n"; + + if ((playerLevel < 15) || (clubFlags & ClubID::Flags[clubID]) == 0) + { + auto l = std::max(15, unlockLevel); + label += "Level " + std::to_string(l) + "\n"; + } + else + { + label += "|\n"; + } + if ((playerLevel < 30) || (clubFlags & ClubID::Flags[clubID]) == 0) + { + label += "Level 30\n"; + } + else + { + label += "|\n"; + } + label += "Top/Side"; + + labelEnt = m_scene.createEntity(); + labelEnt.addComponent().setPosition({ 4.f, -12.f, 0.3f }); + labelEnt.addComponent(); + labelEnt.addComponent(font).setString(label); + labelEnt.getComponent().setCharacterSize(InfoTextSize); + labelEnt.getComponent().setFillColour(LeaderboardTextDark); + labelEnt.getComponent().setVerticalSpacing(-1.f); + statNode.getComponent().addChild(labelEnt.getComponent()); + + + std::vector verts; + + float barPos = -10.f; + const auto createBar = [&](float width, std::int32_t colourIndex) + { + //fudgenstein. + auto c = BarColours[colourIndex]; + if (colourIndex == 2 && playerLevel < 30 + || colourIndex == 1 && playerLevel < 15) + { + c.setAlpha(0.15f); + } + + if ((clubFlags & ClubID::Flags[clubID]) == 0) + { + c.setAlpha(0.15f); + } + + verts.emplace_back(glm::vec2(0.f, barPos), c); + verts.emplace_back(glm::vec2(0.f, barPos - BarHeight), c); + verts.emplace_back(glm::vec2(width, barPos), c); + verts.emplace_back(glm::vec2(width, barPos), c); + verts.emplace_back(glm::vec2(0.f, barPos - BarHeight), c); + verts.emplace_back(glm::vec2(width, barPos - BarHeight), c); + + barPos -= (BarHeight + 1.f); + }; + + //each level + for (auto i = 0; i < 3; ++i) + { + const float barWidth = MaxBarWidth * (Clubs[clubID].getTargetAtLevel(i) / Clubs[ClubID::Driver].getTargetAtLevel(2)); + createBar(std::round(barWidth), i); + } + + //and spin influence + createBar(std::round(MaxBarWidth * Clubs[clubID].getSpinInfluence()), ColourID::Spin); + + + //draw all the bars with one entity + auto barEnt = m_scene.createEntity(); + barEnt.addComponent().setPosition({ 3.f, 0.f, 0.f }); + barEnt.addComponent().setPrimitiveType(GL_TRIANGLES); + barEnt.getComponent().setVertexData(verts); + statNode.getComponent().addChild(barEnt.getComponent()); + }; + + for (auto i = 0; i < ClubID::Putter; ++i) + { + createStat(i); + } } void StatsState::createPerformanceTab(cro::Entity parent, const cro::SpriteSheet& spriteSheet) @@ -432,11 +584,31 @@ void StatsState::createPerformanceTab(cro::Entity parent, const cro::SpriteSheet const auto centre = parent.getComponent().getTextureBounds().width / 2.f; auto entity = m_scene.createEntity(); - entity.addComponent().setPosition({ centre, 38.f, 0.1f }); + entity.addComponent().setPosition({ centre, 56.f, 0.1f }); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("performance"); entity.getComponent().move(glm::vec2(-entity.getComponent().getTextureBounds().width / 2.f, 0.f)); m_tabNodes[TabID::Performance].getComponent().addChild(entity.getComponent()); + + + //TODO ideally we want to use a menu group to disable + //these - but then this breaks our tab buttons + //(oh if only we used bitflags to set multiple groups + //on any given button....) + + //TODO we could also pad this out with disabled inputs + //so that the column/row count properly matches. + + //cpu checkbox + + //previous course + + //next course + + //previous profile + + //next profile + } void StatsState::createHistoryTab(cro::Entity parent) @@ -445,6 +617,9 @@ void StatsState::createHistoryTab(cro::Entity parent) m_tabNodes[TabID::History].addComponent().setPosition({ 0.f, 0.f, 0.2f }); m_tabNodes[TabID::History].getComponent().setScale(glm::vec2(0.f)); parent.getComponent().addChild(m_tabNodes[TabID::History].getComponent()); + + //TODO pie charts - left side number of times personally played a course + //right side aggregated stats from steam of course plays } void StatsState::createAwardsTab(cro::Entity parent) @@ -454,14 +629,16 @@ void StatsState::createAwardsTab(cro::Entity parent) m_tabNodes[TabID::Awards].getComponent().setScale(glm::vec2(0.f)); parent.getComponent().addChild(m_tabNodes[TabID::Awards].getComponent()); - auto& font = m_sharedData.sharedResources->fonts.get(FontID::UI); + const auto centre = parent.getComponent().getTextureBounds().width / 2.f; + + const auto& font = m_sharedData.sharedResources->fonts.get(FontID::UI); auto entity = m_scene.createEntity(); - entity.addComponent().setPosition({ 18.f, 200.f, 0.1f }); + entity.addComponent().setPosition({ centre, 240.f, 0.1f }); entity.addComponent(); entity.addComponent(font).setString("No Awards... sad :("); entity.getComponent().setCharacterSize(UITextSize); entity.getComponent().setFillColour(TextNormalColour); - + centreText(entity); m_tabNodes[TabID::Awards].getComponent().addChild(entity.getComponent()); } @@ -483,6 +660,9 @@ void StatsState::activateTab(std::int32_t tabID) bounds.bottom = bounds.height * m_currentTab; m_tabEntity.getComponent().setTextureRect(bounds); + //TODO set column count to make performance menu more intuitive when + //navigating with a controller / steam deck + m_audioEnts[AudioID::Accept].getComponent().play(); }