⭐ Built in the Agent OS — get it inside the AI Profit BoardroomJoin AIPB →;i>=0;i--){
var e=enemies[i],dx=e.position.x-P.x,dz=e.position.z-P.z;
var d=Math.hypot(dx,dz);
if(d>3.4*e.scale.x)continue;
if((dx/d)*fx+(dz/d)*fz<0.42)continue;
damageEnemy(e,34+Math.random()*10,dx/d,dz/d);
shake=Math.max(shake,0.5);
}
}
function hitPlayer(dmg){
var mul=blocking>0.6?0.28:1;
P.hp-=dmg*mul;hurtF=1;shake=Math.max(shake,mul<1?0.35:0.85);
if(mul<1){
var fp=new THREE.Vector3(P.x-Math.sin(P.yaw)*1.2,1.4,P.z-Math.cos(P.yaw)*1.2);
burst(fp,0xffd070,8,5,0.11);
}
if(P.hp<=0){P.hp=0;die();}
}
function die(){
state='dead';
overTitle.textContent='You Died';overTitle.style.color='#e0402c';
overSub.textContent='The crypt keeps its dead';
overStats.innerHTML='SLAIN '+kills+' · RELICS '+relics+'/3 · GLORY '+souls;
over.style.display='flex';
if(document.exitPointerLock)document.exitPointerLock();
}
function win(){
state='won';
overTitle.textContent='Crypt Cleansed';overTitle.style.color='#7dfaa8';
overSub.textContent='All three relics are yours';
overStats.innerHTML='SLAIN '+kills+' · GLORY '+souls;
over.style.display='flex';
if(document.exitPointerLock)document.exitPointerLock();
}
/* ======================= ENEMY AI ======================= */
function moveActor(e,nx,nz,r){
if(!blocked(nx,e.position.z,r))e.position.x=nx;
if(!blocked(e.position.x,nz,r))e.position.z=nz;
}
function updateEnemies(dt,t){
var near=0;
for(var i=0;i0){
u.flash=Math.max(0,u.flash-dt*3.4);
for(var m=0;m0.001){
var push=(1.5-od)*0.5;
e.position.x+=ox/od*push;e.position.z+=oz/od*push;
o.position.x-=ox/od*push;o.position.z-=oz/od*push;
}
}
/* gait */
walkAnim(e,t,aggro?1.6:0.7);
}
}
/* float / bob */
if(u.float)e.position.y=0.55+Math.sin(t*1.9+u.phase)*0.22;
else e.position.y=Math.abs(Math.sin(t*5+u.phase))*0.05;
}
foeVal.textContent=near;
}
function walkAnim(e,t,amt){
var u=e.userData,s=Math.sin(t*7+u.phase)*0.42*amt;
if(u.leg0)u.leg0.rotation.x=s;
if(u.leg1)u.leg1.rotation.x=-s;
}
/* ======================= PICKUPS ======================= */
function updatePickups(dt,t){
for(var i=pickups.length-1;i>=0;i--){
var p=pickups[i],u=p.userData;
if(u.spin){u.spin.rotation.y+=dt*1.8;u.spin.rotation.x+=dt*0.9;}
if(u.ring)u.ring.rotation.z+=dt*1.2;
if(u.kind==='soul')p.position.y=1.1+Math.sin(t*2.4+p.position.x)*0.16;
var d=Math.hypot(p.position.x-P.x,p.position.z-P.z);
if(u.kind==='soul'&&d<1.9){
P.hp=Math.min(100,P.hp+18);souls+=90;goldF=1;
burst(p.position,COL.gold,18,7,0.14);
pulse($('status'));pulse($('score'));
say('SOUL EMBER — VITALITY RESTORED','#ffd479');
scene.remove(p);pickups.splice(i,1);
}else if(u.kind==='relic'&&d<2.6&&keys['KeyE']){
relics++;goldF=1;
var rp=p.position.clone();rp.y=1.8;
burst(rp,COL.green,26,8,0.18);burst(rp,0xffffff,10,5,0.12);
pulse($('score'));
scene.remove(p);pickups.splice(i,1);
if(relics>=3)win();
else say('RELIC CLAIMED — '+(3-relics)+' REMAIN','#7dfaa8');
}else if(u.kind==='relic'&&d<2.6){
if(bTimer<=0)say('PRESS E TO CLAIM RELIC','#7dfaa8');
}
}
}
/* ======================= PLAYER ======================= */
function updatePlayer(dt,t){
var f=0,s=0;
if(keys['KeyW']||keys['ArrowUp'])f+=1;
if(keys['KeyS']||keys['ArrowDown'])f-=1;
if(keys['KeyA']||keys['ArrowLeft'])s-=1;
if(keys['KeyD']||keys['ArrowRight'])s+=1;
blocking=(keys['Space']?Math.min(1,blocking+dt*6):Math.max(0,blocking-dt*6));
var sprint=(keys['ShiftLeft']||keys['ShiftRight'])&&P.vg>2&&(f||s)&&blocking<0.5;
if(sprint)P.vg=Math.max(0,P.vg-dt*26);else P.vg=Math.min(100,P.vg+dt*15);
var spd=(sprint?9.4:5.6)*(blocking>0.5?0.55:1);
var len=Math.hypot(f,s);if(len>0){f/=len;s/=len;}
var fx=-Math.sin(P.yaw),fz=-Math.cos(P.yaw);
var rx=-fz,rz=fx;
var tvx=(fx*f+rx*s)*spd,tvz=(fz*f+rz*s)*spd;
P.vx+=(tvx-P.vx)*Math.min(1,dt*12);
P.vz+=(tvz-P.vz)*Math.min(1,dt*12);
var r=1.25;
var nx=P.x+P.vx*dt,nz=P.z+P.vz*dt;
if(!blocked(nx,P.z,r))P.x=nx;else P.vx*=-0.15;
if(!blocked(P.x,nz,r))P.z=nz;else P.vz*=-0.15;
var moving=Math.hypot(P.vx,P.vz);
walkT+=dt*moving*(sprint?1.35:1.0);
bob=Math.sin(walkT*2.1)*0.055*Math.min(1,moving/5);
/* swing */
if(atkCd>0)atkCd-=dt;
if(swing>0){
swing-=dt*3.1;
var k=1-Math.max(0,swing);
var arc=Math.sin(Math.min(1,k)*Math.PI);
swordArm.rotation.x=-0.22-arc*1.75;
swordArm.rotation.z=0.24+arc*1.1;
swordArm.rotation.y=-0.3+arc*0.7;
swordArm.position.x=0.46-arc*0.5;
swordArm.position.y=-0.42+arc*0.2;
if(!swingHit&&k>0.3){swingHit=true;doSwingHit();
var mp=new THREE.Vector3(P.x+fx*1.6,1.5,P.z+fz*1.6);
burst(mp,COL.cyan,7,4,0.09,2);}
if(swing<=0){swing=0;}
}else{
var sw=Math.sin(walkT*2.1)*0.05*Math.min(1,moving/5);
swordArm.rotation.x+=(-0.22+sw-blocking*0.25-(0)-swordArm.rotation.x)*Math.min(1,dt*9);
swordArm.rotation.z+=(0.24-swordArm.rotation.z)*Math.min(1,dt*9);
swordArm.rotation.y+=(-0.3-swordArm.rotation.y)*Math.min(1,dt*9);
swordArm.position.x+=(0.46-blocking*0.06-swordArm.position.x)*Math.min(1,dt*9);
swordArm.position.y+=(-0.42+sw*0.5-swordArm.position.y)*Math.min(1,dt*9);
}
/* shield raise */
var tz=-0.66-blocking*0.16,ty=-0.4+blocking*0.34,tx=-0.5+blocking*0.34;
shieldArm.position.x+=(tx-shieldArm.position.x)*Math.min(1,dt*11);
shieldArm.position.y+=(ty+Math.sin(walkT*2.1+1)*0.03-shieldArm.position.y)*Math.min(1,dt*11);
shieldArm.position.z+=(tz-shieldArm.position.z)*Math.min(1,dt*11);
shieldArm.rotation.y+=((0.4-blocking*0.42)-shieldArm.rotation.y)*Math.min(1,dt*11);
shieldArm.rotation.x+=((0.1-blocking*0.2)-shieldArm.rotation.x)*Math.min(1,dt*11);
/* torch flicker */
var fl=0.82+Math.sin(t*17)*0.1+Math.sin(t*41)*0.06;
torchLight.intensity=2.3*fl;
var tfm=hero.userData.torchFlame;
tfm.scale.set(0.85+fl*0.25,0.8+fl*0.35,0.85+fl*0.25);
tfm.material.opacity=0.75+fl*0.2;
hero.userData.torchHead.material.emissiveIntensity=2.0+fl;
/* spawns */
spawnT-=dt;
if(spawnT<=0){spawnT=5.5;if(enemies.length<12&&totalSpawn<90)spawnEnemy();}
}
/* ======================= CAMERA ======================= */
function updateCamera(dt){
var tgt=new THREE.Vector3(P.x,P.y+bob,P.z);
camPos.lerp(tgt,Math.min(1,dt*17));
camera.position.copy(camPos);
if(shake>0){
shake=Math.max(0,shake-dt*2.6);
var s=shake*0.13;
camera.position.x+=(Math.random()-.5)*s;
camera.position.y+=(Math.random()-.5)*s;
}
camera.rotation.set(0,0,0);
camera.rotation.order='YXZ';
camera.rotation.y=P.yaw;
camera.rotation.x=P.pitch;
camera.rotation.z=Math.sin(walkT*2.1)*0.012-P.vx*0.0;
key.position.set(P.x+15,32,P.z+11);
key.target.position.set(P.x,0,P.z);
}
/* ======================= BRAZIER LIGHT POOL ======================= */
function updateBraziers(dt,t){
var scored=[];
for(var i=0;iMR||Math.abs(ddz)>MR)continue;
mapX.fillStyle=p.userData.kind==='relic'?'#63f5a0':'#ffbe55';
mapX.beginPath();
mapX.arc((ddx+MR+0.5)*cellPx,(ddz+MR+0.5)*cellPx,2.6,0,6.3);mapX.fill();
}
for(i=0;iMR||Math.abs(ez)>MR)continue;
mapX.fillStyle=e.userData.type==='jotun'?'#ff5a20':'#ff8f7a';
mapX.fillRect((ex+MR+0.5)*cellPx-2,(ez+MR+0.5)*cellPx-2,4,4);
}
/* player + facing */
var c=MS/2;
mapX.fillStyle='#f3e9d2';mapX.beginPath();mapX.arc(c,c,3.2,0,6.3);mapX.fill();
mapX.strokeStyle='#ffd479';mapX.lineWidth=1.6;mapX.beginPath();mapX.moveTo(c,c);
mapX.lineTo(c-Math.sin(P.yaw)*9,c-Math.cos(P.yaw)*9);mapX.stroke();
mapX.strokeStyle='rgba(190,150,90,.35)';mapX.lineWidth=1;mapX.strokeRect(.5,.5,MS-1,MS-1);
}
/* ======================= HUD UPDATE ======================= */
var mapT=0;
function updateHud(dt){
hpFill.style.width=Math.max(0,P.hp)+'%';
vgFill.style.width=P.vg+'%';
hpNum.textContent=Math.round(P.hp);
vgNum.textContent=Math.round(P.vg);
scoreVal.textContent=Math.round(souls+kills*40);
killVal.textContent=kills;
relicVal.textContent=relics+'/3';
var d=Math.round(Math.hypot(P.x-wx(1),P.z-wx(1))*0.6)+40;
depthEl.textContent='-'+(d<100?('0'+d).slice(-3):d)+' M';
if(hurtF>0){hurtF=Math.max(0,hurtF-dt*2.2);hurtEl.style.opacity=hurtF*0.9;}
if(goldF>0){goldF=Math.max(0,goldF-dt*2.6);goldEl.style.opacity=goldF*0.75;}
if(bTimer>0){bTimer-=dt;if(bTimer<=0)banner.style.opacity=0;}
mapT-=dt;if(mapT<=0){mapT=0.1;drawMap();}
}
/* ======================= LOOP ======================= */
var clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate);
var dt=Math.min(0.05,clock.getDelta()),t=clock.elapsedTime;
if(state==='play'){
updatePlayer(dt,t);
updateEnemies(dt,t);
updatePickups(dt,t);
updateCamera(dt);
}else{
updateCamera(dt);
}
updateBraziers(dt,t);
updateParts(dt);
updateHud(dt);
renderer.render(scene,camera);
}
/* ======================= BOOT ======================= */
buildMaze();
openCells=buildWorldGeometry();
buildProps(openCells);
startRun(false);
drawMap();
animate();
})();