Skip to content

Commit ee596db

Browse files
committed
Sprite Cannon: new Petal Purradise + Crimson Dawn worlds
1 parent 464e745 commit ee596db

1 file changed

Lines changed: 29 additions & 5 deletions

File tree

public/play/spritecannon/index.html

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,10 +1361,13 @@ <h2 id="rewardTitle" style="margin:0 0 6px">WEEKLY PRIZE!</h2>
13611361
// ZERO-G WORLD: low gravity + floating bounce-pads + bobbing hazards. Neon palette, fast-but-floaty.
13621362
// lowGrav = gravity multiplier; floaty = spawn mid-air pads/hazards instead of the ground set.
13631363
{ name:"Zero Purravity", d:1500, sky:["#1a0a52","#6f1ad6","#0a0524"], ac:"#19f0ff", fx:"none", lowGrav:0.40, floaty:true },
1364+
{ name:"Petal Purradise", d:4200, sky:["#ff9ec7","#ffc1de","#ffe6f2"], ac:"#ff4f9e", fx:"none" }, // all-pink cherry-blossom world (Naruto-Sakura vibe; sakura/petals are generic)
1365+
{ name:"Crimson Dawn", d:21500, sky:["#2a0810","#5e0e1a","#0a0306"], ac:"#ff2d4a", fx:"none" }, // ominous red clouds on dark crimson (Akatsuki-inspired but original shapes + non-identical shade)
13641366
];
13651367
ZONES.sort((a,b)=>a.d-b.d); // keep zones ordered by distance after appending the new environments
1366-
const _ZG = ZONES.find(z=>z.name==="Zero Purravity"); // Zero-G zone (for the ?zerog test-jump)
1367-
const _forceZeroG = (typeof Q!=="undefined") && Q.has("zerog"); // ?zerog forces the Zero-G world from the start (testing)
1368+
// World test-jump: ?world=<substring> (or legacy ?zerog) forces a world from the start, for previewing.
1369+
const _forceWorld = ((typeof Q!=="undefined" ? (Q.get("world") || (Q.has("zerog") ? "zero" : "")) : "") || "").toLowerCase();
1370+
function _forcedZone(){ return _forceWorld ? (ZONES.find(z=>z.name.toLowerCase().includes(_forceWorld)) || null) : null; }
13681371
function _h2r(h){ const n=parseInt(h.slice(1),16); return [n>>16&255,n>>8&255,n&255]; }
13691372
function _r2h(r,g,b){ return "#"+[r,g,b].map(v=>Math.max(0,Math.min(255,Math.round(v))).toString(16).padStart(2,"0")).join(""); }
13701373
function _mix(a,b,t){ const A=_h2r(a),B=_h2r(b); return _r2h(A[0]+(B[0]-A[0])*t,A[1]+(B[1]-A[1])*t,A[2]+(B[2]-A[2])*t); }
@@ -1378,7 +1381,7 @@ <h2 id="rewardTitle" style="margin:0 0 6px">WEEKLY PRIZE!</h2>
13781381
sky:[hslHex(hue,0.88,0.54-darken), hslHex(hue+20,0.92,0.38-darken), hslHex(hue+42,0.96,0.20-darken*0.6)],
13791382
ac:hslHex(hue+180,0.97,0.64), fx:_PFX[((k%_PFX.length)+_PFX.length)%_PFX.length] }; }
13801383
function biomeAt(dist){ const last=ZONES.length-1;
1381-
if(_forceZeroG && _ZG){ return { name:_ZG.name, sky:_ZG.sky.slice(), ac:_ZG.ac, fx:_ZG.fx, lowGrav:_ZG.lowGrav, floaty:_ZG.floaty }; } // ?zerog test-jump
1384+
{ const fz=_forcedZone(); if(fz){ return { name:fz.name, sky:fz.sky.slice(), ac:fz.ac, fx:fz.fx||"none", lowGrav:fz.lowGrav, floaty:fz.floaty }; } } // ?world=/?zerog test-jump
13821385
if(dist<=ZONES[last].d){ let i=0; while(i<last && dist>=ZONES[i+1].d) i++; const A=ZONES[i], B=ZONES[Math.min(i+1,last)]; const t=B.d>A.d?_sm((dist-A.d)/(B.d-A.d)):0; const near=t<0.5?A:B;
13831386
return { name:near.name, sky:[_mix(A.sky[0],B.sky[0],t),_mix(A.sky[1],B.sky[1],t),_mix(A.sky[2],B.sky[2],t)], ac:_mix(A.ac,B.ac,t), fx:near.fx||"none", lowGrav:near.lowGrav, floaty:near.floaty }; }
13841387
const over=dist-ZONES[last].d, SP=1500, k=Math.floor(over/SP); const A=(k===0)?ZONES[last]:proceduralBiome(k-1), B=proceduralBiome(k); const t=_sm((over%SP)/SP); const near=t<0.5?A:B;
@@ -2717,11 +2720,30 @@ <h2 id="rewardTitle" style="margin:0 0 6px">WEEKLY PRIZE!</h2>
27172720
const cg=70*D, co=((cam)%cg+cg)%cg; ctx.fillStyle='rgba(255,252,242,0.85)'; for(let x=-co-cg,i=0;x<W+cg;x+=cg,i++){ const col=Math.round((cam-co)/cg)+i; const r=cg*(0.5+0.18*hashY(col)); ctx.beginPath(); ctx.arc(x+cg*0.5,gy+2*D,r,Math.PI,2*Math.PI,false); ctx.fill(); }
27182721
ctx.fillStyle=getAccent(0.5); ctx.fillRect(0,gy,W,2.5*D); for(let i=0;i<10;i++){ const px=(((i*151*D-cam)%W)+W)%W, py=gy+gh*(0.3+0.5*hashY(i)), tw=0.5+0.5*Math.sin(t*3+i*1.7); ctx.fillStyle='rgba(255,244,210,'+(0.25+0.5*tw)+')'; ctx.beginPath(); ctx.arc(px,py,2.2*D,0,6.283); ctx.fill(); } ctx.restore();
27192722
}
2723+
// CRIMSON DAWN scenery: ominous red clouds drifting on a dark crimson sky + a blood moon. Inspired by the
2724+
// Akatsuki red-cloud motif but with original lumpy shapes (not the trademarked cloud-with-white-outline).
2725+
function _redCloud(x,y,s,fill,rim,a){ const lobes=[[0,0,1],[-0.72,0.12,0.7],[0.72,0.12,0.72],[-0.36,-0.3,0.6],[0.42,-0.26,0.62],[1.18,0.18,0.5],[-1.18,0.2,0.48]];
2726+
ctx.save(); ctx.globalAlpha=a; ctx.fillStyle=rim; for(const L of lobes){ ctx.beginPath(); ctx.arc(x+L[0]*s,y+L[1]*s,L[2]*s*1.14,0,6.283); ctx.fill(); }
2727+
ctx.fillStyle=fill; for(const L of lobes){ ctx.beginPath(); ctx.arc(x+L[0]*s,y+L[1]*s,L[2]*s,0,6.283); ctx.fill(); } ctx.restore(); }
2728+
function scapeRedClouds(biome,horizon,t){
2729+
const mx=W*0.74-cam*0.02, my=horizon*0.30; // blood moon
2730+
ctx.save(); ctx.globalCompositeOperation='lighter'; const mg=ctx.createRadialGradient(mx,my,0,mx,my,horizon*0.55);
2731+
mg.addColorStop(0,'rgba(255,60,72,0.32)'); mg.addColorStop(1,'rgba(120,10,22,0)'); ctx.fillStyle=mg; ctx.fillRect(0,0,W,horizon); ctx.restore();
2732+
ctx.fillStyle='#7c0e1a'; ctx.beginPath(); ctx.arc(mx,my,horizon*0.13,0,6.283); ctx.fill();
2733+
ctx.fillStyle=biome.sky?biome.sky[2]:'#0a0306'; ctx.beginPath(); ctx.arc(mx+horizon*0.05,my-horizon*0.028,horizon*0.115,0,6.283); ctx.fill(); // crescent shadow
2734+
const layers=[{f:0.06,y:0.18,sz:0.10,n:5,a:0.85,c:'#b0141f',rim:'#360309',spd:6},
2735+
{f:0.15,y:0.32,sz:0.14,n:5,a:0.93,c:'#dc1f30',rim:'#48060f',spd:11},
2736+
{f:0.27,y:0.50,sz:0.18,n:4,a:0.98,c:'#ff2f44',rim:'#5a0a14',spd:18}];
2737+
for(const L of layers){ const span=W*1.7, drift=cam*L.f + t*L.spd*DPR;
2738+
for(let i=0;i<L.n*2;i++){ const baseX=hashS(i*3.1+L.f*97)*span; const x=(((baseX-drift)%span)+span)%span - span*0.3;
2739+
const y=horizon*(L.y+(hashY(i*2.7)-0.5)*0.10)+Math.sin(t*0.4+i)*4*DPR; const s=horizon*L.sz*(0.7+0.6*hashY(i*5));
2740+
_redCloud(x,y,s,L.c,L.rim,L.a); } }
2741+
}
27202742
// ===== END NEW ENVIRONMENTS =====
2721-
const THEMED_SCAPE = { "Dawnpurr":scapeMeadow, "Candyland":scapeCandyland, "Underwater Reef":scapeReef, "Neon Cyber-City":scapeCyberCity, "Haunted Graveyard":scapeGraveyard, "Sakura Grove":scapeSakura, "Desert Pyramids":scapeDesert, "Frozen Aurora":scapeFrozen, "Sky Kingdom":scapeSky, "Mushroom Hollow":scapeMushroom, "Jungle Ruins":scapeJungle, "Clockwork City":scapeClockwork, "Storm Spire":scapeStormSpire, "Funland":scapeFunland, "Rainbow Road":scapeRainbowRoad, "Synthwave Grid":scapeSynthwave, "Cosmic Nebula":scapeNebula, "Vaporwave":scapeVaporwave, "Geometric Void":scapeGeoVoid, "Bioluminescent Abyss":scapeAbyss, "Elysium":scapeElysium, "Zero Purravity":scapeNebula };
2743+
const THEMED_SCAPE = { "Dawnpurr":scapeMeadow, "Candyland":scapeCandyland, "Underwater Reef":scapeReef, "Neon Cyber-City":scapeCyberCity, "Haunted Graveyard":scapeGraveyard, "Sakura Grove":scapeSakura, "Desert Pyramids":scapeDesert, "Frozen Aurora":scapeFrozen, "Sky Kingdom":scapeSky, "Mushroom Hollow":scapeMushroom, "Jungle Ruins":scapeJungle, "Clockwork City":scapeClockwork, "Storm Spire":scapeStormSpire, "Funland":scapeFunland, "Rainbow Road":scapeRainbowRoad, "Synthwave Grid":scapeSynthwave, "Cosmic Nebula":scapeNebula, "Vaporwave":scapeVaporwave, "Geometric Void":scapeGeoVoid, "Bioluminescent Abyss":scapeAbyss, "Elysium":scapeElysium, "Zero Purravity":scapeNebula, "Petal Purradise":scapeSakura, "Crimson Dawn":scapeRedClouds };
27222744
function _zoneObj(z){ return { name:z.name, sky:z.sky, ac:z.ac, fx:z.fx||"none" }; }
27232745
function zonePair(dist){ const last=ZONES.length-1; // the two biomes you're between + blend t (for crossfading the distant world)
2724-
if(_forceZeroG && _ZG){ return {A:_zoneObj(_ZG), B:_zoneObj(_ZG), tt:0}; } // ?zerog: scenery = Zero-G too (not the low-distance meadow)
2746+
{ const fz=_forcedZone(); if(fz){ return {A:_zoneObj(fz), B:_zoneObj(fz), tt:0}; } } // ?world=/?zerog: scenery matches the forced world
27252747
if(dist<=ZONES[last].d){ let i=0; while(i<last && dist>=ZONES[i+1].d) i++; const A=ZONES[i], B=ZONES[Math.min(i+1,last)]; const tt=B.d>A.d?_sm((dist-A.d)/(B.d-A.d)):0; return {A:_zoneObj(A),B:_zoneObj(B),tt}; }
27262748
const over=dist-ZONES[last].d, SP=1500, k=Math.floor(over/SP); const A=(k===0)?_zoneObj(ZONES[last]):proceduralBiome(k-1), B=proceduralBiome(k); const tt=_sm((over%SP)/SP); return {A:A,B:B,tt}; }
27272749
function _scapeFor(biome,horizon,t){
@@ -3164,6 +3186,8 @@ <h2 id="rewardTitle" style="margin:0 0 6px">WEEKLY PRIZE!</h2>
31643186
const THEMED_FLOOR = {}; // biome name -> drawFloorX(biome,t); themed floors register here
31653187
THEMED_FLOOR["Rainbow Road"]=drawFloorRainbowRoad; THEMED_FLOOR["Synthwave Grid"]=drawFloorSynthwave; THEMED_FLOOR["Vaporwave"]=drawFloorVaporwave; THEMED_FLOOR["Bioluminescent Abyss"]=drawFloorAbyss; THEMED_FLOOR["Elysium"]=drawFloorElysium;
31663188
THEMED_FLOOR["Zero Purravity"]=drawFloorSynthwave; // Zero-G = neon synthwave grid floor (no grass/trees)
3189+
THEMED_FLOOR["Petal Purradise"]=drawFloorCandy; // all-pink sakura = candy floor (pink, no grass)
3190+
THEMED_FLOOR["Crimson Dawn"]=drawFloorAbyss; // red-cloud world = dark void floor
31673191
function drawFloor(biome,t){ const f=THEMED_FLOOR[biome.name]; if(f){ f(biome,t); return; } drawFloorDefault(biome); }
31683192
function drawFloorDefault(biome){
31693193
ctx.fillStyle="#0a1024"; ctx.fillRect(0,GROUND(),W,H-GROUND());

0 commit comments

Comments
 (0)