Added kidney_labe and Cyste_kid

This commit is contained in:
verboomp
2026-04-16 08:14:20 +02:00
parent aa66c030f8
commit 9cc8ac8cad
40 changed files with 6762 additions and 0 deletions

287
sde/js/gameloop.js Normal file
View File

@@ -0,0 +1,287 @@
"use strict";
// ─── Game Loop ─────────
function gameLoop(){
if(curScreen!=="game"||!S)return;
if(showTut){animId=requestAnimationFrame(gameLoop);return}
if(S.won||S.lost)return;
var dt=1/60;
S.time+=dt;frame++;
var mx=0,my=0;
if(keys["w"]||keys["arrowup"])my=-1;
if(keys["s"]||keys["arrowdown"])my=1;
if(keys["a"]||keys["arrowleft"])mx=-1;
if(keys["d"]||keys["arrowright"])mx=1;
if(touch.active&&(Math.abs(touch.dx)>15||Math.abs(touch.dy)>15)){
if(Math.abs(touch.dx)>Math.abs(touch.dy))mx=touch.dx>0?1:-1;
else my=touch.dy>0?1:-1;
}
if(mx&&my){if(Math.abs(mx)>=Math.abs(my))my=0;else mx=0;}
S.player.sprinting=keys.shift&&!S.rollingStone;
var speed=S.rollingStone?PUSH_SPEED:(S.player.sprinting?SPRINT_SPEED:WALK_SPEED);
if(mx||my){
var len=Math.sqrt(mx*mx+my*my);
var nx=S.player.x+(mx/len)*speed;
var ny=S.player.y+(my/len)*speed;
var cp=closestOnPaths(nx,ny,S.net.edges,S.net.nodes);
if(cp.d>PATH_W*0.55){nx=cp.x;ny=cp.y;}
nx=clamp(nx,10,S.net.mapW-10);
ny=clamp(ny,10,S.net.mapH-10);
S.player.x=nx;S.player.y=ny;
S.player.dir=Math.atan2(my,mx);
S.player.frame+=S.player.sprinting?0.25:0.15;
}
if(S.rollingStone){
var rs=S.rollingStone;
var offDist=22;
var sdx=Math.cos(S.player.dir)*offDist;
var sdy=Math.sin(S.player.dir)*offDist;
var tsx=S.player.x+sdx, tsy=S.player.y+sdy;
var scp=closestOnPaths(tsx,tsy,S.net.edges,S.net.nodes);
if(scp.d>PATH_W*0.5){tsx=scp.x;tsy=scp.y;}
rs.x=lerp(rs.x,tsx,0.15);
rs.y=lerp(rs.y,tsy,0.15);
if(mx||my){
rs.rollAngle+=(mx||my)*0.08;
}
}
if(actionQ){actionQ=false;handleAction()}
S.atPharmacy=dist(S.player,S.net.apo)<65;
if(S.phase===2){
S.phase2Timer+=dt;
for(var ki=0;ki<S.keims.length;ki++){
var k=S.keims[ki];
k.size+=S.cfg.keimGrow*(1+S.phase2Timer*0.003);
k.size=Math.min(k.size,1.0);
k.pulse=(k.pulse||0)+0.04;
}
var spawnRate=0.006+curLvl*0.003;
if(Math.random()<spawnRate){
var segs=S.net.edges;
var si=Math.floor(Math.random()*segs.length);
var ea2=S.net.nodes[segs[si][0]], eb=S.net.nodes[segs[si][1]];
var t2=rng(0.15,0.85);
var kp={x:lerp(ea2.x,eb.x,t2),y:lerp(ea2.y,eb.y,t2)};
if(dist(kp,S.player)>100&&dist(kp,S.net.apo)>70&&dist(kp,S.net.lab)>70){
S.keims.push({x:kp.x,y:kp.y,size:0.06,id:Date.now()+Math.random(),pulse:0});
}
}
var totK=0;
for(var ki=0;ki<S.keims.length;ki++)totK+=S.keims[ki].size;
S.kidneyDmg=Math.min(100,totK*2);
if(S.parkedStone&&!S.rollingStone){
if(!S.atPharmacy){
S.stoneAloneTimer+=dt;
}
}else{
S.stoneAloneTimer=Math.max(0,S.stoneAloneTimer-dt*0.8);
}
if(S.kidneyDmg>=100){
S.lost=true;
showGO("Die Kristallkeime haben die Niere kritisch gesch\u00E4digt. Der Stein kann nicht mehr bewegt werden.");
return;
}
if(S.stoneAloneTimer>=S.maxAloneTime){
S.lost=true;
showGO("Der Weg zum Gipfel ist von Kristallen dauerhaft blockiert. Der Zystinstein sitzt fest!");
return;
}
if(S.rollingStone&&S.rollingStone.isCystine&&dist(S.rollingStone,S.net.peak)<50){
S.won=true;
S.score+=Math.max(0,Math.floor(1000-S.phase2Timer*5+(100-S.kidneyDmg)*5));
sfxWin();
setTimeout(function(){showTrans(curLvl)},700);
return;
}
["water","bicarb","thiola"].forEach(function(m){
S.meds[m]=Math.max(0,S.meds[m]-dt*(0.3+curLvl*0.15));
});
}
for(var pi=sprayParticles.length-1;pi>=0;pi--){
var p=sprayParticles[pi];
p.life-=dt;
p.x+=p.vx*dt;p.y+=p.vy*dt;
p.vx*=0.95;p.vy*=0.95;
if(p.life<=0)sprayParticles.splice(pi,1);
}
if(S.msgTimer>0){S.msgTimer-=dt;if(S.msgTimer<=0)S.msg="";}
var tcx=S.player.x-CW/2,tcy=S.player.y-CH/2;
S.camera.x=lerp(S.camera.x,clamp(tcx,0,S.net.mapW-CW),0.1);
S.camera.y=lerp(S.camera.y,clamp(tcy,0,S.net.mapH-CH),0.1);
render();
if(frame%5===0)updateHUD();
animId=requestAnimationFrame(gameLoop);
}
// ─── Action ────────────
function handleAction(){
if(!S)return;
var P=S.player;
if(S.phase===1){
if(!S.rollingStone){
var best=null,bd=Infinity;
S.stones.forEach(function(st){
if(st.scanned||st.rolling)return;
var d=dist(P,st);
if(d<55&&d<bd){best=st;bd=d;}
});
if(best){
S.rollingStone=best;best.rolling=true;
setMsg("Stein aufgehoben! Rolle ihn zum Labor \uD83D\uDD2C",2.5);
sfxPick();
}
}else{
if(dist(P,S.net.lab)<65){
var rs=S.rollingStone;
rs.scanned=true;S.stonesScanned++;
if(rs.isCystine){
setMsg("\u26A0\uFE0F ZYSTINSTEIN ERKANNT! Das Tal erwacht!",3.5,true);
sfxPos();
S.cystineStone=rs;
S.phase=2;S.score+=200;
for(var i=0;i<3+curLvl*2;i++){
var segs=S.net.edges;
var si=Math.floor(Math.random()*segs.length);
var ea2=S.net.nodes[segs[si][0]],eb=S.net.nodes[segs[si][1]];
var t2=rng(0.2,0.8);
S.keims.push({x:lerp(ea2.x,eb.x,t2),y:lerp(ea2.y,eb.y,t2),size:0.08,id:Date.now()+i,pulse:0});
}
}else{
setMsg("Negativ \u2013 kein Zystinstein. Suche weiter!",2);
sfxNeg();
rs.rolling=false;
S.rollingStone=null;
S.score+=20;
}
}else{
S.rollingStone.rolling=false;
S.rollingStone.x=P.x;S.rollingStone.y=P.y;
S.rollingStone=null;
setMsg("Stein abgelegt.",1);
}
}
}
if(S.phase===2){
if(!S.rollingStone&&S.parkedStone&&dist(P,S.parkedStone)<55){
S.rollingStone=S.parkedStone;S.parkedStone=null;
S.stoneAloneTimer=0;
setMsg("Zystinstein aufgenommen! Weiter zum Gipfel!",1.5);
sfxPick();
return;
}
if(S.rollingStone&&S.rollingStone.isCystine&&!(S.parkedStone)){
S.parkedStone=S.rollingStone;
S.parkedStone.x=P.x;S.parkedStone.y=P.y;
S.parkedStone.rolling=false;
S.rollingStone=null;
S.stoneAloneTimer=0;
setMsg("Stein geparkt. K\u00FCmmere dich um die Keime!",1.5);
return;
}
if(dist(P,S.net.apo)<65){
var mid=MEDS[S.selMed].id;
if(S.meds[mid]<S.medsMax){
S.meds[mid]=S.medsMax;
var others=MEDS.filter(function(_,i){return i!==S.selMed});
others.sort(function(a,b){return S.meds[a.id]-S.meds[b.id]});
S.meds[others[0].id]=S.medsMax;
setMsg(MEDS[S.selMed].name+" & "+others[0].name+" aufgef\u00FCllt!",1.5);
sfxRefill();S.score+=10;
return;
}
}
var mid2=MEDS[S.selMed].id;
if(S.meds[mid2]>0){
var hit=false;
for(var ki=0;ki<S.keims.length;ki++){
var k=S.keims[ki];
if(dist(P,k)<70){
var shrink=S.cfg.keimShrink*MEDS[S.selMed].pow;
k.size-=shrink;
hit=true;
for(var pi=0;pi<6;pi++){
sprayParticles.push({
x:k.x+rng(-8,8),y:k.y+rng(-8,8),
vx:rng(-40,40),vy:rng(-40,40),
life:rng(0.3,0.7),
color:MEDS[S.selMed].color
});
}
}
}
if(hit){
S.meds[mid2]=Math.max(0,S.meds[mid2]-2.5);
sfxSpray();
}
S.keims=S.keims.filter(function(k){return k.size>0.02});
}
}
}
function setMsg(m,d,al){S.msg=m;S.msgTimer=d;S.msgAl=!!al;}
// ─── HUD ───────────────
function updateHUD(){
if(!S)return;
$("hud-lv").textContent="Lvl "+(curLvl+1);
$("hud-sc").textContent="\u2B50 "+S.score;
$("hud-ph").textContent=S.phase===1?("\uD83D\uDD0D "+S.stonesScanned+"/"+S.stones.length):"\u26A1 Phase 2";
var kw=$("kid-w"),kf=$("kid-f");
if(S.phase===2){
kw.style.display="flex";
kf.style.width=S.kidneyDmg+"%";
kf.className="bar-f"+(S.kidneyDmg>70?" bar-dn":S.kidneyDmg>40?" bar-wn":" bar-ok");
}else kw.style.display="none";
var te=$("st-tm");
if(S.phase===2&&S.parkedStone&&!S.rollingStone&&S.stoneAloneTimer>0){
var rem=Math.max(0,S.maxAloneTime-S.stoneAloneTimer);
te.textContent="\u23F0 "+rem.toFixed(0)+"s";
te.className="st-tm"+(S.stoneAloneTimer>S.maxAloneTime*0.6?" st-d":" st-w");
te.style.display="inline";
}else te.style.display="none";
var bot=$("hud-bot");
if(S.phase===2)bot.classList.add("vis");else bot.classList.remove("vis");
MEDS.forEach(function(m,i){
var b=$("med-"+i),fl=$("mfl-"+i),st=$("mst-"+i);
var pct=(S.meds[m.id]/S.medsMax)*100;
b.className="med-b"+(S.selMed===i?" sel":"");
b.style.borderColor=S.selMed===i?m.color:"#333";
fl.style.width=pct+"%";
fl.style.background=pct<20?"#e53935":m.color;
st.textContent=pct<20?"NIEDRIG!":Math.round(pct)+"%";
st.className="med-st"+(pct<20?" med-lo":" med-ok");
});
var mp=$("msg-pop");
if(S.msg){mp.style.display="block";mp.textContent=S.msg;mp.className="msg-pop"+(S.msgAl?" msg-al":"");}
else mp.style.display="none";
}