Added kidney_labe and Cyste_kid
This commit is contained in:
287
sde/js/gameloop.js
Normal file
287
sde/js/gameloop.js
Normal 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";
|
||||
}
|
||||
Reference in New Issue
Block a user