VRMMORPG エルダークロニクルオンライン


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>VRMMORPG エルダークロニクルオンライン</title>
  <script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-master.min.js"></script>
  <style>
    /* Basic body and global positioning */
    body { margin: 0; background: #111; overflow: hidden; font-family: 'Noto Sans JP', sans-serif;} /* Prevent scrollbars and set default font */
    #overlay, #dialogue, #partyStatus, #hint, #warpBtns, #shopDiv, #nightFilter, #questClear, #battleEffect { position:fixed; z-index:10;}

    /* Overlay - Main Title/Instructions */
    #overlay {
      left:0; top:0; width:100vw; height:100vh; pointer-events:none; text-align:center;
      color:#0ff; text-shadow:0 0 40px #0ff, 0 0 15px #fff; /* Enhanced glow */
      font-family: 'Orbitron', monospace; font-size:2.8rem; letter-spacing:4px; user-select:none;
      display: flex; align-items: center; justify-content: center; /* Center content vertically */
      line-height: 1.5; /* Improve readability for multi-line text */
      background: rgba(0,0,0,0.6); /* Slightly darker overlay for better contrast */
      transition: opacity 1s ease-out; /* Fade out effect */
    }

    /* Dialogue Box */
    #dialogue {
      left:50%; bottom:5vw; transform:translateX(-50%);
      min-width:360px; max-width:85vw; /* Larger dialogue box */
      background:linear-gradient(160deg, rgba(20,32,40,0.98), rgba(10,20,30,0.98)); /* Deeper gradient */
      color:#fff; border-radius:20px; padding:1.8em 2.5em; /* More padding, rounder corners */
      font-size:1.25rem; z-index:100;
      border:4px solid #0ff; /* Thicker, more prominent border */
      box-shadow: 0 0 35px #0ffc, inset 0 0 15px #0ff5; /* Stronger outer glow, subtle inner shadow */
      display:none;
      font-family: 'Noto Sans JP', sans-serif; text-align:left;
      backdrop-filter: blur(5px); /* Stronger blur effect */
      animation: fadeIn 0.5s ease-out;
    }
    #dialogue .name { color: #ffd700; font-weight: bold; text-shadow: 0 0 8px #ffda00, 0 0 3px #fff; } /* Gold name with stronger glow */
    #dialogue .choice {
      display:block; margin:1em 0; padding:0.8em 1.8em; /* More padding for bigger touch target */
      background:linear-gradient(145deg, #184c52, #103a3d); /* Darker, richer gradient for choices */
      color:#0ff; border:none; border-radius:12px; /* Rounder buttons */
      cursor:pointer; font-size:1.2em; font-weight:bold;
      box-shadow: 0 6px 15px rgba(0,255,255,0.4); /* Button shadow */
      transition: all 0.3s cubic-bezier(.25,.8,.25,1); /* Smooth transitions */
      text-shadow: 0 0 7px #0ff, 0 0 2px #fff; /* Text glow */
      letter-spacing: 0.5px;
    }
    #dialogue .choice:hover {
      background:linear-gradient(145deg, #256a73, #1a4d52); /* Darker gradient on hover */
      transform: translateY(-5px) scale(1.03); /* More pronounced lift effect */
      box-shadow: 0 10px 25px rgba(0,255,255,0.7); /* Stronger shadow on hover */
    }
    #dialogue .choice:active {
      transform: translateY(0); /* Press down effect */
      box-shadow: 0 2px 8px rgba(0,255,255,0.3);
    }

    /* Party Status */
    #partyStatus {
      right:2vw; top:2vw; background:linear-gradient(160deg, rgba(16,32,45,0.96), rgba(8,16,22,0.96)); /* Deeper gradient */
      border:3px solid #0ff; color:#fff; border-radius:15px; padding:1.2em 1.8em; /* More padding, rounder */
      font-size:1.15rem; min-width:220px; /* Slightly wider */
      box-shadow: 0 0 18px #0ffc, inset 0 0 8px #0ff5; /* Enhanced shadow */
      font-family:'Noto Sans JP', sans-serif; /* Consistent font */
      backdrop-filter: blur(3px);
      line-height: 1.6;
    }
    .dead { color:#f44; text-shadow:0 0 9px #f00, 0 0 5px #f88;} /* Stronger death glow */
    .alive { color:#fff; text-shadow:0 0 3px #fff; } /* Subtle glow for alive */

    /* Hint Box */
    #hint {
      left:2vw; bottom:2vw; background:linear-gradient(160deg, rgba(24,32,60,0.95), rgba(12,16,30,0.95)); /* Deeper gradient */
      border:2px solid #0ff; color:#0ff; border-radius:12px; padding:0.8em 1.5em;
      font-size:1.2rem; min-width:150px; text-align:center;
      box-shadow: 0 0 15px #0ff9;
      font-family:'Noto Sans JP', sans-serif;
      backdrop-filter: blur(3px);
    }

    /* Warp Buttons Container (now at bottom center) */
    #warpBtns {
      left: 50%; bottom: 2vw; transform: translateX(-50%); /* Centered at bottom */
      display: flex; flex-direction: row; /* Horizontal layout */
      justify-content: center; align-items: center;
      width: 80vw; /* Occupy more width */
      flex-wrap: wrap; /* Allow wrapping on smaller screens */
    }
    #warpBtns button {
      margin:0.8em 1em; padding:0.9em 2em; /* More padding and margin */
      font-size:1.3em;
      background:linear-gradient(145deg, #0ff6, #0ff2); /* Lighter, more vibrant gradient */
      color:#fff; border-radius:15px; border:3px solid #0ff; /* Thicker border */
      cursor:pointer;
      box-shadow: 0 6px 18px rgba(0,255,255,0.5); /* Stronger shadow */
      transition: all 0.3s cubic-bezier(.25,.8,.25,1); /* Smooth transitions */
      text-shadow: 0 0 10px #fff, 0 0 7px #0ff; /* White core glow */
      letter-spacing: 1px;
      font-weight: bold;
    }
    #warpBtns button:hover {
      background:linear-gradient(145deg, #0ff8, #0ff4); /* Stronger gradient on hover */
      transform: translateY(-6px) scale(1.04); /* More pronounced lift and scale */
      box-shadow: 0 12px 30px rgba(0,255,255,0.8);
      border-color: #fff; /* White border on hover */
    }
    #warpBtns button:active {
      transform: translateY(0) scale(1);
      box-shadow: 0 3px 10px rgba(0,255,255,0.4);
    }

    /* NPC Label - A-Frame text is styled separately */
    .npc-label { color:#ffd700; font-family: 'Noto Sans JP',monospace; font-size:1.0em; text-align:center; text-shadow:0 0 4px #000;}

    /* Shop Div */
    #shopDiv {
      display:none; left:50%; top:50%; transform:translate(-50%,-50%);
      min-width:420px; max-width:95vw; /* Larger shop */
      background:linear-gradient(160deg, rgba(22,42,70,0.99), rgba(11,21,35,0.99)); /* Darker, richer gradient */
      color:#fff; border-radius:25px; padding:2.5em 3em; /* More padding, rounder */
      font-size:1.3rem; z-index:110;
      border:4px solid #ff0; /* Thicker gold border */
      box-shadow: 0 0 40px #ff0c, inset 0 0 20px #ff05; /* Intense gold glow */
      backdrop-filter: blur(6px);
    }
    #shopDiv .shop-item {
      display: flex; justify-content: space-between; align-items: center;
      padding: 0.8em 0; border-bottom: 1px solid rgba(255,255,0,0.2);
    }
    #shopDiv .shop-item:last-child { border-bottom: none; }
    #shopDiv button {
      margin:0.8em 0.8em 0 0; padding:0.9em 1.8em; /* More padding */
      font-size:1.2em; border-radius:15px; border:3px solid #ff0;
      background:linear-gradient(145deg, #282828, #181818); /* Dark gradient */
      color:#ff0; cursor:pointer;
      box-shadow: 0 4px 12px rgba(255,255,0,0.5);
      transition: all 0.3s cubic-bezier(.25,.8,.25,1);
      text-shadow: 0 0 7px #ff0;
      font-weight: bold;
    }
    #shopDiv button:hover {
      background:linear-gradient(145deg, #ff0, #e0d000); /* Gold gradient on hover */
      color:#222;
      transform: translateY(-3px);
      box-shadow: 0 7px 20px rgba(255,255,0,0.7);
    }
    #shopDiv button:active {
      transform: translateY(0);
      box-shadow: 0 2px 5px rgba(255,255,0,0.3);
    }
    #shopDiv .close-button {
      position: absolute; top: 15px; right: 20px;
      background: none; border: none; font-size: 2em; color: #ff0;
      cursor: pointer; text-shadow: 0 0 10px #ff0;
    }
    #shopDiv .close-button:hover {
      color: #fff; text-shadow: 0 0 15px #fff;
    }

    /* Night Filter */
    #nightFilter {left:0;top:0;width:100vw;height:100vh;z-index:5;pointer-events:none; background:rgba(0,8,38,0.27);transition:background 2s ease-in-out;}

    /* Quest Clear Notification */
    #questClear {
      display:none; left:0; top:0; width:100vw; height:100vh; z-index:150;
      background:rgba(16,255,255,0.45); /* More opaque */
      color:#fff; font-size:6vw; font-family:Orbitron,sans-serif; text-align:center;
      line-height:100vh; letter-spacing:0.25em; text-shadow:0 0 50px #0ff,0 0 20px #fff; /* Stronger glow */
      animation: pulseGlow 1.2s infinite alternate; /* Faster pulsing animation */
    }

    /* Battle Effect Overlay */
    #battleEffect {
      display: none;
      left: 0; top: 0; width: 100vw; height: 100vh; z-index: 120;
      background: rgba(255, 0, 0, 0.2); /* Red tint */
      pointer-events: none;
      animation: battleFlash 0.3s ease-out;
    }

    /* Keyframe for Quest Clear pulse effect */
    @keyframes pulseGlow {
      from { text-shadow:0 0 50px #0ff,0 0 20px #fff; transform: scale(1); opacity: 1; }
      to { text-shadow:0 0 65px #0ff,0 0 25px #fff; transform: scale(1.03); opacity: 0.9; }
    }

    /* Keyframe for Battle Flash effect */
    @keyframes battleFlash {
      0% { opacity: 0; }
      50% { opacity: 1; }
      100% { opacity: 0; }
    }

    /* Fade In Animation for UI elements */
    @keyframes fadeIn {
      from { opacity: 0; transform: translate(-50%, -40%); }
      to { opacity: 1; transform: translate(-50%, 0); }
    }
  </style>
</head>
<body>
  <div id="overlay">WASDで移動・マウスで視点/クリックで会話・バトル・進行!<br>第一章~魔界まで全章フルシナリオ&全機能!</div>
  <div id="dialogue"></div>
  <div id="partyStatus"></div>
  <div id="hint">右下の転移ボタンでどこでもワープ。NPC/Enemy/宝箱/ショップも全てクリック対応!</div>
  <div id="warpBtns"></div>
  <div id="shopDiv"></div>
  <div id="nightFilter"></div>
  <div id="questClear">クエストクリア!</div>
  <div id="battleEffect"></div>

  <a-scene shadow="type:pcfsoft" renderer="antialias: true; colorManagement: true;">
    <!-- Ground and Sky -->
    <a-box id="ground" width="200" height="1" depth="200" color="#7ac870" position="0 0 0" shadow="receive: true"></a-box>
    <a-sky id="sky" color="#b8e6ff"></a-sky>

    <!-- Player Rig and Camera -->
    <a-entity id="playerRig" position="0 1.6 22">
      <a-camera id="playerCam" wasd-controls="acceleration:36" look-controls="pointerLockEnabled: true"></a-camera>
    </a-entity>

    <!-- Lights -->
    <a-light id="dirlight" type="directional" color="#fff" intensity="1.13" position="10 20 10" castShadow="true"></a-light>
    <a-light id="ambientlight" type="ambient" color="#b8e6ff" intensity="0.62"></a-light>

    <!-- Character Models (Marichan, Marianne, Elina) -->
    <!-- 真理 -->
    <a-entity id="marichan" position="0 0.1 22">
      <a-sphere position="0 1.7 0" radius="0.43" color="#f44"></a-sphere>
      <a-torus position="0 1.95 0" radius="0.25" radius-tubular="0.06" color="#e44" rotation="90 0 0"></a-torus>
      <a-cylinder position="0 1.1 0" radius="0.21" height="1.1" color="#222"></a-cylinder>
      <a-sphere position="0.21 1.45 0" radius="0.09" color="#ff8888"></a-sphere>
      <a-sphere position="-0.21 1.45 0" radius="0.09" color="#ff8888"></a-sphere>
      <a-cylinder position="0.36 1.1 0" radius="0.06" height="0.5" color="#f99" rotation="0 0 22"></a-cylinder>
      <a-cylinder position="-0.36 1.1 0" radius="0.06" height="0.5" color="#f99" rotation="0 0 -22"></a-cylinder>
      <a-cylinder position="0.11 0.35 0" radius="0.07" height="0.47" color="#222" rotation="0 0 13"></a-cylinder>
      <a-cylinder position="-0.11 0.35 0" radius="0.07" height="0.47" color="#222" rotation="0 0 -13"></a-cylinder>
      <a-box position="0.37 1.0 0.12" width="0.07" height="0.5" depth="0.07" color="#222"></a-box>
      <a-box position="0.37 1.27 0.12" width="0.05" height="0.18" depth="0.05" color="#bfb"></a-box>
      <a-sphere position="0.12 1.82 0.39" radius="0.04" color="#fff"></a-sphere>
      <a-sphere position="-0.12 1.82 0.39" radius="0.04" color="#fff"></a-sphere>
      <a-sphere position="0.12 1.82 0.43" radius="0.018" color="#111"></a-sphere>
      <a-sphere position="-0.12 1.82 0.43" radius="0.018" color="#111"></a-sphere>
      <a-torus position="0 1.71 0.43" radius="0.07" radius-tubular="0.011" color="#e88" rotation="90 0 0"></a-torus>
      <a-text value="真理" color="#fff" position="0 2.23 0" align="center" width="7"></a-text>
    </a-entity>
    <!-- マリアンヌ -->
    <a-entity id="marianne" position="2.5 0.1 22">
      <a-sphere position="0 1.7 0" radius="0.42" color="#ffe89b"></a-sphere>
      <a-cone position="0 2.18 0" radius-bottom="0.13" radius-top="0.01" height="0.23" color="#ffd700"></a-cone>
      <a-cylinder position="0 1.05 0" radius="0.21" height="1.1" color="#e4e7f4"></a-cylinder>
      <a-cylinder position="0.28 1.12 0.09" radius="0.06" height="0.54" color="#eac" rotation="0 0 24"></a-cylinder>
      <a-cylinder position="-0.28 1.12 0.09" radius="0.06" height="0.54" color="#eac" rotation="0 0 -24"></a-cylinder>
      <a-box position="0.02 1.7 0.28" width="0.10" height="0.03" depth="0.01" color="#22f"></a-box>
      <a-text value="マリアンヌ" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
    </a-entity>
    <!-- エリナ -->
    <a-entity id="elina" position="-2.5 0.1 22">
      <a-sphere position="0 1.7 0" radius="0.42" color="#70e59b"></a-sphere>
      <a-cylinder position="0 1.05 0" radius="0.21" height="1.1" color="#e3ffe3"></a-cylinder>
      <a-cone position="0.32 1.85 0" radius-bottom="0.06" radius-top="0.01" height="0.18" color="#fff" rotation="0 0 50"></a-cone>
      <a-cone position="-0.32 1.85 0" radius-bottom="0.06" radius-top="0.01" height="0.18" color="#fff" rotation="0 0 -50"></a-cone>
      <a-text value="エリナ" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
    </a-entity>

    <!-- Enhanced Town (街) Graphics - Position: 0 0 22 -->
    <a-entity id="town-scene" position="0 0 22">
      <!-- Main Building 1 (Guild) -->
      <a-box position="-8 2.5 -5" width="6" height="5" depth="6" color="#a0522d" shadow="cast: true"></a-box>
      <a-cone position="-8 5.5 -5" radius-bottom="4" height="3" color="#8b4513" rotation="0 45 0" shadow="cast: true"></a-cone>
      <a-text value="GUILD" color="#333" position="-8 4.5 -1.9" align="center" width="10"></a-text>
      <!-- Main Building 2 (Tavern) -->
      <a-box position="8 2.5 -5" width="6" height="5" depth="6" color="#a0522d" shadow="cast: true"></a-box>
      <a-cone position="8 5.5 -5" radius-bottom="4" height="3" color="#8b4513" rotation="0 -45 0" shadow="cast: true"></a-cone>
      <a-text value="TAVERN" color="#333" position="8 4.5 -1.9" align="center" width="10"></a-text>
      <!-- Inn -->
      <a-box position="0 2.5 -10" width="8" height="5" depth="7" color="#deb887" shadow="cast: true"></a-box>
      <a-cone position="0 5.5 -10" radius-bottom="5" height="3" color="#cd853f" shadow="cast: true"></a-cone>
      <a-text value="INN" color="#333" position="0 4.5 -6.4" align="center" width="10"></a-text>
      <!-- Shop -->
      <a-box id="town-shop" position="12 2.5 0" width="5" height="5" depth="5" color="#8fbc8f" shadow="cast: true"></a-box>
      <a-pyramid position="12 5.5 0" width="5" height="3" depth="5" color="#6b8e23" shadow="cast: true"></a-pyramid>
      <a-text value="SHOP" color="#333" position="12 4.5 2.6" align="center" width="10"></a-text>
      <!-- Fountain -->
      <a-cylinder position="0 0.5 0" radius="2" height="1" color="#4682b4" shadow="cast: true"></a-cylinder>
      <a-cylinder position="0 1.5 0" radius="0.5" height="1" color="#add8e6" shadow="cast: true"></a-cylinder>
      <a-sphere position="0 2.5 0" radius="0.3" color="#add8e6" shadow="cast: true"></a-sphere>
      <!-- Trees -->
      <a-cylinder position="-10 2.5 5" radius="0.5" height="5" color="#8b4513" shadow="cast: true"></a-cylinder>
      <a-cone position="-10 6.5 5" radius-bottom="2" height="4" color="#228b22" shadow="cast: true"></a-cone>
      <a-cylinder position="10 2.5 5" radius="0.5" height="5" color="#8b4513" shadow="cast: true"></a-cylinder>
      <a-cone position="10 6.5 5" radius-bottom="2" height="4" color="#228b22" shadow="cast: true"></a-cone>
      <!-- Town NPC -->
      <a-entity id="town-npc-1" position="-5 0.1 0" class="interactable-npc">
        <a-sphere position="0 1.7 0" radius="0.4" color="#f0e68c"></a-sphere> <!-- Head -->
        <a-cylinder position="0 1.0 0" radius="0.2" height="1.2" color="#808080"></a-cylinder> <!-- Body -->
        <a-text value="村人" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
      </a-entity>
    </a-entity>

    <!-- Enhanced Home (自宅) Graphics - Position: 18 0 31 -->
    <a-entity id="home-scene" position="18 0 31">
      <!-- House Body -->
      <a-box position="0 2.5 0" width="8" height="5" depth="8" color="#d2b48c" shadow="cast: true"></a-box>
      <!-- Roof -->
      <a-box position="0 5.5 0" width="9" height="0.5" depth="9" color="#8b4513" rotation="0 45 0" shadow="cast: true"></a-box>
      <a-cone position="0 7.5 0" radius-bottom="5" height="4" color="#8b4513" shadow="cast: true"></a-cone>
      <!-- Chimney -->
      <a-box position="3 7 3" width="1" height="3" depth="1" color="#654321" shadow="cast: true"></a-box>
      <!-- Door -->
      <a-box position="0 1.5 4.01" width="2" height="3" depth="0.1" color="#654321"></a-box>
      <!-- Windows -->
      <a-box position="3 2.5 4.01" width="1.5" height="2" depth="0.1" color="#87ceeb"></a-box>
      <a-box position="-3 2.5 4.01" width="1.5" height="2" depth="0.1" color="#87ceeb"></a-box>
      <a-box position="4.01 2.5 0" width="0.1" height="2" depth="1.5" rotation="0 90 0" color="#87ceeb"></a-box>
      <!-- Small Garden & Fence -->
      <a-plane position="0 0.01 6" width="10" height="4" rotation="-90 0 0" color="#556b2f" shadow="receive: true"></a-plane>
      <a-sphere position="2 0.5 6" radius="0.5" color="#ff69b4"></a-sphere>
      <a-sphere position="-2 0.5 6" radius="0.5" color="#ff69b4"></a-sphere>
      <a-cylinder position="0 0.5 7" radius="0.3" height="1" color="#8b4513"></a-cylinder>
      <a-cone position="0 1.5 7" radius-bottom="1" height="2" color="#228b22"></a-cone>
      <!-- Fence -->
      <a-box position="0 0.5 8" width="10" height="1" depth="0.1" color="#a0522d"></a-box>
      <a-box position="4.9 0.5 6" width="0.1" height="1" depth="4" color="#a0522d"></a-box>
      <a-box position="-4.9 0.5 6" width="0.1" height="1" depth="4" color="#a0522d"></a-box>
      <!-- Interior elements (simple) -->
      <a-box position="0 0.7 -2" width="6" height="0.5" depth="3" color="#a0522d"></a-box> <!-- Floor inside -->
      <a-box position="2 1.5 -3" width="1.5" height="1" depth="0.8" color="#654321"></a-box> <!-- Table -->
      <a-box position="2.5 0.7 -2.5" width="0.5" height="0.8" depth="0.5" color="#654321"></a-box> <!-- Chair -->
      <a-box position="-2 1.5 -3.8" width="3" height="0.5" depth="1.5" color="#8b4513"></a-box> <!-- Bed -->
    </a-entity>

    <!-- Enhanced Dungeon Entrance (ダンジョン入口) Graphics - Position: -28 0 41 -->
    <a-entity id="dungeon-scene" position="-28 0 41">
      <!-- Cave Entrance Arch (more rugged) -->
      <a-torus position="0 3 0" radius="4.5" radius-tubular="1.5" arc="180" rotation="90 0 0" color="#444" shadow="cast: true"></a-torus>
      <a-box position="0 0.5 0" width="9" height="1.5" depth="1.5" color="#444" shadow="cast: true"></a-box>
      <!-- Cave Interior (deeper, darker) -->
      <a-box position="0 2.5 -8" width="12" height="6" depth="15" color="#222" shadow="cast: true" material="shader: flat; side: back"></a-box>
      <!-- Jagged Rocks around entrance -->
      <a-tetrahedron position="4 1.5 1" radius="1.5" color="#555" rotation="0 45 0" shadow="cast: true"></a-tetrahedron>
      <a-tetrahedron position="-4 1.5 1" radius="1.5" color="#555" rotation="0 -45 0" shadow="cast: true"></a-tetrahedron>
      <a-box position="0 0.8 2" width="6" height="1.5" depth="2" color="#555" rotation="0 15 0" shadow="cast: true"></a-box>
      <!-- Pillars (more textured) -->
      <a-cylinder position="3.5 2.5 3" radius="0.9" height="5" color="#666" shadow="cast: true"></a-cylinder>
      <a-cylinder position="-3.5 2.5 3" radius="0.9" height="5" color="#666" shadow="cast: true"></a-cylinder>
      <!-- Glowing Crystals (more prominent) -->
      <a-dodecahedron position="5.5 1.8 -5" radius="0.9" color="#0ff" material="emissive: #0ff; emissiveIntensity: 1.2"></a-dodecahedron>
      <a-dodecahedron position="-5.5 1.8 -5" radius="0.9" color="#0ff" material="emissive: #0ff; emissiveIntensity: 1.2"></a-dodecahedron>
      <!-- Eerie light from deep within -->
      <a-light type="point" color="#0ff" intensity="0.5" position="0 2 -10"></a-light>
      <!-- Dungeon Enemy (Slime) -->
      <a-entity id="dungeon-slime-1" position="3 0.5 -5" class="interactable-enemy">
        <a-sphere position="0 0.5 0" radius="0.5" color="#8a2be2" opacity="0.7"></a-sphere>
        <a-text value="スライム" color="#fff" position="0 1.2 0" align="center" width="5"></a-text>
      </a-entity>
    </a-entity>

    <!-- Enhanced Large Castle (大城) Graphics - Position: 0 0 65 -->
    <a-entity id="castle-scene" position="0 0 65">
      <!-- Main Castle Wall (more imposing) -->
      <a-box position="0 6 0" width="40" height="12" depth="20" color="#808080" shadow="cast: true"></a-box>
      <!-- Battlements on main wall -->
      <a-box position="18 12.5 0" width="2" height="1" depth="20" color="#696969" shadow="cast: true"></a-box>
      <a-box position="-18 12.5 0" width="2" height="1" depth="20" color="#696969" shadow="cast: true"></a-box>
      <a-box position="0 12.5 8" width="40" height="1" depth="2" color="#696969" shadow="cast: true"></a-box>
      <a-box position="0 12.5 -8" width="40" height="1" depth="2" color="#696969" shadow="cast: true"></a-box>
      <!-- Main Tower 1 (taller, more detailed) -->
      <a-cylinder position="15 12 0" radius="4" height="24" color="#777" shadow="cast: true"></a-cylinder>
      <a-cone position="15 25 0" radius-bottom="4.5" height="6" color="#555" shadow="cast: true"></a-cone>
      <!-- Tower 1 Battlements -->
      <a-box position="15 24.5 0" width="9" height="1" depth="9" color="#696969" rotation="0 45 0" shadow="cast: true"></a-box>
      <!-- Main Tower 2 (taller, more detailed) -->
      <a-cylinder position="-15 12 0" radius="4" height="24" color="#777" shadow="cast: true"></a-cylinder>
      <a-cone position="-15 25 0" radius-bottom="4.5" height="6" color="#555" shadow="cast: true"></a-cone>
      <!-- Tower 2 Battlements -->
      <a-box position="-15 24.5 0" width="9" height="1" depth="9" color="#696969" rotation="0 45 0" shadow="cast: true"></a-box>
      <!-- Gate (more complex drawbridge style) -->
      <a-box position="0 4 -9.9" width="10" height="8" depth="0.2" color="#444" shadow="cast: true"></a-box>
      <a-box position="0 9 -9.9" width="12" height="2" depth="0.2" color="#444" shadow="cast: true"></a-box>
      <a-box position="0 11 -9.9" width="14" height="1" depth="0.2" color="#444" shadow="cast: true"></a-box>
      <!-- Drawbridge elements -->
      <a-box position="0 0.5 -9.8" width="8" height="1" depth="0.1" color="#654321" rotation="0 0 0"></a-box>
      <a-box position="4.5 5 -9.8" width="0.2" height="10" depth="0.2" color="#333" rotation="0 0 15"></a-box>
      <a-box position="-4.5 5 -9.8" width="0.2" height="10" depth="0.2" color="#333" rotation="0 0 -15"></a-box>
      <!-- Flags on towers -->
      <a-box position="15 28 0" width="0.6" height="4" depth="0.6" color="#8b4513"></a-box>
      <a-plane position="15 30 1.8" width="2.5" height="1.8" rotation="0 90 0" color="#ffd700"></a-plane>
      <a-box position="-15 28 0" width="0.6" height="4" depth="0.6" color="#8b4513"></a-box>
      <a-plane position="-15 30 1.8" width="2.5" height="1.8" rotation="0 90 0" color="#ffd700"></a-plane>
    </a-entity>

    <!-- Field Enemy (Slime) -->
    <a-entity id="field-slime-1" position="10 0.1 10" class="interactable-enemy">
      <a-sphere position="0 0.5 0" radius="0.5" color="#00ff00" opacity="0.7"></a-sphere>
      <a-text value="スライム" color="#fff" position="0 1.2 0" align="center" width="5"></a-text>
    </a-entity>

    <!-- Boss Placeholder (for evo, ogre, dragon) -->
    <a-entity id="bossmon" visible="false"></a-entity>

  </a-scene>
<script>
// UI Elements
const dialogue = document.getElementById('dialogue');
const partyStatus = document.getElementById('partyStatus');
const hint = document.getElementById('hint');
const warpBtns = document.getElementById('warpBtns');
const shopDiv = document.getElementById('shopDiv');
const questClear = document.getElementById('questClear');
const battleEffect = document.getElementById('battleEffect');
const overlay = document.getElementById('overlay');

// Game State
let state = {
  party: [
    {name:'真理', lv:5, hp:60, maxhp:60, alive:true, job:'剣姫', skills:['ファイアーボール','ファイアーイノセント']},
    {name:'マリアンヌ', lv:5, hp:50, maxhp:50, alive:true, job:'魔法姫', skills:['ファイアーボール']},
    {name:'エリナ', lv:5, hp:46, maxhp:46, alive:true, job:'エルフ賢者', skills:['アクアスパイク']}
  ],
  gold:3000,
  inventory: [],
  currentStoryStep: "intro_01", // Current point in the story
  lastPlayerPosition: {x:0, y:1.6, z:22}, // To track movement for progress
  shopOpen: false,
  inBattle: false,
  evoCleared:false, ogreCleared:false, dragonCleared:false,
  evoTry:0, evoLv:15, ogreLv:20, dragonLv:30,
};

// Warp Points (now with more descriptive names for UI)
const warpPoints = [
  {name:"街", pos:{x:0, y:1.6, z:22}},
  {name:"自宅", pos:{x:18, y:1.6, z:31}},
  {name:"ダンジョン入口", pos:{x:-28, y:1.6, z:41}},
  {name:"大城", pos:{x:0, y:1.6, z:65}}
];

// Shop Items
const shopItems = [
  {id: 'potion', name: 'ポーション', price: 100, type: 'consumable', effect: 'HP回復'},
  {id: 'longsword', name: 'ロングソード', price: 500, type: 'weapon', atk: 10},
  {id: 'ironshield', name: 'アイアンシールド', price: 300, type: 'armor', def: 5}
];

// Story Data (structured for branching)
const storyData = {
  "intro_01": { speaker: "宅間", text: "あ~暇だなネットサーフィンでもするか", next: "intro_02" },
  "intro_02": { speaker: "Narration", text: "新作VR機器を注文して翌日届いた。", next: "intro_03" },
  "intro_03": { speaker: "宅間", text: "ふひひ!幼女になれた!俺は幼女なんだ", next: "intro_04" },
  "intro_04": { speaker: "Narration", text: "ハンドルネームを真理にしてログイン。最初の街ポルッドに着く。", next: "marianne_meet_01" },
  "marianne_meet_01": { speaker: "マリアンヌ", text: "かわいいわね私はマリアンヌっていうの", next: "marianne_meet_02" },
  "marianne_meet_02": { speaker: "真理", text: "お主、なかなかの美少女でござるなぁ…", next: "marianne_meet_03" },
  "marianne_meet_03": { speaker: "マリアンヌ", text: "もしかして中身キモヲタのおっさん!?", next: "marianne_meet_04" },
  "marianne_meet_04": { speaker: "真理", text: "うるちゃい!俺は幼女なのだぁ…!", next: "marianne_meet_05" },
  "marianne_meet_05": { speaker: "マリアンヌ", text: "まあ見た目かわいいから一緒に行くか", next: "to_castle_01" },
  "to_castle_01": { speaker: "Narration", text: "真理はだんだん幼女化していった。お城へ。", next: "king_meet_01" },
  "king_meet_01": { speaker: "王様", text: "おお、マリアンヌ何処に行ってたのだ?おやその子は?", next: "king_meet_02" },
  "king_meet_02": { speaker: "真理", text: "真理なのじゃ!!", next: "king_meet_03" },
  "king_meet_03": { speaker: "王様", text: "何と可愛らしい…", next: "king_meet_04" },
  "king_meet_04": { speaker: "マリアンヌ", text: "私決めたわ!この子と一緒に旅することに!", next: "king_meet_05" },
  "king_meet_05": { speaker: "王様", text: "それは行かん!", next: "king_meet_06" },
  "king_meet_06": { speaker: "真理", text: "行くのじゃ!", next: "leave_castle_01" },
  "leave_castle_01": { speaker: "Narration", text: "マリアンヌの手を繋いで城を出た。", next: "leave_castle_02" },
  "leave_castle_02": { speaker: "マリアンヌ", text: "ちょっと待ちなさいよ城から出て行く当てでもあるの?", next: "leave_castle_03" },
  "leave_castle_03": { speaker: "真理", text: "次の街を探してみようと思うのじゃ", next: "leave_castle_04" },
  "leave_castle_04": { speaker: "マリアンヌ", text: "ここから一番近くの街って言ったらイデルかしら", next: "leave_castle_05" },
  "leave_castle_05": { speaker: "真理", text: "イデルってどういった場所なのかの?", next: "leave_castle_06" },
  "leave_castle_06": { speaker: "マリアンヌ", text: "知らないわよ私街から出たことないし…", next: "leave_castle_07" },
  "leave_castle_07": { speaker: "真理", text: "それじゃお試しでいくのじゃ!", next: "rest_01" },
  "rest_01": { speaker: "Narration", text: "焚火→テントで休憩→朝", next: "start_journey_01" },
  "start_journey_01": { speaker: "真理", text: "それじゃ向かうのじゃ!", next: "slime_appear_01" },
  "slime_appear_01": { speaker: "Narration", text: "フィールドに出るとスライムが現れた", next: "battle_slime_01" },
  "battle_slime_01": {
    speaker: "真理",
    text: "ファイアーボール!!",
    onComplete: () => { startBattle('slime'); }, // Trigger battle function
    next: "slime_defeated_01"
  },
  "slime_defeated_01": { speaker: "Narration", text: "真理の初級呪文でスライムを倒した", next: "marianne_magic_01" },
  "marianne_magic_01": { speaker: "マリアンヌ", text: "へぇあんた呪文使えるのね", next: "marianne_magic_02" },
  "marianne_magic_02": { speaker: "真理", text: "必要ならば教えてあげることも可能なのじゃ", next: "marianne_magic_03" },
  "marianne_magic_03": { speaker: "マリアンヌ", text: "教えて教えて!", next: "marianne_magic_04" },
  "marianne_magic_04": { speaker: "真理", text: "しょうがないのう", next: "marianne_magic_05" },
  "marianne_magic_05": { speaker: "Narration", text: "真理はマリアンヌに初級魔術を教えた。", next: "marianne_magic_06" },
  "marianne_magic_06": { speaker: "マリアンヌ", text: "こうですの?", next: "marianne_magic_07" },
  "marianne_magic_07": { speaker: "Narration", text: "炎の魔術を繰り出した", next: "marianne_magic_08" },
  "marianne_magic_08": { speaker: "真理", text: "そうそう", next: "marianne_magic_09" },
  "marianne_magic_09": { speaker: "マリアンヌ", text: "結構簡単でしたね", next: "marianne_magic_10" },
  "marianne_magic_10": { speaker: "真理", text: "初級魔術だからね今から魔術書で中級を覚える予定", next: "master_mid_magic_01" },
  "master_mid_magic_01": { speaker: "Narration", text: "次の日、中級魔術をマスター", next: "ogre_appear_01" },
  "ogre_appear_01": { speaker: "真理", text: "ファイアーイノセント!!", next: "ogre_defeated_01" },
  "ogre_defeated_01": { speaker: "Narration", text: "巨大なオークを丸焼きに", next: "ogre_defeated_02" },
  "ogre_defeated_02": { speaker: "マリアンヌ", text: "オークを一撃でやるなんてすごいわね", next: "ogre_defeated_03" },
  "ogre_defeated_03": { speaker: "真理", text: "いつの間にかに強くなってたのじゃ", next: "dungeon_found_01" },
  "dungeon_found_01": { speaker: "Narration", text: "洞窟を発見。中へ入る。", next: "dungeon_intro_01" },
  "dungeon_intro_01": { speaker: "マリアンヌ", text: "どうやらここは本で書いてあった始まりの洞窟のようね", next: "lizardman_appear_01" },
  "lizardman_appear_01": { speaker: "Narration", text: "リザードマンが出現!", next: "lizardman_battle_01" },
  "lizardman_battle_01": { speaker: "真理", text: "リザードマンなのじゃ気を付けるのじゃ", next: "lizardman_battle_02" },
  "lizardman_battle_02": { speaker: "マリアンヌ", text: "ファイアーボール!!", next: "lizardman_battle_03" },
  "lizardman_battle_03": { speaker: "Narration", text: "リザードマン「兄貴!尻尾が燃えてますぜ」", next: "lizardman_battle_04" },
  "lizardman_battle_04": { speaker: "真理", text: "もっと燃やしてやるのじゃ", next: "lizardman_defeated_01" },
  "lizardman_defeated_01": { speaker: "Narration", text: "炎の魔術でリザードマン撃破!", next: "lizardman_defeated_02" },
  "lizardman_defeated_02": { speaker: "マリアンヌ", text: "やるじゃない私も早く中級魔術使えるようになりたいわ", next: "lizardman_defeated_03" },
  "lizardman_defeated_03": { speaker: "真理", text: "今日の夜にでも教えてあげるのじゃ", next: "treasure_chest_01" },
  "treasure_chest_01": { speaker: "Narration", text: "洞窟の分岐で宝箱(ロングソード)を発見。", next: "treasure_chest_02" },
  "treasure_chest_02": { speaker: "真理", text: "やった宝箱なのじゃー!", next: "treasure_chest_03" },
  "treasure_chest_03": { speaker: "マリアンヌ", text: "中身は何かしらね", next: "treasure_chest_04" },
  "treasure_chest_04": { speaker: "Narration", text: "中身はロングソードだった", next: "treasure_chest_05" },
  "treasure_chest_05": { speaker: "真理", text: "何だ剣か…", next: "treasure_chest_06" },
  "treasure_chest_06": { speaker: "マリアンヌ", text: "杖の方が良かったわねでもせっかくだから装備して見ましょう", next: "sword_lv_up_01" },
  "sword_lv_up_01": { speaker: "Narration", text: "剣術レベル1になった", next: "sword_lv_up_02" },
  "sword_lv_up_02": { speaker: "真理", text: "剣をもっと使いこなしたいのう", next: "sword_lv_up_03" },
  "sword_lv_up_03": { speaker: "マリアンヌ", text: "剣術が出来る人を探すしかないわね", next: "exit_dungeon_01" },
  "exit_dungeon_01": { speaker: "Narration", text: "洞窟出口→外に出る。", next: "exit_dungeon_02" },
  "exit_dungeon_02": { speaker: "真理", text: "外に出たのじゃ!!", next: "idel_arrival_01" },
  "idel_arrival_01": { speaker: "Narration", text: "街イデルに到着。", next: "idel_guide_01" },
  "idel_guide_01": { speaker: "案内人", text: "ようこそイデルへ!ここは仲間を集めたりする酒場や、クエストを受けられる冒険者ギルドがあります", next: "idel_guide_02" },
  "idel_guide_02": { speaker: "真理", text: "それじゃあまずは酒場で情報収集と仲間集めと行きますか", next: "idel_guide_03" },
  "idel_guide_03": { speaker: "マリアンヌ", text: "どんな人がいるかしらね", next: "elina_appear_01" },
  "elina_appear_01": { speaker: "Narration", text: "酒場でエリナ登場。", next: "elina_join_01" },
  "elina_join_01": { speaker: "真理", text: "そこの美しいお方、わらわ達と冒険しませんか?", next: "elina_join_02" },
  "elina_join_02": { speaker: "エリナ", text: "私はエリナ・カリオットよ見ての通りエルフで魔術師をやっているわ", next: "elina_join_03" },
  "elina_join_03": { speaker: "真理", text: "よかったら仲間になってくれませんか?", next: "elina_join_04" },
  "elina_join_04": { speaker: "エリナ", text: "クエストを手伝ってくれたら考えてもいいわよ", next: "elina_join_05" },
  "elina_join_05": { speaker: "真理", text: "わーい手伝う手伝う!", next: "to_guild_with_elina_01" },
  "to_guild_with_elina_01": { speaker: "Narration", text: "エリナと共に冒険者ギルドへ。", next: "gigborg_quest_01" },
  "gigborg_quest_01": { speaker: "エリナ", text: "あなた達にはギグボルグの討伐をやって貰うわ", next: "gigborg_quest_02" },
  "gigborg_quest_02": { speaker: "真理", text: "どんなモンスターかわからにゃいけど頑張るにゃ", next: "gigborg_quest_03" },
  "gigborg_quest_03": { speaker: "マリアンヌ", text: "何でちょっと猫語になってんの", next: "gigborg_quest_04" },
  "gigborg_quest_04": { speaker: "真理", text: "ふにゃ??分からないにゃ", next: "gigborg_quest_05" },
  "gigborg_quest_05": { speaker: "エリナ", text: "場所は始まりの塔よ。北にあるわ。", next: "to_tower_01" },
  "to_tower_01": { speaker: "Narration", text: "始まりの塔へ向かう。", next: "goblin_battle_01" },
  "goblin_battle_01": { speaker: "Narration", text: "始まりの塔でゴブリン達とバトル!", next: "goblin_battle_02" },
  "goblin_battle_02": { speaker: "真理", text: "ファイアーイノセント!!", onComplete: () => { startBattle('goblin'); }, next: "goblin_defeated_01" },
  "goblin_defeated_01": { speaker: "Narration", text: "ゴブリンたちは焼き尽くされた。", next: "open_door_01" },
  "open_door_01": { speaker: "Narration", text: "塔の中でアイテム端末を使い扉を開く。", next: "boss_warning_01" },
  "boss_warning_01": { speaker: "冒険者", text: "この先ボス部屋があるぞ強いから注意しておけ", next: "gigborg_boss_01" },
  "gigborg_boss_01": { speaker: "Narration", text: "ボス部屋でギグボルグLv10戦。", next: "gigborg_boss_02" },
  "gigborg_boss_02": { speaker: "マリアンヌ", text: "ファイアーボール!!", next: "gigborg_boss_03" },
  "gigborg_boss_03": { speaker: "エリナ", text: "アクアスパイク!!", next: "gigborg_boss_04" },
  "gigborg_boss_04": { speaker: "Narration", text: "ギグボルグの斧で全滅→イデルで復活。", next: "level_up_needed_01" },
  "level_up_needed_01": { speaker: "マリアンヌ", text: "やられたみたいねレベル上げでもしましょ", next: "goblin_hunt_01" },
  "goblin_hunt_01": { speaker: "Narration", text: "西の森でゴブリン狩りLv10にアップ。", next: "lightning_vortex_01" },
  "lightning_vortex_01": { speaker: "真理", text: "ライトニングヴォルテックスを覚えた!", next: "gigborg_retry_01" },
  "gigborg_retry_01": { speaker: "Narration", text: "再挑戦でギグボルグ撃破。", next: "gigborg_defeated_01" },
  "gigborg_defeated_01": { speaker: "マリアンヌ", text: "やったわ!ギグボルグ撃破よあんたおっさんの癖になかなかやるわね", next: "gigborg_defeated_02" },
  "gigborg_defeated_02": { speaker: "真理", text: "おっさん言うな!どうみてもかわいい女の子じゃろ?", next: "gigborg_defeated_03" },
  "gigborg_defeated_03": { speaker: "マリアンヌ", text: "そ、そうね…", next: "elina_officially_join_01" },
  "elina_officially_join_01": { speaker: "エリナ", text: "約束通り仲間になってあげるわよ", next: "elina_officially_join_02" },
  "elina_officially_join_02": { speaker: "真理", text: "よろしくなのじゃ", next: "evolvia_quest_01" },
  "evolvia_quest_01": { speaker: "エリナ", text: "次はエボルビア討伐に行くわよ", next: "evolvia_quest_02" },
  "evolvia_quest_02": { speaker: "Narration", text: "海にいる怪物エボルビア討伐へ。", next: "evolvia_battle_01" },
  "evolvia_battle_01": { speaker: "真理", text: "ライトニングヴォルテックス!", next: "evolvia_retreat_01" },
  "evolvia_retreat_01": { speaker: "Narration", text: "レベル差で撤退、レベル上げ&アイテム集め。", next: "lightning_judgment_01" },
  "lightning_judgment_01": { speaker: "Narration", text: "Lv20到達でライトニングジャッジメントを覚える。", next: "evolvia_rematch_01" },
  "evolvia_rematch_01": { speaker: "真理", text: "エボルビアを倒しに行くわよ", next: "evolvia_rematch_02" },
  "evolvia_rematch_02": { speaker: "マリアンヌ", text: "今度こそ倒すわよ", next: "evolvia_rematch_03" },
  "evolvia_rematch_03": { speaker: "真理", text: "ライトニングジャッジメント!!", next: "evolvia_defeated_01" },
  "evolvia_defeated_01": { speaker: "Narration", text: "エボルビア討伐、クエストクリアー。", next: "quest_clear_effect_01" },
  "quest_clear_effect_01": { speaker: "エリナ", text: "おっしゃークリア!お疲れ!", onComplete: () => { showQuestClear(); }, next: "ogre_quest_01" },
  "ogre_quest_01": { speaker: "Narration", text: "オーガ討伐→オーガの爪1万Gで売却。", next: "buy_equip_01" },
  "buy_equip_01": { speaker: "Narration", text: "武器・防具を買い強化。宿屋で体力回復。", next: "rank_up_quest_01" },
  "rank_up_quest_01": { speaker: "受付の人", text: "現在のランクはEクラスです。ランクアップクエストはドラゴン討伐です。", next: "dragon_quest_01" },
  "dragon_quest_01": { speaker: "真理", text: "ドラゴンの討伐いくのじゃ", next: "dragon_quest_02" },
  "dragon_quest_02": { speaker: "Narration", text: "竜の山へピクニック→ドラゴンの巣で爆弾を設置して爆破!", next: "dragon_quest_03" },
  "dragon_quest_03": { speaker: "真理", text: "少々やりすぎたのじゃ", next: "rank_up_d_01" },
  "rank_up_d_01": { speaker: "Narration", text: "イデルでクエスト報告、Dランクにアップ&飛空艇GET。", next: "d_rank_quest_01" },
  "d_rank_quest_01": { speaker: "マリアンヌ", text: "Dランクのクエストも受けておきましょ", next: "hero_tower_01" },
  "hero_tower_01": { speaker: "Narration", text: "飛空艇で英雄の塔→闇の騎士ボス戦", next: "dark_knight_battle_01" },
  "dark_knight_battle_01": { speaker: "マリアンヌ", text: "受け取って!", next: "dark_knight_battle_02" },
  "dark_knight_battle_02": { speaker: "Narration", text: "マリアンヌが剣を投げ真理が闇の騎士を討つ。", next: "dark_knight_defeated_01" },
  "dark_knight_defeated_01": { speaker: "真理", text: "どうじゃ?わらわの力は?", next: "dark_knight_defeated_02" },
  "dark_knight_defeated_02": { speaker: "マリアンヌ", text: "あの時、私が剣を渡してなかったら死んでたわよ", next: "dark_knight_defeated_03" },
  "dark_knight_defeated_03": { speaker: "真理", text: "それは感謝してるのじゃ", next: "sky_castle_01" },
  "sky_castle_01": { speaker: "Narration", text: "塔の景色→空に浮かぶ城エリクッドへ。", next: "angel_meet_01" },
  "angel_meet_01": { speaker: "天使", text: "ようこそエリクッドへ", next: "king_attacked_01" },
  "king_attacked_01": { speaker: "王様", text: "今エリクッドは魔界の悪魔の攻撃を受けている", next: "demon_realm_quest_01" },
  "demon_realm_quest_01": { speaker: "真理", text: "んじゃわらわたちが魔界に行って悪魔を倒してやるのじゃ!", next: "demon_realm_quest_02" },
  "demon_realm_quest_02": { speaker: "王様", text: "異界の門はここからずっと北にある", next: "to_demon_gate_01" },
  "to_demon_gate_01": { speaker: "Narration", text: "異界の門へ→ワイバーン撃破→サタン戦", next: "satan_battle_01" },
  "satan_battle_01": { speaker: "サタン", text: "ここから先は通さないぜ", next: "satan_battle_02" },
  "satan_battle_02": { speaker: "真理", text: "サンダージャッジメント!!", next: "satan_battle_03" },
  "satan_battle_03": { speaker: "エリナ", text: "プラチナソード!!", next: "satan_defeated_01" },
  "satan_defeated_01": { speaker: "Narration", text: "サタン討伐、魔界へ突入!", next: "demon_realm_arrival_01" },
  "demon_realm_arrival_01": { speaker: "案内人", text: "ようこそ、魔界の街モルドへ", next: "demon_realm_arrival_02" },
  "demon_realm_arrival_02": { speaker: "マリアンヌ", text: "ここの悪魔達は敵対心がないようね", next: "demon_realm_arrival_03" },
  "demon_realm_arrival_03": { speaker: "真理", text: "もう歩き疲れたのじゃ宿屋に泊りたいのじゃ~", next: "demon_castle_01" },
  "demon_castle_01": { speaker: "Narration", text: "魔界の城を目指しゾンビ戦→エリナが撃破!", next: "elina_defeat_zombie_01" },
  "elina_defeat_zombie_01": { speaker: "エリナ", text: "私に任せなさい!!", next: "end_chapter" },
  "end_chapter": { speaker: "Narration", text: "次章へ続く…", next: null }
};

let currentStoryId = state.currentStoryStep;

// --- UI Functions ---
function setWarpUI() {
  warpBtns.innerHTML = '';
  warpPoints.forEach(p=>{
    let btn = document.createElement('button');
    btn.textContent = p.name + "へ転移";
    btn.onclick = ()=>{
      document.querySelector('#playerRig').setAttribute('position', `${p.pos.x} ${p.pos.y} ${p.pos.z}`);
      closeDialogue(); // Close dialogue on warp
      closeShop(); // Close shop on warp
    };
    warpBtns.appendChild(btn);
  });
}

function updatePartyStatus() {
  partyStatus.innerHTML = `<b>パーティ</b><br>`;
  state.party.forEach(p=>{
    partyStatus.innerHTML += `<span class="${p.alive?'alive':'dead'}">${p.name}(Lv${p.lv}) ${p.hp}/${p.maxhp}HP</span><br>`;
  });
  partyStatus.innerHTML += `<b>G:</b> ${state.gold}<br>`;
  partyStatus.innerHTML += `<b>アイテム:</b> ${state.inventory.map(item => item.name).join(', ') || 'なし'}`;
}

// --- Day/Night Cycle ---
let skyColor="#b8e6ff", nightColor="#26305a";
let night=false;
function setDayNight(toNight){
  night=toNight;
  document.getElementById('sky').setAttribute('color',night?nightColor:skyColor);
  document.getElementById('dirlight').setAttribute('intensity',night?0.28:1.13);
  document.getElementById('ambientlight').setAttribute('intensity',night?0.16:0.62);
  document.getElementById('nightFilter').style.background=night?"rgba(0,8,38,0.53)":"rgba(0,8,38,0.0)";
}
setInterval(()=>{setDayNight(!night);},15000); setDayNight(false);

// --- Dialogue System ---
function showDialogueBlock(dialogueId) {
  const data = storyData[dialogueId];
  if (!data) {
    closeDialogue();
    return;
  }

  dialogue.innerHTML = '';
  dialogue.style.display = 'block';

  if (data.speaker && data.speaker !== 'Narration') {
    const n = document.createElement('span');
    n.className = 'name';
    n.textContent = data.speaker + ' ';
    dialogue.appendChild(n);
  }
  const t = document.createElement('span');
  t.textContent = data.text;
  dialogue.appendChild(t);

  if (data.choices) {
    data.choices.forEach(c => {
      const btn = document.createElement('button');
      btn.className = 'choice';
      btn.textContent = c.text;
      btn.onclick = () => {
        if (c.onChoose) c.onChoose(); // Execute choice-specific logic
        currentStoryId = c.next;
        showDialogueBlock(currentStoryId);
      };
      dialogue.appendChild(btn);
    });
  } else {
    dialogue.onclick = () => {
      dialogue.onclick = null; // Prevent multiple clicks
      if (data.onComplete) data.onComplete(); // Execute onComplete logic
      currentStoryId = data.next;
      showDialogueBlock(currentStoryId);
    };
  }
}

function closeDialogue() {
  dialogue.style.display = 'none';
  dialogue.onclick = null;
}

// --- Battle System (Simplified) ---
function startBattle(enemyType) {
  state.inBattle = true;
  battleEffect.style.display = 'block';
  // Simulate battle outcome instantly for now
  setTimeout(() => {
    battleEffect.style.display = 'none';
    state.inBattle = false;
    // Example: Party takes some damage, or enemy is defeated
    state.party.forEach(p => {
      if (p.alive) p.hp = Math.max(0, p.hp - 5); // Simulate damage
      if (p.hp === 0) p.alive = false;
    });
    updatePartyStatus();
    // Logic for enemy defeat, item drops, XP gain would go here
    // For now, the story progresses automatically after the simulated battle
  }, 300); // Flash for 0.3 seconds
}

function showQuestClear() {
  questClear.style.display = 'block';
  setTimeout(() => {
    questClear.style.display = 'none';
  }, 3000); // Show for 3 seconds
}

// --- Shop System ---
function openShop() {
  state.shopOpen = true;
  shopDiv.style.display = 'block';
  shopDiv.innerHTML = `
    <h2>ショップ</h2>
    <p>ゴールド: <span id="shopGold">${state.gold}</span>G</p>
    <div id="shopItemsContainer"></div>
    <button class="close-button" onclick="closeShop()">X</button>
  `;
  const shopItemsContainer = document.getElementById('shopItemsContainer');

  shopItems.forEach(item => {
    const itemDiv = document.createElement('div');
    itemDiv.className = 'shop-item';
    itemDiv.innerHTML = `
      <span>${item.name} (${item.price}G)</span>
      <button onclick="buyItem('${item.id}')">購入</button>
    `;
    shopItemsContainer.appendChild(itemDiv);
  });
  // Add a simple "Sell" option
  if (state.inventory.length > 0) {
    const sellSection = document.createElement('div');
    sellSection.innerHTML = `<h3>持ち物売却</h3>`;
    state.inventory.forEach((item, index) => {
      sellSection.innerHTML += `
        <div class="shop-item">
          <span>${item.name} (${Math.floor(item.price / 2)}G)</span>
          <button onclick="sellItem(${index})">売却</button>
        </div>
      `;
    });
    shopItemsContainer.appendChild(sellSection);
  }
}

function buyItem(itemId) {
  const item = shopItems.find(i => i.id === itemId);
  if (item && state.gold >= item.price) {
    state.gold -= item.price;
    state.inventory.push(item);
    updatePartyStatus();
    document.getElementById('shopGold').textContent = state.gold;
    // Re-render shop to show updated inventory for selling
    openShop();
  } else if (item) {
    // Using dialogue for messages instead of alert
    showDialogueBlock({ speaker: "ショップ店員", text: "ゴールドが足りません!" });
  }
}

function sellItem(index) {
  if (index >= 0 && index < state.inventory.length) {
    const item = state.inventory.splice(index, 1)[0];
    state.gold += Math.floor(item.price / 2); // Sell for half price
    updatePartyStatus();
    document.getElementById('shopGold').textContent = state.gold;
    openShop(); // Re-render shop
  }
}

function closeShop() {
  state.shopOpen = false;
  shopDiv.style.display = 'none';
}

// --- Interaction System ---
// Add click listeners to interactable entities
document.addEventListener('DOMContentLoaded', () => {
  const scene = document.querySelector('a-scene');

  // Event listener for NPC interaction
  const townNpc = document.getElementById('town-npc-1');
  if (townNpc) {
    townNpc.addEventListener('click', () => {
      if (!state.inBattle && !state.shopOpen) {
        showDialogueBlock({ speaker: "村人", text: "こんにちは、冒険者さん!この街は平和ですよ。" });
      }
    });
  }

  // Event listener for Shop interaction
  const townShop = document.getElementById('town-shop');
  if (townShop) {
    townShop.addEventListener('click', () => {
      if (!state.inBattle && !state.shopOpen) {
        openShop();
      }
    });
  }

  // Event listeners for Enemy interaction (Slimes)
  const fieldSlime = document.getElementById('field-slime-1');
  if (fieldSlime) {
    fieldSlime.addEventListener('click', () => {
      if (!state.inBattle && !state.shopOpen) {
        showDialogueBlock({
          speaker: "Narration",
          text: "スライムが現れた!戦いますか?",
          choices: [
            { text: "戦う", onChoose: () => { startBattle('slime'); fieldSlime.setAttribute('visible', 'false'); }, next: "battle_result_slime" },
            { text: "逃げる", next: "flee_result" }
          ]
        });
      }
    });
  }

  const dungeonSlime = document.getElementById('dungeon-slime-1');
  if (dungeonSlime) {
    dungeonSlime.addEventListener('click', () => {
      if (!state.inBattle && !state.shopOpen) {
        showDialogueBlock({
          speaker: "Narration",
          text: "ダンジョンスライムが現れた!戦いますか?",
          choices: [
            { text: "戦う", onChoose: () => { startBattle('dungeon-slime'); dungeonSlime.setAttribute('visible', 'false'); }, next: "battle_result_dungeon_slime" },
            { text: "逃げる", next: "flee_result" }
          ]
        });
      }
    });
  }

  // Define battle results (simple for now)
  storyData["battle_result_slime"] = { speaker: "Narration", text: "スライムを倒した!経験値と10Gを獲得した。", onComplete: () => { state.gold += 10; updatePartyStatus(); }, next: currentStoryId };
  storyData["battle_result_dungeon_slime"] = { speaker: "Narration", text: "ダンジョンスライムを倒した!経験値と20Gを獲得した。", onComplete: () => { state.gold += 20; updatePartyStatus(); }, next: currentStoryId };
  storyData["flee_result"] = { speaker: "Narration", text: "なんとか逃げ切った。", next: currentStoryId };

  // Initial story start
  showDialogueBlock(currentStoryId);
});

// --- Main Game Loop for Progress (simplified) ---
function checkProgress() {
  updatePartyStatus();
  const playerRig = document.querySelector('#playerRig');
  if (!playerRig) return;
  let pos = playerRig.object3D.position;

  // Only progress linear story if no dialogue is open and not in battle/shop
  if (dialogue.style.display === 'none' && !state.inBattle && !state.shopOpen) {
    // Auto-progress main story
    if (storyData[currentStoryId] && !storyData[currentStoryId].choices) {
      // Simulate dialogue click if it's not a choice block
      if (storyData[currentStoryId].next) {
        // This part is tricky with auto-progression.
        // For now, the story relies on user clicks, or specific triggers.
        // The `onComplete` in storyData handles the actual progression.
      }
    }
  }

  // Example: Trigger story based on proximity (can be expanded)
  // This is just a basic example, the main story progression is click-driven.
  const townCenter = new THREE.Vector3(0, 1.6, 22);
  if (pos.distanceTo(townCenter) < 10 && state.currentStoryStep === "intro_04") {
    // This is handled by the initial showDialogueBlock, but a more complex game
    // might trigger different dialogue based on location.
  }
}

setInterval(checkProgress, 700); // Check progress every 0.7 seconds

// Initial setup calls
setWarpUI();
updatePartyStatus();
</script>
</body>
</html>

投稿者: chosuke

趣味はゲームやアニメや漫画などです

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です