@@ -405,8 +405,10 @@ local gShowDebug
405405-- COVERS each level teardown/rebuild (no visible construction). gAtTitle is
406406-- true while the title is up (SPACE starts); gCardFade is the fade-out ramp
407407-- (0 = opaque ... 100 = clear); gCardGen invalidates a stale fade send when a
408- -- newer cover supersedes it; gTitleHeroSpr is the title's live hero preview.
409- local gAtTitle, gCardFade, gCardGen, gTitleHeroSpr
408+ -- newer cover supersedes it; gTitleHeroSpr is the title's live hero preview;
409+ -- gCardArt is the CR-list of per-level illustrated-card art controls (long ids)
410+ -- that fade/raise/hide as a set alongside the tint base + title text.
411+ local gAtTitle, gCardFade, gCardGen, gTitleHeroSpr, gCardArt
410412local gGate, gGateSprA, gGateSprB, gPlateHoldMS, gPlateLook, gCamOK
411413local gIntroPan, gRunStart, gFalls, gWinLock, gFlagHint, gWinSecs
412414local gOuches -- Wave 2: contact hits (knockbacks) - falls stay falls
@@ -798,13 +800,63 @@ function pfHeroName pSkin
798800 return toUpper(char 1 of pSkin) & (char 2 to -1 of pSkin)
799801end pfHeroName
800802
801- -- The composed text for a LEVEL card (shown while that level builds behind it).
803+ -- The composed text for a LEVEL card (shown in the upper band while that level
804+ -- builds behind it; the illustrated cast row sits below it - see pfCardArtBuildLevel).
802805function pfLevelCardText
803806 local tMsg
804- put "LEVEL " & gLevel & " / 7" & cr & cr & cr & pfLevelTitle(gLevel) & cr & cr & pfLevelFlavour(gLevel) & cr & cr & cr & cr & cr & "Arrows / WASD · SPACE jump · ESC controls " into tMsg
807+ put "LEVEL " & gLevel & " of 7" & cr & cr & pfLevelTitle(gLevel) & cr & cr & pfLevelFlavour(gLevel) & cr & cr & "Collect every coin, then reach the flag " into tMsg
805808 return tMsg
806809end pfLevelCardText
807810
811+ -- The biome BACKDROP frame for a level's card (the same Kenney bg art the level
812+ -- itself uses). L6/L7 build their backdrops procedurally - no frame - so the
813+ -- card falls back to its solid biome tint there.
814+ function pfLevelBgFrame pN
815+ switch pN
816+ case 1
817+ case 2
818+ return "background_color_hills"
819+ case 3
820+ return "background_fade_hills"
821+ case 4
822+ return "background_color_mushrooms"
823+ case 5
824+ return "background_color_desert"
825+ default
826+ return ""
827+ end switch
828+ end pfLevelBgFrame
829+
830+ -- A representative FOE frame per biome for the card's cast row (all on the
831+ -- always-loaded "foes" sheet - no optional-sheet dependency). A missing frame
832+ -- just drops that one cast slot (pfCardCastMember no-ops).
833+ function pfLevelFoeFrame pN
834+ switch pN
835+ case 2
836+ return "bee_a"
837+ case 3
838+ return "snail_walk_a"
839+ case 5
840+ return "ladybug_walk_a"
841+ case 6
842+ return "mouse_walk_a"
843+ case 7
844+ return "saw_a"
845+ default
846+ return "slime_normal_walk_a"
847+ end switch
848+ end pfLevelFoeFrame
849+
850+ -- Darken an "r,g,b" toward black by pFactor (0..1) - for the card's ground band.
851+ function pfShade pRGB, pFactor
852+ local tR, tG, tB
853+ set the itemDelimiter to comma
854+ put round((item 1 of pRGB) * pFactor) into tR
855+ put round((item 2 of pRGB) * pFactor) into tG
856+ put round((item 3 of pRGB) * pFactor) into tB
857+ return tR & comma & tG & comma & tB
858+ end pfShade
859+
808860-- The composed text for the boot TITLE card. The hero chooser highlights the
809861-- current pick; the live hero sprite (pfTitleHero) floats over the blank band
810862-- between the title and the chooser.
@@ -825,12 +877,148 @@ function pfTitleCardText
825877 return tMsg
826878end pfTitleCardText
827879
828- -- COVER the screen with the card NOW: compose its colour + text, raise it (and
829- -- its text) above every other control, make it opaque, and force ONE repaint so
830- -- it is on screen BEFORE the teardown/build that follows paints underneath it.
831- -- pLight true = light text (for the dark tints).
880+ -- ===== ILLUSTRATED-CARD ART (the per-level scene behind the title text) =====
881+ -- The biome backdrop, a dimming scrim, a ground band, and a CAST ROW of real
882+ -- game sprites (hero + a biome foe + a coin + the goal flag). All card-level
883+ -- controls named pfCard* (so pfWipeStage leaves them) and tracked in gCardArt so
884+ -- they fade/raise/hide as a set. EVERY piece is optional - on a missing sheet or
885+ -- a failed slice it is simply skipped, and the always-present opaque pfCardShade
886+ -- tint base guarantees the cover still hides the build. =====
887+
888+ -- Remember an art control so the fade/raise/hide handlers see it.
889+ command pfCardAddArt pCtrl
890+ if pCtrl is empty then exit pfCardAddArt
891+ put pCtrl & cr after gCardArt
892+ end pfCardAddArt
893+
894+ -- Drop every per-level art control and reset the list. Called before each cover,
895+ -- and at the title. Deletes BY NAME (a proper type-checked existence test); the
896+ -- names are fixed, so this clears every art control whether or not gCardArt is in
897+ -- sync, and avoids referencing a possibly-stale long id.
898+ command pfCardArtClear
899+ local tC
900+ set the itemDelimiter to comma
901+ repeat for each item tC in "pfCardBgA,pfCardBgB,pfCardScrim,pfCardGround,pfCardCastHero,pfCardCastFoe,pfCardCastCoin,pfCardCastFlag"
902+ if there is an image tC then delete image tC
903+ if there is a graphic tC then delete graphic tC
904+ end repeat
905+ put empty into gCardArt
906+ end pfCardArtClear
907+
908+ -- Clone a sliced atlas frame onto the CARD as a plain (camera-free) image named
909+ -- pName, hidden and at native sliced size. Uses the proven throwaway-sprite path
910+ -- (pfTextureSlab): the sprite carries the frame as its icon, which we clone. The
911+ -- throwaway is created in whatever camera group is live, then removed; the clone
912+ -- is a card control. Returns pName, or empty on any failure.
913+ function pfCardArtImage pSheet, pFrame, pName
914+ local tSpr, tImg
915+ if gAssetsOK is not true then return empty
916+ if not b2kSheetHasFrame(pSheet, pFrame) then return empty
917+ b2kSpriteNew pSheet, pFrame, -4000, -4000
918+ put the result into tSpr
919+ if tSpr is empty then return empty
920+ put the icon of tSpr into tImg
921+ if tImg is empty or there is no image id tImg then
922+ b2kSpriteRemove tSpr
923+ return empty
924+ end if
925+ if there is an image pName then delete image pName
926+ clone image id tImg
927+ set the name of the last image to pName
928+ set the lockLoc of the last image to true
929+ set the visible of the last image to false
930+ b2kSpriteRemove tSpr
931+ return pName
932+ end pfCardArtImage
933+
934+ -- One cast-row figure: clone pFrame, scale the clone to target height pH (aspect
935+ -- kept), stand it centred at x pCX with its feet on pFeetY, and track it. No
936+ -- b2kSheetScale juggling - the clone is resized directly, so the game's sheet
937+ -- scales are never touched.
938+ command pfCardCastMember pSheet, pFrame, pName, pCX, pFeetY, pH
939+ local tNW, tNH, tW
940+ if pfCardArtImage(pSheet, pFrame, pName) is empty then exit pfCardCastMember
941+ put the width of image pName into tNW
942+ put the height of image pName into tNH
943+ if tNH < 1 then
944+ delete image pName
945+ exit pfCardCastMember
946+ end if
947+ put max(1, round(tNW * pH / tNH)) into tW
948+ set the rect of image pName to pCX - (tW div 2), pFeetY - pH, pCX + (tW div 2), pFeetY
949+ set the uCardBaseBlend of image pName to 0
950+ pfCardAddArt the long id of image pName
951+ end pfCardCastMember
952+
953+ -- Build the illustrated scene for gLevel into gCardArt (hidden; pfCardShow shows
954+ -- it under the cover lock). The uCardBaseBlend property holds each layer's RESTING
955+ -- blendLevel so the fade ramp can add progress on top without flattening the
956+ -- scrim/ground translucency.
957+ command pfCardArtBuildLevel
958+ local tBg, tGY
959+ if gAssetsOK is not true then exit pfCardArtBuildLevel -- placeholder mode: tint card only
960+ put 480 into tGY -- the ground line the cast stands on
961+ -- the biome backdrop (L1-L5), as two overlapping panels filling the 1024 width
962+ put pfLevelBgFrame(gLevel) into tBg
963+ if tBg is not empty and b2kSheetHasFrame("bg", tBg) then
964+ if pfCardArtImage("bg", tBg, "pfCardBgA") is not empty then
965+ set the rect of image "pfCardBgA" to 0, 0, 592, 640
966+ set the uCardBaseBlend of image "pfCardBgA" to 0
967+ pfCardAddArt the long id of image "pfCardBgA"
968+ end if
969+ if pfCardArtImage("bg", tBg, "pfCardBgB") is not empty then
970+ set the rect of image "pfCardBgB" to 432, 0, 1024, 640
971+ set the uCardBaseBlend of image "pfCardBgB" to 0
972+ pfCardAddArt the long id of image "pfCardBgB"
973+ end if
974+ -- a dark scrim so the title text reads over the busy backdrop (rests at 42)
975+ create graphic "pfCardScrim"
976+ set the style of graphic "pfCardScrim" to "rectangle"
977+ set the rect of graphic "pfCardScrim" to 0, 0, 1024, 640
978+ set the filled of graphic "pfCardScrim" to true
979+ set the backgroundColor of graphic "pfCardScrim" to "14,16,26"
980+ set the blendLevel of graphic "pfCardScrim" to 42
981+ set the uCardBaseBlend of graphic "pfCardScrim" to 42
982+ set the visible of graphic "pfCardScrim" to false -- hidden until pfCardShow reveals the whole cover at once
983+ pfCardAddArt the long id of graphic "pfCardScrim"
984+ end if
985+ -- a ground band the cast stands on (a darker shade of the biome tint)
986+ create graphic "pfCardGround"
987+ set the style of graphic "pfCardGround" to "rectangle"
988+ set the rect of graphic "pfCardGround" to 0, tGY, 1024, 640
989+ set the filled of graphic "pfCardGround" to true
990+ set the backgroundColor of graphic "pfCardGround" to pfShade(pfLevelTint(gLevel), 0.45)
991+ set the blendLevel of graphic "pfCardGround" to 6
992+ set the uCardBaseBlend of graphic "pfCardGround" to 6
993+ set the visible of graphic "pfCardGround" to false -- hidden until pfCardShow reveals the whole cover at once
994+ pfCardAddArt the long id of graphic "pfCardGround"
995+ -- THE CAST: real game art previewing this world (each figure optional)
996+ pfCardCastMember "chars", ("character_" & gHeroSkin & "_idle"), "pfCardCastHero", 336, tGY + 8, 96
997+ pfCardCastMember "foes", pfLevelFoeFrame(gLevel), "pfCardCastFoe", 492, tGY + 8, 64
998+ pfCardCastMember "tiles", "coin_gold", "pfCardCastCoin", 606, tGY + 4, 40
999+ pfCardCastMember "tiles", "flag_yellow_a", "pfCardCastFlag", 706, tGY + 8, 96
1000+ end pfCardArtBuildLevel
1001+
1002+ -- Raise the whole card stack to the very top, in z-order: tint base, then the
1003+ -- art (in build order: backdrop, scrim, ground, cast), then the title text on
1004+ -- top. Each "set the layer ... to tTop" lands at the top, pushing the previous
1005+ -- one down, so the LAST raised (the text) ends up frontmost.
1006+ command pfCardRaise
1007+ local tTop, tC
1008+ put the number of controls of this card into tTop
1009+ if there is a graphic "pfCardShade" then set the layer of graphic "pfCardShade" to tTop
1010+ repeat for each line tC in gCardArt
1011+ if tC is not empty then set the layer of tC to tTop
1012+ end repeat
1013+ if there is a field "pfCardText" then set the layer of field "pfCardText" to tTop
1014+ end pfCardRaise
1015+
1016+ -- COVER the screen with the card NOW: compose its colour + text, raise the whole
1017+ -- stack above every other control, make it opaque, and force ONE repaint so it is
1018+ -- on screen BEFORE the teardown/build that follows paints underneath it. pLight
1019+ -- true = light text (for the dark tints).
8321020command pfCardShow pText, pTint, pLight
833- local tTop
1021+ local tC
8341022 add 1 to gCardGen -- invalidate any in-flight fade ramp
8351023 if there is no graphic "pfCardShade" then exit pfCardShow
8361024 set the backgroundColor of graphic "pfCardShade" to pTint
@@ -844,18 +1032,23 @@ command pfCardShow pText, pTint, pLight
8441032 set the textColor of field "pfCardText" to "28,30,42"
8451033 end if
8461034 end if
847- put the number of controls of this card into tTop
848- set the layer of graphic "pfCardShade" to tTop
849- if there is a field "pfCardText" then set the layer of field "pfCardText" to tTop
1035+ pfCardRaise
8501036 lock screen
8511037 set the visible of graphic "pfCardShade" to true
1038+ repeat for each line tC in gCardArt
1039+ if tC is not empty then set the visible of tC to true
1040+ end repeat
8521041 if there is a field "pfCardText" then set the visible of field "pfCardText" to true
8531042 unlock screen -- flush: the cover is on screen now
8541043end pfCardShow
8551044
8561045-- Cover for the CURRENT level (called at the very top of pfStartGame, before the
857- -- teardown) - the biome card for gLevel.
1046+ -- teardown): build the illustrated biome scene, then cover with it. The title
1047+ -- text sits in the upper band, the cast row below it.
8581048command pfCardCover
1049+ pfCardArtClear
1050+ pfCardArtBuildLevel
1051+ if there is a field "pfCardText" then set the rect of field "pfCardText" to 40, 70, 984, 410
8591052 pfCardShow pfLevelCardText(), pfLevelTint(gLevel), pfLevelLight(gLevel)
8601053end pfCardCover
8611054
@@ -868,20 +1061,30 @@ command pfCardReveal
8681061 send ("pfCardFadeStep" && gCardGen) to me in 620 milliseconds
8691062end pfCardReveal
8701063
871- -- One step of the fade-out ramp (0 = opaque ... 100 = clear). pGen guards
872- -- against a stale ramp left over when a newer cover superseded this one.
1064+ -- One step of the fade-out ramp (0 = opaque ... 100 = clear), across the tint
1065+ -- base, the title text, and every illustrated-art layer. Each layer fades from
1066+ -- its RESTING blend (uCardBaseBlend: 0 for opaque pieces, 42/6 for scrim/ground) up to
1067+ -- 100, so the scrim's dimming holds through the fade instead of flashing opaque.
1068+ -- pGen guards against a stale ramp left when a newer cover superseded this one.
8731069command pfCardFadeStep pGen
1070+ local tC
8741071 if pGen is not gCardGen then exit pfCardFadeStep
8751072 if there is no graphic "pfCardShade" then exit pfCardFadeStep
8761073 add 12 to gCardFade
8771074 if gCardFade >= 100 then
8781075 set the visible of graphic "pfCardShade" to false
8791076 if there is a field "pfCardText" then set the visible of field "pfCardText" to false
1077+ repeat for each line tC in gCardArt
1078+ if tC is not empty then set the visible of tC to false
1079+ end repeat
8801080 set the blendLevel of graphic "pfCardShade" to 0 -- reset for the next cover
8811081 exit pfCardFadeStep
8821082 end if
8831083 set the blendLevel of graphic "pfCardShade" to gCardFade
8841084 if there is a field "pfCardText" then set the blendLevel of field "pfCardText" to gCardFade
1085+ repeat for each line tC in gCardArt
1086+ if tC is not empty then set the blendLevel of tC to min(100, (the uCardBaseBlend of tC) + gCardFade)
1087+ end repeat
8851088 send ("pfCardFadeStep" && gCardGen) to me in 45 milliseconds
8861089end pfCardFadeStep
8871090
@@ -897,6 +1100,8 @@ command pfShowTitle
8971100 -- load the atlases ONCE (prompts for the folder the first time, then cached +
8981101 -- persisted into the game); skip the reload once the chars sheet is present
8991102 if not b2kSheetHasFrame("chars", "character_beige_idle") then put pfLoadSheets() into gAssetsOK
1103+ pfCardArtClear -- the title has no level scene (just the hero preview)
1104+ if there is a field "pfCardText" then set the rect of field "pfCardText" to 32, 40, 992, 600
9001105 pfCardShow pfTitleCardText(), "26,30,44", true
9011106 pfTitleHero -- the live hero preview, above the card
9021107end pfShowTitle
@@ -5128,13 +5333,11 @@ command pfChromeFront
51285333 repeat for each item tC in "pfbtn_pause,pfbtn_reset,pfbtn_again,pfbtn_level"
51295334 if there is a button tC then set the layer of button tC to tN
51305335 end repeat
5131- -- the transition card sits ABOVE all other chrome while it is covering: the
5132- -- build created controls over it, so re-raise it here (last) to keep it on top
5133- -- until the reveal fade hides it. Skipped when hidden (e.g. on the win screen).
5134- if there is a graphic "pfCardShade" and the visible of graphic "pfCardShade" is true then
5135- set the layer of graphic "pfCardShade" to tN
5136- if there is a field "pfCardText" then set the layer of field "pfCardText" to tN
5137- end if
5336+ -- the transition card (tint base + illustrated art + title text) sits ABOVE all
5337+ -- other chrome while it is covering: the build created controls over it, so
5338+ -- re-raise the whole stack here to keep it on top until the reveal fade hides
5339+ -- it. Skipped when hidden (e.g. on the win screen).
5340+ if there is a graphic "pfCardShade" and the visible of graphic "pfCardShade" is true then pfCardRaise
51385341end pfChromeFront
51395342
51405343command pfWipeStage
0 commit comments