⭐ 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(); })();