<!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>