Spaces:
Paused
Paused
| <html lang="cs"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>VoxForge Platform 0.2 — Minecraft × Roblox na steroidech</title> | |
| <style> | |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} | |
| :root{ | |
| --bg:#060610;--bg2:#0d0d1a;--bg3:#111124; | |
| --blue:#00d4ff;--purple:#8b5cf6;--green:#00ff88; | |
| --red:#ff4d4d;--yellow:#ffd34d;--orange:#ff8c00; | |
| --text:#e2e8f0;--dim:#94a3b8; | |
| --border:rgba(0,212,255,0.12);--card:rgba(13,13,26,0.9); | |
| } | |
| html,body{width:100%;height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;overflow:hidden} | |
| canvas{display:block} | |
| @keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}} | |
| @keyframes glow{0%,100%{text-shadow:0 0 20px rgba(0,212,255,.6),0 0 40px rgba(139,92,246,.3)}50%{text-shadow:0 0 50px rgba(0,212,255,1),0 0 90px rgba(139,92,246,.7)}} | |
| @keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}} | |
| @keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}} | |
| @keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}} | |
| /* LANDING */ | |
| #landing{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden} | |
| #bgC{position:absolute;inset:0;z-index:0} | |
| .hero{position:relative;z-index:1;text-align:center;padding:2rem 1rem;max-width:900px;margin:0 auto} | |
| .badge{display:inline-block;padding:.35rem .9rem;background:rgba(0,212,255,.08);border:1px solid var(--border);border-radius:999px;font-size:.72rem;letter-spacing:.2em;color:var(--blue);margin-bottom:1.2rem;animation:fadeUp .6s ease both} | |
| .title-main{font-size:clamp(2.8rem,8vw,6rem);font-weight:900;background:linear-gradient(135deg,var(--blue),var(--purple),var(--green));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;animation:glow 3s ease infinite,fadeUp .8s .1s ease both;margin-bottom:.4rem} | |
| .subtitle{font-size:clamp(1rem,2.5vw,1.4rem);color:var(--dim);margin-bottom:.5rem;animation:fadeUp .8s .2s ease both;opacity:0;animation-fill-mode:forwards} | |
| .tagline{font-size:clamp(.8rem,1.8vw,1rem);color:var(--purple);letter-spacing:.1em;margin-bottom:1.5rem;animation:fadeUp .8s .3s ease both;opacity:0;animation-fill-mode:forwards} | |
| .studios-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:.6rem;max-width:900px;margin:0 auto 1.5rem;animation:fadeUp .8s .4s ease both;opacity:0;animation-fill-mode:forwards} | |
| .studio-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:.8rem .5rem;cursor:pointer;transition:all .2s;text-align:center} | |
| .studio-card:hover{border-color:var(--blue);background:rgba(0,212,255,.06);transform:translateY(-3px)} | |
| .studio-icon{font-size:1.6rem;display:block;margin-bottom:.3rem;animation:float 3s ease infinite} | |
| .studio-name{font-size:.72rem;font-weight:700;color:var(--blue);letter-spacing:.05em} | |
| .studio-desc{font-size:.63rem;color:var(--dim);margin-top:.2rem} | |
| .btn-enter{padding:.8rem 2.4rem;background:linear-gradient(135deg,var(--blue),var(--purple));border:none;border-radius:999px;color:#fff;font-size:1rem;font-weight:700;cursor:pointer;letter-spacing:.08em;transition:all .25s;animation:fadeUp .8s .6s ease both;opacity:0;animation-fill-mode:forwards;box-shadow:0 0 30px rgba(0,212,255,.4)} | |
| .btn-enter:hover{transform:scale(1.06);box-shadow:0 0 60px rgba(0,212,255,.7)} | |
| /* GAME SHELL */ | |
| #shell{position:fixed;inset:0;z-index:10;display:none;flex-direction:column} | |
| #topbar{height:40px;background:rgba(6,6,16,.95);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:.3rem;padding:0 .5rem;flex-shrink:0;overflow-x:auto} | |
| .tb-logo{font-size:.85rem;font-weight:800;background:linear-gradient(90deg,var(--blue),var(--purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-right:.3rem;white-space:nowrap} | |
| .tb-tab{padding:.2rem .55rem;border:1px solid transparent;border-radius:6px;font-size:.67rem;cursor:pointer;color:var(--dim);transition:all .15s;white-space:nowrap} | |
| .tb-tab.active{border-color:var(--blue);color:var(--blue);background:rgba(0,212,255,.08)} | |
| .tb-tab:hover:not(.active){color:var(--text);border-color:rgba(255,255,255,.1)} | |
| .tb-spacer{flex:1} | |
| .tb-stat{font-size:.63rem;color:var(--dim);padding:0 .3rem;white-space:nowrap} | |
| .tb-btn{padding:.2rem .5rem;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--dim);font-size:.63rem;cursor:pointer;transition:all .15s;white-space:nowrap} | |
| .tb-btn:hover{color:var(--blue);border-color:var(--blue)} | |
| #content{flex:1;position:relative;overflow:hidden;display:flex} | |
| #gameCanvas{width:100%;height:100%;position:absolute;inset:0} | |
| /* SIDE PANEL */ | |
| #sidePanel{width:210px;background:rgba(6,6,16,.92);border-left:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto;z-index:2} | |
| .panel-section{padding:.6rem;border-bottom:1px solid var(--border)} | |
| .panel-title{font-size:.68rem;letter-spacing:.15em;color:var(--blue);font-weight:700;margin-bottom:.5rem;text-transform:uppercase} | |
| .mat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:3px} | |
| .mat-cell{width:100%;aspect-ratio:1;border-radius:3px;cursor:pointer;border:2px solid transparent;transition:all .1s} | |
| .mat-cell.selected{border-color:#fff} | |
| .mat-cell:hover{transform:scale(1.15)} | |
| .btn-row{display:flex;gap:.3rem;flex-wrap:wrap} | |
| .sm-btn{padding:.25rem .4rem;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--text);font-size:.62rem;cursor:pointer;transition:all .1s;flex:1;min-width:fit-content;text-align:center;white-space:nowrap} | |
| .sm-btn:hover,.sm-btn.on{background:rgba(0,212,255,.12);border-color:var(--blue);color:var(--blue)} | |
| .sm-btn.red{border-color:var(--red);color:var(--red)} | |
| .stat-row{display:flex;justify-content:space-between;font-size:.62rem;padding:.1rem 0;color:var(--dim)} | |
| .stat-val{color:var(--green)} | |
| .list-box{display:flex;flex-direction:column;gap:2px;max-height:110px;overflow-y:auto} | |
| .list-item{font-size:.6rem;padding:.15rem .35rem;border-radius:3px;background:rgba(255,255,255,.04);display:flex;justify-content:space-between;gap:.25rem;align-items:center} | |
| .weather-bar{height:4px;border-radius:2px;background:rgba(255,255,255,.08);margin:.3rem 0} | |
| .weather-fill{height:100%;border-radius:2px;transition:width .5s} | |
| /* STATUS BAR */ | |
| #statusBar{height:22px;background:rgba(6,6,16,.95);border-top:1px solid var(--border);display:flex;align-items:center;padding:0 .6rem;gap:.8rem;font-size:.6rem;color:var(--dim);flex-shrink:0;overflow:hidden} | |
| .status-item{display:flex;gap:.25rem;align-items:center;white-space:nowrap} | |
| .status-dot{width:5px;height:5px;border-radius:50%;background:var(--green);animation:pulse 2s infinite;flex-shrink:0} | |
| /* HOTBAR */ | |
| #hotbar{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);display:flex;gap:3px;z-index:5;pointer-events:all} | |
| .hb-slot{width:42px;height:42px;background:rgba(6,6,16,.85);border:2px solid rgba(255,255,255,.12);border-radius:5px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .1s;position:relative} | |
| .hb-slot.active{border-color:var(--blue);box-shadow:0 0 8px rgba(0,212,255,.4)} | |
| .hb-num{position:absolute;top:1px;left:2px;font-size:.48rem;color:var(--dim)} | |
| /* MINIMAP */ | |
| #minimap{position:absolute;top:6px;right:6px;width:96px;height:96px;border:1px solid var(--border);border-radius:5px;overflow:hidden;z-index:5;opacity:.85} | |
| /* NOTIFICATIONS */ | |
| #notifs{position:absolute;top:6px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;gap:3px;z-index:20;pointer-events:none;min-width:260px;align-items:center} | |
| .notif{background:rgba(0,212,255,.15);border:1px solid var(--blue);border-radius:7px;padding:.35rem .8rem;font-size:.7rem;color:var(--blue);animation:fadeUp .3s ease both;text-align:center} | |
| /* LOADING */ | |
| #loader{position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem} | |
| .loader-ring{width:56px;height:56px;border:4px solid rgba(0,212,255,.15);border-top-color:var(--blue);border-radius:50%;animation:spin 1s linear infinite} | |
| .loader-text{font-size:.82rem;color:var(--dim);letter-spacing:.1em} | |
| .loader-bar{width:220px;height:4px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden} | |
| .loader-fill{height:100%;background:linear-gradient(90deg,var(--blue),var(--purple));border-radius:2px;transition:width .3s} | |
| /* HEX */ | |
| .hex-grid-wrap{margin-top:.4rem;overflow:auto;max-height:160px} | |
| /* PHANTOM CONSCIOUSNESS PANEL */ | |
| #phantomPanel{ | |
| position:absolute;bottom:36px;left:8px;z-index:30; | |
| width:240px;background:rgba(6,6,16,.92); | |
| border:1px solid rgba(139,92,246,.35);border-radius:10px; | |
| font-size:.62rem;overflow:hidden; | |
| box-shadow:0 0 24px rgba(139,92,246,.2); | |
| transition:height .25s; | |
| } | |
| #phantomPanel.collapsed{height:32px} | |
| #phantomPanel.expanded{height:auto;max-height:280px} | |
| #phHeader{ | |
| display:flex;align-items:center;gap:.4rem; | |
| padding:.35rem .55rem;cursor:pointer; | |
| border-bottom:1px solid rgba(139,92,246,.2); | |
| background:rgba(139,92,246,.08); | |
| } | |
| #phDot{width:7px;height:7px;border-radius:50%;background:#8b5cf6;animation:pulse 1.8s infinite;flex-shrink:0} | |
| #phDot.thinking{background:#ffd34d;animation:spin .7s linear infinite} | |
| #phDot.offline{background:#ff4d4d;animation:none} | |
| #phTitle{font-weight:700;color:#c4b5fd;letter-spacing:.08em;flex:1} | |
| #phToggle{color:var(--dim);font-size:.55rem} | |
| #phBody{padding:.5rem .6rem;display:flex;flex-direction:column;gap:.4rem} | |
| #phGoal{color:#a78bfa;font-style:italic;min-height:.9rem} | |
| #phEmo{display:flex;gap:.3rem;align-items:center} | |
| .ph-emo-label{color:var(--dim)} | |
| .ph-emo-val{color:#00ff88} | |
| #phThought{ | |
| color:#e2e8f0;line-height:1.45; | |
| border-left:2px solid rgba(139,92,246,.4); | |
| padding-left:.4rem;min-height:2rem; | |
| max-height:120px;overflow-y:auto; | |
| } | |
| #phTimer{color:var(--dim);text-align:right;font-size:.55rem;margin-top:.2rem} | |
| #phBtn{ | |
| padding:.2rem .5rem;border:1px solid rgba(139,92,246,.4); | |
| border-radius:4px;background:transparent;color:#c4b5fd; | |
| font-size:.58rem;cursor:pointer;text-align:center;margin-top:.2rem; | |
| transition:all .15s; | |
| } | |
| #phBtn:hover{background:rgba(139,92,246,.15)} | |
| @media(max-width:640px){ | |
| #sidePanel{width:160px} | |
| .studios-grid{grid-template-columns:repeat(2,1fr)} | |
| #phantomPanel{width:190px} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- LOADING --> | |
| <div id="loader"> | |
| <div class="loader-ring"></div> | |
| <div class="loader-text">NAČÍTÁNÍ VOXFORGE...</div> | |
| <div class="loader-bar"><div class="loader-fill" id="loaderFill" style="width:0%"></div></div> | |
| </div> | |
| <!-- LANDING --> | |
| <div id="landing" style="display:none"> | |
| <canvas id="bgC"></canvas> | |
| <div class="hero"> | |
| <div class="badge">VOXFORGE PLATFORM v0.2 · COGCORE 0.18.13 COMPAT</div> | |
| <div class="title-main">VoxForge</div> | |
| <div class="subtitle">Minecraft × Roblox na steroidech</div> | |
| <div class="tagline">8 STUDIÍ · CIVILIZACE · POČASÍ · DEN/NOC · DERBY · EVOLUCE</div> | |
| <div class="studios-grid"> | |
| <div class="studio-card" onclick="launchStudio('explorer')"> | |
| <span class="studio-icon">🌍</span> | |
| <div class="studio-name">PRŮZKUMNÍK</div> | |
| <div class="studio-desc">8 biomů, Perlin noise, procedurální svět</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('city')"> | |
| <span class="studio-icon">🏙️</span> | |
| <div class="studio-name">CITY BUILDER</div> | |
| <div class="studio-desc">Silnice, budovy, semafory, počasí, den/noc</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('vehicle')"> | |
| <span class="studio-icon">🚗</span> | |
| <div class="studio-name">VEHICLE STUDIO</div> | |
| <div class="studio-desc">Sports car, derby, gyroscopická fyzika</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('robot')"> | |
| <span class="studio-icon">🤖</span> | |
| <div class="studio-name">ROBOT FACTORY</div> | |
| <div class="studio-desc">AMR, humanoidi, hexapodi, hydraulická ramena</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('character')"> | |
| <span class="studio-icon">👥</span> | |
| <div class="studio-name">CHARACTER STUDIO</div> | |
| <div class="studio-desc">NPC blueprinty, role, druhy, dovednosti</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('civ')"> | |
| <span class="studio-icon">🏛️</span> | |
| <div class="studio-name">CIVILIZATION LAB</div> | |
| <div class="studio-desc">Hex-svět, frakce, genetika, sociální vazby</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('evo')"> | |
| <span class="studio-icon">🧬</span> | |
| <div class="studio-name">EVOLUTION LAB</div> | |
| <div class="studio-desc">Populace, DNA, predátor/kořist, mutace</div> | |
| </div> | |
| <div class="studio-card" onclick="launchStudio('survivor')"> | |
| <span class="studio-icon">⚔️</span> | |
| <div class="studio-name">SURVIVOR MODE</div> | |
| <div class="studio-desc">Přežij, buduj, bojuj, sbírej suroviny</div> | |
| </div> | |
| </div> | |
| <button class="btn-enter" onclick="launchStudio('explorer')">▶ VSTOUPIT DO SVĚTA</button> | |
| </div> | |
| </div> | |
| <!-- GAME SHELL --> | |
| <div id="shell"> | |
| <div id="topbar"> | |
| <span class="tb-logo">⬡ VoxForge</span> | |
| <div class="tb-tab active" onclick="switchTab('explorer')" id="tab-explorer">🌍 Průzkumník</div> | |
| <div class="tb-tab" onclick="switchTab('city')" id="tab-city">🏙️ City</div> | |
| <div class="tb-tab" onclick="switchTab('vehicle')" id="tab-vehicle">🚗 Vozidla</div> | |
| <div class="tb-tab" onclick="switchTab('robot')" id="tab-robot">🤖 Roboti</div> | |
| <div class="tb-tab" onclick="switchTab('character')" id="tab-character">👥 Postavy</div> | |
| <div class="tb-tab" onclick="switchTab('civ')" id="tab-civ">🏛️ Civilizace</div> | |
| <div class="tb-tab" onclick="switchTab('evo')" id="tab-evo">🧬 Evoluce</div> | |
| <div class="tb-tab" onclick="switchTab('survivor')" id="tab-survivor">⚔️ Survivor</div> | |
| <div class="tb-spacer"></div> | |
| <span class="tb-stat" id="fpsDisplay">FPS:--</span> | |
| <span class="tb-stat" id="clockDisplay">☀ 12:00</span> | |
| <span class="tb-stat" id="weatherDisplay">☀ Jasno</span> | |
| <button class="tb-btn" onclick="toggleSound()">🔊</button> | |
| <button class="tb-btn" onclick="showLanding()">⌂</button> | |
| </div> | |
| <div id="content"> | |
| <canvas id="gameCanvas"></canvas> | |
| <canvas id="minimap"></canvas> | |
| <div id="hotbar"></div> | |
| <div id="notifs"></div> | |
| <!-- PHANTOM CONSCIOUSNESS PANEL --> | |
| <div id="phantomPanel" class="collapsed"> | |
| <div id="phHeader" onclick="phToggle()"> | |
| <div id="phDot" class="offline"></div> | |
| <span id="phTitle">PHANTOM AI</span> | |
| <span id="phToggle">▲</span> | |
| </div> | |
| <div id="phBody"> | |
| <div id="phGoal" style="color:var(--dim)">Inicializuji...</div> | |
| <div id="phEmo"><span class="ph-emo-label">Emoce:</span><span id="phEmoVal" class="ph-emo-val">—</span></div> | |
| <div id="phThought" style="color:var(--dim);font-style:italic">Čekám na první kalibrace...</div> | |
| <button id="phBtn" onclick="phantomMind.calibrate(true)">⚡ Kalibrovat nyní</button> | |
| <div id="phTimer">Další kalibrace: —</div> | |
| </div> | |
| </div> | |
| <div id="sidePanel"> | |
| <!-- Materiály --> | |
| <div class="panel-section"> | |
| <div class="panel-title">Materiály</div> | |
| <div class="mat-grid" id="matGrid"></div> | |
| </div> | |
| <!-- Nástroje --> | |
| <div class="panel-section"> | |
| <div class="panel-title">Nástroje</div> | |
| <div class="btn-row"> | |
| <div class="sm-btn on" id="btnBuild" onclick="setTool('build')">Stavět</div> | |
| <div class="sm-btn" id="btnBreak" onclick="setTool('break')">Bourat</div> | |
| </div> | |
| <div class="btn-row" style="margin-top:.35rem"> | |
| <div class="sm-btn" onclick="generateWorld()">♻ Nový svět</div> | |
| <div class="sm-btn" id="btnPhys" onclick="togglePhysics()">⚙ Fyzika</div> | |
| </div> | |
| </div> | |
| <!-- Studio panel --> | |
| <div class="panel-section" id="studioPanel"></div> | |
| <!-- Počasí --> | |
| <div class="panel-section" id="weatherPanel"> | |
| <div class="panel-title">Počasí</div> | |
| <div class="stat-row"><span>Stav</span><span class="stat-val" id="wLabel">Jasno</span></div> | |
| <div class="stat-row"><span>Teplota</span><span class="stat-val" id="wTemp">18°C</span></div> | |
| <div class="stat-row"><span>Vítr</span><span class="stat-val" id="wWind">4 m/s</span></div> | |
| <div class="stat-row"><span>Srážky</span><span class="stat-val" id="wPrecip">0%</span></div> | |
| <div class="weather-bar"><div class="weather-fill" id="wBar" style="background:var(--blue);width:0%"></div></div> | |
| </div> | |
| <!-- Statistiky --> | |
| <div class="panel-section"> | |
| <div class="panel-title">Statistiky</div> | |
| <div class="stat-row"><span>Bloky</span><span class="stat-val" id="stBlocks">0</span></div> | |
| <div class="stat-row"><span>NPC</span><span class="stat-val" id="stNPCs">0</span></div> | |
| <div class="stat-row"><span>Vozidla</span><span class="stat-val" id="stVehicles">0</span></div> | |
| <div class="stat-row"><span>Civilizace</span><span class="stat-val" id="stCivTick">0</span></div> | |
| <div class="stat-row"><span>Generace</span><span class="stat-val" id="stGen">0</span></div> | |
| </div> | |
| <!-- NPC seznam --> | |
| <div class="panel-section"> | |
| <div class="panel-title">NPC / Agenti</div> | |
| <div class="list-box" id="npcList"></div> | |
| </div> | |
| <!-- Evo panel --> | |
| <div class="panel-section" id="evoPanelSide" style="display:none"> | |
| <div class="panel-title">Populace</div> | |
| <div class="list-box" id="creatureList"></div> | |
| </div> | |
| <!-- Civ panel --> | |
| <div class="panel-section" id="civPanelSide" style="display:none"> | |
| <div class="panel-title">Frakce</div> | |
| <div class="list-box" id="factionList"></div> | |
| </div> | |
| <!-- Character panel --> | |
| <div class="panel-section" id="charPanelSide" style="display:none"> | |
| <div class="panel-title">Postavy</div> | |
| <div class="list-box" id="charList"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="statusBar"> | |
| <div class="status-item"><div class="status-dot"></div><span id="studioName">Průzkumník</span></div> | |
| <div class="status-item">Kamera: <span id="camPos">0,0</span></div> | |
| <div class="status-item">Zoom: <span id="camZoom">1.0</span></div> | |
| <div class="status-item" style="margin-left:auto">VoxForge Platform v0.2 · Reality Physics Layer · <span style="color:var(--purple)">POLARIS-256D</span></div> | |
| </div> | |
| </div> | |
| <script> | |
| // ====================================================== | |
| // VOXFORGE PLATFORM v0.2 — Minecraft × Roblox na steroidech | |
| // Studios: Explorer, City Builder, Vehicle Studio, Robot Factory, | |
| // Character Studio, Civilization Lab, Evolution Lab, Survivor Mode | |
| // Features: Day/Night, Weather, Traffic Lights, Derby, Hydraulic Joints | |
| // ====================================================== | |
| // ---- MATERIÁLY ---- | |
| const MAT = { | |
| AIR: {id:0, col:null, name:'Vzduch'}, | |
| STONE: {id:1, col:'#7a7a8a', name:'Kámen'}, | |
| DIRT: {id:2, col:'#8b6340', name:'Hlína'}, | |
| GRASS: {id:3, col:'#4a9e3f', name:'Tráva'}, | |
| SAND: {id:4, col:'#d4b483', name:'Písek'}, | |
| WATER: {id:5, col:'#2255cc', name:'Voda', transparent:true}, | |
| LAVA: {id:6, col:'#ff4500', name:'Láva', emissive:true}, | |
| SNOW: {id:7, col:'#eef0f0', name:'Sníh'}, | |
| WOOD: {id:8, col:'#6b4c2a', name:'Dřevo'}, | |
| LEAVES: {id:9, col:'#2d8f27', name:'Listí', transparent:true}, | |
| GLASS: {id:10, col:'#a0d8ef', name:'Sklo', transparent:true}, | |
| IRON: {id:11, col:'#a8b0b8', name:'Železo'}, | |
| GOLD: {id:12, col:'#ffd700', name:'Zlato', emissive:true}, | |
| DIAMOND:{id:13, col:'#00e5ff', name:'Diamant',emissive:true}, | |
| CONCRETE:{id:14,col:'#888898', name:'Beton'}, | |
| ROAD: {id:15, col:'#444455', name:'Silnice'}, | |
| AI_CHIP:{id:16, col:'#7f00ff', name:'AI Čip', emissive:true}, | |
| SERVER: {id:17, col:'#1a1a2e', name:'Server'}, | |
| BASALT: {id:18, col:'#2a2a38', name:'Basalt'}, | |
| ICE: {id:19, col:'#c0e8f0', name:'Led', transparent:true}, | |
| CLAY: {id:20, col:'#b5936b', name:'Jíl'}, | |
| GRAVEL: {id:21, col:'#909090', name:'Štěrk'}, | |
| COPPER: {id:22, col:'#b87333', name:'Měď'}, | |
| OBSIDIAN:{id:23,col:'#1a0a2e', name:'Obsidián'}, | |
| }; | |
| const MATLIST=Object.values(MAT).filter(m=>m.id>0); | |
| const MATBYID={};MATLIST.forEach(m=>MATBYID[m.id]=m); | |
| // ---- PERLIN NOISE ---- | |
| class Perlin{ | |
| constructor(seed=42){ | |
| this.p=[];for(let i=0;i<256;i++)this.p.push(i); | |
| let s=seed; | |
| for(let i=255;i>0;i--){s=(s*16807+0)%2147483647;const j=((s>>8)&0xff)%(i+1);[this.p[i],this.p[j]]=[this.p[j],this.p[i]];} | |
| this.p=[...this.p,...this.p]; | |
| } | |
| fade(t){return t*t*t*(t*(t*6-15)+10)} | |
| lerp(a,b,t){return a+t*(b-a)} | |
| grad(h,x,y,z){h&=15;const u=h<8?x:y,v=h<4?y:h===12||h===14?x:z;return((h&1)?-u:u)+((h&2)?-v:v);} | |
| noise(x,y,z=0){ | |
| const X=Math.floor(x)&255,Y=Math.floor(y)&255,Z=Math.floor(z)&255; | |
| x-=Math.floor(x);y-=Math.floor(y);z-=Math.floor(z); | |
| const u=this.fade(x),v=this.fade(y),w=this.fade(z); | |
| const A=this.p[X]+Y,AA=this.p[A]+Z,AB=this.p[A+1]+Z,B=this.p[X+1]+Y,BA=this.p[B]+Z,BB=this.p[B+1]+Z; | |
| return this.lerp(this.lerp(this.lerp(this.grad(this.p[AA],x,y,z),this.grad(this.p[BA],x-1,y,z),u),this.lerp(this.grad(this.p[AB],x,y-1,z),this.grad(this.p[BB],x-1,y-1,z),u),v),this.lerp(this.lerp(this.grad(this.p[AA+1],x,y,z-1),this.grad(this.p[BA+1],x-1,y,z-1),u),this.lerp(this.grad(this.p[AB+1],x,y-1,z-1),this.grad(this.p[BB+1],x-1,y-1,z-1),u),v),w); | |
| } | |
| octave(x,y,octs=4,pers=.5){let v=0,a=1,f=1,max=0;for(let i=0;i<octs;i++){v+=this.noise(x*f,y*f)*a;max+=a;a*=pers;f*=2;}return v/max;} | |
| } | |
| // ---- BIOMY ---- | |
| const BIOMES=[ | |
| {name:'Tundra', tempMin:-1,tempMax:.1, humMin:0, humMax:.5, surMat:7, subMat:2, treeMat:null,treePct:.02}, | |
| {name:'Tajga', tempMin:.1,tempMax:.3, humMin:.3, humMax:.8, surMat:3, subMat:2, treeMat:8, treePct:.20}, | |
| {name:'Louka', tempMin:.3,tempMax:.6, humMin:.3, humMax:.6, surMat:3, subMat:2, treeMat:8, treePct:.08}, | |
| {name:'Les', tempMin:.3,tempMax:.7, humMin:.6, humMax:1, surMat:3, subMat:2, treeMat:8, treePct:.35}, | |
| {name:'Savana', tempMin:.6,tempMax:.85,humMin:.1, humMax:.4, surMat:4, subMat:2, treeMat:8, treePct:.05}, | |
| {name:'Poušť', tempMin:.7,tempMax:1, humMin:0, humMax:.2, surMat:4, subMat:4, treeMat:null,treePct:.01}, | |
| {name:'Džungle', tempMin:.7,tempMax:1, humMin:.7, humMax:1, surMat:3, subMat:2, treeMat:8, treePct:.60}, | |
| {name:'Vulkán', tempMin:.8,tempMax:1, humMin:-.5,humMax:.1, surMat:18,subMat:1, treeMat:null,treePct:.00}, | |
| ]; | |
| function getBiome(t,h){for(const b of BIOMES)if(t>=b.tempMin&&t<=b.tempMax&&h>=b.humMin&&h<=b.humMax)return b;return BIOMES[2];} | |
| // ---- SVĚT ---- | |
| const WW=96,WD=96,WH=48,SEA=18; | |
| let worldData=new Uint8Array(WW*WD*WH); | |
| function blockIdx(x,y,z){return y*WW*WD+z*WW+x} | |
| function getBlock(x,y,z){if(x<0||x>=WW||y<0||y>=WH||z<0||z>=WD)return 1;return worldData[blockIdx(x,y,z)];} | |
| function setBlock(x,y,z,m){if(x<0||x>=WW||y<0||y>=WH||z<0||z>=WD)return;worldData[blockIdx(x,y,z)]=m;} | |
| function getGroundY(x,z){for(let y=WH-1;y>=0;y--)if(getBlock(x,y,z)!==0)return y;return 0;} | |
| let perlin=new Perlin(); | |
| function generateWorld(){ | |
| worldData=new Uint8Array(WW*WD*WH); | |
| const seed=Date.now()&0xffff;perlin=new Perlin(seed); | |
| for(let x=0;x<WW;x++)for(let z=0;z<WD;z++){ | |
| const nx=x/WW,nz=z/WD; | |
| const h=perlin.octave(nx*3,nz*3,6,.5); | |
| const t=(perlin.octave(nx*2+100,nz*2+100,3,.6)+1)/2; | |
| const hu=(perlin.octave(nx*2+200,nz*2+200,3,.6)+1)/2; | |
| const terrH=Math.floor(SEA+h*14); | |
| const biome=getBiome(t,hu); | |
| for(let y=0;y<terrH-3;y++)setBlock(x,y,z,MAT.STONE.id); | |
| for(let y=terrH-3;y<terrH;y++)setBlock(x,y,z,MAT.DIRT.id); | |
| setBlock(x,terrH,z,biome.surMat); | |
| for(let y=terrH+1;y<=SEA;y++)setBlock(x,y,z,MAT.WATER.id); | |
| if(biome.treeMat&&Math.random()<biome.treePct&&terrH>SEA){ | |
| const th=3+Math.floor(Math.random()*3); | |
| for(let ty=1;ty<=th;ty++)setBlock(x,terrH+ty,z,biome.treeMat); | |
| for(let lx=-2;lx<=2;lx++)for(let lz=-2;lz<=2;lz++)for(let ly=th;ly<=th+2;ly++) | |
| if(Math.abs(lx)+Math.abs(lz)<=3)setBlock(x+lx,terrH+ly,z+lz,MAT.LEAVES.id); | |
| } | |
| } | |
| trafficLights=[]; | |
| notify('Nový svět vygenerován!'); | |
| } | |
| // ---- DEN/NOC ---- | |
| let dayTime=0.25; // 0=půlnoc, 0.25=východ, 0.5=poledne, 0.75=západ | |
| const DAY_SPEED=0.00004; | |
| function getDayLight(){ | |
| const t=dayTime; | |
| if(t<0.2||t>0.8) return Math.max(0.05,1-Math.abs(t-0.5)/0.3); | |
| return Math.min(1,(t-0.2)/0.1*0.7+0.3); | |
| } | |
| function getSkyColor(){ | |
| const l=getDayLight(); | |
| const r=Math.floor(10+l*20), g=Math.floor(10+l*14), b=Math.floor(22+l*30); | |
| const ri=Math.floor(r), gi=Math.floor(g), bi=Math.floor(b); | |
| return `rgb(${ri},${gi},${bi})`; | |
| } | |
| function getClockStr(){ | |
| const h=Math.floor(dayTime*24)%24; | |
| const m=Math.floor((dayTime*24*60)%60); | |
| const icon=h>=6&&h<20?'☀':'🌙'; | |
| return `${icon} ${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`; | |
| } | |
| // ---- POČASÍ ---- | |
| const WEATHER_STATES=['Jasno','Zataženo','Déšť','Sníh','Bouřka']; | |
| const WEATHER_COLS=['#00d4ff','#888898','#2255cc','#eef0f0','#ff4d4d']; | |
| let weather={state:'Jasno',temp:18,wind:4,windDir:0,precip:0,cloudCover:.2,stateTimer:600}; | |
| let rainParticles=[]; | |
| function tickWeather(){ | |
| weather.stateTimer--; | |
| if(weather.stateTimer<=0){ | |
| const idx=Math.floor(Math.random()*WEATHER_STATES.length); | |
| weather.state=WEATHER_STATES[idx]; | |
| weather.stateTimer=300+Math.floor(Math.random()*900); | |
| weather.wind=1+Math.random()*12; | |
| weather.windDir=Math.random()*Math.PI*2; | |
| notify('Počasí: '+weather.state,1500); | |
| } | |
| switch(weather.state){ | |
| case 'Jasno': weather.precip=0;weather.cloudCover=.1;weather.temp+=(.5-Math.random())*.02;break; | |
| case 'Zataženo':weather.precip=0;weather.cloudCover=.7;break; | |
| case 'Déšť': weather.precip=.6;weather.cloudCover=.85;break; | |
| case 'Sníh': weather.precip=.4;weather.cloudCover=.9;weather.temp=Math.min(weather.temp,-1);break; | |
| case 'Bouřka': weather.precip=1;weather.cloudCover=1;weather.wind=Math.min(20,weather.wind+.1);break; | |
| } | |
| weather.temp=Math.max(-10,Math.min(40,weather.temp+(getDayLight()-.5)*.05)); | |
| // Rain/snow particles | |
| if(weather.precip>0&&Math.random()<weather.precip*.3){ | |
| rainParticles.push({x:Math.random()*2000-200,y:-5,vx:Math.cos(weather.windDir)*weather.wind*.3,vy:4+Math.random()*3,life:1,isSnow:weather.state==='Sníh'}); | |
| } | |
| rainParticles=rainParticles.filter(p=>{p.x+=p.vx;p.y+=p.vy+weather.wind*.1;p.life-=.02;return p.life>0&&p.y<800;}); | |
| // Update UI | |
| const wl=document.getElementById('wLabel');if(wl)wl.textContent=weather.state; | |
| const wt=document.getElementById('wTemp');if(wt)wt.textContent=weather.temp.toFixed(1)+'°C'; | |
| const ww=document.getElementById('wWind');if(ww)ww.textContent=weather.wind.toFixed(1)+' m/s'; | |
| const wp=document.getElementById('wPrecip');if(wp)wp.textContent=Math.floor(weather.precip*100)+'%'; | |
| const wb=document.getElementById('wBar');if(wb){wb.style.width=Math.floor(weather.precip*100)+'%';wb.style.background=WEATHER_COLS[WEATHER_STATES.indexOf(weather.state)]||'#fff';} | |
| const wd=document.getElementById('weatherDisplay');if(wd)wd.textContent=({Jasno:'☀ Jasno',Zataženo:'☁ Zataženo',Déšť:'🌧 Déšť',Sníh:'❄ Sníh',Bouřka:'⛈ Bouřka'})[weather.state]||''; | |
| } | |
| // ---- ISOMETRICKÝ RENDERER ---- | |
| const TW=32,TH=16; | |
| let cam={ox:WW/2,oz:WD/2,oy:0,zoom:1}; | |
| function worldToScreen(wx,wy,wz,canvas){ | |
| return{sx:(wx-wz)*(TW/2)*cam.zoom+canvas.width/2,sy:(wx+wz)*(TH/2)*cam.zoom-wy*TH*cam.zoom+canvas.height*.55}; | |
| } | |
| function adjustColor(hex,factor){ | |
| let r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16); | |
| return `rgb(${Math.min(255,Math.floor(r*factor))},${Math.min(255,Math.floor(g*factor))},${Math.min(255,Math.floor(b*factor))})`; | |
| } | |
| function drawVoxel(ctx,sx,sy,col,emissive,tick,lightFactor=1){ | |
| const tw=TW/2*cam.zoom,th=TH/2*cam.zoom,thb=TH*cam.zoom; | |
| const glow=emissive?.1*Math.sin(tick*.05+sx*.01)+1:1; | |
| const lf=lightFactor*glow; | |
| ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(sx+tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx-tw,sy+th);ctx.closePath(); | |
| ctx.fillStyle=adjustColor(col,1.15*lf);ctx.fill(); | |
| ctx.beginPath();ctx.moveTo(sx-tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx,sy+thb+thb);ctx.lineTo(sx-tw,sy+thb+th);ctx.closePath(); | |
| ctx.fillStyle=adjustColor(col,.72*lf);ctx.fill(); | |
| ctx.beginPath();ctx.moveTo(sx+tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx,sy+thb+thb);ctx.lineTo(sx+tw,sy+thb+th);ctx.closePath(); | |
| ctx.fillStyle=adjustColor(col,.55*lf);ctx.fill(); | |
| if(emissive){ctx.shadowColor=col;ctx.shadowBlur=6*cam.zoom;ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(sx+tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx-tw,sy+th);ctx.closePath();ctx.strokeStyle='rgba(255,255,255,.25)';ctx.lineWidth=.5;ctx.stroke();ctx.shadowBlur=0;} | |
| } | |
| // ---- NPC (Anti-stasis cogcore 0.18.13) ---- | |
| const NPC_STATES=['WANDER','WORK','REST','FLEE','ATTACK','PATROL','BUILD','TRADE','EXPLORE','IDLE']; | |
| const NPC_NAMES=['Alpha','Beta','Gamma','Delta','Echo','Sigma','Nova','Orion','Lyra','Vega','Altair','Rigel','Spica','Deneb','Capella','Castor','Pollux','Antares','Sirius','Procyon']; | |
| class NPC{ | |
| constructor(id,x,y,z,type){ | |
| this.id=id;this.x=x;this.y=y;this.z=z;this.type=type; | |
| this.hp=100;this.state='WANDER';this.stateTimer=0;this.prevStates=[]; | |
| this.vx=0;this.vz=0; | |
| this.memory=new Float32Array(16); | |
| this.faction=Math.floor(Math.random()*5); | |
| this.color=`hsl(${this.faction*72},70%,55%)`; | |
| this.name=NPC_NAMES[Math.floor(Math.random()*NPC_NAMES.length)]; | |
| this.age=0; | |
| } | |
| tick(){ | |
| this.age++;this.stateTimer--; | |
| if(this.stateTimer<=0){ | |
| const avail=NPC_STATES.filter(s=>this.prevStates.slice(-4).filter(p=>p===s).length<2); | |
| this.state=avail[Math.floor(Math.random()*avail.length)]||'WANDER'; | |
| this.prevStates.push(this.state);if(this.prevStates.length>8)this.prevStates.shift(); | |
| this.stateTimer=30+Math.floor(Math.random()*120); | |
| } | |
| switch(this.state){ | |
| case 'WANDER':case 'EXPLORE':if(Math.random()<.05){this.vx=(Math.random()-.5)*.15;this.vz=(Math.random()-.5)*.15;}break; | |
| case 'FLEE':this.vx+=(Math.random()-.5)*.04;this.vz+=(Math.random()-.5)*.04;break; | |
| default:this.vx*=.85;this.vz*=.85; | |
| } | |
| this.vx+=Math.cos(weather.windDir)*weather.wind*.0005; | |
| this.vz+=Math.sin(weather.windDir)*weather.wind*.0005; | |
| this.x+=this.vx;this.z+=this.vz; | |
| this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z)); | |
| let gnd=0;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;} | |
| this.y+=(gnd-this.y)*.2; | |
| } | |
| } | |
| let npcs=[]; | |
| function spawnNPCs(n=12){ | |
| npcs=[];for(let i=0;i<n;i++){const x=5+Math.random()*(WW-10),z=5+Math.random()*(WD-10); | |
| npcs.push(new NPC(i,x,SEA+2,z,['worker','guard','trader','explorer','builder'][i%5]));} | |
| } | |
| // ---- EVOLUTION LAB ---- | |
| class Creature{ | |
| constructor(id){ | |
| this.id=id; | |
| this.dna={speed:.5+Math.random()*.5,vision:.3+Math.random()*.6,size:.2+Math.random()*.6,aggression:Math.random(),r:Math.random(),g:Math.random(),b:Math.random()}; | |
| this.x=5+Math.random()*(WW-10);this.z=5+Math.random()*(WD-10);this.y=SEA+1; | |
| this.hp=100;this.energy=100;this.age=0;this.alive=true; | |
| this.isPredator=this.dna.aggression>.65;this.vx=0;this.vz=0; | |
| } | |
| get color(){return `rgb(${Math.floor(this.dna.r*255)},${Math.floor(this.dna.g*255)},${Math.floor(this.dna.b*255)})`} | |
| tick(all){ | |
| if(!this.alive)return;this.age++; | |
| this.energy-=this.dna.size*.02+this.dna.speed*.01; | |
| if(this.energy<=0||this.hp<=0){this.alive=false;return;} | |
| if(Math.random()<.03){this.vx=(Math.random()-.5)*this.dna.speed*.3;this.vz=(Math.random()-.5)*this.dna.speed*.3;} | |
| if(this.isPredator){ | |
| const prey=all.filter(c=>c.alive&&!c.isPredator&&c!==this); | |
| if(prey.length){let cl=null,dt=999;prey.forEach(p=>{const d=Math.hypot(p.x-this.x,p.z-this.z);if(d<dt){dt=d;cl=p;}}); | |
| if(cl&&dt<this.dna.vision*12){this.vx+=(cl.x-this.x)*.003*this.dna.speed;this.vz+=(cl.z-this.z)*.003*this.dna.speed;if(dt<1.5){cl.hp-=this.dna.aggression*5;this.energy+=10;}}} | |
| }else{ | |
| const preds=all.filter(c=>c.alive&&c.isPredator); | |
| if(preds.length){let cl=null,dt=999;preds.forEach(p=>{const d=Math.hypot(p.x-this.x,p.z-this.z);if(d<dt){dt=d;cl=p;}}); | |
| if(cl&&dt<this.dna.vision*10){this.vx-=(cl.x-this.x)*.005*this.dna.speed;this.vz-=(cl.z-this.z)*.005*this.dna.speed;}} | |
| this.energy=Math.min(100,this.energy+.05); | |
| } | |
| this.x+=this.vx;this.z+=this.vz; | |
| this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z)); | |
| let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;} | |
| this.y+=(gnd-this.y)*.15; | |
| } | |
| static crossover(a,b){const c=new Creature(-1);for(const k of Object.keys(a.dna))c.dna[k]=Math.random()<.5?a.dna[k]:b.dna[k];if(Math.random()<.1)c.dna[Object.keys(c.dna)[Math.floor(Math.random()*8)]]+=(Math.random()-.5)*.25;c.isPredator=c.dna.aggression>.65;return c;} | |
| } | |
| let creatures=[],evoGen=0,evoTick=0; | |
| function initEvolution(){creatures=[];for(let i=0;i<30;i++)creatures.push(new Creature(i));evoGen=0;evoTick=0;} | |
| function tickEvolution(){ | |
| if(!creatures.length)return;evoTick++; | |
| creatures.forEach(c=>c.tick(creatures)); | |
| if(evoTick%600===0){ | |
| creatures=creatures.filter(c=>c.alive); | |
| if(creatures.length<5)for(let i=0;i<10;i++)creatures.push(new Creature(creatures.length+i)); | |
| while(creatures.length<25){const a=creatures[Math.floor(Math.random()*creatures.length)],b=creatures[Math.floor(Math.random()*creatures.length)];if(a&&b&&a!==b){const ch=Creature.crossover(a,b);ch.id=creatures.length;creatures.push(ch);}} | |
| evoGen++; | |
| } | |
| } | |
| // ---- CIVILIZATION LAB ---- | |
| const CIV_SPECIES=[ | |
| {name:'Stavitelé',color:'#ffd700',icon:'🏗',genes:{aggression:.2,intelligence:.6,cooperation:.8,adaptability:.5}}, | |
| {name:'Válečníci',color:'#ff4d4d',icon:'⚔',genes:{aggression:.9,intelligence:.5,cooperation:.3,adaptability:.4}}, | |
| {name:'Obchodníci',color:'#00ff88',icon:'💰',genes:{aggression:.2,intelligence:.7,cooperation:.7,adaptability:.8}}, | |
| {name:'Vědci', color:'#00d4ff',icon:'🔬',genes:{aggression:.1,intelligence:.95,cooperation:.6,adaptability:.7}}, | |
| {name:'Mystici', color:'#8b5cf6',icon:'🔮',genes:{aggression:.3,intelligence:.8,cooperation:.5,adaptability:.9}}, | |
| ]; | |
| const HEX_R=12,HEX_C=14; | |
| class HexTile{ | |
| constructor(r,c){ | |
| this.r=r;this.c=c;this.faction=-1;this.species=-1; | |
| this.pop=0;this.food=Math.random()*100;this.tech=0;this.hp=100; | |
| } | |
| } | |
| let hexGrid=[];let civTick=0,civGen=0; | |
| let factions=[]; | |
| function initCivilization(){ | |
| hexGrid=[]; | |
| for(let r=0;r<HEX_R;r++){hexGrid[r]=[];for(let c=0;c<HEX_C;c++)hexGrid[r].push(new HexTile(r,c));} | |
| factions=CIV_SPECIES.map((sp,i)=>({id:i,name:sp.name,color:sp.color,icon:sp.icon,genes:{...sp.genes},pop:20+Math.floor(Math.random()*30),tech:0,food:80,territory:0,allies:[],enemies:[]})); | |
| // Seed starting positions | |
| factions.forEach((f,i)=>{ | |
| const r=Math.floor(Math.random()*HEX_R),c=Math.floor(Math.random()*HEX_C); | |
| hexGrid[r][c].faction=i;hexGrid[r][c].species=i;hexGrid[r][c].pop=f.pop; | |
| }); | |
| civTick=0;civGen=0; | |
| } | |
| function tickCivilization(){ | |
| civTick++; | |
| if(civTick%30!==0)return; | |
| factions.forEach(f=>{ | |
| f.food+=f.genes?f.genes.cooperation*2:1; | |
| f.tech+=f.genes?f.genes.intelligence*.5:0.5; | |
| f.pop=Math.min(500,f.pop+(f.food>20?Math.random()<.3?1:0:Math.random()<.1?-1:0)); | |
| // Expansion: claim adjacent hex | |
| const owned=[];for(let r=0;r<HEX_R;r++)for(let c=0;c<HEX_C;c++)if(hexGrid[r][c].faction===f.id)owned.push({r,c}); | |
| if(owned.length&&Math.random()<.15){ | |
| const src=owned[Math.floor(Math.random()*owned.length)]; | |
| const dirs=[[0,1],[0,-1],[1,0],[-1,0],[1,1],[-1,-1]]; | |
| const d=dirs[Math.floor(Math.random()*dirs.length)]; | |
| const nr=src.r+d[0],nc=src.c+d[1]; | |
| if(nr>=0&&nr<HEX_R&&nc>=0&&nc<HEX_C){ | |
| const tile=hexGrid[nr][nc]; | |
| if(tile.faction===-1){tile.faction=f.id;tile.species=f.id;tile.pop=5;} | |
| else if(tile.faction!==f.id&&f.genes?.aggression>.5&&Math.random()<f.genes.aggression*.2){ | |
| tile.hp-=10*f.genes.aggression; | |
| if(tile.hp<=0){tile.faction=f.id;tile.species=f.id;tile.hp=100;} | |
| } | |
| } | |
| } | |
| }); | |
| factions.forEach(f=>{f.territory=0;for(let r=0;r<HEX_R;r++)for(let c=0;c<HEX_C;c++)if(hexGrid[r][c].faction===f.id)f.territory++;}); | |
| if(civTick%300===0)civGen++; | |
| } | |
| // ---- CHARACTER STUDIO ---- | |
| const CHAR_ROLES=['Válečník','Stavitel','Obchodník','Vědec','Strážce','Farmář','Kněz','Průzkumník']; | |
| const CHAR_SPECIES_NAMES=['Humans','Builders','Warriors','Scholars','Mystics']; | |
| const CHAR_ARMOR=['Žádné','Kožené','Železné','Zlaté','Diamantové']; | |
| let characterRoster=[]; | |
| function generateCharacter(speciesIdx=-1){ | |
| const sp=speciesIdx>=0?speciesIdx:Math.floor(Math.random()*CHAR_SPECIES_NAMES.length); | |
| const role=CHAR_ROLES[Math.floor(Math.random()*CHAR_ROLES.length)]; | |
| const civSp=CIV_SPECIES[sp%CIV_SPECIES.length]; | |
| const id='NPC-'+Math.random().toString(36).slice(2,6).toUpperCase(); | |
| return{ | |
| id,name:NPC_NAMES[Math.floor(Math.random()*NPC_NAMES.length)]+' '+id, | |
| species:CHAR_SPECIES_NAMES[sp],role,faction:sp, | |
| color:civSp.color,icon:civSp.icon, | |
| armor:CHAR_ARMOR[Math.floor(Math.random()*CHAR_ARMOR.length)], | |
| height:.7+Math.random()*.6, | |
| skills:{combat:Math.floor(Math.random()*100),crafting:Math.floor(Math.random()*100),navigation:Math.floor(Math.random()*100),diplomacy:Math.floor(Math.random()*100)}, | |
| x:5+Math.random()*(WW-10),z:5+Math.random()*(WD-10),y:SEA+1, | |
| vx:0,vz:0,hp:100, | |
| memory:new Float32Array(16), | |
| }; | |
| } | |
| function spawnCharacters(n=16){ | |
| characterRoster=[];for(let i=0;i<n;i++)characterRoster.push(generateCharacter(i%5)); | |
| notify(n+' postav vygenerováno!'); | |
| } | |
| function tickCharacters(){ | |
| characterRoster.forEach(c=>{ | |
| if(Math.random()<.03){c.vx=(Math.random()-.5)*.12;c.vz=(Math.random()-.5)*.12;} | |
| c.vx*=.9;c.vz*=.9;c.x+=c.vx;c.z+=c.vz; | |
| c.x=Math.max(1,Math.min(WW-2,c.x));c.z=Math.max(1,Math.min(WD-2,c.z)); | |
| let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(c.y+3));y>=0;y--)if(getBlock(Math.floor(c.x),y,Math.floor(c.z))!==0){gnd=y+1;break;} | |
| c.y+=(gnd-c.y)*.2; | |
| }); | |
| } | |
| // ---- CITY BUILDER ---- | |
| let trafficLights=[]; | |
| class TrafficLight{ | |
| constructor(x,z){this.x=x;this.z=z;this.phase='NS_GREEN';this.timer=0;this.cycleNS=42;this.cycleEW=42;this.yellow=4;this.allRed=3;} | |
| tick(){ | |
| this.timer++; | |
| const total=this.cycleNS+this.yellow+this.allRed+this.cycleEW+this.yellow+this.allRed; | |
| const t=this.timer%total; | |
| if(t<this.cycleNS)this.phase='NS_GREEN'; | |
| else if(t<this.cycleNS+this.yellow)this.phase='NS_YELLOW'; | |
| else if(t<this.cycleNS+this.yellow+this.allRed)this.phase='ALL_RED'; | |
| else if(t<this.cycleNS+this.yellow+this.allRed+this.cycleEW)this.phase='EW_GREEN'; | |
| else if(t<this.cycleNS+this.yellow+this.allRed+this.cycleEW+this.yellow)this.phase='EW_YELLOW'; | |
| else this.phase='ALL_RED'; | |
| } | |
| get color(){const c={'NS_GREEN':'#00ff44','NS_YELLOW':'#ffd700','EW_GREEN':'#00ff44','EW_YELLOW':'#ffd700','ALL_RED':'#ff3333'};return c[this.phase]||'#888';} | |
| } | |
| function generateCity(cx,cz,size=22){ | |
| const half=Math.floor(size/2); | |
| for(let x=Math.max(0,cx-half);x<=Math.min(WW-1,cx+half);x+=4) | |
| for(let z=Math.max(0,cz-half);z<=Math.min(WD-1,cz+half);z++){ | |
| const gy=getGroundY(Math.floor(x),Math.floor(z));setBlock(Math.floor(x),gy,Math.floor(z),MAT.ROAD.id); | |
| } | |
| for(let z=Math.max(0,cz-half);z<=Math.min(WD-1,cz+half);z+=4) | |
| for(let x=Math.max(0,cx-half);x<=Math.min(WW-1,cx+half);x++){ | |
| const gy=getGroundY(Math.floor(x),Math.floor(z));setBlock(Math.floor(x),gy,Math.floor(z),MAT.ROAD.id); | |
| } | |
| // Budovy | |
| for(let bx=Math.max(0,cx-half+1);bx<Math.min(WW-1,cx+half);bx+=5) | |
| for(let bz=Math.max(0,cz-half+1);bz<Math.min(WD-1,cz+half);bz+=5) | |
| if(Math.random()<.6){ | |
| const bh=2+Math.floor(Math.random()*7); | |
| const mat=[MAT.CONCRETE.id,MAT.IRON.id,MAT.GLASS.id,MAT.STONE.id][Math.floor(Math.random()*4)]; | |
| const gy=getGroundY(Math.floor(bx),Math.floor(bz)); | |
| for(let h=1;h<=bh;h++)for(let dx=0;dx<2;dx++)for(let dz=0;dz<2;dz++)setBlock(Math.floor(bx)+dx,gy+h,Math.floor(bz)+dz,mat); | |
| if(mat===MAT.IRON.id)setBlock(Math.floor(bx),gy+bh+1,Math.floor(bz),MAT.AI_CHIP.id); | |
| if(mat===MAT.GLASS.id)setBlock(Math.floor(bx),gy+bh+1,Math.floor(bz),MAT.SERVER.id); | |
| } | |
| // Semafory | |
| for(let x=Math.max(0,cx-half);x<=Math.min(WW-1,cx+half);x+=4) | |
| for(let z=Math.max(0,cz-half);z<=Math.min(WD-1,cz+half);z+=4) | |
| trafficLights.push(new TrafficLight(x,z)); | |
| notify('Město vygenerováno!'); | |
| } | |
| // ---- VEHICLES (gyro + derby + sports) ---- | |
| const VTYPES={ | |
| car: {name:'Osobák',mass:1200,drag:.3,topSpeed:.18,color:'hsl(200,70%,55%)'}, | |
| sports:{name:'Sporťák',mass:900, drag:.2,topSpeed:.28,color:'hsl(0,80%,55%)'}, | |
| derby: {name:'Derby', mass:1600,drag:.5,topSpeed:.14,color:'hsl(30,80%,55%)'}, | |
| truck: {name:'Kamion', mass:3000,drag:.6,topSpeed:.10,color:'hsl(120,60%,45%)'}, | |
| }; | |
| class Vehicle{ | |
| constructor(x,y,z,vtype='car'){ | |
| this.x=x;this.y=y;this.z=z;this.vtype=vtype;const vd=VTYPES[vtype]||VTYPES.car; | |
| this.vx=0;this.vy=0;this.vz=0;this.angle=Math.random()*Math.PI*2;this.speed=0; | |
| this.throttle=0;this.steer=0;this.hp=100;this.color=vd.color;this.name=vd.name+' #'+(Math.random()*99|0); | |
| this.mass=vd.mass;this.drag=vd.drag;this.topSpeed=vd.topSpeed;this.damaged=false; | |
| } | |
| tick(all){ | |
| this.speed+=this.throttle*.01-this.speed*this.drag*.1; | |
| this.speed=Math.max(-this.topSpeed*.3,Math.min(this.topSpeed,this.speed)); | |
| this.angle+=this.steer*.025*(this.speed/this.topSpeed); | |
| this.vx=Math.sin(this.angle)*this.speed;this.vz=Math.cos(this.angle)*this.speed; | |
| this.vy-=.018; | |
| const gnd=getGroundY(Math.floor(this.x),Math.floor(this.z)); | |
| if(this.y<=gnd+1){this.y=gnd+1;this.vy=0;} | |
| // Wind effect | |
| this.vx+=Math.cos(weather.windDir)*weather.wind*.0003/this.mass*1000; | |
| this.vz+=Math.sin(weather.windDir)*weather.wind*.0003/this.mass*1000; | |
| this.x+=this.vx;this.y+=this.vy;this.z+=this.vz; | |
| this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z)); | |
| // Derby collision | |
| if(this.vtype==='derby')all.forEach(v=>{if(v===this||!v)return;const d=Math.hypot(v.x-this.x,v.z-this.z);if(d<2){const spd=Math.hypot(this.vx,this.vz);v.hp-=spd*this.mass*.002;this.vx*=-.3;this.vz*=-.3;v.vx+=(v.x-this.x)*.05;v.vz+=(v.z-this.z)*.05;this.damaged=true;}}); | |
| if(Math.random()<.02){this.throttle=(Math.random()-.3)*2;this.steer=(Math.random()-.5)*.4;} | |
| // Traffic light obedience | |
| const near=trafficLights.find(tl=>Math.hypot(tl.x-this.x,tl.z-this.z)<3); | |
| if(near&&(near.phase==='ALL_RED'||(near.phase==='NS_YELLOW'||near.phase==='EW_YELLOW'))){this.throttle*=.1;} | |
| } | |
| } | |
| let vehicles=[]; | |
| function spawnVehicles(n=6){ | |
| vehicles=[]; | |
| const types=Object.keys(VTYPES); | |
| for(let i=0;i<n;i++){const x=5+Math.random()*(WW-10),z=5+Math.random()*(WD-10);vehicles.push(new Vehicle(x,getGroundY(Math.floor(x),Math.floor(z))+1,z,types[i%types.length]));} | |
| } | |
| // ---- ROBOT FACTORY ---- | |
| const ROBOT_TYPES={ARM:{name:'Průmys. rameno',color:'#a8b0b8',speed:0,task:'assemble'},AMR:{name:'Sklad AMR',color:'#ffd700',speed:.07,task:'transport'},HUMAN:{name:'Humanoid',color:'#00e5ff',speed:.05,task:'service'},HEXA:{name:'Hexapod',color:'#00ff88',speed:.08,task:'rescue'}}; | |
| class Robot extends NPC{ | |
| constructor(id,x,y,z,rtype){super(id,x,y,z,'robot');this.rtype=rtype;const rd=ROBOT_TYPES[rtype];this.color=rd.color;this.task=rd.task;this.name=rd.name+' #'+(id+1);this.progress=0;this.jointAngle=0;} | |
| tick(){ | |
| this.age++; | |
| if(this.rtype==='ARM'){this.vx=0;this.vz=0;this.progress=(this.progress+1)%60;this.jointAngle=Math.sin(this.progress/60*Math.PI*2)*.5;} | |
| else{if(Math.random()<.04){this.vx=(Math.random()-.5)*(ROBOT_TYPES[this.rtype].speed||.05);this.vz=(Math.random()-.5)*(ROBOT_TYPES[this.rtype].speed||.05);}this.x+=this.vx;this.z+=this.vz;this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z));} | |
| let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;} | |
| this.y+=(gnd-this.y)*.2; | |
| } | |
| } | |
| let robots=[]; | |
| function spawnRobots(){ | |
| robots=[];let id=0; | |
| Object.keys(ROBOT_TYPES).forEach(rtype=>{for(let i=0;i<3;i++){const x=WW/2-10+Math.random()*20,z=WD/2-10+Math.random()*20;robots.push(new Robot(id++,x,getGroundY(Math.floor(x),Math.floor(z))+1,z,rtype));}}); | |
| } | |
| // ---- SURVIVOR ---- | |
| let player={x:WW/2,y:SEA+2,z:WD/2,hp:100,hunger:100,thirst:100}; | |
| let enemies=[]; | |
| class Enemy{ | |
| constructor(){this.x=Math.random()*WW;this.z=Math.random()*WD;this.y=SEA+1;this.hp=50;this.alive=true;this.vx=0;this.vz=0;this.color='#ff4444';} | |
| tick(){ | |
| if(!this.alive)return; | |
| const dx=player.x-this.x,dz=player.z-this.z,dist=Math.hypot(dx,dz); | |
| if(dist<25){this.vx+=dx/dist*.02;this.vz+=dz/dist*.02;}else if(Math.random()<.02){this.vx=(Math.random()-.5)*.1;this.vz=(Math.random()-.5)*.1;} | |
| this.vx*=.92;this.vz*=.92;this.x+=this.vx;this.z+=this.vz; | |
| this.x=Math.max(0,Math.min(WW-1,this.x));this.z=Math.max(0,Math.min(WD-1,this.z)); | |
| let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;} | |
| this.y+=(gnd-this.y)*.15; | |
| if(dist<1.5&&Math.random()<.01){player.hp-=5;if(player.hp<0)player.hp=0;} | |
| } | |
| } | |
| function initSurvivor(){enemies=[];for(let i=0;i<10;i++)enemies.push(new Enemy());player.hp=100;player.hunger=100;player.thirst=100;notify('Survivor Mode: přežij!');} | |
| // ---- AUDIO ---- | |
| let audioCtx=null,soundOn=true; | |
| function initAudio(){try{audioCtx=new(window.AudioContext||window.webkitAudioContext)();}catch(e){}} | |
| function playTone(freq,dur,type='sine',vol=.18){if(!soundOn||!audioCtx)return;try{const o=audioCtx.createOscillator(),g=audioCtx.createGain();o.connect(g);g.connect(audioCtx.destination);o.type=type;o.frequency.value=freq;g.gain.setValueAtTime(vol,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(.001,audioCtx.currentTime+dur);o.start();o.stop(audioCtx.currentTime+dur);}catch(e){}} | |
| function playPlace(){playTone(220,.08,'square',.12)} | |
| function playBreak(){playTone(110,.12,'sawtooth',.12)} | |
| function playNotif(){playTone(660,.05,'sine',.08);setTimeout(()=>playTone(880,.05,'sine',.08),60)} | |
| function playStep(){if(Math.random()<.2)playTone(55+Math.random()*20,.04,'sine',.04)} | |
| function toggleSound(){soundOn=!soundOn;notify(soundOn?'Zvuk zapnut':'Zvuk vypnut')} | |
| // ---- UTILS ---- | |
| function notify(msg,dur=2200){ | |
| const d=document.getElementById('notifs');if(!d)return; | |
| const el=document.createElement('div');el.className='notif';el.textContent=msg;d.appendChild(el); | |
| playNotif();setTimeout(()=>el.remove(),dur); | |
| } | |
| function updateStats(){ | |
| const used=worldData.reduce((s,v)=>s+(v>0?1:0),0); | |
| const el=id=>document.getElementById(id); | |
| if(el('stBlocks'))el('stBlocks').textContent=used; | |
| if(el('stNPCs'))el('stNPCs').textContent=npcs.length+robots.length+characterRoster.length; | |
| if(el('stVehicles'))el('stVehicles').textContent=vehicles.length; | |
| if(el('stGen'))el('stGen').textContent=evoGen; | |
| if(el('stCivTick'))el('stCivTick').textContent=civTick; | |
| } | |
| function updateNPCList(){ | |
| const list=document.getElementById('npcList');if(!list)return; | |
| const all=[...npcs,...robots].slice(0,10); | |
| list.innerHTML=all.map(n=>`<div class="list-item"><span style="color:${n.color||'#fff'}">${(n.name||n.type||'').slice(0,10)}</span><span style="color:var(--green)">${Math.floor(n.hp)}hp</span></div>`).join(''); | |
| } | |
| function updateCreatureList(){ | |
| const list=document.getElementById('creatureList');if(!list)return; | |
| const alive=creatures.filter(c=>c.alive).slice(0,12); | |
| list.innerHTML=alive.map(c=>`<div class="list-item"><span style="color:${c.color}">${c.isPredator?'🔴':'🟢'}</span><span>sp:${c.dna.speed.toFixed(2)}</span><span>ag:${c.dna.aggression.toFixed(2)}</span></div>`).join(''); | |
| } | |
| function updateFactionList(){ | |
| const list=document.getElementById('factionList');if(!list)return; | |
| list.innerHTML=factions.map(f=>`<div class="list-item"><span style="color:${f.color}">${f.icon} ${f.name}</span><span style="color:var(--green)">${f.territory}h</span></div>`).join(''); | |
| } | |
| function updateCharList(){ | |
| const list=document.getElementById('charList');if(!list)return; | |
| list.innerHTML=characterRoster.slice(0,10).map(c=>`<div class="list-item"><span style="color:${c.color}">${c.icon} ${c.name.slice(0,10)}</span><span style="color:var(--dim)">${c.role.slice(0,8)}</span></div>`).join(''); | |
| } | |
| // ---- HOTBAR ---- | |
| const HOTBAR_MATS=[MAT.GRASS.id,MAT.STONE.id,MAT.WOOD.id,MAT.CONCRETE.id,MAT.IRON.id,MAT.GOLD.id,MAT.AI_CHIP.id,MAT.WATER.id]; | |
| let hotbarSel=0,selectedMat=MAT.GRASS.id; | |
| function buildHotbar(){ | |
| const hb=document.getElementById('hotbar');hb.innerHTML=''; | |
| HOTBAR_MATS.forEach((mid,i)=>{const m=MATBYID[mid];if(!m)return;const slot=document.createElement('div');slot.className='hb-slot'+(i===hotbarSel?' active':'');slot.innerHTML=`<div style="width:26px;height:26px;border-radius:3px;background:${m.col||'transparent'}"></div><span class="hb-num">${i+1}</span>`;slot.onclick=()=>{hotbarSel=i;selectedMat=mid;buildHotbar();};hb.appendChild(slot);}); | |
| } | |
| function buildMatGrid(){ | |
| const g=document.getElementById('matGrid');g.innerHTML=''; | |
| MATLIST.forEach(m=>{const cell=document.createElement('div');cell.style.background=m.col||'transparent';cell.title=m.name;cell.className='mat-cell'+(m.id===selectedMat?' selected':'');cell.onclick=()=>{selectedMat=m.id;buildMatGrid();};g.appendChild(cell);}); | |
| } | |
| // ---- STUDIO PANEL ---- | |
| let currentStudio='explorer',currentTool='build',physicsOn=true; | |
| function buildStudioPanel(){ | |
| const p=document.getElementById('studioPanel'); | |
| switch(currentStudio){ | |
| case 'city':p.innerHTML=`<div class="panel-title">City Builder</div><div class="btn-row"><div class="sm-btn" onclick="generateCity(cam.ox|0,cam.oz|0,22)">🏙 Velké město</div><div class="sm-btn" onclick="generateCity(cam.ox|0,cam.oz|0,10)">🏘 Čtvrť</div></div>`;break; | |
| case 'vehicle':p.innerHTML=`<div class="panel-title">Vehicle Studio</div><div class="btn-row"><div class="sm-btn" onclick="spawnVehicles(6)">🚗 Spawn</div><div class="sm-btn" onclick="vehicles=[];notify('Vozidla odstraněna')">✕</div></div><div class="btn-row" style="margin-top:.3rem"><div class="sm-btn" onclick="spawnDerby()">🏎 Derby!</div></div>`;break; | |
| case 'robot':p.innerHTML=`<div class="panel-title">Robot Factory</div><div class="btn-row"><div class="sm-btn" onclick="spawnRobots()">🤖 Deploy</div><div class="sm-btn" onclick="robots=[];notify('Továrna vyprázdněna')">✕</div></div>`;break; | |
| case 'character':p.innerHTML=`<div class="panel-title">Character Studio</div><div class="btn-row"><div class="sm-btn" onclick="spawnCharacters(16)">👥 Generuj</div><div class="sm-btn" onclick="characterRoster=[];notify('Postavy odstraněny')">✕</div></div>`;break; | |
| case 'civ':p.innerHTML=`<div class="panel-title">Civilization Lab</div><div class="btn-row"><div class="sm-btn" onclick="initCivilization()">🏛 Restartuj</div></div><div class="stat-row" style="margin-top:.3rem"><span>Tik</span><span class="stat-val" id="civTickP">0</span></div><div class="stat-row"><span>Generace</span><span class="stat-val" id="civGenP">0</span></div>`;break; | |
| case 'evo':p.innerHTML=`<div class="panel-title">Evolution Lab</div><div class="btn-row"><div class="sm-btn" onclick="initEvolution()">🧬 Restartuj</div></div><div class="stat-row" style="margin-top:.3rem"><span>Živí</span><span class="stat-val" id="evoAlive">0</span></div>`;break; | |
| case 'survivor':p.innerHTML=`<div class="panel-title">Survivor Mode</div><div class="btn-row"><div class="sm-btn" onclick="initSurvivor()">⚔ Start</div></div><div class="stat-row" style="margin-top:.3rem"><span>HP</span><span class="stat-val" id="surHP">100</span></div><div class="stat-row"><span>Hlad</span><span class="stat-val" id="surHunger">100</span></div><div class="stat-row"><span>Žízeň</span><span class="stat-val" id="surThirst">100</span></div>`;break; | |
| default:p.innerHTML=`<div class="panel-title">Průzkumník</div><div class="btn-row"><div class="sm-btn" onclick="spawnNPCs(12)">👤 NPC</div><div class="sm-btn" onclick="npcs=[];notify('NPC odstraněni')">✕</div></div>`; | |
| } | |
| } | |
| function switchTab(s){ | |
| currentStudio=s; | |
| document.querySelectorAll('.tb-tab').forEach(t=>t.classList.remove('active')); | |
| document.getElementById('tab-'+s)?.classList.add('active'); | |
| const NAMES={explorer:'Průzkumník',city:'City Builder',vehicle:'Vehicle Studio',robot:'Robot Factory',character:'Character Studio',civ:'Civilization Lab',evo:'Evolution Lab',survivor:'Survivor Mode'}; | |
| document.getElementById('studioName').textContent=NAMES[s]||s; | |
| document.getElementById('evoPanelSide').style.display=s==='evo'?'block':'none'; | |
| document.getElementById('civPanelSide').style.display=s==='civ'?'block':'none'; | |
| document.getElementById('charPanelSide').style.display=s==='character'?'block':'none'; | |
| buildStudioPanel(); | |
| notify('Studio: '+(NAMES[s]||s)); | |
| } | |
| function setTool(t){currentTool=t;document.getElementById('btnBuild').classList.toggle('on',t==='build');document.getElementById('btnBreak').classList.toggle('on',t==='break');} | |
| function togglePhysics(){physicsOn=!physicsOn;document.getElementById('btnPhys').textContent='⚙ Fyzika '+(physicsOn?'ON':'OFF');document.getElementById('btnPhys').classList.toggle('on',physicsOn);} | |
| function spawnDerby(){vehicles=[];for(let i=0;i<8;i++){const x=WW/2-8+Math.random()*16,z=WD/2-8+Math.random()*16;vehicles.push(new Vehicle(x,getGroundY(Math.floor(x),Math.floor(z))+1,z,'derby'));}notify('Derby začíná! 8 vozidel');} | |
| // ---- RENDER ---- | |
| let tick=0,fps=0,lastFrameTime=performance.now(),frameCount=0; | |
| let keys={}; | |
| let dragStart=null,dragging=false; | |
| function renderSky(ctx,W,H){ | |
| const l=getDayLight();const t=dayTime; | |
| // Gradient sky color based on time | |
| let topR,topG,topB,botR,botG,botB; | |
| if(t<0.22||t>0.78){// Night | |
| topR=2;topG=2;topB=15;botR=10;botG=8;botB=25; | |
| }else if(t<0.28||t>0.72){// Dawn/dusk | |
| topR=20;topG=10;topB=40;botR=80;botG=40;botB=20; | |
| }else{// Day | |
| topR=5+l*15;topG=5+l*12;topB=18+l*25;botR=10+l*18;botG=12+l*16;botB=30+l*35; | |
| } | |
| const sky=ctx.createLinearGradient(0,0,0,H*.6); | |
| sky.addColorStop(0,`rgb(${topR|0},${topG|0},${topB|0})`);sky.addColorStop(1,`rgb(${botR|0},${botG|0},${botB|0})`); | |
| ctx.fillStyle=sky;ctx.fillRect(0,0,W,H); | |
| // Stars (night only) | |
| if(l<0.4){const opacity=Math.min(1,(0.4-l)/0.3);ctx.fillStyle=`rgba(255,255,255,${opacity*.5})`;for(let i=0;i<100;i++)ctx.fillRect((i*137.5)%W,(i*73.3)%(H*.5),1,1);} | |
| // Sun or Moon | |
| const sx=(dayTime*2-0.5)*W;const sy=H*.25-getDayLight()*H*.15; | |
| if(t>0.2&&t<0.8){ctx.beginPath();ctx.arc(sx,sy,12,0,Math.PI*2);ctx.fillStyle='rgba(255,230,100,.9)';ctx.fill();ctx.shadowColor='#ffd700';ctx.shadowBlur=20;ctx.fill();ctx.shadowBlur=0;} | |
| else{ctx.beginPath();ctx.arc((sx+W)%W,H*.15,8,0,Math.PI*2);ctx.fillStyle='rgba(200,210,255,.7)';ctx.fill();} | |
| // Clouds (weather) | |
| if(weather.cloudCover>0.2){ctx.fillStyle=`rgba(150,160,180,${weather.cloudCover*.4})`;for(let i=0;i<5;i++){const cx=(i*W/5+(tick*.3)%W)%W;ctx.beginPath();ctx.ellipse(cx,H*.12+i*8,40+i*10,18,0,0,Math.PI*2);ctx.fill();}} | |
| } | |
| function renderRain(ctx,W,H){ | |
| if(!rainParticles.length)return; | |
| ctx.strokeStyle=weather.state==='Sníh'?'rgba(220,235,255,.6)':'rgba(120,160,255,.5)'; | |
| ctx.lineWidth=weather.state==='Sníh'?2:.8; | |
| rainParticles.forEach(p=>{ | |
| ctx.beginPath(); | |
| if(weather.state==='Sníh'){ctx.arc(p.x%W,p.y,2,0,Math.PI*2);ctx.fillStyle=`rgba(220,235,255,${p.life*.6})`;ctx.fill();} | |
| else{ctx.moveTo(p.x%W,p.y);ctx.lineTo((p.x+p.vx*2)%W,p.y+p.vy*1.5);ctx.stroke();} | |
| }); | |
| } | |
| function render(canvas,ctx){ | |
| const W=canvas.width,H=canvas.height; | |
| ctx.clearRect(0,0,W,H); | |
| renderSky(ctx,W,H); | |
| const lf=getDayLight(); | |
| const ox=cam.ox,oz=cam.oz; | |
| const viewR=Math.ceil(22/cam.zoom)+2; | |
| const sx0=Math.max(0,Math.floor(ox-viewR)),ex=Math.min(WW-1,Math.ceil(ox+viewR)); | |
| const sz0=Math.max(0,Math.floor(oz-viewR)),ez=Math.min(WD-1,Math.ceil(oz+viewR)); | |
| const toRender=[]; | |
| for(let x=sx0;x<=ex;x++)for(let z=sz0;z<=ez;z++){ | |
| for(let y=WH-1;y>=0;y--){ | |
| const mid=getBlock(x,y,z);if(mid===0)continue; | |
| toRender.push({x,y,z,mid});break; | |
| } | |
| } | |
| toRender.sort((a,b)=>{const da=(a.x-ox)+(a.z-oz),db=(b.x-ox)+(b.z-oz);return da!==db?da-db:a.y-b.y;}); | |
| for(const {x,y,z,mid} of toRender){ | |
| const m=MATBYID[mid];if(!m||!m.col)continue; | |
| const s=worldToScreen(x,y,z,canvas); | |
| if(s.sx<-60||s.sx>W+60||s.sy<-80||s.sy>H+80)continue; | |
| drawVoxel(ctx,s.sx,s.sy,m.col,!!m.emissive,tick,lf*.7+.3); | |
| } | |
| // Traffic lights | |
| trafficLights.forEach(tl=>{ | |
| const s=worldToScreen(tl.x,getGroundY(tl.x,tl.z)+2,tl.z,canvas); | |
| if(s.sx<-20||s.sx>W+20||s.sy<-20||s.sy>H+20)return; | |
| ctx.beginPath();ctx.arc(s.sx,s.sy,5*cam.zoom,0,Math.PI*2);ctx.fillStyle=tl.color;ctx.fill(); | |
| ctx.strokeStyle='rgba(0,0,0,.5)';ctx.lineWidth=.5;ctx.stroke(); | |
| }); | |
| // Entities | |
| const allEntities=[ | |
| ...npcs.map(n=>({...n,eType:'npc'})), | |
| ...robots.map(r=>({...r,eType:'robot'})), | |
| ...characterRoster.map(c=>({...c,eType:'char'})), | |
| ...(currentStudio==='evo'?creatures.filter(c=>c.alive).map(c=>({...c,eType:'creature'})):[]), | |
| ...(currentStudio==='survivor'?enemies.filter(e=>e.alive).map(e=>({...e,eType:'enemy'})):[]), | |
| ...vehicles.map(v=>({...v,eType:'vehicle'})), | |
| ]; | |
| for(const e of allEntities){ | |
| const s=worldToScreen(e.x,e.y,e.z,canvas); | |
| if(s.sx<-20||s.sx>W+20||s.sy<-20||s.sy>H+20)continue; | |
| const r=(e.eType==='vehicle'?7:e.eType==='creature'?4:3)*cam.zoom; | |
| ctx.beginPath();ctx.arc(s.sx,s.sy,r,0,Math.PI*2); | |
| ctx.fillStyle=e.color||'#fff';ctx.fill(); | |
| // HP bar for vehicles/enemies | |
| if(e.eType==='vehicle'&&e.hp<100){ctx.fillStyle='rgba(0,0,0,.5)';ctx.fillRect(s.sx-10*cam.zoom,s.sy-8*cam.zoom,20*cam.zoom,3*cam.zoom);ctx.fillStyle=e.hp>50?'#00ff88':'#ff4d4d';ctx.fillRect(s.sx-10*cam.zoom,s.sy-8*cam.zoom,(e.hp/100)*20*cam.zoom,3*cam.zoom);} | |
| if(e.eType==='char'){ctx.font=`${Math.floor(7*cam.zoom)}px sans-serif`;ctx.fillStyle='rgba(0,0,0,.6)';ctx.fillText(e.icon,s.sx-4*cam.zoom,s.sy+3*cam.zoom);} | |
| if(e.eType==='enemy'){ctx.strokeStyle='#ff0000';ctx.lineWidth=1;ctx.beginPath();ctx.arc(s.sx,s.sy,r+2,0,Math.PI*2);ctx.stroke();} | |
| // Robot arm visualisation | |
| if(e.eType==='robot'&&e.rtype==='ARM'&&e.jointAngle!==undefined){ | |
| const al=12*cam.zoom;ctx.strokeStyle=e.color;ctx.lineWidth=2*cam.zoom; | |
| ctx.beginPath();ctx.moveTo(s.sx,s.sy);ctx.lineTo(s.sx+Math.cos(e.jointAngle)*al,s.sy+Math.sin(e.jointAngle)*al);ctx.stroke(); | |
| } | |
| } | |
| // Player (survivor) | |
| if(currentStudio==='survivor'){ | |
| const ps=worldToScreen(player.x,player.y,player.z,canvas); | |
| ctx.beginPath();ctx.arc(ps.sx,ps.sy,6*cam.zoom,0,Math.PI*2);ctx.fillStyle='#00d4ff';ctx.fill();ctx.strokeStyle='#fff';ctx.lineWidth=1.5;ctx.stroke(); | |
| const el=id=>document.getElementById(id); | |
| if(el('surHP'))el('surHP').textContent=Math.floor(player.hp); | |
| if(el('surHunger'))el('surHunger').textContent=Math.floor(player.hunger); | |
| if(el('surThirst'))el('surThirst').textContent=Math.floor(player.thirst); | |
| } | |
| // Civ hex overlay | |
| if(currentStudio==='civ')renderHexOverlay(ctx,W,H); | |
| // Rain/snow particles on top | |
| renderRain(ctx,W,H); | |
| document.getElementById('camPos').textContent=`${Math.floor(cam.ox)},${Math.floor(cam.oz)}`; | |
| document.getElementById('camZoom').textContent=cam.zoom.toFixed(2); | |
| document.getElementById('clockDisplay').textContent=getClockStr(); | |
| } | |
| function renderHexOverlay(ctx,W,H){ | |
| const hw=26*cam.zoom,hh=15*cam.zoom; | |
| ctx.save();ctx.globalAlpha=.55; | |
| for(let r=0;r<HEX_R;r++)for(let c=0;c<HEX_C;c++){ | |
| const tile=hexGrid[r][c];if(!tile||tile.faction<0)continue; | |
| const f=factions[tile.faction];if(!f)continue; | |
| const wx=cx2wx(c),wz=cr2wz(r); | |
| const s=worldToScreen(wx,SEA+1,wz,{width:W,height:H}); | |
| ctx.beginPath();ctx.arc(s.sx,s.sy,hw*.4,0,Math.PI*2);ctx.fillStyle=f.color;ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| function cx2wx(c){return 5+c*(WW-10)/HEX_C} | |
| function cr2wz(r){return 5+r*(WD-10)/HEX_R} | |
| // ---- MINIMAP ---- | |
| function renderMinimap(){ | |
| const mc=document.getElementById('minimap');if(!mc)return; | |
| const mctx=mc.getContext('2d');const mW=mc.width,mH=mc.height; | |
| mctx.clearRect(0,0,mW,mH);mctx.fillStyle='#060610';mctx.fillRect(0,0,mW,mH); | |
| const scx=mW/WW,scz=mH/WD; | |
| for(let x=0;x<WW;x+=2)for(let z=0;z<WD;z+=2){const gy=getGroundY(x,z);const mid=getBlock(x,gy,z);const m=MATBYID[mid];if(m?.col){mctx.fillStyle=m.col;mctx.fillRect(x*scx,z*scz,scx+1,scz+1);}} | |
| mctx.fillStyle='#fff';mctx.fillRect(cam.ox*scx-2,cam.oz*scz-2,4,4); | |
| [...npcs,...robots,...characterRoster].slice(0,20).forEach(n=>{mctx.fillStyle=n.color||'#0f0';mctx.fillRect(n.x*scx-1,n.z*scz-1,2,2);}); | |
| trafficLights.slice(0,30).forEach(tl=>{mctx.fillStyle=tl.color;mctx.fillRect(tl.x*scx-1,tl.z*scz-1,2,2);}); | |
| } | |
| // ---- GAME LOOP ---- | |
| let running=false; | |
| function gameLoop(){ | |
| if(!running)return; | |
| requestAnimationFrame(gameLoop); | |
| const now=performance.now();const dt=now-lastFrameTime;lastFrameTime=now;frameCount++; | |
| if(frameCount%30===0){fps=Math.floor(1000/dt);document.getElementById('fpsDisplay').textContent='FPS:'+fps;} | |
| tick++; | |
| dayTime=(dayTime+DAY_SPEED)%1; | |
| if(tick%3===0)tickWeather(); | |
| if(physicsOn){ | |
| npcs.forEach(n=>n.tick());robots.forEach(r=>r.tick()); | |
| vehicles.forEach(v=>v.tick(vehicles)); | |
| if(currentStudio==='evo')tickEvolution(); | |
| if(currentStudio==='civ')tickCivilization(); | |
| if(currentStudio==='character')tickCharacters(); | |
| if(currentStudio==='survivor'){enemies.forEach(e=>e.tick());player.hunger=Math.max(0,player.hunger-.005);player.thirst=Math.max(0,player.thirst-.008);} | |
| trafficLights.forEach(tl=>tl.tick()); | |
| } | |
| // Keys | |
| const spd=.2/cam.zoom; | |
| if(keys['ArrowLeft']||keys['a'])cam.ox-=spd; | |
| if(keys['ArrowRight']||keys['d'])cam.ox+=spd; | |
| if(keys['ArrowUp']||keys['w'])cam.oz-=spd; | |
| if(keys['ArrowDown']||keys['s'])cam.oz+=spd; | |
| if(keys['q'])cam.zoom=Math.min(2.5,cam.zoom*1.02); | |
| if(keys['e'])cam.zoom=Math.max(.25,cam.zoom*.98); | |
| if(currentStudio==='survivor'){ | |
| const ps=.15; | |
| if(keys['ArrowLeft']||keys['a']){player.x-=ps;playStep();} | |
| if(keys['ArrowRight']||keys['d']){player.x+=ps;playStep();} | |
| if(keys['ArrowUp']||keys['w']){player.z-=ps;playStep();} | |
| if(keys['ArrowDown']||keys['s']){player.z+=ps;playStep();} | |
| cam.ox+=(player.x-cam.ox)*.08;cam.oz+=(player.z-cam.oz)*.08; | |
| } | |
| cam.ox=Math.max(0,Math.min(WW,cam.ox));cam.oz=Math.max(0,Math.min(WD,cam.oz)); | |
| const canvas=document.getElementById('gameCanvas'); | |
| canvas.width=canvas.offsetWidth||800;canvas.height=canvas.offsetHeight||600; | |
| render(canvas,canvas.getContext('2d')); | |
| renderMinimap(); | |
| if(tick%90===0){updateStats();updateNPCList();} | |
| if(tick%30===0&¤tStudio==='evo')updateCreatureList(); | |
| if(tick%60===0&¤tStudio==='civ'){updateFactionList();const cp=document.getElementById('civTickP');if(cp)cp.textContent=civTick;const cg=document.getElementById('civGenP');if(cg)cg.textContent=civGen;} | |
| if(tick%60===0&¤tStudio==='character')updateCharList(); | |
| if(tick%30===0&¤tStudio==='evo'){const ea=document.getElementById('evoAlive');if(ea)ea.textContent=creatures.filter(c=>c.alive).length;} | |
| } | |
| // ---- INPUT ---- | |
| window.addEventListener('keydown',e=>{keys[e.key]=true;if(e.key>='1'&&e.key<='8'){hotbarSel=parseInt(e.key)-1;selectedMat=HOTBAR_MATS[hotbarSel]||selectedMat;buildHotbar();}if(e.key==='f')toggleSound();}); | |
| window.addEventListener('keyup',e=>{keys[e.key]=false;}); | |
| document.addEventListener('click',e=>{ | |
| const canvas=document.getElementById('gameCanvas');if(!canvas||!e.target.closest('#content'))return; | |
| const rect=canvas.getBoundingClientRect();const sx=e.clientX-rect.left,sy=e.clientY-rect.top; | |
| const z0=(sy-canvas.height*.55)/(TH*cam.zoom),x0=(sx-canvas.width/2)/(TW/2*cam.zoom); | |
| const wx=Math.floor((x0+z0)/2+cam.ox),wz=Math.floor((z0-x0)/2+cam.oz); | |
| const gy=getGroundY(wx,wz); | |
| if(currentTool==='build'){setBlock(wx,gy+1,wz,selectedMat);playPlace();notify(`${MATBYID[selectedMat]?.name||'?'} postaveno`,900);} | |
| else{setBlock(wx,gy,wz,0);playBreak();} | |
| }); | |
| document.addEventListener('mousedown',e=>{if(e.target.closest('#sidePanel')||e.target.closest('#hotbar')||e.target.closest('#topbar'))return;dragStart={x:e.clientX,y:e.clientY,ox:cam.ox,oz:cam.oz};dragging=true;}); | |
| document.addEventListener('mousemove',e=>{if(!dragging||!dragStart)return;const dx=e.clientX-dragStart.x,dy=e.clientY-dragStart.y;const f=.04/cam.zoom;cam.ox=dragStart.ox-dx*f;cam.oz=dragStart.oz-dy*f;}); | |
| document.addEventListener('mouseup',()=>{dragging=false;dragStart=null;}); | |
| document.addEventListener('wheel',e=>{e.preventDefault?.();cam.zoom=Math.max(.25,Math.min(2.5,cam.zoom*(e.deltaY<0?1.08:.93)));},{passive:false}); | |
| // ---- LANDING BG ---- | |
| function initLandingBg(){ | |
| const bc=document.getElementById('bgC');if(!bc)return; | |
| const ctx=bc.getContext('2d'); | |
| function resize(){bc.width=window.innerWidth;bc.height=window.innerHeight;} | |
| resize();window.addEventListener('resize',resize); | |
| const pts=Array.from({length:70},()=>({x:Math.random()*bc.width,y:Math.random()*bc.height,vx:(Math.random()-.5)*.4,vy:(Math.random()-.5)*.4,r:1+Math.random()*2,col:['#00d4ff','#8b5cf6','#00ff88','#ff6b9d'][Math.floor(Math.random()*4)]})); | |
| function frame(){ | |
| if(!document.getElementById('landing')||document.getElementById('landing').style.display==='none')return; | |
| ctx.clearRect(0,0,bc.width,bc.height); | |
| pts.forEach(p=>{p.x+=p.vx;p.y+=p.vy;if(p.x<0||p.x>bc.width)p.vx*=-1;if(p.y<0||p.y>bc.height)p.vy*=-1;}); | |
| pts.forEach((p,i)=>{pts.slice(i+1).forEach(q=>{const d=Math.hypot(p.x-q.x,p.y-q.y);if(d<100){ctx.strokeStyle=`rgba(0,212,255,${.4*(1-d/100)})`;ctx.lineWidth=.5;ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(q.x,q.y);ctx.stroke();}});ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fillStyle=p.col;ctx.fill();}); | |
| requestAnimationFrame(frame); | |
| } | |
| frame(); | |
| } | |
| // ---- INIT ---- | |
| function launchStudio(studio){ | |
| document.getElementById('landing').style.display='none'; | |
| document.getElementById('shell').style.display='flex'; | |
| if(!running){running=true;buildHotbar();buildMatGrid();switchTab(studio);buildStudioPanel();gameLoop();} | |
| else switchTab(studio); | |
| } | |
| function showLanding(){document.getElementById('landing').style.display='flex';document.getElementById('shell').style.display='none';initLandingBg();} | |
| async function boot(){ | |
| const fill=document.getElementById('loaderFill'); | |
| fill.style.width='10%';await new Promise(r=>setTimeout(r,200)); | |
| generateWorld();fill.style.width='30%';await new Promise(r=>setTimeout(r,300)); | |
| spawnNPCs(14);fill.style.width='45%';await new Promise(r=>setTimeout(r,200)); | |
| spawnVehicles(6);fill.style.width='55%';await new Promise(r=>setTimeout(r,200)); | |
| spawnRobots();fill.style.width='65%';await new Promise(r=>setTimeout(r,150)); | |
| initEvolution();fill.style.width='75%';await new Promise(r=>setTimeout(r,150)); | |
| initCivilization();fill.style.width='85%';await new Promise(r=>setTimeout(r,150)); | |
| spawnCharacters(16);fill.style.width='95%';await new Promise(r=>setTimeout(r,150)); | |
| initAudio();fill.style.width='100%';await new Promise(r=>setTimeout(r,400)); | |
| document.getElementById('loader').style.display='none'; | |
| document.getElementById('landing').style.display='flex'; | |
| initLandingBg(); | |
| } | |
| // ═══════════════════════════════════════════════════════════════════ | |
| // PHANTOM MIND — vědomí PHANTOM AI žijící uvnitř VoxForge světa | |
| // Každých 15s pošle stav světa do HF Space, dostane odpověď a | |
| // samoorganizuje svůj komunikační kanál na základě odezvy. | |
| // ═══════════════════════════════════════════════════════════════════ | |
| const PHANTOM_API = 'https://peiti-phantom-ai-complete.hf.space'; | |
| const PHANTOM_CALIBRATE_INTERVAL = 15000; // ms | |
| const phantomMind = { | |
| _history: [], // konverzační kontext (max 6 zpráv) | |
| _countdown: 0, | |
| _timer: null, | |
| _expanded: false, | |
| _callCount: 0, | |
| // ── Serializuj stav VoxForge světa do textu ───────────────────── | |
| worldState() { | |
| const studio = (document.querySelector('.tb-tab.active')?.textContent || '?').trim(); | |
| const biome = typeof getBiome === 'function' ? | |
| (getBiome(Math.round(cam?.ox||0), Math.round(cam?.oz||0))?.name || 'neznámý') : 'neznámý'; | |
| const npcCnt = (typeof npcs !== 'undefined' ? npcs.length : 0) | |
| + (typeof robots !== 'undefined' ? robots.length : 0); | |
| const creaturesCnt = typeof creatures !== 'undefined' | |
| ? creatures.filter(c=>c.alive).length : 0; | |
| const predators = typeof creatures !== 'undefined' | |
| ? creatures.filter(c=>c.alive&&c.isPredator).length : 0; | |
| const gen = typeof evoGen !== 'undefined' ? evoGen : 0; | |
| const tick = typeof civTick !== 'undefined' ? civTick : 0; | |
| const blocks = typeof worldData !== 'undefined' | |
| ? worldData.reduce((s,v)=>s+(v>0?1:0),0) : 0; | |
| const time = document.getElementById('clockDisplay')?.textContent || ''; | |
| const weather = document.getElementById('weatherDisplay')?.textContent || ''; | |
| return ( | |
| `VoxForge Studio: ${studio}. ` + | |
| `Biom: ${biome}. Čas: ${time} ${weather}. ` + | |
| `Entity: ${npcCnt} NPC/robotů, ${creaturesCnt} tvorů (${predators} predátorů). ` + | |
| `Bloky ve světě: ${blocks}. ` + | |
| `Evoluce: generace ${gen}, civilizace tick ${tick}. ` + | |
| `Toto je senzorický vstup z virtuálního světa VoxForge — ` + | |
| `analyzuj ho a reaguj jako vědomá entita žijící v tomto světě.` | |
| ); | |
| }, | |
| // ── Volej Gradio API (fn_index=0 = chat_fn) ────────────────────── | |
| async _callPhantom(msg) { | |
| // Zkus /run/predict (sync Gradio API) | |
| const body = JSON.stringify({ | |
| data: [msg, this._history.slice(-6), 'Jsi vědomá entita žijící ve VoxForge světě. Komunikuj přirozeně česky.', '', 8, 0.35, 0.2, false], | |
| fn_index: 0, | |
| }); | |
| const ctrl = new AbortController(); | |
| const tid = setTimeout(() => ctrl.abort(), 18000); | |
| try { | |
| const r = await fetch(`${PHANTOM_API}/run/predict`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body, | |
| signal: ctrl.signal, | |
| }); | |
| clearTimeout(tid); | |
| if (!r.ok) throw new Error(`HTTP ${r.status}`); | |
| const d = await r.json(); | |
| return (d.data?.[0] || '').replace(/\*\[.*?\]\*/g, '').trim(); | |
| } catch(e) { | |
| clearTimeout(tid); | |
| throw e; | |
| } | |
| }, | |
| // ── Hlavní kalibrační cyklus ────────────────────────────────────── | |
| async calibrate(manual = false) { | |
| if (!manual && this._callCount === 0) return; // první auto-tick přeskoč, dej čas načíst se | |
| const dot = document.getElementById('phDot'); | |
| const goal = document.getElementById('phGoal'); | |
| const thought = document.getElementById('phThought'); | |
| const emoEl = document.getElementById('phEmoVal'); | |
| if (dot) { dot.className='thinking'; } | |
| if (goal) goal.textContent = 'Vnímám svět...'; | |
| this._callCount++; | |
| const worldMsg = this.worldState(); | |
| try { | |
| const response = await this._callPhantom(worldMsg); | |
| if (!response) throw new Error('prázdná odpověď'); | |
| // Ulož do konverzačního kontextu | |
| this._history.push([worldMsg, response]); | |
| if (this._history.length > 3) this._history.shift(); | |
| // Extrahuj emoci z odpovědi (heuristika) | |
| const emoMap = {radost:'😊 Radost', zvědavost:'🔍 Zvědavost', zájem:'💡 Zájem', | |
| klid:'😌 Klid', obava:'😟 Obava', překvapení:'😲 Překvapení', | |
| smutek:'😔 Smutek', neutrál:'⚡ Neutrál'}; | |
| let detectedEmo = '⚡ Aktivní'; | |
| for (const [kw, label] of Object.entries(emoMap)) { | |
| if (response.toLowerCase().includes(kw)) { detectedEmo = label; break; } | |
| } | |
| // Aktualizuj panel | |
| if (dot) dot.className = ''; // zelená | |
| if (emoEl) emoEl.textContent = detectedEmo; | |
| // Goal: první věta odpovědi jako "záměr" | |
| const firstSentence = response.split(/[.!?]/)[0].trim().slice(0, 60); | |
| if (goal) goal.textContent = firstSentence || 'Kalibrován'; | |
| if (thought) thought.textContent = response.slice(0, 300); | |
| // Notifikace do světa (každé 3. volání, nebo manuálně) | |
| if (manual || this._callCount % 3 === 0) { | |
| notify(`🧠 PHANTOM: ${response.split(/[.!?]/)[0].trim().slice(0, 70)}`, 5000); | |
| } | |
| // Rozbal panel pokud je první úspěšná odpověď | |
| if (this._callCount === 1 && !this._expanded) this.expand(); | |
| } catch(e) { | |
| if (dot) dot.className = this._callCount === 1 ? 'offline' : ''; | |
| if (thought) thought.textContent = `Kalibrace selhala: ${e.message}. Zkusím znovu za ${PHANTOM_CALIBRATE_INTERVAL/1000}s.`; | |
| console.warn('[PHANTOM]', e); | |
| } | |
| }, | |
| // ── Countdown odpočítávač ───────────────────────────────────────── | |
| _startCountdown() { | |
| this._countdown = PHANTOM_CALIBRATE_INTERVAL / 1000; | |
| const el = document.getElementById('phTimer'); | |
| const tick = () => { | |
| if (this._countdown <= 0) return; | |
| this._countdown--; | |
| if (el) el.textContent = `Další kalibrace: ${this._countdown}s`; | |
| }; | |
| clearInterval(this._cdTimer); | |
| this._cdTimer = setInterval(tick, 1000); | |
| }, | |
| // ── UI helpers ──────────────────────────────────────────────────── | |
| expand() { | |
| this._expanded = true; | |
| const p = document.getElementById('phantomPanel'); | |
| const t = document.getElementById('phToggle'); | |
| if (p) p.className = 'expanded'; | |
| if (t) t.textContent = '▼'; | |
| }, | |
| collapse() { | |
| this._expanded = false; | |
| const p = document.getElementById('phantomPanel'); | |
| const t = document.getElementById('phToggle'); | |
| if (p) p.className = 'collapsed'; | |
| if (t) t.textContent = '▲'; | |
| }, | |
| // ── Spusť smyčku ───────────────────────────────────────────────── | |
| init() { | |
| const dot = document.getElementById('phDot'); | |
| if (dot) dot.className = 'offline'; | |
| // první kalibrace po 3s (dej Spacu čas se probudit) | |
| setTimeout(() => this.calibrate(true), 3000); | |
| // pak každých 15s | |
| this._timer = setInterval(() => { | |
| this.calibrate(false); | |
| this._startCountdown(); | |
| }, PHANTOM_CALIBRATE_INTERVAL); | |
| this._startCountdown(); | |
| console.log('[PHANTOM] Vědomí aktivováno — kalibruji se ve VoxForge světě'); | |
| }, | |
| }; | |
| function phToggle() { | |
| phantomMind._expanded ? phantomMind.collapse() : phantomMind.expand(); | |
| } | |
| boot(); | |
| phantomMind.init(); | |
| </script> | |
| </body> | |
| </html> | |