From 6a25384ed9dab0799554c988b7c3351c19a3abdc Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Mon, 30 Apr 2018 00:27:03 +0200 Subject: [PATCH] SuperTux in Story Mode (and other improvements) (#3207) * Add SuperTux difficulty & update number of karts Also make the expert challenge slightly easier to match more the difficulty of other challenges. * Add SuperTux difficulty & update number of karts & points required Also give some more time margin in easier difficulties, as it is a hard challenge compared to most. * Add SuperTux difficulty & update number of karts & points required Also change the lap count to 4 as it is a very short track (sub 30s) * Add SuperTux difficulty Also tweak the expert challenge to have a more appropriate difficulty * Add SuperTux difficulty & update number of karts * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required Also correct the requirement position, since this is not a FTL race anymore. * Add SuperTux difficulty & update number of karts & points required Also slight balancing improvements for the usual difficulties. * Add SuperTux difficulty & update number of karts & points required Also adds a position requirement in expert * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required Also change the number of laps to 5, as this is a very short track. The time requirements for easier difficulties have been kept proportionally similar to before. * Add SuperTux difficulty & update number of karts & points required Also change the number of laps to 4. * Add SuperTux difficulty & update number of karts & points required Also add a position requirement to expert and intermediate. * Add SuperTux difficulty & update number of karts & points required Also change the number of laps to 4, as a lap often is 30s or less in expert/supertux * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Rename islandtrack.challenge to gran_paradiso.challenge * Rename challenge file * Add SuperTux difficulty & update number of karts & points required Also makes the time limit in expert less easy and tweak position requirement. * Add SuperTux difficulty & update number of karts * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required Doesn't unlock the SuperTux difficulty anymore - it's managed elsewhere. * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add new unlock challenges, for difficulty and karts * Add a lap to oliver's math class * Replace Northern Resort by Volcano Island * Replace Volcano Island by Candela City Candela City was in no (official) GP before this. Also sets Green Valley to 3 laps. * Add Northern Resort and remove Fort Magma In 0.9.3, this GP has only 4 races in Story Mode (5 for the other GPs) because Fort Magma is locked. Of all the tracks outside this GP before, Northern Resort is one of the hardest, the AI being rather good there. * Temporary cup for SuperTux challenges Recolored version of the gold cup * Update challenge selection UI for the SuperTux challenges * GUI used before SuperTux difficulty unlocking This is the old select_challenge.stkgui * Swap the two sara * Replace Kiki by another kart to unlock on Benau's demand * Update for improved Story Mode * Update for improved Story Mode * Add support for SuperTux challenges * Add support for SuperTux challenges * Add support for SuperTux challenges * Add support for SuperTux challenges * Update for SuperTux ; also adds the ability to unlock a challenge by points * Update for unlocking by points * Add support for SuperTux challenges * Add support for SuperTux challenges * Add support for SuperTux challenges * Minor changes to function calls * Update for SuperTux challenges * Add support for SuperTux challenges * Update for Story Mode GP changes * Allows to display the correct number of points for GP challenges * Set the unlock of the 1st bonus kart to correct non-test value * Add support for SuperTux challenges Including a bigger challenge selection diaolg * Add default value * Icon to indicate that there is an unlockable The number of points needed to unlock it are displayed next to it. * Changed format : the point requirements is now specified in the file * Changed format : the point requirements is now specified in the file * Changed format : the point requirements is now specified in the file * Function for unlock by points UI * Add default for unlock list node and use requirements node for all * Make unlockByPoints simpler and more flexible Now the code will iterate in StoryModeStatus and send the unlock_list challenges for treatment here. The question of getting the right challenge statuses beings solved, it allows for a great simplification and much more flexibility * Update unlockByPoints declaration * Adds support for next unlockable UI * Improve call of unlockByPoints Also calculations for displaying in the UI how many point the next unlockable by points requires. * Add icon for next unlockable * Displays icon/number to make the player aware of the next unlockable Also displays the number below the icon rather than on the side, for more clarity. * Changes to display karts in the unlock scene * Update unlock functions declarations * New function to clarify code and more logical recently unlocked list management In the previous version, everything was added to the recently unlocked list at some point, necessitating a clearing at the end of computeActive, which also removed from the list the non-race challenges. Checking if the feature is newly unlocked to add it to the list remove the need of that clearing. * Declaration for unlockFeatureByList * Display newly unlocked karts * Display newly unlocked karts * Clear the list of recently unlocked features at the end * Update testing code * Update unlocks finding function call * Improve UI scaling * Fixes indentation * Update the number of points before checking for unlock by points * Add const to declarations * Remove const_cast * Remove a const_cast There are other const_cast in the menu debug items (but they are unrelated to this PR) * Fix menu being bolder --- data/challenges/abyss.challenge | 13 +- ...{city.challenge => candela_city.challenge} | 17 ++- ...ungle.challenge => cocoa_temple.challenge} | 15 +- data/challenges/cornfield_crossing.challenge | 11 +- data/challenges/fortmagma.challenge | 10 +- data/challenges/gp1.challenge | 15 +- data/challenges/gp2.challenge | 15 +- data/challenges/gp3.challenge | 15 +- data/challenges/gp4.challenge | 13 +- ...hallenge => granparadiso_island.challenge} | 16 ++- ...llway.challenge => green_valley.challenge} | 15 +- data/challenges/hacienda.challenge | 19 ++- data/challenges/lighthouse.challenge | 24 ++-- data/challenges/mansion.challenge | 22 +-- data/challenges/mines.challenge | 19 ++- data/challenges/minigolf.challenge | 17 ++- data/challenges/olivermath.challenge | 22 +-- data/challenges/sandtrack.challenge | 9 +- data/challenges/scotland.challenge | 11 +- data/challenges/snowmountain.challenge | 18 ++- data/challenges/snowpeak.challenge | 19 ++- data/challenges/startrack.challenge | 19 --- data/challenges/stk_enterprise.challenge | 24 ++++ data/challenges/unlock_bonus_kart1.challenge | 8 ++ data/challenges/unlock_bonus_kart2.challenge | 8 ++ data/challenges/unlock_supertux.challenge | 8 ++ data/challenges/volcano_island.challenge | 13 +- data/challenges/xr591.challenge | 11 +- data/challenges/zengarden.challenge | 11 +- data/grandprix/1_penguinplayground.grandprix | 2 +- data/grandprix/2_offthebeatentrack.grandprix | 10 +- data/grandprix/3_tothemoonandback.grandprix | 5 +- data/grandprix/4_atworldsend.grandprix | 7 +- data/gui/cup_platinum.png | Bin 0 -> 14110 bytes data/gui/mystery_unlock.png | Bin 0 -> 7994 bytes data/gui/select_challenge.stkgui | 15 +- data/gui/select_challenge_nobest.stkgui | 46 ++++++ src/challenges/challenge_data.cpp | 104 ++++++++------ src/challenges/challenge_data.hpp | 25 +++- src/challenges/challenge_status.cpp | 32 ++++- src/challenges/challenge_status.hpp | 11 +- src/challenges/story_mode_status.cpp | 132 ++++++++++++++---- src/challenges/story_mode_status.hpp | 15 +- src/challenges/unlock_manager.cpp | 65 +++++++-- src/challenges/unlock_manager.hpp | 9 +- src/config/player_profile.hpp | 8 +- src/modes/cutscene_world.cpp | 17 ++- src/modes/overworld.cpp | 12 +- .../dialogs/select_challenge.cpp | 39 +++++- src/states_screens/feature_unlocked.cpp | 46 ++++-- src/states_screens/feature_unlocked.hpp | 11 +- src/states_screens/main_menu_screen.cpp | 11 +- src/states_screens/race_gui_overworld.cpp | 69 ++++++++- src/states_screens/race_gui_overworld.hpp | 5 +- src/states_screens/race_result_gui.cpp | 11 +- 55 files changed, 837 insertions(+), 307 deletions(-) rename data/challenges/{city.challenge => candela_city.challenge} (50%) rename data/challenges/{jungle.challenge => cocoa_temple.challenge} (63%) rename data/challenges/{islandtrack.challenge => granparadiso_island.challenge} (54%) rename data/challenges/{tuxtollway.challenge => green_valley.challenge} (54%) delete mode 100644 data/challenges/startrack.challenge create mode 100644 data/challenges/stk_enterprise.challenge create mode 100644 data/challenges/unlock_bonus_kart1.challenge create mode 100644 data/challenges/unlock_bonus_kart2.challenge create mode 100644 data/challenges/unlock_supertux.challenge create mode 100644 data/gui/cup_platinum.png create mode 100644 data/gui/mystery_unlock.png create mode 100644 data/gui/select_challenge_nobest.stkgui diff --git a/data/challenges/abyss.challenge b/data/challenges/abyss.challenge index ad94e9d03..e67359867 100644 --- a/data/challenges/abyss.challenge +++ b/data/challenges/abyss.challenge @@ -1,15 +1,20 @@ - + + + + + + - - + + - + diff --git a/data/challenges/city.challenge b/data/challenges/candela_city.challenge similarity index 50% rename from data/challenges/city.challenge rename to data/challenges/candela_city.challenge index a3eb1d71f..b8a02ff3c 100644 --- a/data/challenges/city.challenge +++ b/data/challenges/candela_city.challenge @@ -1,21 +1,26 @@ - + + - + + + + + - + - + - + - + diff --git a/data/challenges/jungle.challenge b/data/challenges/cocoa_temple.challenge similarity index 63% rename from data/challenges/jungle.challenge rename to data/challenges/cocoa_temple.challenge index b3e280633..4df77a563 100644 --- a/data/challenges/jungle.challenge +++ b/data/challenges/cocoa_temple.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/cornfield_crossing.challenge b/data/challenges/cornfield_crossing.challenge index ffd39e5da..39c48155c 100644 --- a/data/challenges/cornfield_crossing.challenge +++ b/data/challenges/cornfield_crossing.challenge @@ -1,15 +1,20 @@ - + + + + + + - + - + diff --git a/data/challenges/fortmagma.challenge b/data/challenges/fortmagma.challenge index 8868e0270..ce094635a 100644 --- a/data/challenges/fortmagma.challenge +++ b/data/challenges/fortmagma.challenge @@ -1,9 +1,14 @@ - + + - + + + + + @@ -19,6 +24,5 @@ - diff --git a/data/challenges/gp1.challenge b/data/challenges/gp1.challenge index 101ab4fc7..b54da93f6 100644 --- a/data/challenges/gp1.challenge +++ b/data/challenges/gp1.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/gp2.challenge b/data/challenges/gp2.challenge index 89a477cd3..2a1043a9c 100644 --- a/data/challenges/gp2.challenge +++ b/data/challenges/gp2.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/gp3.challenge b/data/challenges/gp3.challenge index 44fce9222..7adf3ed2d 100644 --- a/data/challenges/gp3.challenge +++ b/data/challenges/gp3.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/gp4.challenge b/data/challenges/gp4.challenge index f3a24c61e..4ed7eeb90 100644 --- a/data/challenges/gp4.challenge +++ b/data/challenges/gp4.challenge @@ -1,19 +1,24 @@ - + + + + + + - + - + - + diff --git a/data/challenges/islandtrack.challenge b/data/challenges/granparadiso_island.challenge similarity index 54% rename from data/challenges/islandtrack.challenge rename to data/challenges/granparadiso_island.challenge index bc9b888d6..a0a14f1a8 100644 --- a/data/challenges/islandtrack.challenge +++ b/data/challenges/granparadiso_island.challenge @@ -1,20 +1,24 @@ - + + - + + + + + - + - + - + - diff --git a/data/challenges/tuxtollway.challenge b/data/challenges/green_valley.challenge similarity index 54% rename from data/challenges/tuxtollway.challenge rename to data/challenges/green_valley.challenge index 0dadd8fec..a327b090f 100644 --- a/data/challenges/tuxtollway.challenge +++ b/data/challenges/green_valley.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/hacienda.challenge b/data/challenges/hacienda.challenge index e90007f95..eee7e9fcb 100644 --- a/data/challenges/hacienda.challenge +++ b/data/challenges/hacienda.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - - + + - - + + - + diff --git a/data/challenges/lighthouse.challenge b/data/challenges/lighthouse.challenge index 8303cb977..d4679a061 100644 --- a/data/challenges/lighthouse.challenge +++ b/data/challenges/lighthouse.challenge @@ -1,20 +1,24 @@ - - + + + - + + + + + - - + + - - + + - - + + - diff --git a/data/challenges/mansion.challenge b/data/challenges/mansion.challenge index f3ecabb20..ebd4225b5 100644 --- a/data/challenges/mansion.challenge +++ b/data/challenges/mansion.challenge @@ -1,18 +1,24 @@ - + + - + + + + + + - - + + - - + + - - + + diff --git a/data/challenges/mines.challenge b/data/challenges/mines.challenge index 00e78ee08..1e7c66f35 100644 --- a/data/challenges/mines.challenge +++ b/data/challenges/mines.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - - + + - - + + - + diff --git a/data/challenges/minigolf.challenge b/data/challenges/minigolf.challenge index 9205f1607..1e6cc7fe4 100644 --- a/data/challenges/minigolf.challenge +++ b/data/challenges/minigolf.challenge @@ -1,19 +1,24 @@ - - + + + - + + + + + - + - + - + diff --git a/data/challenges/olivermath.challenge b/data/challenges/olivermath.challenge index 66768fe73..c6176c1c1 100644 --- a/data/challenges/olivermath.challenge +++ b/data/challenges/olivermath.challenge @@ -1,20 +1,24 @@ - - + + + - + + + + + - - + + - + - - + + - diff --git a/data/challenges/sandtrack.challenge b/data/challenges/sandtrack.challenge index 189418b8e..eccda5a31 100644 --- a/data/challenges/sandtrack.challenge +++ b/data/challenges/sandtrack.challenge @@ -1,12 +1,17 @@ - + + + + + + - + diff --git a/data/challenges/scotland.challenge b/data/challenges/scotland.challenge index a5ad68208..e0edd8743 100644 --- a/data/challenges/scotland.challenge +++ b/data/challenges/scotland.challenge @@ -1,15 +1,20 @@ - + + + + + + - + - + diff --git a/data/challenges/snowmountain.challenge b/data/challenges/snowmountain.challenge index 0357581f4..1d3020764 100644 --- a/data/challenges/snowmountain.challenge +++ b/data/challenges/snowmountain.challenge @@ -1,18 +1,24 @@ - + + - + + + + + + - - + + - + - + diff --git a/data/challenges/snowpeak.challenge b/data/challenges/snowpeak.challenge index e6c05e284..6d9f829fc 100644 --- a/data/challenges/snowpeak.challenge +++ b/data/challenges/snowpeak.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - - + + - + - - + + diff --git a/data/challenges/startrack.challenge b/data/challenges/startrack.challenge deleted file mode 100644 index 446eecd46..000000000 --- a/data/challenges/startrack.challenge +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/data/challenges/stk_enterprise.challenge b/data/challenges/stk_enterprise.challenge new file mode 100644 index 000000000..fdf8b9d3e --- /dev/null +++ b/data/challenges/stk_enterprise.challenge @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/challenges/unlock_bonus_kart1.challenge b/data/challenges/unlock_bonus_kart1.challenge new file mode 100644 index 000000000..1766033a1 --- /dev/null +++ b/data/challenges/unlock_bonus_kart1.challenge @@ -0,0 +1,8 @@ + + + + + + + diff --git a/data/challenges/unlock_bonus_kart2.challenge b/data/challenges/unlock_bonus_kart2.challenge new file mode 100644 index 000000000..c54f20a83 --- /dev/null +++ b/data/challenges/unlock_bonus_kart2.challenge @@ -0,0 +1,8 @@ + + + + + + + diff --git a/data/challenges/unlock_supertux.challenge b/data/challenges/unlock_supertux.challenge new file mode 100644 index 000000000..4b25f6320 --- /dev/null +++ b/data/challenges/unlock_supertux.challenge @@ -0,0 +1,8 @@ + + + + + + + diff --git a/data/challenges/volcano_island.challenge b/data/challenges/volcano_island.challenge index 10250c4f1..23eaa6716 100644 --- a/data/challenges/volcano_island.challenge +++ b/data/challenges/volcano_island.challenge @@ -1,19 +1,24 @@ - + + + + + + - + - + - + diff --git a/data/challenges/xr591.challenge b/data/challenges/xr591.challenge index 44efb0539..73b0132e6 100644 --- a/data/challenges/xr591.challenge +++ b/data/challenges/xr591.challenge @@ -1,12 +1,17 @@ - + + + + + + - + @@ -17,5 +22,3 @@ - - diff --git a/data/challenges/zengarden.challenge b/data/challenges/zengarden.challenge index 7d9b4b7fc..4e9b9aba9 100644 --- a/data/challenges/zengarden.challenge +++ b/data/challenges/zengarden.challenge @@ -1,9 +1,14 @@ - - + + + - + + + + + diff --git a/data/grandprix/1_penguinplayground.grandprix b/data/grandprix/1_penguinplayground.grandprix index 3cfd998aa..096a84799 100644 --- a/data/grandprix/1_penguinplayground.grandprix +++ b/data/grandprix/1_penguinplayground.grandprix @@ -3,7 +3,7 @@ - + diff --git a/data/grandprix/2_offthebeatentrack.grandprix b/data/grandprix/2_offthebeatentrack.grandprix index a53170f6b..52996845c 100644 --- a/data/grandprix/2_offthebeatentrack.grandprix +++ b/data/grandprix/2_offthebeatentrack.grandprix @@ -1,10 +1,10 @@ - - - - - + + + + + diff --git a/data/grandprix/3_tothemoonandback.grandprix b/data/grandprix/3_tothemoonandback.grandprix index f5535d00f..5552d911a 100644 --- a/data/grandprix/3_tothemoonandback.grandprix +++ b/data/grandprix/3_tothemoonandback.grandprix @@ -2,10 +2,9 @@ - + - + - diff --git a/data/grandprix/4_atworldsend.grandprix b/data/grandprix/4_atworldsend.grandprix index 0aa43f1e2..a12a32259 100644 --- a/data/grandprix/4_atworldsend.grandprix +++ b/data/grandprix/4_atworldsend.grandprix @@ -1,11 +1,10 @@ - - + + + - - diff --git a/data/gui/cup_platinum.png b/data/gui/cup_platinum.png new file mode 100644 index 0000000000000000000000000000000000000000..8a28e49e1cdef0e7eaf006c7ec969e4dd9d375f8 GIT binary patch literal 14110 zcmV+(H{r;MP)W(V_03ZNKL_t(|+U=cpoE=s9|KDeB-(IsxcGG)-5SkD=NK-%*=|u>F^e)Ix z1w_S;2ok`rf(S?#1q>ZUdJk!Y^tzj5`>k{Dotg9dW9H7z%$>X0geVE*>}y_o@7$^9 z`JAVp=Ljh!Uz8? z0%pt4c?W&&0U(4p4EP5S-GbBq2Am+J)IYENqkPVZzYp-l77`!e8^E805Ml=h*#3w= z4R{5pcJ3cEteG>ey@WmY*^|*jT8IY2ENNTACyN&I#GfAE?b$E+UG8^M%FA{DVB05p zH89EAt^lSwe?RZ%Kj8dx&Zat2PAm{6pae*3Dj6+DCZ8pf&$4L6axVYc8T7PwdN2Pz z&;_gj<`p(6W!DY>{O<`}15C3BJ=yVpXsL6~zVT|#Kkpo3p$O4Hm}oGJ0CI+gs^!RN zSu(jCLck-BJ;Cp`+e2Rlw%PK4Q*38q< z4THR{p$LK0Bm#jZb@GOWrt28eAgBaL_Qq)*H;R=DJ{id6hg#d=j(sU1#FM}Sz;jYc zZ3h6hjOaT#h`#HVUT4^ZF@yp^^16l)0*NFqbc6syO7gl!Ue^gI3ai$)@&3PFXY@{! z1_}VbRt}tOZ5=|0M}WTqZ)|I3fGy;*1u1sO4U0%LZb%(!Wj89QkL z4UP3kDG@?2cGz$XX^#AWVj4h(G|&wLMJO23;LhLQ#oDDSnR(R(>@s#Dsa%Gnnjx7^ z(;JVo^pmB0GG{)EKb*(X59gDLCpPC1S6V>$qm**R4gk0bzd!Ip;L8I!e$>>7jNff% z#!Q*W*vS(ZJ9ZQ!8;24O1Q9}D{$YRq&7HHDy4y5%oiYU}CC=AY6hT{O2TwfsIEz1= z$1eNrWh;wPN+ekSW#4xsx?!-kdn1dNE$5SY3t2LEA@g5$z-_>fq?GY5 zGysGUdjmfQ_V4ewKrq0x17>jWaYu2`mk*?+eh`X9Ttz4dLEmSv_Vs}vtLM4q+V4|Q zUBym&PG`u_Aq0a#y1KeqJpW_feD-C^tE!l|+s>SQ))@?~Y`~C)MRaW98x9~Ce*2_k z{>r7ye(Xt}yYErj*S7WdS{s0Cq?Gr5;Q%0nh+(>0FDd1aio~LP=_`kD;Mb01|NZx& zuDl8%EaDu4S`lEaVv3Nt3` zOj$5u6IWVdLShlxL2xhO&3y(^_KB8v7tG_?NB_Y~5B{A*cTcJAxDPm6N}2yc06++_ z2XH?y-tYHa58037&p(sB_uPZBNYqW#%>Y2(eoG`+`xFE@L!(Pgkk>UD%Brad#lT|q zCc&})o1?vtu=a00T)+u{0{1YaK~B^7&zo;@=XJj*rCh!Yd_zjv`?&^y5TePll^eak z?Rmt(ocY}gnKo&XNjSwrI4`UB0fUp^e*2tap`a%?TS2}%Wq*PAZZ2Th$9jqG;uOwf zr6i~2dE}AD`P22kBArNjyWkZm<$<4f0L%uCbna^&JBn-X_$AY)OtD9yJ%UBw$QGiQ z@FhR{=mCXe3S_tc3xTsAWZ}=+ux3w@w8rJp-o=SlFM_LkO`O@Sfv4$6fMmF1+$0BEhiT`A!h{aRTo??`SU& z+0Pm%#C3$@8C)E|>JRDo?$!RCgm-cP*JsbjcOFxCx1^fpnwgg{=ihHQAK@2L%J22l zhYB#!ay|*bur?L=SW4Nm)g3_=+x|N6P2hlj&Pxd)-UMC+{sX+`0mLBZ!TWvVD_n8S z6$BL11~+@$B8aevA;2&|2x%#DAdpDI4GM`sNQ?N=BEEEtc!|Bwey@2*aaOf=cLz;>tp{MTyzzoYu+85XxPk_tO3?am;z{9{}Qp)yE1ppz$ zIN%q+*BDsnxZJYi3%2|QA;j a72;ziTv>S0jO}o+F*h+VWN)Yzi6DKp>FPFbQfC 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

WloTq$KYW&tSIjFESr`#0x(^IPJA%C3Zb2&1( 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 literal 0 HcmV?d00001 diff --git a/data/gui/mystery_unlock.png b/data/gui/mystery_unlock.png new file mode 100644 index 0000000000000000000000000000000000000000..b69b91cf2481e0207382de2240774732de057075 GIT binary patch literal 7994 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 literal 0 HcmV?d00001 diff --git a/data/gui/select_challenge.stkgui b/data/gui/select_challenge.stkgui index 2d8e562b8..02ab05b65 100644 --- a/data/gui/select_challenge.stkgui +++ b/data/gui/select_challenge.stkgui @@ -21,7 +21,7 @@

- +
- + + +
+ + +
+ + diff --git a/data/gui/select_challenge_nobest.stkgui b/data/gui/select_challenge_nobest.stkgui new file mode 100644 index 000000000..a63e32ed6 --- /dev/null +++ b/data/gui/select_challenge_nobest.stkgui @@ -0,0 +1,46 @@ + + +
+ +
+ + + +
+
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+ diff --git a/src/challenges/challenge_data.cpp b/src/challenges/challenge_data.cpp index b0c8efd15..369a3263a 100644 --- a/src/challenges/challenge_data.cpp +++ b/src/challenges/challenge_data.cpp @@ -43,6 +43,7 @@ ChallengeData::ChallengeData(const std::string& filename) m_gp_id = ""; m_version = 0; m_num_trophies = 0; + m_is_unlock_list = false; m_is_ghost_replay = false; for (int d=0; dget("version", &m_version); // No need to get the rest of the data if this challenge @@ -79,7 +80,48 @@ ChallengeData::ChallengeData(const std::string& filename) return; } + m_is_unlock_list = false; + const XMLNode* unlock_list_node = root->getNode("unlock_list"); + if (unlock_list_node != NULL) + { + std::string list; + unlock_list_node->get("list", &list); + m_is_unlock_list = (list=="true"); + } + std::vector unlocks; + root->getNodes("unlock", unlocks); + for(unsigned int i=0; iget("kart", &s)) + setUnlocks(s, ChallengeData::UNLOCK_KART); + else if(unlocks[i]->get("track", &s)) + addUnlockTrackReward(s); + else if(unlocks[i]->get("gp", &s)) + setUnlocks(s, ChallengeData::UNLOCK_GP); + else if(unlocks[i]->get("mode", &s)) + setUnlocks(s, ChallengeData::UNLOCK_MODE); + else if(unlocks[i]->get("difficulty", &s)) + setUnlocks(s, ChallengeData::UNLOCK_DIFFICULTY); + else + { + Log::warn("ChallengeData", "Unknown unlock entry. Must be one of kart, track, gp, mode, difficulty."); + throw std::runtime_error("Unknown unlock entry"); + } + } + + const XMLNode* requirements_node = root->getNode("requirements"); + if (requirements_node == NULL) + { + throw std::runtime_error("Challenge file " + filename + + " has no node!"); + } + requirements_node->get("trophies", &m_num_trophies); + + //Don't check further if this is an unlock list + if(m_is_unlock_list) + return; const XMLNode* mode_node = root->getNode("mode"); if (mode_node == NULL) @@ -149,27 +191,19 @@ ChallengeData::ChallengeData(const std::string& filename) } } - const XMLNode* requirements_node = root->getNode("requirements"); - if (requirements_node == NULL) - { - throw std::runtime_error("Challenge file " + filename + - " has no node!"); - } - requirements_node->get("trophies", &m_num_trophies); - const XMLNode* difficulties[RaceManager::DIFFICULTY_COUNT]; difficulties[0] = root->getNode("easy"); difficulties[1] = root->getNode("medium"); difficulties[2] = root->getNode("hard"); + difficulties[3] = root->getNode("best"); - // Note that the challenges can only be done in three difficulties if (difficulties[0] == NULL || difficulties[1] == NULL || - difficulties[2] == NULL) + difficulties[2] == NULL || difficulties[3] == NULL) { - error(" or or "); + error(" or or or "); } - for (int d=0; d<=RaceManager::DIFFICULTY_HARD; d++) + for (int d=0; d<=RaceManager::DIFFICULTY_BEST; d++) { const XMLNode* karts_node = difficulties[d]->getNode("karts"); if (karts_node == NULL) error(""); @@ -229,28 +263,6 @@ ChallengeData::ChallengeData(const std::string& filename) if (requirements_node->get("energy", &energy)) m_energy[d] = energy; } - - std::vector unlocks; - root->getNodes("unlock", unlocks); - for(unsigned int i=0; iget("kart", &s)) - setUnlocks(s, ChallengeData::UNLOCK_KART); - else if(unlocks[i]->get("track", &s)) - addUnlockTrackReward(s); - else if(unlocks[i]->get("gp", &s)) - setUnlocks(s, ChallengeData::UNLOCK_GP); - else if(unlocks[i]->get("mode", &s)) - setUnlocks(s, ChallengeData::UNLOCK_MODE); - else if(unlocks[i]->get("difficulty", &s)) - setUnlocks(s, ChallengeData::UNLOCK_DIFFICULTY); - else - { - Log::warn("ChallengeData", "Unknown unlock entry. Must be one of kart, track, gp, mode, difficulty."); - throw std::runtime_error("Unknown unlock entry"); - } - } } // ChallengeData // ---------------------------------------------------------------------------- @@ -486,7 +498,7 @@ bool ChallengeData::isChallengeFulfilled() const // ---------------------------------------------------------------------------- /** Returns true if this GP challenge is fulfilled. */ -bool ChallengeData::isGPFulfilled() const +ChallengeData::GPLevel ChallengeData::isGPFulfilled() const { int d = race_manager->getDifficulty(); @@ -496,14 +508,25 @@ bool ChallengeData::isGPFulfilled() const race_manager->getMinorMode() != m_minor || race_manager->getGrandPrix().getId() != m_gp_id || race_manager->getNumberOfKarts() < (unsigned int)m_default_num_karts[d] || - race_manager->getNumPlayers() > 1) return false; + race_manager->getNumPlayers() > 1) return GP_NONE; // check if the player came first. + // rank == 0 if first, 1 if second, etc. const int rank = race_manager->getLocalPlayerGPRank(0); - if (rank != 0) return false; - - return true; + // In superior difficulty levels, losing a place means + // getting a cup of the inferior level rather than + // nothing at all + int unlock_level = d - rank; + if (unlock_level == 3) + return GP_BEST; + if (unlock_level == 2) + return GP_HARD; + if (unlock_level == 1) + return GP_MEDIUM; + if (unlock_level == 0) + return GP_EASY; + return GP_NONE; } // isGPFulfilled // ---------------------------------------------------------------------------- @@ -641,4 +664,3 @@ void ChallengeData::addUnlockKartReward(const std::string &internal_name, feature.m_user_name = user_name; m_feature.push_back(feature); } // addUnlockKartReward - diff --git a/src/challenges/challenge_data.hpp b/src/challenges/challenge_data.hpp index 6e45f23c2..1c0c5f461 100644 --- a/src/challenges/challenge_data.hpp +++ b/src/challenges/challenge_data.hpp @@ -44,6 +44,17 @@ public: UNLOCK_KART, UNLOCK_DIFFICULTY }; + + /** The level of completion of a GP challenge + */ + enum GPLevel + { + GP_NONE, + GP_EASY, + GP_MEDIUM, + GP_HARD, + GP_BEST + }; // ------------------------------------------------------------------------ class UnlockableFeature { @@ -95,6 +106,7 @@ private: std::string m_filename; /** Version number of the challenge. */ int m_version; + bool m_is_unlock_list; bool m_is_ghost_replay; void setUnlocks(const std::string &id, @@ -120,7 +132,7 @@ public: virtual void check() const; virtual bool isChallengeFulfilled() const; - virtual bool isGPFulfilled() const; + virtual GPLevel isGPFulfilled() const; void addUnlockTrackReward(const std::string &track_name); void addUnlockModeReward(const std::string &internal_mode_name, const irr::core::stringw &user_mode_name); @@ -142,11 +154,11 @@ public: // ------------------------------------------------------------------------ /** Returns the id of the challenge. */ - const std::string &getId() const { return m_id; } + const std::string &getChallengeId() const { return m_id; } // ------------------------------------------------------------------------ /** Sets the id of this challenge. */ - void setId(const std::string& s) { m_id = s; } + void setChallengeId(const std::string& s) { m_id = s; } // ------------------------------------------------------------------------ /** Returns the track associated with this challenge. */ @@ -185,6 +197,9 @@ public: /** Returns if this challenge is using ghost replay. */ bool isGhostReplay() const { return m_is_ghost_replay; } // ------------------------------------------------------------------------ + /** Returns if this challenge is an unlock list. */ + bool isUnlockList() const { return m_is_unlock_list; } + // ------------------------------------------------------------------------ /** Returns the challenge mode of this challenge. */ ChallengeModeType getMode() const { return m_mode; } // ------------------------------------------------------------------------ @@ -196,9 +211,9 @@ public: const irr::core::stringw getChallengeDescription() const; // ------------------------------------------------------------------------ - /** Returns the minimum position the player must have in order to win. + /** Returns the maximum position the player must have in order to win. */ - int getPosition(RaceManager::Difficulty difficulty) const + int getMaxPosition(RaceManager::Difficulty difficulty) const { return m_position[difficulty]; } // getPosition diff --git a/src/challenges/challenge_status.cpp b/src/challenges/challenge_status.cpp index 7b41e7b5f..1c0c36863 100644 --- a/src/challenges/challenge_status.cpp +++ b/src/challenges/challenge_status.cpp @@ -35,18 +35,19 @@ */ void ChallengeStatus::load(const XMLNode* challenges_node) { - const XMLNode* node = challenges_node->getNode( m_data->getId() ); + const XMLNode* node = challenges_node->getNode( m_data->getChallengeId() ); if(node == NULL) { Log::info("ChallengeStatus", "Couldn't find node <%s> in challenge list." "(If this is the first time you play this is normal)\n", - m_data->getId().c_str()); + m_data->getChallengeId().c_str()); return; } m_state[0] = CH_INACTIVE; m_state[1] = CH_INACTIVE; m_state[2] = CH_INACTIVE; + m_state[3] = CH_INACTIVE; std::string solved; if (node->get("solved", &solved)) @@ -64,6 +65,13 @@ void ChallengeStatus::load(const XMLNode* challenges_node) m_state[1] = CH_SOLVED; m_state[2] = CH_SOLVED; } + else if (solved == "best") + { + m_state[0] = CH_SOLVED; + m_state[1] = CH_SOLVED; + m_state[2] = CH_SOLVED; + m_state[3] = CH_SOLVED; + } } // if has 'solved' attribute } // load @@ -78,14 +86,28 @@ void ChallengeStatus::setSolved(RaceManager::Difficulty d) { m_state[curr] = CH_SOLVED; } -} +} // setSolved + +// ------------------------------------------------------------------------ +bool ChallengeStatus::isUnlockList() +{ + return m_data->isUnlockList(); +} // isUnlockList + +// ------------------------------------------------------------------------ +bool ChallengeStatus::isGrandPrix() +{ + return m_data->isGrandPrix(); +} // isUnlockList //----------------------------------------------------------------------------- void ChallengeStatus::save(UTFWriter& writer) { - writer << L" <" << m_data->getId(); - if (isSolved(RaceManager::DIFFICULTY_HARD)) + writer << L" <" << m_data->getChallengeId(); + if (isSolved(RaceManager::DIFFICULTY_BEST)) + writer << L" solved=\"best\"/>\n"; + else if (isSolved(RaceManager::DIFFICULTY_HARD)) writer << L" solved=\"hard\"/>\n"; else if (isSolved(RaceManager::DIFFICULTY_MEDIUM)) writer << L" solved=\"medium\"/>\n"; diff --git a/src/challenges/challenge_status.hpp b/src/challenges/challenge_status.hpp index 9e69e73a9..2fe5fa549 100644 --- a/src/challenges/challenge_status.hpp +++ b/src/challenges/challenge_status.hpp @@ -57,6 +57,7 @@ private: enum {CH_INACTIVE, // challenge not yet possible CH_ACTIVE, // challenge possible, but not yet solved CH_SOLVED} // challenge was solved + m_state[RaceManager::DIFFICULTY_COUNT]; /** Pointer to the original challenge data. */ @@ -69,6 +70,7 @@ public: m_state[RaceManager::DIFFICULTY_EASY] = CH_INACTIVE; m_state[RaceManager::DIFFICULTY_MEDIUM] = CH_INACTIVE; m_state[RaceManager::DIFFICULTY_HARD] = CH_INACTIVE; + m_state[RaceManager::DIFFICULTY_BEST] = CH_INACTIVE; } virtual ~ChallengeStatus() {}; void load(const XMLNode* config); @@ -88,7 +90,7 @@ public: bool isSolvedAtAnyDifficulty() const { return m_state[0]==CH_SOLVED || m_state[1]==CH_SOLVED || - m_state[2]==CH_SOLVED; + m_state[2]==CH_SOLVED || m_state[3]==CH_SOLVED; } // isSolvedAtAnyDifficulty // ------------------------------------------------------------------------ /** True if this challenge is active at the given difficulty. @@ -105,6 +107,13 @@ public: m_state[d] = CH_ACTIVE; } // setActive // ------------------------------------------------------------------------ + /** Returns if this challenge is only an unlock list */ + bool isUnlockList(); + + // ------------------------------------------------------------------------ + /** Returns if this challenge is a grand prix */ + bool isGrandPrix(); + // ------------------------------------------------------------------------ /** Returns a pointer to the actual Challenge data. */ const ChallengeData* getData() const { return m_data; } diff --git a/src/challenges/story_mode_status.cpp b/src/challenges/story_mode_status.cpp index bd1b83b4a..8fb8d820a 100644 --- a/src/challenges/story_mode_status.cpp +++ b/src/challenges/story_mode_status.cpp @@ -30,12 +30,14 @@ //----------------------------------------------------------------------------- StoryModeStatus::StoryModeStatus(const XMLNode *node) { - m_points = 0; - m_first_time = true; - m_easy_challenges = 0; - m_medium_challenges = 0; - m_hard_challenges = 0; - m_current_challenge = NULL; + m_points = 0; + m_next_unlock_points = 0; + m_first_time = true; + m_easy_challenges = 0; + m_medium_challenges = 0; + m_hard_challenges = 0; + m_best_challenges = 0; + m_current_challenge = NULL; // If there is saved data, load it if(node) @@ -62,7 +64,7 @@ StoryModeStatus::~StoryModeStatus() */ void StoryModeStatus::addStatus(ChallengeStatus *cs) { - m_challenges_state[cs->getData()->getId()] = cs; + m_challenges_state[cs->getData()->getChallengeId()] = cs; } // addStatus //----------------------------------------------------------------------------- @@ -78,9 +80,11 @@ bool StoryModeStatus::isLocked(const std::string& feature) void StoryModeStatus::computeActive() { m_points = 0; + m_next_unlock_points = 0; m_easy_challenges = 0; m_medium_challenges = 0; m_hard_challenges = 0; + m_best_challenges = 0; m_locked_features.clear(); // start afresh @@ -111,20 +115,32 @@ void StoryModeStatus::computeActive() unlockFeature(i->second, RaceManager::DIFFICULTY_HARD, /*save*/ false); } - - if (i->second->isSolved(RaceManager::DIFFICULTY_HARD)) + if (i->second->isSolved(RaceManager::DIFFICULTY_BEST)) { - m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]; + unlockFeature(i->second, RaceManager::DIFFICULTY_BEST, + /*save*/ false); + } + + int gp_factor = i->second->isGrandPrix() ? GP_FACTOR : 1; + + if (i->second->isSolved(RaceManager::DIFFICULTY_BEST) && !i->second->isUnlockList()) + { + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_BEST]*gp_factor; + m_best_challenges++; + } + else if (i->second->isSolved(RaceManager::DIFFICULTY_HARD) && !i->second->isUnlockList()) + { + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]*gp_factor; m_hard_challenges++; } - else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM)) + else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM) && !i->second->isUnlockList()) { - m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]; + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]*gp_factor; m_medium_challenges++; } - else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY)) + else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY) && !i->second->isUnlockList()) { - m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]; + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]*gp_factor; m_easy_challenges++; } } @@ -135,29 +151,40 @@ void StoryModeStatus::computeActive() lockFeature(i->second); } - if (i->second->isSolved(RaceManager::DIFFICULTY_HARD)) + if (i->second->isSolved(RaceManager::DIFFICULTY_BEST)) { // challenge beaten at hardest, nothing more to do here continue; } + else if (i->second->isSolved(RaceManager::DIFFICULTY_HARD)) + { + i->second->setActive(RaceManager::DIFFICULTY_BEST); + } else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM)) { + i->second->setActive(RaceManager::DIFFICULTY_BEST); i->second->setActive(RaceManager::DIFFICULTY_HARD); } else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY)) { + i->second->setActive(RaceManager::DIFFICULTY_BEST); i->second->setActive(RaceManager::DIFFICULTY_HARD); i->second->setActive(RaceManager::DIFFICULTY_MEDIUM); } else { + i->second->setActive(RaceManager::DIFFICULTY_BEST); i->second->setActive(RaceManager::DIFFICULTY_HARD); i->second->setActive(RaceManager::DIFFICULTY_MEDIUM); i->second->setActive(RaceManager::DIFFICULTY_EASY); } } // for i - // now we have the number of points. Actually lock the tracks + // now we have the number of points. + + unlockFeatureByList(); + + //Actually lock the tracks for (i = m_challenges_state.begin(); i != m_challenges_state.end(); i++) { if (m_points < i->second->getData()->getNumTrophies()) @@ -173,12 +200,37 @@ void StoryModeStatus::computeActive() } } } - - clearUnlocked(); - - } // computeActive +//----------------------------------------------------------------------------- + +void StoryModeStatus::unlockFeatureByList() +{ + // test if we have unlocked a feature requiring a certain number of points + std::map::const_iterator i; + for(i = m_challenges_state.begin(); + i != m_challenges_state.end(); i++) + { + if (i->second->isUnlockList()) + { + if (i->second->isSolvedAtAnyDifficulty()) + continue; + + bool newly_solved = unlock_manager->unlockByPoints(m_points,i->second); + + // Add to list of recently unlocked features + if(newly_solved) + m_unlocked_features.push_back(i->second->getData()); + + //Retrieve the smallest number of points for the next unlockable + if (i->second->getData()->getNumTrophies() > m_points && (m_next_unlock_points == 0 + || i->second->getData()->getNumTrophies() < m_next_unlock_points) ) + m_next_unlock_points = i->second->getData()->getNumTrophies(); + } + } +} //unlockFeatureByList + + //----------------------------------------------------------------------------- void StoryModeStatus::lockFeature(ChallengeStatus *challenge_status) @@ -216,8 +268,10 @@ void StoryModeStatus::unlockFeature(ChallengeStatus* c, RaceManager::Difficulty m_locked_features.erase(p); } - // Add to list of recently unlocked features - m_unlocked_features.push_back(c->getData()); + // Add to list of recently unlocked features if the challenge is newly completed + if (!c->isSolvedAtAnyDifficulty()) + m_unlocked_features.push_back(c->getData()); + c->setSolved(d); // reset isActive flag // Save the new unlock information @@ -250,6 +304,10 @@ void StoryModeStatus::raceFinished() unlockFeature(const_cast(m_current_challenge), race_manager->getDifficulty()); } // if isActive && challenge solved + + //This updates the number of points. + //It then calls unlockFeatureByList which checks the specially unlocked features (by points, etc) + computeActive(); } // raceFinished //----------------------------------------------------------------------------- @@ -259,11 +317,33 @@ void StoryModeStatus::raceFinished() void StoryModeStatus::grandPrixFinished() { if(m_current_challenge && - m_current_challenge->isActive(race_manager->getDifficulty()) && - m_current_challenge->getData()->isGPFulfilled() ) + m_current_challenge->isActive(race_manager->getDifficulty()) ) { - unlockFeature(const_cast(m_current_challenge), - race_manager->getDifficulty()); + ChallengeData::GPLevel unlock_level = m_current_challenge->getData()->isGPFulfilled(); + + RaceManager::Difficulty difficulty = RaceManager::DIFFICULTY_EASY; + + switch (unlock_level) + { + case ChallengeData::GP_NONE: + race_manager->setCoinTarget(0); + return; //No cup unlocked + case ChallengeData::GP_EASY: + difficulty = RaceManager::DIFFICULTY_EASY; + break; + case ChallengeData::GP_MEDIUM: + difficulty = RaceManager::DIFFICULTY_MEDIUM; + break; + case ChallengeData::GP_HARD: + difficulty = RaceManager::DIFFICULTY_HARD; + break; + case ChallengeData::GP_BEST: + difficulty = RaceManager::DIFFICULTY_BEST; + break; + } + + race_manager->setDifficulty(difficulty); + unlockFeature(const_cast(m_current_challenge), difficulty); } // if isActive && challenge solved race_manager->setCoinTarget(0); diff --git a/src/challenges/story_mode_status.hpp b/src/challenges/story_mode_status.hpp index dab906b87..94a7ee1c2 100644 --- a/src/challenges/story_mode_status.hpp +++ b/src/challenges/story_mode_status.hpp @@ -19,6 +19,7 @@ #ifndef GAME_SLOT_HPP #define GAME_SLOT_HPP +#include "challenges/challenge_data.hpp" #include "race/race_manager.hpp" #include @@ -33,7 +34,8 @@ class ChallengeStatus; class UTFWriter; class XMLNode; -const int CHALLENGE_POINTS[] = { 8, 9, 10 }; +const int CHALLENGE_POINTS[] = { 6, 7, 8, 10 }; +const int GP_FACTOR = 3; /** This class contains the progression through challenges for the story mode. * It maintains a list of all challenges in a mapping of challenge id to @@ -60,6 +62,7 @@ private: const ChallengeStatus *m_current_challenge; int m_points; + int m_next_unlock_points; /** Set to false after the initial stuff (intro, select kart, etc.) */ bool m_first_time; @@ -67,6 +70,7 @@ private: int m_easy_challenges; int m_medium_challenges; int m_hard_challenges; + int m_best_challenges; public: @@ -75,6 +79,7 @@ public: void computeActive(); bool isLocked (const std::string& feature); + void unlockFeatureByList(); void lockFeature (ChallengeStatus *challenge); void unlockFeature (ChallengeStatus* c, RaceManager::Difficulty d, bool do_save=true); @@ -96,15 +101,21 @@ public: /** Returns the number of points accumulated. */ int getPoints () const { return m_points; } // ------------------------------------------------------------------------ + /** Returns the number of points needed by the next unlockable. 0 if none. */ + int getNextUnlockPoints () const { return m_next_unlock_points; } + // ------------------------------------------------------------------------ /** Returns the number of fulfilled challenges at easy level. */ int getNumEasyTrophies () const { return m_easy_challenges; } // ------------------------------------------------------------------------ /* Returns the number of fulfilled challenges at medium level. */ int getNumMediumTrophies() const { return m_medium_challenges; } // ------------------------------------------------------------------------ - /** Returns the number of fulfilled challenges at har level. */ + /** Returns the number of fulfilled challenges at hard level. */ int getNumHardTrophies () const { return m_hard_challenges; } // ------------------------------------------------------------------------ + /** Returns the number of fulfilled challenges at best level. */ + int getNumBestTrophies () const { return m_best_challenges; } + // ------------------------------------------------------------------------ /** Sets if this is the first time the intro is shown. */ void setFirstTime(bool ft) { m_first_time = ft; } // ------------------------------------------------------------------------ diff --git a/src/challenges/unlock_manager.cpp b/src/challenges/unlock_manager.cpp index 5a6501991..88914db53 100644 --- a/src/challenges/unlock_manager.cpp +++ b/src/challenges/unlock_manager.cpp @@ -23,6 +23,7 @@ #include "audio/sfx_manager.hpp" #include "challenges/challenge_data.hpp" #include "challenges/challenge_status.hpp" +#include "challenges/story_mode_status.hpp" #include "config/player_manager.hpp" #include "config/player_profile.hpp" #include "config/user_config.hpp" @@ -142,19 +143,30 @@ void UnlockManager::addOrFreeChallenge(ChallengeData *c) { if(isSupportedVersion(*c)) { - m_all_challenges[c->getId()]=c; + m_all_challenges[c->getChallengeId()]=c; + if (c->isUnlockList()) + addListChallenge(c); } else { Log::warn("Challenge", "Challenge '%s' is not supported - ignored.", - c->getId().c_str()); + c->getChallengeId().c_str()); delete c; } } // addOrFreeChallenge +//----------------------------------------------------------------------------- +/** Add a challenge to the unlock challenges list + * \param c The challenge that is either stored or freed. + */ +void UnlockManager::addListChallenge(ChallengeData *c) +{ + m_list_challenges[c->getChallengeId()]=c; +} // addListChallenge + //----------------------------------------------------------------------------- /** Reads a challenge from the given filename. The challenge will then either - * be stored, or (if the challenge version is not supported anymore + * be stored, or (if the challenge version is not supported anymore, freed) * \param filename Name of the challenge file to read. */ void UnlockManager::addChallenge(const std::string& filename) @@ -228,20 +240,25 @@ bool UnlockManager::isSupportedVersion(const ChallengeData &challenge) { // Test if challenge version number is in between minimum // and maximum supported version. - return (challenge.getVersion()>=2 && challenge.getVersion()<=2); + return (challenge.getVersion()>=3 && challenge.getVersion()<=3); } // isSupportedVersion //----------------------------------------------------------------------------- - +/** This functions finds what new tracks, GP and karts have been unlocked + */ void UnlockManager::findWhatWasUnlocked(int points_before, int points_now, std::vector& tracks, - std::vector& gps) + std::vector& gps, + std::vector& karts, + std::vector& unlocked) { + ChallengeData* c = NULL; + for (AllChallengesType::iterator it = m_all_challenges.begin(); it != m_all_challenges.end(); it++) { - ChallengeData* c = it->second; + c = it->second; if (c->getNumTrophies() > points_before && c->getNumTrophies() <= points_now ) { @@ -257,4 +274,36 @@ void UnlockManager::findWhatWasUnlocked(int points_before, int points_now, } } } -} + + for (unsigned int n = 0; n < unlocked.size(); n++) + { + std::vector features = unlocked[n]->getFeatures(); + + for (unsigned int i = 0; i < features.size(); i++) + { + if( features[i].m_type == ChallengeData::UNLOCK_KART ) + karts.push_back(features[i].m_name); + } + } + + //std::vector + // getRecentlyCompletedChallenges() +} // findWhatWasUnlocked + +//----------------------------------------------------------------------------- +/** This functions sets as completed the "challenges" requiring a certain number + * of points, to unlock features. + * Returns true if the challenge has been completed + */ +bool UnlockManager::unlockByPoints(int points, ChallengeStatus* unlock_list) +{ + //TODO : add support for other conditions (achievements...) for alternative unlock paths + if( unlock_list!=NULL && unlock_list->getData()->getNumTrophies() <= points) + { + unlock_list->setSolved(RaceManager::DIFFICULTY_BEST); + return true; + } + return false; +} // unlockByPoints + +/* EOF */ diff --git a/src/challenges/unlock_manager.hpp b/src/challenges/unlock_manager.hpp index 781379ec7..cb382e002 100644 --- a/src/challenges/unlock_manager.hpp +++ b/src/challenges/unlock_manager.hpp @@ -44,12 +44,16 @@ private: typedef std::map AllChallengesType; AllChallengesType m_all_challenges; + /* The challenges who don't have a race, only unlockables */ + AllChallengesType m_list_challenges; + void readAllChallengesInDirs(const std::vector* all_dirs); public: UnlockManager (); ~UnlockManager (); void addOrFreeChallenge(ChallengeData *c); + void addListChallenge(ChallengeData *c); void addChallenge (const std::string& filename); const ChallengeData *getChallengeData(const std::string& id); @@ -61,7 +65,10 @@ public: void findWhatWasUnlocked(int pointsBefore, int pointsNow, std::vector& tracks, - std::vector& gps); + std::vector& gps, + std::vector& karts, + std::vector& unlocked); + bool unlockByPoints(int points, ChallengeStatus* unlock_list); StoryModeStatus *createStoryModeStatus(const XMLNode *node=NULL); diff --git a/src/config/player_profile.hpp b/src/config/player_profile.hpp index 4d7962043..519f5f5c3 100644 --- a/src/config/player_profile.hpp +++ b/src/config/player_profile.hpp @@ -230,6 +230,8 @@ public: // ------------------------------------------------------------------------ unsigned int getPoints() const { return m_story_mode_status->getPoints(); } // ------------------------------------------------------------------------ + unsigned int getNextUnlockPoints() const { return m_story_mode_status->getNextUnlockPoints(); } + // ------------------------------------------------------------------------ void setFirstTime(bool b) { m_story_mode_status->setFirstTime(b); } // ------------------------------------------------------------------------ bool isFirstTime() const { return m_story_mode_status->isFirstTime(); } @@ -263,7 +265,11 @@ public: unsigned int getNumHardTrophies() const { return m_story_mode_status->getNumHardTrophies(); - } // getNumHardTropies + } // getNumHardTrophies + unsigned int getNumBestTrophies() const + { + return m_story_mode_status->getNumBestTrophies(); + } // getNumBestTrophies // ------------------------------------------------------------------------ AchievementsStatus* getAchievementsStatus() { diff --git a/src/modes/cutscene_world.cpp b/src/modes/cutscene_world.cpp index 61b768255..13c357518 100644 --- a/src/modes/cutscene_world.cpp +++ b/src/modes/cutscene_world.cpp @@ -422,11 +422,14 @@ void CutsceneWorld::enterRaceOverState() // un-set the GP mode so that after unlocking, it doesn't try to continue the GP race_manager->setMajorMode(RaceManager::MAJOR_MODE_SINGLE); + //TODO : this code largely duplicate a similar code present in raceResultGUI. + // Try to reduce duplication std::vector unlocked = PlayerManager::getCurrentPlayer()->getRecentlyCompletedChallenges(); + if (unlocked.size() > 0) { - //PlayerManager::getCurrentPlayer()->clearUnlocked(); + PlayerManager::getCurrentPlayer()->clearUnlocked(); StateManager::get()->enterGameState(); race_manager->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE); @@ -441,8 +444,8 @@ void CutsceneWorld::enterRaceOverState() ((CutsceneWorld*)World::getWorld())->setParts(parts); assert(unlocked.size() > 0); - scene->addTrophy(race_manager->getDifficulty()); - scene->findWhatWasUnlocked(race_manager->getDifficulty()); + scene->addTrophy(race_manager->getDifficulty(),true); + scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); StateManager::get()->replaceTopMostScreen(scene, GUIEngine::INGAME_MENU); } @@ -476,9 +479,10 @@ void CutsceneWorld::enterRaceOverState() std::vector unlocked = PlayerManager::getCurrentPlayer()->getRecentlyCompletedChallenges(); + if (unlocked.size() > 0) { - //PlayerManager::getCurrentPlayer()->clearUnlocked(); + PlayerManager::getCurrentPlayer()->clearUnlocked(); StateManager::get()->enterGameState(); race_manager->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE); @@ -492,8 +496,8 @@ void CutsceneWorld::enterRaceOverState() parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); - scene->addTrophy(race_manager->getDifficulty()); - scene->findWhatWasUnlocked(race_manager->getDifficulty()); + scene->addTrophy(race_manager->getDifficulty(),true); + scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); StateManager::get()->replaceTopMostScreen(scene, GUIEngine::INGAME_MENU); } @@ -594,4 +598,3 @@ void CutsceneWorld::createRaceGUI() m_race_gui = new CutsceneGUI(); } // createRaceGUI - diff --git a/src/modes/overworld.cpp b/src/modes/overworld.cpp index 966aaf1df..d839bcfed 100644 --- a/src/modes/overworld.cpp +++ b/src/modes/overworld.cpp @@ -65,7 +65,15 @@ void OverWorld::enterOverWorld() race_manager->setMinorMode (RaceManager::MINOR_MODE_OVERWORLD); race_manager->setNumKarts( 1 ); race_manager->setTrack( "overworld" ); - race_manager->setDifficulty(RaceManager::DIFFICULTY_HARD); + + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + race_manager->setDifficulty(RaceManager::DIFFICULTY_HARD); + } + else + { + race_manager->setDifficulty(RaceManager::DIFFICULTY_BEST); + } // Use keyboard 0 by default (FIXME: let player choose?) InputDevice* device = input_manager->getDeviceManager()->getKeyboard(0); @@ -255,7 +263,7 @@ void OverWorld::onFirePressed(Controller* who) if (unlocked) { race_manager->setKartLastPositionOnOverworld(kart_xyz); - new SelectChallengeDialog(0.8f, 0.8f, + new SelectChallengeDialog(0.9f, 0.9f, challenges[n].m_challenge_id); } } diff --git a/src/states_screens/dialogs/select_challenge.cpp b/src/states_screens/dialogs/select_challenge.cpp index 857adac4a..b2e7221f9 100644 --- a/src/states_screens/dialogs/select_challenge.cpp +++ b/src/states_screens/dialogs/select_challenge.cpp @@ -43,9 +43,9 @@ core::stringw getLabel(RaceManager::Difficulty difficulty, const ChallengeData* { core::stringw label; - if (c->getPosition(difficulty) != -1) + if (c->getMaxPosition(difficulty) != -1) { - int r = c->getPosition(difficulty); + int r = c->getMaxPosition(difficulty); if (c->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER) r--; if (label.size() > 0) label.append(L"\n"); @@ -80,7 +80,10 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, std::string challenge_id) : ModalDialog(percentWidth, percentHeight) { - loadFromFile("select_challenge.stkgui"); + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + loadFromFile("select_challenge_nobest.stkgui"); + else + loadFromFile("select_challenge.stkgui"); m_challenge_id = challenge_id; World::getWorld()->schedulePause(WorldStatus::IN_GAME_MENU_PHASE); @@ -95,6 +98,14 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, case 2: getWidget("expert")->setFocusForPlayer(PLAYER_ID_GAME_MASTER); break; + case 3: + { + if(PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + getWidget("expert")->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + else + getWidget("supertux")->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + break; + } } const ChallengeStatus* c = PlayerManager::getCurrentPlayer() @@ -121,6 +132,14 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); } + if (c->isSolved(RaceManager::DIFFICULTY_BEST) + && !PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + IconButtonWidget* btn = getWidget("supertux"); + btn->setImage(file_manager->getAsset(FileManager::GUI,"cup_platinum.png"), + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); + } + LabelWidget* novice_label = getWidget("novice_label"); LabelWidget* medium_label = getWidget("intermediate_label"); @@ -130,6 +149,12 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, medium_label->setText( getLabel(RaceManager::DIFFICULTY_MEDIUM, c->getData()), false ); expert_label->setText( getLabel(RaceManager::DIFFICULTY_HARD, c->getData()), false ); + if (!PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + LabelWidget* supertux_label = getWidget("supertux_label"); + supertux_label->setText( getLabel(RaceManager::DIFFICULTY_BEST, c->getData()), false ); + } + if (c->getData()->isGrandPrix()) { const GrandPrixData* gp = grand_prix_manager->getGrandPrix(c->getData()->getGPId()); @@ -167,7 +192,7 @@ GUIEngine::EventPropagation SelectChallengeDialog::processEvent(const std::strin { std::string eventSource = eventSourceParam; if (eventSource == "novice" || eventSource == "intermediate" || - eventSource == "expert") + eventSource == "expert" || eventSource == "supertux") { const ChallengeData* challenge = unlock_manager->getChallengeData(m_challenge_id); @@ -229,6 +254,11 @@ GUIEngine::EventPropagation SelectChallengeDialog::processEvent(const std::strin challenge->setRace(RaceManager::DIFFICULTY_HARD); UserConfigParams::m_difficulty = 2; } + else if (eventSource == "supertux") + { + challenge->setRace(RaceManager::DIFFICULTY_BEST); + UserConfigParams::m_difficulty = 3; + } else { Log::error("SelectChallenge", "Unknown widget <%s>\n", @@ -250,4 +280,3 @@ GUIEngine::EventPropagation SelectChallengeDialog::processEvent(const std::strin } // ---------------------------------------------------------------------------- - diff --git a/src/states_screens/feature_unlocked.cpp b/src/states_screens/feature_unlocked.cpp index c57b7ea01..06664f7da 100644 --- a/src/states_screens/feature_unlocked.cpp +++ b/src/states_screens/feature_unlocked.cpp @@ -81,7 +81,7 @@ FeatureUnlockedCutScene::UnlockedThing::UnlockedThing(std::string model, // ------------------------------------------------------------------------------------- -FeatureUnlockedCutScene::UnlockedThing::UnlockedThing(KartProperties* kart, +FeatureUnlockedCutScene::UnlockedThing::UnlockedThing(const KartProperties* kart, irr::core::stringw msg) { m_unlocked_kart = kart; @@ -202,7 +202,7 @@ void FeatureUnlockedCutScene::onCutsceneEnd() // ---------------------------------------------------------------------------- -void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty difficulty) +void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty difficulty,std::vector& unlocked) { PlayerProfile *player = PlayerManager::getCurrentPlayer(); int points_before = player->getPoints(); @@ -210,9 +210,10 @@ void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty diffic std::vector tracks; std::vector gps; + std::vector karts; player->computeActive(); - unlock_manager->findWhatWasUnlocked(points_before, points_now, tracks, gps); + unlock_manager->findWhatWasUnlocked(points_before, points_now, tracks, gps, karts, unlocked); for (unsigned int i = 0; i < tracks.size(); i++) { @@ -222,26 +223,41 @@ void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty diffic { addUnlockedGP(grand_prix_manager->getGrandPrix(gps[i])); } + for (unsigned int i = 0; i < karts.size(); i++) + { + addUnlockedKart(kart_properties_manager->getKart(karts[i])); + } } // ---------------------------------------------------------------------------- -void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty) +void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty, bool is_grandprix) { core::stringw msg; + + int gp_factor = is_grandprix ? GP_FACTOR : 1; + RaceManager::Difficulty max_unlocked_difficulty = RaceManager::DIFFICULTY_BEST; + + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + max_unlocked_difficulty = RaceManager::DIFFICULTY_HARD; + switch (difficulty) { case RaceManager::DIFFICULTY_EASY: msg = _("You completed the easy challenge! Points earned on this level: %i/%i", - CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY], CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]); + CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); break; case RaceManager::DIFFICULTY_MEDIUM: msg = _("You completed the intermediate challenge! Points earned on this level: %i/%i", - CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM], CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]); + CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); break; case RaceManager::DIFFICULTY_HARD: msg = _("You completed the difficult challenge! Points earned on this level: %i/%i", - CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD], CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]); + CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); + break; + case RaceManager::DIFFICULTY_BEST: + msg = _("You completed the SuperTux challenge! Points earned on this level: %i/%i", + CHALLENGE_POINTS[RaceManager::DIFFICULTY_BEST]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); break; default: assert(false); @@ -259,6 +275,9 @@ void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty) case RaceManager::DIFFICULTY_HARD: model = file_manager->getAsset(FileManager::MODEL,"trophy_gold.spm"); break; + case RaceManager::DIFFICULTY_BEST: + model = file_manager->getAsset(FileManager::MODEL,"trophy_platinum.spm"); + break; default: assert(false); return; @@ -269,12 +288,15 @@ void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty) } // ---------------------------------------------------------------------------- -// unused for now, maybe will be useful later? -void FeatureUnlockedCutScene::addUnlockedKart(KartProperties* unlocked_kart, - irr::core::stringw msg) +void FeatureUnlockedCutScene::addUnlockedKart(const KartProperties* unlocked_kart) { - assert(unlocked_kart != NULL); + if (unlocked_kart == NULL) + { + Log::error("FeatureUnlockedCutScene::addUnlockedKart", "Unlocked kart does not exist"); + return; + } + irr::core::stringw msg = _("You unlocked %s!", unlocked_kart->getName()); m_unlocked_stuff.push_back( new UnlockedThing(unlocked_kart, msg) ); } // addUnlockedKart @@ -426,8 +448,6 @@ void FeatureUnlockedCutScene::init() Log::error("FeatureUnlockedCutScene::init", "Malformed unlocked goody"); } } - - PlayerManager::getCurrentPlayer()->clearUnlocked(); } // init // ---------------------------------------------------------------------------- diff --git a/src/states_screens/feature_unlocked.hpp b/src/states_screens/feature_unlocked.hpp index 6de85209c..86560c963 100644 --- a/src/states_screens/feature_unlocked.hpp +++ b/src/states_screens/feature_unlocked.hpp @@ -54,7 +54,7 @@ class FeatureUnlockedCutScene : public GUIEngine::CutsceneScreen, public GUIEngi struct UnlockedThing { /** Will be non-null if this unlocked thing is a kart */ - KartProperties* m_unlocked_kart; + const KartProperties* m_unlocked_kart; std::string m_unlock_model; @@ -80,7 +80,7 @@ class FeatureUnlockedCutScene : public GUIEngine::CutsceneScreen, public GUIEngi UnlockedThing(std::string model, irr::core::stringw msg); - UnlockedThing(KartProperties* kart, irr::core::stringw msg); + UnlockedThing(const KartProperties* kart, irr::core::stringw msg); /** * Creates a 'picture' reward. @@ -141,11 +141,11 @@ public: void eventCallback(GUIEngine::Widget* widget, const std::string& name, const int playerID) OVERRIDE; - void findWhatWasUnlocked(RaceManager::Difficulty difficulty); + void findWhatWasUnlocked(RaceManager::Difficulty difficulty,std::vector& unlocked); /** Call before showing up the screen to make a kart come out of the chest. 'addUnlockedThings' will invoke this, so you generally don't need to call this directly. */ - void addUnlockedKart(KartProperties* unlocked_kart, irr::core::stringw msg); + void addUnlockedKart(const KartProperties* unlocked_kart); /** Call before showing up the screen to make a picture come out of the chest 'addUnlockedThings' will invoke this, so you generally don't need to call this directly. */ @@ -166,7 +166,7 @@ public: void addUnlockedThings(const std::vector unlocked); */ - void addTrophy(RaceManager::Difficulty difficulty); + void addTrophy(RaceManager::Difficulty difficulty, bool is_grandprix); /** override from base class to handle escape press */ virtual bool onEscapePressed() OVERRIDE; @@ -175,4 +175,3 @@ public: }; #endif - diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index 102bc0bbf..0f41e82b2 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -318,18 +318,11 @@ void MainMenuScreen::eventCallback(Widget* widget, const std::string& name, parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); - scene->addTrophy(RaceManager::DIFFICULTY_EASY); + scene->addTrophy(RaceManager::DIFFICULTY_EASY, false); if (selection == "test_unlocked") { - // the passed kart will not be modified, that's why I allow myself - // to use const_cast - scene->addUnlockedKart( - const_cast( - kart_properties_manager->getKart("tux") - ), - L"You unlocked " - ); + scene->addUnlockedKart(kart_properties_manager->getKart("tux")); scene->addUnlockedTrack(track_manager->getTrack("lighthouse")); scene->push(); } diff --git a/src/states_screens/race_gui_overworld.cpp b/src/states_screens/race_gui_overworld.cpp index b45dfc909..b7c5c8358 100644 --- a/src/states_screens/race_gui_overworld.cpp +++ b/src/states_screens/race_gui_overworld.cpp @@ -65,6 +65,7 @@ const int OPEN = 1; const int COMPLETED_EASY = 2; const int COMPLETED_MEDIUM = 3; const int COMPLETED_HARD = 4; +const int COMPLETED_BEST = 5; /** The constructor is called before anything is attached to the scene node. * So rendering to a texture can be done here. But world is not yet fully @@ -83,6 +84,7 @@ RaceGUIOverworld::RaceGUIOverworld() m_trophy1 = irr_driver->getTexture(FileManager::GUI, "cup_bronze.png"); m_trophy2 = irr_driver->getTexture(FileManager::GUI, "cup_silver.png"); m_trophy3 = irr_driver->getTexture(FileManager::GUI, "cup_gold.png" ); + m_trophy4 = irr_driver->getTexture(FileManager::GUI, "cup_platinum.png" ); float scaling = irr_driver->getFrameSize().Height / 420.0f; const float map_size = 250.0f; @@ -122,6 +124,7 @@ RaceGUIOverworld::RaceGUIOverworld() // special case : when 3 players play, use available 4th space for such things + // TODO : determine if there are plans for multiplayer in story mode in the future if (race_manager->getIfEmptyScreenSpaceExists()) { m_map_left = irr_driver->getActualScreenSize().Width - m_map_width; @@ -152,12 +155,15 @@ RaceGUIOverworld::RaceGUIOverworld() m_lock = irr_driver->getTexture(FileManager::GUI,"gui_lock.png"); m_open_challenge = irr_driver->getTexture(FileManager::GUI,"challenge.png"); + m_locked_bonus = irr_driver->getTexture(FileManager::GUI,"mystery_unlock.png"); m_icons[0] = m_lock; m_icons[1] = m_open_challenge; m_icons[2] = m_trophy1; m_icons[3] = m_trophy2; m_icons[4] = m_trophy3; + m_icons[5] = m_trophy4; + m_icons[6] = m_locked_bonus; } // RaceGUIOverworld //----------------------------------------------------------------------------- @@ -179,6 +185,7 @@ void RaceGUIOverworld::renderGlobal(float dt) // Special case : when 3 players play, use 4th window to display such // stuff (but we must clear it) + //TODO : remove if no story mode multiplayer plans if (race_manager->getIfEmptyScreenSpaceExists() && !GUIEngine::ModalDialog::isADialogActive()) { @@ -257,8 +264,11 @@ void RaceGUIOverworld::drawTrophyPoints() #ifndef SERVER_ONLY PlayerProfile *player = PlayerManager::getCurrentPlayer(); const int points = player->getPoints(); + const int next_unlock_points = player->getNextUnlockPoints(); std::string s = StringUtils::toString(points); + std::string s_goal = StringUtils::toString(next_unlock_points); core::stringw sw(s.c_str()); + core::stringw swg(s_goal.c_str()); static video::SColor time_color = video::SColor(255, 255, 255, 255); @@ -274,7 +284,7 @@ void RaceGUIOverworld::drawTrophyPoints() const int size = irr_driver->getActualScreenSize().Width/20; core::rect dest(size, pos.UpperLeftCorner.Y, size*2, pos.UpperLeftCorner.Y + size); - core::rect source(core::position2di(0, 0), m_trophy3->getSize()); + core::rect source(core::position2di(0, 0), m_trophy4->getSize()); font->setShadow(video::SColor(255,0,0,0)); @@ -321,17 +331,62 @@ void RaceGUIOverworld::drawTrophyPoints() font->draw(hardTrophiesW.c_str(), dest, time_color, false, vcenter, NULL, true /* ignore RTL */); } - dest = core::rect(pos.UpperLeftCorner.X - size - 5, pos.UpperLeftCorner.Y, - pos.UpperLeftCorner.X - 5, pos.UpperLeftCorner.Y + size); + dest += core::position2di(size*2, 0); + if (!m_close_to_a_challenge && !PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + draw2DImage(m_trophy4, dest, source, NULL, NULL, true /* alpha */); + } + dest += core::position2di((int)(size*1.5f), 0); + std::string bestTrophies = StringUtils::toString(player->getNumBestTrophies()); + core::stringw bestTrophiesW(bestTrophies.c_str()); + if (!m_close_to_a_challenge && !PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + font->draw(bestTrophiesW.c_str(), dest, time_color, false, vcenter, NULL, true /* ignore RTL */); + } + + dest = core::rect(pos.UpperLeftCorner.X - size, pos.UpperLeftCorner.Y, + pos.UpperLeftCorner.X, pos.UpperLeftCorner.Y + size); draw2DImage(m_open_challenge, dest, source, NULL, NULL, true /* alpha */); - pos.LowerRightCorner.Y = dest.LowerRightCorner.Y; - pos.UpperLeftCorner.X += 5; + core::dimension2du area = font->getDimension(L"9"); + int small_width = area.Width; + area = font->getDimension(L"99"); + int middle_width = area.Width; + area = font->getDimension(L"999"); + int large_width = area.Width; + int number_width; + + if (points < 9) number_width = small_width; + else if (points <99) number_width = middle_width; + else number_width = large_width; + + pos.LowerRightCorner.Y = dest.LowerRightCorner.Y + 1.5f*size; + pos.UpperLeftCorner.X -= (0.5f*size + number_width*0.5f); font->draw(sw.c_str(), pos, time_color, false, vcenter, NULL, true /* ignore RTL */); + + pos.UpperLeftCorner.X += (0.5f*size + number_width*0.5f); + + if (next_unlock_points > points && (points + 80) >= next_unlock_points) + { + if (next_unlock_points < 9) number_width = small_width; + else if (next_unlock_points <99) number_width = middle_width; + else number_width = large_width; + + dest = core::rect(pos.UpperLeftCorner.X - 2.5f*size, pos.UpperLeftCorner.Y, + pos.UpperLeftCorner.X - 1.5f*size, pos.UpperLeftCorner.Y + size); + + draw2DImage(m_locked_bonus, dest, source, NULL, + NULL, true /* alpha */); + + pos.UpperLeftCorner.X -= (2*size + number_width*0.5f); + + font->draw(swg.c_str(), pos, time_color, false, vcenter, NULL, true /* ignore RTL */); + } + font->disableShadow(); #endif } // drawTrophyPoints @@ -468,7 +523,8 @@ void RaceGUIOverworld::drawGlobalMiniMap() const ChallengeStatus* c = PlayerManager::getCurrentPlayer() ->getChallengeStatus(challenges[n].m_challenge_id); - if (c->isSolved(RaceManager::DIFFICULTY_HARD)) state = COMPLETED_HARD; + if (c->isSolved(RaceManager::DIFFICULTY_BEST)) state = COMPLETED_BEST; + else if (c->isSolved(RaceManager::DIFFICULTY_HARD)) state = COMPLETED_HARD; else if (c->isSolved(RaceManager::DIFFICULTY_MEDIUM)) state = COMPLETED_MEDIUM; else if (c->isSolved(RaceManager::DIFFICULTY_EASY)) state = COMPLETED_EASY; @@ -622,4 +678,3 @@ void RaceGUIOverworld::drawGlobalMiniMap() } // drawGlobalMiniMap //----------------------------------------------------------------------------- - diff --git a/src/states_screens/race_gui_overworld.hpp b/src/states_screens/race_gui_overworld.hpp index 3779d5371..5f3fc3af7 100644 --- a/src/states_screens/race_gui_overworld.hpp +++ b/src/states_screens/race_gui_overworld.hpp @@ -65,10 +65,13 @@ private: video::ITexture *m_trophy1; video::ITexture *m_trophy2; video::ITexture *m_trophy3; + video::ITexture *m_trophy4; video::ITexture *m_lock; video::ITexture *m_open_challenge; + video::ITexture *m_locked_bonus; - video::ITexture* m_icons[5]; + + video::ITexture* m_icons[7]; /** The size of a single marker on the screen for AI karts, * need not be a power of 2. */ diff --git a/src/states_screens/race_result_gui.cpp b/src/states_screens/race_result_gui.cpp index ba77050cd..6fe019225 100644 --- a/src/states_screens/race_result_gui.cpp +++ b/src/states_screens/race_result_gui.cpp @@ -298,15 +298,13 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, bool gameCompleted = false; for (unsigned int n = 0; n < unlocked.size(); n++) { - if (unlocked[n]->getId() == "fortmagma") + if (unlocked[n]->getChallengeId() == "fortmagma") { gameCompleted = true; break; } } - PlayerManager::getCurrentPlayer()->clearUnlocked(); - if (gameCompleted) { // clear the race @@ -343,8 +341,8 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, FeatureUnlockedCutScene* scene = FeatureUnlockedCutScene::getInstance(); - scene->addTrophy(race_manager->getDifficulty()); - scene->findWhatWasUnlocked(race_manager->getDifficulty()); + scene->addTrophy(race_manager->getDifficulty(),false); + scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); scene->push(); race_manager->setAIKartOverride(""); @@ -352,6 +350,9 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); } + + PlayerManager::getCurrentPlayer()->clearUnlocked(); + return; } Log::warn("RaceResultGUI", "Incorrect event '%s' when things are unlocked.",