Files
kaltaquise-gamification/Cyst_Kid/js/movement.js
2026-04-16 08:14:20 +02:00

214 lines
7.6 KiB
JavaScript

// ========== MOVEMENT ==========
// Extends Game.prototype — must be loaded after game.js
Object.assign(Game.prototype, {
// -------- ENTITY MOVEMENT SYSTEM --------
// Core idea: An entity is either AT a tile center (moving=false)
// or IN TRANSIT to the next tile (moving=true).
// When at center, pick a direction. Then move pixel-by-pixel toward the
// next tile center in that direction. Once the center is reached or passed,
// snap to it and set moving=false again.
_moveEntity(ent, spd, pickDir){
if(!ent.moving){
const dir = pickDir(ent);
if(dir){ ent.dir = dir; ent.moving = true; }
return;
}
const dx = DC[ent.dir] * spd;
const dy = DR[ent.dir] * spd;
ent.x += dx; ent.y += dy;
const tgtC = ent.gc + DC[ent.dir];
const tgtR = ent.gr + DR[ent.dir];
const tgtX = tgtC * TILE;
const tgtY = tgtR * TILE + HUD_TOP;
let reached = false;
if(ent.dir==='right' && ent.x >= tgtX) reached=true;
if(ent.dir==='left' && ent.x <= tgtX) reached=true;
if(ent.dir==='down' && ent.y >= tgtY) reached=true;
if(ent.dir==='up' && ent.y <= tgtY) reached=true;
if(reached){
ent.gc = tgtC; ent.gr = tgtR;
ent.x = tgtC * TILE; ent.y = tgtR * TILE + HUD_TOP;
ent.moving = false;
}
},
// -------- PLAYER MOVEMENT --------
_movePl(wantDir){
const p = this.pl;
const spd = p.spd;
this._moveEntity(p, spd, (ent) => {
// This function is called when player is at tile center.
// Return the direction to move, or null to stay.
// Try the queued direction (allows turning at the next opening)
if(wantDir){
const nr = ent.gr + DR[wantDir];
const nc = ent.gc + DC[wantDir];
if(plOk(nr, nc)){
ent.lastFace = wantDir;
return wantDir;
}
// Queued turn not yet possible — keep moving straight
const nr2 = ent.gr + DR[ent.dir];
const nc2 = ent.gc + DC[ent.dir];
if(plOk(nr2, nc2)) return ent.dir;
}
// No input yet or fully blocked — stay put
return null;
});
// Handle tunnel wrap AFTER movement
if(p.gr === T_ROW){
if(p.gc < 0){
p.gc = COLS-1; p.x = p.gc*TILE;
p.gr = T_ROW; p.y = T_ROW*TILE+HUD_TOP;
p.moving = false;
this._onTun();
return;
}
if(p.gc >= COLS){
p.gc = 0; p.x = 0;
p.gr = T_ROW; p.y = T_ROW*TILE+HUD_TOP;
p.moving = false;
this._onTun();
return;
}
}
// Allow changing direction mid-transit if the player wants to reverse (180°)
if(p.moving && wantDir && wantDir === OPP[p.dir]){
p.dir = wantDir;
p.lastFace = wantDir;
}
// Allow turning at intersections mid-transit if close to grid on perpendicular axis
if(p.moving && wantDir && wantDir !== p.dir && wantDir !== OPP[p.dir]){
const cx = p.gc * TILE;
const cy = p.gr * TILE + HUD_TOP;
const isHoriz = (p.dir==='left'||p.dir==='right');
const aligned = isHoriz ? Math.abs(p.y - cy) < 1 : Math.abs(p.x - cx) < 1;
if(aligned){
const snapC = Math.round(p.x / TILE);
const snapR = Math.round((p.y - HUD_TOP) / TILE);
const nr = snapR + DR[wantDir];
const nc = snapC + DC[wantDir];
if(plOk(nr, nc)){
p.gc = snapC; p.gr = snapR;
p.x = snapC * TILE; p.y = snapR * TILE + HUD_TOP;
p.dir = wantDir;
p.lastFace = wantDir;
}
}
}
if(wantDir) p.lastFace = wantDir;
// Mouth anim
if(p.moving){
p.mouth+=.15*p.mouthD;if(p.mouth>1){p.mouth=1;p.mouthD=-1}if(p.mouth<0){p.mouth=0;p.mouthD=1}
}
// Eat dots
for(const d of this.dots)if(!d.e&&d.r===p.gr&&d.c===p.gc){d.e=true;this.eDots++;this.lsc++;if(this.fr%4===0)this.snd.bite()}
// Eat cystine
for(const cs of this.cysts)if(cs.v&&!cs.e&&cs.r===p.gr&&cs.c===p.gc){
cs.e=true;cs.v=false;this.ateSt=true;this.lsc+=10;this.snd.laser();
this.fx.push({x:p.x+TILE/2,y:p.y+TILE/2,co:'#FFD700',t:30,mx:30,tp:'glow'});
this.cysts.forEach(x=>{if(!x.e)x.v=false});this._sBio();
}
// Eat bio
for(let i=this.bios.length-1;i>=0;i--){const b=this.bios[i];if(b.r===p.gr&&b.c===p.gc){
this.bios.splice(i,1);this.ateBi=true;this.lsc+=10;this.snd.laser();
this.fx.push({x:p.x+TILE/2,y:p.y+TILE/2,co:'#FFD700',t:30,mx:30,tp:'glow'});
}}
},
_onTun(){
if(this.ateSt&&this.ateBi&&!this.sup){
this._sSup();this.ateSt=false;this.ateBi=false;this.combD++;this.lsc+=20;
}
},
_sBio(){
const cs=[];for(let r=1;r<ROWS-1;r++)for(let c=1;c<COLS-1;c++)if(isPath(r,c)&&Math.abs(r-this.pl.gr)+Math.abs(c-this.pl.gc)>5)cs.push({r,c});
if(cs.length)this.bios.push(cs[0|Math.random()*cs.length]);
},
_sSup(){
this.sup=true;this.supT=SUPER_SEC*FPS;this.snd.laser();
this.pains.forEach(p=>p.scared=true);
this.fx.push({x:this.pl.x+TILE/2,y:this.pl.y+TILE/2,co:CO.sup,t:40,mx:40,tp:'glow'});
},
_esup(){
this.sup=false;
PH.forEach((h,i)=>{const p=this.pains[i];p.scared=false;p.eaten=false;p.inHouse=true;p.leaving=false;p.x=h.c*TILE;p.y=h.r*TILE+HUD_TOP;p.gc=h.c;p.gr=h.r;p.relT=i*60+30;p.moving=false});
if(this.combD<this.combN){const rem=this.cysts.filter(c=>!c.e);const pos=this._rp(rem.length,1,20);rem.forEach((cs,i)=>{if(pos[i]){cs.r=pos[i].r;cs.c=pos[i].c;cs.v=true}})}
},
// -------- PAIN MOVEMENT --------
_movePains(){
for(const p of this.pains){
if(p.eaten)continue;
p.pulse+=.06;
// In house
if(p.inHouse){
p.relT--;
if(p.relT<=0){
p.inHouse=false;p.leaving=true;
p.gc=13;p.gr=9;p.x=p.gc*TILE;p.y=p.gr*TILE+HUD_TOP;
p.dir='up';p.moving=false;
}
continue;
}
// Leaving house — move upward through door to row 7
if(p.leaving){
p.y-=p.spd;
const tgtY=7*TILE+HUD_TOP;
if(p.y<=tgtY){
p.gr=7;p.gc=13;p.x=p.gc*TILE;p.y=p.gr*TILE+HUD_TOP;
p.leaving=false;p.moving=false;p.dir='left';
}
continue;
}
// Normal movement using the entity system
const spd=p.scared?p.spd*.55:p.spd;
this._moveEntity(p, spd, (ent)=>{
const valid=DIRS.filter(d=>{
const nr=ent.gr+DR[d], nc=ent.gc+DC[d];
return painOk(nr,nc);
});
const fwd=valid.filter(d=>d!==OPP[ent.dir]);
const ch=fwd.length>0?fwd:valid;
if(ch.length===0)return null;
return this._ppd(ent,ch);
});
// Tunnel wrap
if(p.gc<0){p.gc=COLS-1;p.x=p.gc*TILE;p.moving=false}
if(p.gc>=COLS){p.gc=0;p.x=0;p.moving=false}
}
},
_ppd(pain,ch){
const pr=this.pl.gr,pc=this.pl.gc;
const dist=d=>{const nr=pain.gr+DR[d],nc=pain.gc+DC[d];return Math.abs(nr-pr)+Math.abs(nc-pc)};
if(pain.scared)return ch.reduce((a,b)=>dist(a)>dist(b)?a:b);
switch(pain.strat){
case'chase':return ch.reduce((a,b)=>dist(a)<dist(b)?a:b);
case'ambush':{const pdir=this.pl.dir||this.pl.lastFace||'right';const ar=pr+(DR[pdir]||0)*4,ac=pc+(DC[pdir]||0)*4;const ad=d=>{const nr=pain.gr+DR[d],nc=pain.gc+DC[d];return Math.abs(nr-ar)+Math.abs(nc-ac)};return ch.reduce((a,b)=>ad(a)<ad(b)?a:b)}
case'patrol':{if(Math.abs(pain.gr-pr)+Math.abs(pain.gc-pc)<8)return ch.reduce((a,b)=>dist(a)<dist(b)?a:b);return ch[0|Math.random()*ch.length]}
default:return ch[0|Math.random()*ch.length];
}
}
});