Spaces:
Running
Running
Add 1 files
Browse files- index.html +378 -256
index.html
CHANGED
|
@@ -79,15 +79,6 @@
|
|
| 79 |
transition: width 0.3s ease;
|
| 80 |
}
|
| 81 |
|
| 82 |
-
.dialog-choice {
|
| 83 |
-
transition: all 0.2s ease;
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
.dialog-choice:hover {
|
| 87 |
-
transform: translateX(5px);
|
| 88 |
-
background-color: rgba(214, 158, 46, 0.2);
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
.typewriter {
|
| 92 |
overflow: hidden;
|
| 93 |
border-right: 2px solid var(--accent);
|
|
@@ -121,35 +112,6 @@
|
|
| 121 |
100% { transform: rotate(360deg); }
|
| 122 |
}
|
| 123 |
|
| 124 |
-
.tooltip {
|
| 125 |
-
position: relative;
|
| 126 |
-
display: inline-block;
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
.tooltip .tooltiptext {
|
| 130 |
-
visibility: hidden;
|
| 131 |
-
width: 120px;
|
| 132 |
-
background-color: #2d3748;
|
| 133 |
-
color: #fff;
|
| 134 |
-
text-align: center;
|
| 135 |
-
border-radius: 6px;
|
| 136 |
-
padding: 5px;
|
| 137 |
-
position: absolute;
|
| 138 |
-
z-index: 1;
|
| 139 |
-
bottom: 125%;
|
| 140 |
-
left: 50%;
|
| 141 |
-
margin-left: -60px;
|
| 142 |
-
opacity: 0;
|
| 143 |
-
transition: opacity 0.3s;
|
| 144 |
-
font-size: 12px;
|
| 145 |
-
border: 1px solid #d69e2e;
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
.tooltip:hover .tooltiptext {
|
| 149 |
-
visibility: visible;
|
| 150 |
-
opacity: 1;
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
.notification {
|
| 154 |
position: fixed;
|
| 155 |
top: 1rem;
|
|
@@ -206,6 +168,37 @@
|
|
| 206 |
opacity: 0.5;
|
| 207 |
cursor: not-allowed;
|
| 208 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
</style>
|
| 210 |
</head>
|
| 211 |
<body class="h-screen flex flex-col">
|
|
@@ -213,10 +206,6 @@
|
|
| 213 |
<header class="bg-gray-900 text-yellow-500 p-4 flex justify-between items-center border-b border-yellow-700">
|
| 214 |
<div class="flex items-center space-x-4">
|
| 215 |
<h1 class="text-2xl font-bold medieval">Isekai RPG Adventure</h1>
|
| 216 |
-
<div class="flex space-x-2">
|
| 217 |
-
<button id="visualNovelBtn" class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white rounded medieval transition btn-click">Visual Novel</button>
|
| 218 |
-
<button id="textRpgBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded medieval transition btn-click">Text RPG</button>
|
| 219 |
-
</div>
|
| 220 |
</div>
|
| 221 |
<div class="flex items-center space-x-4">
|
| 222 |
<button id="newGameBtn" class="text-yellow-400 hover:text-yellow-300 btn-click">
|
|
@@ -359,64 +348,36 @@
|
|
| 359 |
|
| 360 |
<!-- Center Panel - Game Content -->
|
| 361 |
<div class="flex-1 flex flex-col">
|
| 362 |
-
<!--
|
| 363 |
-
<div
|
| 364 |
-
<div class="flex-
|
| 365 |
-
<
|
| 366 |
-
<div
|
| 367 |
-
<div class="w-full h-full bg-cover bg-center" style="background-image: url('https://images.unsplash.com/photo-1518709766631-a6a7f45921c3?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');"></div>
|
| 368 |
-
<div class="absolute inset-0 bg-black bg-opacity-40"></div>
|
| 369 |
-
</div>
|
| 370 |
-
|
| 371 |
-
<!-- Text Display -->
|
| 372 |
-
<div id="vnTextContainer" class="absolute bottom-0 left-0 right-0 p-6">
|
| 373 |
-
<div class="bg-gray-900 bg-opacity-80 rounded-lg p-4 parchment">
|
| 374 |
-
<div id="vnCharacterName" class="text-yellow-400 font-bold mb-1 medieval">Old Man</div>
|
| 375 |
-
<div id="vnText" class="text-white mb-4">
|
| 376 |
-
Welcome, traveler, to the Rusty Tankard. What brings you to our humble establishment this fine evening?
|
| 377 |
-
</div>
|
| 378 |
-
<div id="vnChoices" class="space-y-2">
|
| 379 |
-
<button class="dialog-choice w-full text-left p-2 bg-gray-800 hover:bg-gray-700 rounded border border-yellow-700 text-yellow-100 btn-click">
|
| 380 |
-
"I'm looking for work. Any jobs for a capable adventurer?"
|
| 381 |
-
</button>
|
| 382 |
-
<button class="dialog-choice w-full text-left p-2 bg-gray-800 hover:bg-gray-700 rounded border border-yellow-700 text-yellow-100 btn-click">
|
| 383 |
-
"Just passing through. What's good to drink here?"
|
| 384 |
-
</button>
|
| 385 |
-
<button class="dialog-choice w-full text-left p-2 bg-gray-800 hover:bg-gray-700 rounded border border-yellow-700 text-yellow-100 btn-click">
|
| 386 |
-
"I heard rumors of trouble in these parts. Know anything about that?"
|
| 387 |
-
</button>
|
| 388 |
-
</div>
|
| 389 |
-
</div>
|
| 390 |
-
</div>
|
| 391 |
</div>
|
| 392 |
</div>
|
| 393 |
|
| 394 |
-
<!--
|
| 395 |
-
<div id="
|
| 396 |
-
<div class="
|
| 397 |
-
|
| 398 |
-
<div class="text-yellow-400 font-bold medieval">System:</div>
|
| 399 |
-
<div class="text-gray-300">Welcome to the world of Eldoria. Your adventure begins now.</div>
|
| 400 |
-
|
| 401 |
-
<div class="text-yellow-400 font-bold medieval">Narrator:</div>
|
| 402 |
-
<div class="text-gray-300">You find yourself standing in the middle of a bustling tavern. The air is thick with the smell of ale and roasted meat. Patrons laugh loudly at nearby tables while a bard strums a lute in the corner. The tavern keeper wipes down the bar with a rag, occasionally glancing in your direction.</div>
|
| 403 |
-
|
| 404 |
-
<div class="text-yellow-400 font-bold medieval">System:</div>
|
| 405 |
-
<div class="text-gray-300">What would you like to do? You can examine your surroundings, talk to NPCs, or check your inventory.</div>
|
| 406 |
-
</div>
|
| 407 |
</div>
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
<
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
</div>
|
| 421 |
</div>
|
| 422 |
</div>
|
|
@@ -577,42 +538,6 @@
|
|
| 577 |
</button>
|
| 578 |
</div>
|
| 579 |
</div>
|
| 580 |
-
|
| 581 |
-
<!-- Summarizer Configuration -->
|
| 582 |
-
<div class="bg-gray-700 p-4 rounded border border-gray-600">
|
| 583 |
-
<h5 class="font-bold text-yellow-300 mb-2">Summarizer Model</h5>
|
| 584 |
-
<div class="space-y-2">
|
| 585 |
-
<div>
|
| 586 |
-
<label class="block text-gray-300 mb-1">API Key</label>
|
| 587 |
-
<input type="text" id="summarizerApiKey" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="Enter API key">
|
| 588 |
-
</div>
|
| 589 |
-
<div>
|
| 590 |
-
<label class="block text-gray-300 mb-1">URL/IP</label>
|
| 591 |
-
<input type="text" id="summarizerUrl" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="http://localhost:5002/summarize">
|
| 592 |
-
</div>
|
| 593 |
-
<button id="testSummarizer" class="mt-2 bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded text-sm medieval btn-click">
|
| 594 |
-
Test Connection
|
| 595 |
-
</button>
|
| 596 |
-
</div>
|
| 597 |
-
</div>
|
| 598 |
-
|
| 599 |
-
<!-- Diffusion Model Configuration -->
|
| 600 |
-
<div class="bg-gray-700 p-4 rounded border border-gray-600">
|
| 601 |
-
<h5 class="font-bold text-yellow-300 mb-2">Diffusion Model</h5>
|
| 602 |
-
<div class="space-y-2">
|
| 603 |
-
<div>
|
| 604 |
-
<label class="block text-gray-300 mb-1">API Key</label>
|
| 605 |
-
<input type="text" id="diffusionApiKey" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="Enter API key">
|
| 606 |
-
</div>
|
| 607 |
-
<div>
|
| 608 |
-
<label class="block text-gray-300 mb-1">URL/IP</label>
|
| 609 |
-
<input type="text" id="diffusionUrl" class="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white" placeholder="http://localhost:5003/diffusion">
|
| 610 |
-
</div>
|
| 611 |
-
<button id="testDiffusion" class="mt-2 bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded text-sm medieval btn-click">
|
| 612 |
-
Test Connection
|
| 613 |
-
</button>
|
| 614 |
-
</div>
|
| 615 |
-
</div>
|
| 616 |
</div>
|
| 617 |
</div>
|
| 618 |
|
|
@@ -644,8 +569,7 @@
|
|
| 644 |
|
| 645 |
<!-- Save/Load Modal -->
|
| 646 |
<div id="saveLoadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
|
| 647 |
-
<div class="bg-gray-
|
| 648 |
-
00 rounded-lg w-full max-w-md border border-yellow-700">
|
| 649 |
<div class="p-4 border-b border-gray-700 flex justify-between items-center">
|
| 650 |
<h3 id="modalTitle" class="text-xl font-bold text-yellow-400 medieval">Save Game</h3>
|
| 651 |
<button id="closeSaveLoad" class="text-gray-400 hover:text-white btn-click">
|
|
@@ -678,7 +602,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 678 |
* This object holds all the game state data including character info, inventory, etc.
|
| 679 |
*/
|
| 680 |
const gameState = {
|
| 681 |
-
mode: 'visualNovel',
|
| 682 |
character: {
|
| 683 |
name: 'Sir Aldric',
|
| 684 |
class: 'Knight',
|
|
@@ -744,9 +667,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 744 |
}
|
| 745 |
],
|
| 746 |
worldMap: [],
|
| 747 |
-
currentDialogue: null,
|
| 748 |
gameLog: ["> Entered the Rusty Tankard"],
|
| 749 |
-
summaries: [],
|
| 750 |
settings: {
|
| 751 |
autoSave: true,
|
| 752 |
typewriterEffect: true,
|
|
@@ -757,9 +678,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 757 |
// AI Configuration
|
| 758 |
let aiConfig = {
|
| 759 |
storyTeller: { apiKey: "", url: "http://localhost:5000/story" },
|
| 760 |
-
mechanics: { apiKey: "", url: "http://localhost:5001/mechanics" }
|
| 761 |
-
summarizer: { apiKey: "", url: "http://localhost:5002/summarize" },
|
| 762 |
-
diffusion: { apiKey: "", url: "http://localhost:5003/diffusion" }
|
| 763 |
};
|
| 764 |
|
| 765 |
// Load saved AI config from localStorage if available
|
|
@@ -777,10 +696,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 777 |
* DOM Elements
|
| 778 |
* Cache all frequently accessed DOM elements for better performance
|
| 779 |
*/
|
| 780 |
-
const visualNovelBtn = document.getElementById('visualNovelBtn');
|
| 781 |
-
const textRpgBtn = document.getElementById('textRpgBtn');
|
| 782 |
-
const visualNovelMode = document.getElementById('visualNovelMode');
|
| 783 |
-
const textRpgMode = document.getElementById('textRpgMode');
|
| 784 |
const newGameBtn = document.getElementById('newGameBtn');
|
| 785 |
const settingsBtn = document.getElementById('settingsBtn');
|
| 786 |
const saveBtn = document.getElementById('saveBtn');
|
|
@@ -796,14 +711,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 796 |
const modalTitle = document.getElementById('modalTitle');
|
| 797 |
const confirmSaveLoad = document.getElementById('confirmSaveLoad');
|
| 798 |
const cancelSaveLoad = document.getElementById('cancelSaveLoad');
|
| 799 |
-
const
|
| 800 |
-
const
|
| 801 |
const submitText = document.getElementById('submitText');
|
| 802 |
const submitSpinner = document.getElementById('submitSpinner');
|
| 803 |
-
const
|
| 804 |
-
const vnChoices = document.getElementById('vnChoices');
|
| 805 |
-
const vnText = document.getElementById('vnText');
|
| 806 |
-
const vnCharacterName = document.getElementById('vnCharacterName');
|
| 807 |
const sceneImage = document.getElementById('sceneImage');
|
| 808 |
const gameLog = document.getElementById('gameLog');
|
| 809 |
const questLog = document.getElementById('questLog');
|
|
@@ -832,14 +744,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 832 |
const storyTellerUrl = document.getElementById('storyTellerUrl');
|
| 833 |
const mechanicsApiKey = document.getElementById('mechanicsApiKey');
|
| 834 |
const mechanicsUrl = document.getElementById('mechanicsUrl');
|
| 835 |
-
const summarizerApiKey = document.getElementById('summarizerApiKey');
|
| 836 |
-
const summarizerUrl = document.getElementById('summarizerUrl');
|
| 837 |
-
const diffusionApiKey = document.getElementById('diffusionApiKey');
|
| 838 |
-
const diffusionUrl = document.getElementById('diffusionUrl');
|
| 839 |
const testStoryTeller = document.getElementById('testStoryTeller');
|
| 840 |
const testMechanics = document.getElementById('testMechanics');
|
| 841 |
-
const testSummarizer = document.getElementById('testSummarizer');
|
| 842 |
-
const testDiffusion = document.getElementById('testDiffusion');
|
| 843 |
const saveSettings = document.getElementById('saveSettings');
|
| 844 |
const cancelSettings = document.getElementById('cancelSettings');
|
| 845 |
const autoSave = document.getElementById('autoSave');
|
|
@@ -851,12 +757,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 851 |
|
| 852 |
// Verify all required elements exist
|
| 853 |
const requiredElements = [
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
];
|
| 861 |
|
| 862 |
requiredElements.forEach(element => {
|
|
@@ -870,10 +776,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 870 |
* Set up all event handlers for user interactions
|
| 871 |
*/
|
| 872 |
function setupEventListeners() {
|
| 873 |
-
// Mode switching buttons
|
| 874 |
-
visualNovelBtn?.addEventListener('click', switchToVisualNovelMode);
|
| 875 |
-
textRpgBtn?.addEventListener('click', switchToTextRpgMode);
|
| 876 |
-
|
| 877 |
// Main menu buttons
|
| 878 |
newGameBtn?.addEventListener('click', () => toggleModal(newGameModal));
|
| 879 |
settingsBtn?.addEventListener('click', openSettingsModal);
|
|
@@ -898,26 +800,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 898 |
// Test connection buttons
|
| 899 |
testStoryTeller?.addEventListener('click', () => testAIConnection('storyTeller'));
|
| 900 |
testMechanics?.addEventListener('click', () => testAIConnection('mechanics'));
|
| 901 |
-
testSummarizer?.addEventListener('click', () => testAIConnection('summarizer'));
|
| 902 |
-
testDiffusion?.addEventListener('click', () => testAIConnection('diffusion'));
|
| 903 |
|
| 904 |
// Save/Load Modal
|
| 905 |
closeSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
|
| 906 |
cancelSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
|
| 907 |
confirmSaveLoad?.addEventListener('click', handleSaveLoadConfirm);
|
| 908 |
|
| 909 |
-
//
|
| 910 |
-
|
| 911 |
-
|
| 912 |
if (e.key === 'Enter') {
|
| 913 |
-
|
| 914 |
-
}
|
| 915 |
-
});
|
| 916 |
-
|
| 917 |
-
// Visual Novel Choices - Using event delegation for dynamic elements
|
| 918 |
-
vnChoices?.addEventListener('click', function(e) {
|
| 919 |
-
if (e.target && e.target.classList.contains('dialog-choice')) {
|
| 920 |
-
handleVNChoice(e.target.textContent.trim());
|
| 921 |
}
|
| 922 |
});
|
| 923 |
}
|
|
@@ -952,9 +845,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 952 |
* Initialize game state and UI
|
| 953 |
*/
|
| 954 |
function initGame() {
|
| 955 |
-
// Set initial mode
|
| 956 |
-
switchToVisualNovelMode();
|
| 957 |
-
|
| 958 |
// Initialize world map
|
| 959 |
initWorldMap();
|
| 960 |
|
|
@@ -969,36 +859,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 969 |
updateSaveSlots();
|
| 970 |
}
|
| 971 |
|
| 972 |
-
/**
|
| 973 |
-
* Switch to Visual Novel mode
|
| 974 |
-
*/
|
| 975 |
-
function switchToVisualNovelMode() {
|
| 976 |
-
console.log('Switching to Visual Novel mode');
|
| 977 |
-
gameState.mode = 'visualNovel';
|
| 978 |
-
visualNovelMode?.classList.remove('hidden');
|
| 979 |
-
textRpgMode?.classList.add('hidden');
|
| 980 |
-
visualNovelBtn?.classList.add('bg-yellow-600', 'hover:bg-yellow-700');
|
| 981 |
-
visualNovelBtn?.classList.remove('bg-gray-700', 'hover:bg-gray-600');
|
| 982 |
-
textRpgBtn?.classList.add('bg-gray-700', 'hover:bg-gray-600');
|
| 983 |
-
textRpgBtn?.classList.remove('bg-yellow-600', 'hover:bg-yellow-700');
|
| 984 |
-
addToGameLog("Switched to Visual Novel mode");
|
| 985 |
-
}
|
| 986 |
-
|
| 987 |
-
/**
|
| 988 |
-
* Switch to Text RPG mode
|
| 989 |
-
*/
|
| 990 |
-
function switchToTextRpgMode() {
|
| 991 |
-
console.log('Switching to Text RPG mode');
|
| 992 |
-
gameState.mode = 'textRpg';
|
| 993 |
-
visualNovelMode?.classList.add('hidden');
|
| 994 |
-
textRpgMode?.classList.remove('hidden');
|
| 995 |
-
textRpgBtn?.classList.add('bg-yellow-600', 'hover:bg-yellow-700');
|
| 996 |
-
textRpgBtn?.classList.remove('bg-gray-700', 'hover:bg-gray-600');
|
| 997 |
-
visualNovelBtn?.classList.add('bg-gray-700', 'hover:bg-gray-600');
|
| 998 |
-
visualNovelBtn?.classList.remove('bg-yellow-600', 'hover:bg-yellow-700');
|
| 999 |
-
addToGameLog("Switched to Text RPG mode");
|
| 1000 |
-
}
|
| 1001 |
-
|
| 1002 |
/**
|
| 1003 |
* Toggle modal visibility
|
| 1004 |
* @param {HTMLElement} modal - The modal element to toggle
|
|
@@ -1015,15 +875,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1015 |
* Open settings modal and load current settings
|
| 1016 |
*/
|
| 1017 |
function openSettingsModal() {
|
|
|
|
|
|
|
| 1018 |
// Load current config into settings modal
|
| 1019 |
if (storyTellerApiKey) storyTellerApiKey.value = aiConfig.storyTeller.apiKey;
|
| 1020 |
if (storyTellerUrl) storyTellerUrl.value = aiConfig.storyTeller.url;
|
| 1021 |
if (mechanicsApiKey) mechanicsApiKey.value = aiConfig.mechanics.apiKey;
|
| 1022 |
if (mechanicsUrl) mechanicsUrl.value = aiConfig.mechanics.url;
|
| 1023 |
-
if (summarizerApiKey) summarizerApiKey.value = aiConfig.summarizer.apiKey;
|
| 1024 |
-
if (summarizerUrl) summarizerUrl.value = aiConfig.summarizer.url;
|
| 1025 |
-
if (diffusionApiKey) diffusionApiKey.value = aiConfig.diffusion.apiKey;
|
| 1026 |
-
if (diffusionUrl) diffusionUrl.value = aiConfig.diffusion.url;
|
| 1027 |
|
| 1028 |
// Load game settings
|
| 1029 |
if (autoSave) autoSave.checked = gameState.settings.autoSave;
|
|
@@ -1037,6 +895,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1037 |
* Open save modal
|
| 1038 |
*/
|
| 1039 |
function openSaveModal() {
|
|
|
|
| 1040 |
if (!modalTitle || !saveLoadModal) return;
|
| 1041 |
modalTitle.textContent = 'Save Game';
|
| 1042 |
updateSaveSlots();
|
|
@@ -1047,6 +906,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1047 |
* Open load modal
|
| 1048 |
*/
|
| 1049 |
function openLoadModal() {
|
|
|
|
| 1050 |
if (!modalTitle || !saveLoadModal) return;
|
| 1051 |
modalTitle.textContent = 'Load Game';
|
| 1052 |
updateSaveSlots();
|
|
@@ -1538,58 +1398,320 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1538 |
}
|
| 1539 |
|
| 1540 |
/**
|
| 1541 |
-
* Handle
|
| 1542 |
*/
|
| 1543 |
-
async function
|
| 1544 |
-
if (!
|
| 1545 |
|
| 1546 |
-
const input =
|
| 1547 |
if (!input) return;
|
| 1548 |
|
| 1549 |
try {
|
| 1550 |
// Show loading state
|
| 1551 |
submitText.textContent = "Processing...";
|
| 1552 |
submitSpinner.classList.remove('hidden');
|
| 1553 |
-
|
| 1554 |
|
| 1555 |
-
// Add player
|
| 1556 |
-
|
| 1557 |
addToGameLog(`> ${input}`);
|
| 1558 |
|
| 1559 |
-
//
|
| 1560 |
-
|
| 1561 |
-
|
| 1562 |
-
|
| 1563 |
-
|
| 1564 |
-
|
| 1565 |
-
|
| 1566 |
-
|
| 1567 |
-
|
| 1568 |
-
|
| 1569 |
-
|
| 1570 |
-
|
| 1571 |
-
|
| 1572 |
-
|
| 1573 |
-
|
| 1574 |
-
|
| 1575 |
-
|
| 1576 |
-
|
| 1577 |
-
|
| 1578 |
-
|
| 1579 |
-
if (response.mechanics) {
|
| 1580 |
-
if (response.mechanics.quest) {
|
| 1581 |
-
const questExists = gameState.quests.some(q => q.title === response.mechanics.quest);
|
| 1582 |
-
if (!questExists) {
|
| 1583 |
-
gameState.quests.push({
|
| 1584 |
-
title: response.mechanics.quest,
|
| 1585 |
-
description: response.mechanics.description,
|
| 1586 |
-
status: 'active'
|
| 1587 |
-
});
|
| 1588 |
-
updateQuestLog();
|
| 1589 |
-
}
|
| 1590 |
}
|
| 1591 |
}
|
| 1592 |
|
| 1593 |
-
|
| 1594 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1595 |
</html>
|
|
|
|
| 79 |
transition: width 0.3s ease;
|
| 80 |
}
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
.typewriter {
|
| 83 |
overflow: hidden;
|
| 84 |
border-right: 2px solid var(--accent);
|
|
|
|
| 112 |
100% { transform: rotate(360deg); }
|
| 113 |
}
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
.notification {
|
| 116 |
position: fixed;
|
| 117 |
top: 1rem;
|
|
|
|
| 168 |
opacity: 0.5;
|
| 169 |
cursor: not-allowed;
|
| 170 |
}
|
| 171 |
+
|
| 172 |
+
/* Chat message styling */
|
| 173 |
+
.chat-message {
|
| 174 |
+
max-width: 80%;
|
| 175 |
+
margin-bottom: 1rem;
|
| 176 |
+
padding: 0.75rem 1rem;
|
| 177 |
+
border-radius: 0.5rem;
|
| 178 |
+
word-wrap: break-word;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.player-message {
|
| 182 |
+
background-color: #2d3748;
|
| 183 |
+
border-left: 3px solid #d69e2e;
|
| 184 |
+
margin-left: auto;
|
| 185 |
+
margin-right: 0;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.npc-message {
|
| 189 |
+
background-color: #2d3748;
|
| 190 |
+
border-left: 3px solid #4299e1;
|
| 191 |
+
margin-right: auto;
|
| 192 |
+
margin-left: 0;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.system-message {
|
| 196 |
+
background-color: #2d3748;
|
| 197 |
+
border-left: 3px solid #48bb78;
|
| 198 |
+
margin-right: auto;
|
| 199 |
+
margin-left: 0;
|
| 200 |
+
font-style: italic;
|
| 201 |
+
}
|
| 202 |
</style>
|
| 203 |
</head>
|
| 204 |
<body class="h-screen flex flex-col">
|
|
|
|
| 206 |
<header class="bg-gray-900 text-yellow-500 p-4 flex justify-between items-center border-b border-yellow-700">
|
| 207 |
<div class="flex items-center space-x-4">
|
| 208 |
<h1 class="text-2xl font-bold medieval">Isekai RPG Adventure</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
</div>
|
| 210 |
<div class="flex items-center space-x-4">
|
| 211 |
<button id="newGameBtn" class="text-yellow-400 hover:text-yellow-300 btn-click">
|
|
|
|
| 348 |
|
| 349 |
<!-- Center Panel - Game Content -->
|
| 350 |
<div class="flex-1 flex flex-col">
|
| 351 |
+
<!-- Scene Image -->
|
| 352 |
+
<div class="h-1/3 relative">
|
| 353 |
+
<div id="sceneImage" class="absolute inset-0 bg-gray-900 flex items-center justify-center">
|
| 354 |
+
<div class="w-full h-full bg-cover bg-center" style="background-image: url('https://images.unsplash.com/photo-1518709766631-a6a7f45921c3?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');"></div>
|
| 355 |
+
<div class="absolute inset-0 bg-black bg-opacity-40"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
</div>
|
| 357 |
</div>
|
| 358 |
|
| 359 |
+
<!-- Chat Log -->
|
| 360 |
+
<div id="chatLog" class="flex-1 p-4 overflow-y-auto scrollbar-custom">
|
| 361 |
+
<div class="chat-message system-message">
|
| 362 |
+
Welcome to the world of Eldoria. Your adventure begins now.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
</div>
|
| 364 |
+
<div class="chat-message npc-message">
|
| 365 |
+
<div class="font-bold text-blue-400 medieval">Old Man:</div>
|
| 366 |
+
Welcome, traveler, to the Rusty Tankard. What brings you to our humble establishment this fine evening?
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
|
| 370 |
+
<!-- Chat Input -->
|
| 371 |
+
<div class="p-4 border-t border-gray-700">
|
| 372 |
+
<div class="flex">
|
| 373 |
+
<input type="text" id="chatInput" placeholder="What will you do or say?" class="flex-1 bg-gray-700 border border-yellow-700 rounded-l px-4 py-2 text-white focus:outline-none focus:ring-1 focus:ring-yellow-500">
|
| 374 |
+
<button id="chatSubmit" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded-r medieval flex items-center justify-center min-w-[100px] btn-click">
|
| 375 |
+
<span id="submitText">Send</span>
|
| 376 |
+
<div id="submitSpinner" class="loading-spinner ml-2 hidden"></div>
|
| 377 |
+
</button>
|
| 378 |
+
</div>
|
| 379 |
+
<div class="mt-2 text-xs text-gray-400">
|
| 380 |
+
Examples: "I ask the old man about work", "I draw my sword", "I look around the tavern"
|
| 381 |
</div>
|
| 382 |
</div>
|
| 383 |
</div>
|
|
|
|
| 538 |
</button>
|
| 539 |
</div>
|
| 540 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
</div>
|
| 542 |
</div>
|
| 543 |
|
|
|
|
| 569 |
|
| 570 |
<!-- Save/Load Modal -->
|
| 571 |
<div id="saveLoadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
|
| 572 |
+
<div class="bg-gray-800 rounded-lg w-full max-w-md border border-yellow-700">
|
|
|
|
| 573 |
<div class="p-4 border-b border-gray-700 flex justify-between items-center">
|
| 574 |
<h3 id="modalTitle" class="text-xl font-bold text-yellow-400 medieval">Save Game</h3>
|
| 575 |
<button id="closeSaveLoad" class="text-gray-400 hover:text-white btn-click">
|
|
|
|
| 602 |
* This object holds all the game state data including character info, inventory, etc.
|
| 603 |
*/
|
| 604 |
const gameState = {
|
|
|
|
| 605 |
character: {
|
| 606 |
name: 'Sir Aldric',
|
| 607 |
class: 'Knight',
|
|
|
|
| 667 |
}
|
| 668 |
],
|
| 669 |
worldMap: [],
|
|
|
|
| 670 |
gameLog: ["> Entered the Rusty Tankard"],
|
|
|
|
| 671 |
settings: {
|
| 672 |
autoSave: true,
|
| 673 |
typewriterEffect: true,
|
|
|
|
| 678 |
// AI Configuration
|
| 679 |
let aiConfig = {
|
| 680 |
storyTeller: { apiKey: "", url: "http://localhost:5000/story" },
|
| 681 |
+
mechanics: { apiKey: "", url: "http://localhost:5001/mechanics" }
|
|
|
|
|
|
|
| 682 |
};
|
| 683 |
|
| 684 |
// Load saved AI config from localStorage if available
|
|
|
|
| 696 |
* DOM Elements
|
| 697 |
* Cache all frequently accessed DOM elements for better performance
|
| 698 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
const newGameBtn = document.getElementById('newGameBtn');
|
| 700 |
const settingsBtn = document.getElementById('settingsBtn');
|
| 701 |
const saveBtn = document.getElementById('saveBtn');
|
|
|
|
| 711 |
const modalTitle = document.getElementById('modalTitle');
|
| 712 |
const confirmSaveLoad = document.getElementById('confirmSaveLoad');
|
| 713 |
const cancelSaveLoad = document.getElementById('cancelSaveLoad');
|
| 714 |
+
const chatInput = document.getElementById('chatInput');
|
| 715 |
+
const chatSubmit = document.getElementById('chatSubmit');
|
| 716 |
const submitText = document.getElementById('submitText');
|
| 717 |
const submitSpinner = document.getElementById('submitSpinner');
|
| 718 |
+
const chatLog = document.getElementById('chatLog');
|
|
|
|
|
|
|
|
|
|
| 719 |
const sceneImage = document.getElementById('sceneImage');
|
| 720 |
const gameLog = document.getElementById('gameLog');
|
| 721 |
const questLog = document.getElementById('questLog');
|
|
|
|
| 744 |
const storyTellerUrl = document.getElementById('storyTellerUrl');
|
| 745 |
const mechanicsApiKey = document.getElementById('mechanicsApiKey');
|
| 746 |
const mechanicsUrl = document.getElementById('mechanicsUrl');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
const testStoryTeller = document.getElementById('testStoryTeller');
|
| 748 |
const testMechanics = document.getElementById('testMechanics');
|
|
|
|
|
|
|
| 749 |
const saveSettings = document.getElementById('saveSettings');
|
| 750 |
const cancelSettings = document.getElementById('cancelSettings');
|
| 751 |
const autoSave = document.getElementById('autoSave');
|
|
|
|
| 757 |
|
| 758 |
// Verify all required elements exist
|
| 759 |
const requiredElements = [
|
| 760 |
+
newGameBtn, settingsBtn, saveBtn, loadBtn, settingsModal, closeSettings,
|
| 761 |
+
newGameModal, closeNewGame, startNewGame, cancelNewGame, saveLoadModal,
|
| 762 |
+
closeSaveLoad, modalTitle, confirmSaveLoad, cancelSaveLoad, chatInput,
|
| 763 |
+
chatSubmit, submitText, submitSpinner, chatLog, sceneImage, gameLog,
|
| 764 |
+
questLog, inventoryItems, equippedItems, skillsList, miniMapGrid,
|
| 765 |
+
statPointsDisplay, skillNotification
|
| 766 |
];
|
| 767 |
|
| 768 |
requiredElements.forEach(element => {
|
|
|
|
| 776 |
* Set up all event handlers for user interactions
|
| 777 |
*/
|
| 778 |
function setupEventListeners() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 779 |
// Main menu buttons
|
| 780 |
newGameBtn?.addEventListener('click', () => toggleModal(newGameModal));
|
| 781 |
settingsBtn?.addEventListener('click', openSettingsModal);
|
|
|
|
| 800 |
// Test connection buttons
|
| 801 |
testStoryTeller?.addEventListener('click', () => testAIConnection('storyTeller'));
|
| 802 |
testMechanics?.addEventListener('click', () => testAIConnection('mechanics'));
|
|
|
|
|
|
|
| 803 |
|
| 804 |
// Save/Load Modal
|
| 805 |
closeSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
|
| 806 |
cancelSaveLoad?.addEventListener('click', () => toggleModal(saveLoadModal));
|
| 807 |
confirmSaveLoad?.addEventListener('click', handleSaveLoadConfirm);
|
| 808 |
|
| 809 |
+
// Chat Input
|
| 810 |
+
chatSubmit?.addEventListener('click', handleChatInput);
|
| 811 |
+
chatInput?.addEventListener('keypress', (e) => {
|
| 812 |
if (e.key === 'Enter') {
|
| 813 |
+
handleChatInput();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
}
|
| 815 |
});
|
| 816 |
}
|
|
|
|
| 845 |
* Initialize game state and UI
|
| 846 |
*/
|
| 847 |
function initGame() {
|
|
|
|
|
|
|
|
|
|
| 848 |
// Initialize world map
|
| 849 |
initWorldMap();
|
| 850 |
|
|
|
|
| 859 |
updateSaveSlots();
|
| 860 |
}
|
| 861 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
/**
|
| 863 |
* Toggle modal visibility
|
| 864 |
* @param {HTMLElement} modal - The modal element to toggle
|
|
|
|
| 875 |
* Open settings modal and load current settings
|
| 876 |
*/
|
| 877 |
function openSettingsModal() {
|
| 878 |
+
console.log('Opening settings modal');
|
| 879 |
+
|
| 880 |
// Load current config into settings modal
|
| 881 |
if (storyTellerApiKey) storyTellerApiKey.value = aiConfig.storyTeller.apiKey;
|
| 882 |
if (storyTellerUrl) storyTellerUrl.value = aiConfig.storyTeller.url;
|
| 883 |
if (mechanicsApiKey) mechanicsApiKey.value = aiConfig.mechanics.apiKey;
|
| 884 |
if (mechanicsUrl) mechanicsUrl.value = aiConfig.mechanics.url;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 885 |
|
| 886 |
// Load game settings
|
| 887 |
if (autoSave) autoSave.checked = gameState.settings.autoSave;
|
|
|
|
| 895 |
* Open save modal
|
| 896 |
*/
|
| 897 |
function openSaveModal() {
|
| 898 |
+
console.log('Opening save modal');
|
| 899 |
if (!modalTitle || !saveLoadModal) return;
|
| 900 |
modalTitle.textContent = 'Save Game';
|
| 901 |
updateSaveSlots();
|
|
|
|
| 906 |
* Open load modal
|
| 907 |
*/
|
| 908 |
function openLoadModal() {
|
| 909 |
+
console.log('Opening load modal');
|
| 910 |
if (!modalTitle || !saveLoadModal) return;
|
| 911 |
modalTitle.textContent = 'Load Game';
|
| 912 |
updateSaveSlots();
|
|
|
|
| 1398 |
}
|
| 1399 |
|
| 1400 |
/**
|
| 1401 |
+
* Handle chat input
|
| 1402 |
*/
|
| 1403 |
+
async function handleChatInput() {
|
| 1404 |
+
if (!chatInput || !chatSubmit || !submitText || !submitSpinner || !chatLog) return;
|
| 1405 |
|
| 1406 |
+
const input = chatInput.value.trim();
|
| 1407 |
if (!input) return;
|
| 1408 |
|
| 1409 |
try {
|
| 1410 |
// Show loading state
|
| 1411 |
submitText.textContent = "Processing...";
|
| 1412 |
submitSpinner.classList.remove('hidden');
|
| 1413 |
+
chatSubmit.disabled = true;
|
| 1414 |
|
| 1415 |
+
// Add player message to chat
|
| 1416 |
+
addToChatLog(input, 'player');
|
| 1417 |
addToGameLog(`> ${input}`);
|
| 1418 |
|
| 1419 |
+
// Clear input
|
| 1420 |
+
chatInput.value = '';
|
| 1421 |
+
|
| 1422 |
+
// Process input with Story Teller AI
|
| 1423 |
+
const response = await getStoryTellerResponse(input);
|
| 1424 |
+
|
| 1425 |
+
// Add response to chat
|
| 1426 |
+
addToChatLog(response.text, 'npc', response.character);
|
| 1427 |
+
|
| 1428 |
+
// Handle mechanics if present
|
| 1429 |
+
if (response.mechanics) {
|
| 1430 |
+
if (response.mechanics.quest) {
|
| 1431 |
+
const questExists = gameState.quests.some(q => q.title === response.mechanics.quest);
|
| 1432 |
+
if (!questExists) {
|
| 1433 |
+
gameState.quests.push({
|
| 1434 |
+
title: response.mechanics.quest,
|
| 1435 |
+
description: response.mechanics.description,
|
| 1436 |
+
status: 'active'
|
| 1437 |
+
});
|
| 1438 |
+
updateQuestLog();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1439 |
}
|
| 1440 |
}
|
| 1441 |
|
| 1442 |
+
if (response.mechanics.location) {
|
| 1443 |
+
gameState.location.name = response.mechanics.location.name;
|
| 1444 |
+
gameState.location.description = response.mechanics.location.description;
|
| 1445 |
+
updateCharacterDisplay();
|
| 1446 |
+
}
|
| 1447 |
+
|
| 1448 |
+
if (response.mechanics.image) {
|
| 1449 |
+
updateSceneImage(response.mechanics.image);
|
| 1450 |
+
}
|
| 1451 |
+
}
|
| 1452 |
+
|
| 1453 |
+
// Auto-save if enabled
|
| 1454 |
+
if (gameState.settings.autoSave) {
|
| 1455 |
+
saveGame();
|
| 1456 |
+
}
|
| 1457 |
+
} catch (error) {
|
| 1458 |
+
console.error('Error processing input:', error);
|
| 1459 |
+
addToChatLog("An error occurred while processing your input.", 'system');
|
| 1460 |
+
} finally {
|
| 1461 |
+
// Reset button state
|
| 1462 |
+
submitText.textContent = "Send";
|
| 1463 |
+
submitSpinner.classList.add('hidden');
|
| 1464 |
+
chatSubmit.disabled = false;
|
| 1465 |
+
}
|
| 1466 |
+
}
|
| 1467 |
+
|
| 1468 |
+
/**
|
| 1469 |
+
* Add message to chat log
|
| 1470 |
+
* @param {string} message - The message text
|
| 1471 |
+
* @param {string} type - The message type (player, npc, system)
|
| 1472 |
+
* @param {string} [character] - The character name for NPC messages
|
| 1473 |
+
*/
|
| 1474 |
+
function addToChatLog(message, type, character = '') {
|
| 1475 |
+
if (!chatLog) return;
|
| 1476 |
+
|
| 1477 |
+
const messageElement = document.createElement('div');
|
| 1478 |
+
messageElement.className = `chat-message ${type}-message`;
|
| 1479 |
+
|
| 1480 |
+
if (type === 'player') {
|
| 1481 |
+
messageElement.innerHTML = `
|
| 1482 |
+
<div class="font-bold text-yellow-400 medieval">You:</div>
|
| 1483 |
+
<div class="text-gray-300">${message}</div>
|
| 1484 |
+
`;
|
| 1485 |
+
} else if (type === 'npc') {
|
| 1486 |
+
messageElement.innerHTML = `
|
| 1487 |
+
<div class="font-bold text-blue-400 medieval">${character}:</div>
|
| 1488 |
+
<div class="text-gray-300">${message}</div>
|
| 1489 |
+
`;
|
| 1490 |
+
} else {
|
| 1491 |
+
messageElement.innerHTML = `
|
| 1492 |
+
<div class="text-gray-300">${message}</div>
|
| 1493 |
+
`;
|
| 1494 |
+
}
|
| 1495 |
+
|
| 1496 |
+
chatLog.appendChild(messageElement);
|
| 1497 |
+
|
| 1498 |
+
// Scroll to bottom
|
| 1499 |
+
chatLog.scrollTop = chatLog.scrollHeight;
|
| 1500 |
+
}
|
| 1501 |
+
|
| 1502 |
+
/**
|
| 1503 |
+
* Update scene image
|
| 1504 |
+
* @param {string} imageUrl - The URL of the new scene image
|
| 1505 |
+
*/
|
| 1506 |
+
function updateSceneImage(imageUrl) {
|
| 1507 |
+
if (!sceneImage) return;
|
| 1508 |
+
|
| 1509 |
+
const imageDiv = sceneImage.querySelector('div');
|
| 1510 |
+
if (imageDiv) {
|
| 1511 |
+
imageDiv.style.backgroundImage = `url('${imageUrl}')`;
|
| 1512 |
+
}
|
| 1513 |
+
}
|
| 1514 |
+
|
| 1515 |
+
/**
|
| 1516 |
+
* Show notification
|
| 1517 |
+
* @param {string} message - The notification message
|
| 1518 |
+
*/
|
| 1519 |
+
function showNotification(message) {
|
| 1520 |
+
if (!skillNotification) return;
|
| 1521 |
+
|
| 1522 |
+
skillNotification.textContent = message;
|
| 1523 |
+
skillNotification.classList.remove('hidden');
|
| 1524 |
+
|
| 1525 |
+
setTimeout(() => {
|
| 1526 |
+
skillNotification.classList.add('hidden');
|
| 1527 |
+
}, 3000);
|
| 1528 |
+
}
|
| 1529 |
+
|
| 1530 |
+
/**
|
| 1531 |
+
* Get response from Story Teller AI
|
| 1532 |
+
* @param {string} input - The player's input
|
| 1533 |
+
* @returns {Promise<Object>} - The AI response
|
| 1534 |
+
*/
|
| 1535 |
+
async function getStoryTellerResponse(input) {
|
| 1536 |
+
// In a real implementation, this would call your AI API
|
| 1537 |
+
// For demo purposes, we'll simulate a response
|
| 1538 |
+
|
| 1539 |
+
const responses = [
|
| 1540 |
+
{
|
| 1541 |
+
text: "The old man strokes his beard thoughtfully. 'Work, you say? Well, there's been trouble with bandits on the north road. The local merchants would pay handsomely to have that problem dealt with.'",
|
| 1542 |
+
character: "Old Man",
|
| 1543 |
+
mechanics: {
|
| 1544 |
+
quest: "The Bandit Threat",
|
| 1545 |
+
description: "The tavern keeper mentioned bandits attacking travelers on the north road. Investigate and deal with the threat."
|
| 1546 |
+
}
|
| 1547 |
+
},
|
| 1548 |
+
{
|
| 1549 |
+
text: "'Ah, our house specialty is Dragon's Breath Ale!' the old man says with a chuckle. 'Strong enough to put hair on a dwarf's chest, that one.'",
|
| 1550 |
+
character: "Old Man"
|
| 1551 |
+
},
|
| 1552 |
+
{
|
| 1553 |
+
text: "The old man's expression darkens. 'Aye, there's been strange happenings in these parts. Folk disappearing in the night, livestock found drained of blood. Some say it's vampires, others blame the cult that's taken up in the old ruins.'",
|
| 1554 |
+
character: "Old Man",
|
| 1555 |
+
mechanics: {
|
| 1556 |
+
image: "https://images.unsplash.com/photo-1518709766631-a6a7f45921c3?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80"
|
| 1557 |
+
}
|
| 1558 |
+
}
|
| 1559 |
+
];
|
| 1560 |
+
|
| 1561 |
+
// Return a random response for demo purposes
|
| 1562 |
+
return responses[Math.floor(Math.random() * responses.length)];
|
| 1563 |
+
}
|
| 1564 |
+
|
| 1565 |
+
/**
|
| 1566 |
+
* Calculate game mechanics
|
| 1567 |
+
* @param {string} action - The action being performed
|
| 1568 |
+
* @param {Object} item - The item being used (if applicable)
|
| 1569 |
+
* @returns {Promise<string>} - The result description
|
| 1570 |
+
*/
|
| 1571 |
+
async function calculateMechanics(action, item) {
|
| 1572 |
+
// In a real implementation, this would call your mechanics API
|
| 1573 |
+
// For demo purposes, we'll simulate results
|
| 1574 |
+
|
| 1575 |
+
if (action === 'useItem') {
|
| 1576 |
+
if (item.name === 'Health Potion') {
|
| 1577 |
+
const healAmount = item.effect.heal;
|
| 1578 |
+
gameState.character.health = Math.min(
|
| 1579 |
+
gameState.character.health + healAmount,
|
| 1580 |
+
gameState.character.maxHealth
|
| 1581 |
+
);
|
| 1582 |
+
return `Restored ${healAmount} health.`;
|
| 1583 |
+
} else if (item.name === 'Scroll of Fireball') {
|
| 1584 |
+
return "You unleash a fiery explosion! (30 damage)";
|
| 1585 |
+
}
|
| 1586 |
+
}
|
| 1587 |
+
|
| 1588 |
+
return "Action completed.";
|
| 1589 |
+
}
|
| 1590 |
+
|
| 1591 |
+
/**
|
| 1592 |
+
* Test AI connection
|
| 1593 |
+
* @param {string} type - The AI type to test (storyTeller, mechanics)
|
| 1594 |
+
*/
|
| 1595 |
+
async function testAIConnection(type) {
|
| 1596 |
+
const apiKey = document.getElementById(`${type}ApiKey`)?.value;
|
| 1597 |
+
const url = document.getElementById(`${type}Url`)?.value;
|
| 1598 |
+
|
| 1599 |
+
if (!apiKey || !url) {
|
| 1600 |
+
alert('Please enter both API Key and URL');
|
| 1601 |
+
return;
|
| 1602 |
+
}
|
| 1603 |
+
|
| 1604 |
+
try {
|
| 1605 |
+
// In a real implementation, this would test the API connection
|
| 1606 |
+
// For demo purposes, we'll simulate a successful test
|
| 1607 |
+
showNotification(`${type} connection successful!`);
|
| 1608 |
+
} catch (error) {
|
| 1609 |
+
console.error(`Error testing ${type} connection:`, error);
|
| 1610 |
+
alert(`Failed to connect to ${type} API`);
|
| 1611 |
+
}
|
| 1612 |
+
}
|
| 1613 |
+
|
| 1614 |
+
/**
|
| 1615 |
+
* Save settings handler
|
| 1616 |
+
*/
|
| 1617 |
+
function saveSettingsHandler() {
|
| 1618 |
+
// Save AI config
|
| 1619 |
+
aiConfig.storyTeller.apiKey = storyTellerApiKey?.value || '';
|
| 1620 |
+
aiConfig.storyTeller.url = storyTellerUrl?.value || '';
|
| 1621 |
+
aiConfig.mechanics.apiKey = mechanicsApiKey?.value || '';
|
| 1622 |
+
aiConfig.mechanics.url = mechanicsUrl?.value || '';
|
| 1623 |
+
|
| 1624 |
+
// Save game settings
|
| 1625 |
+
gameState.settings.autoSave = autoSave?.checked || false;
|
| 1626 |
+
gameState.settings.typewriterEffect = typewriterEffect?.checked || false;
|
| 1627 |
+
gameState.settings.showImages = showImages?.checked || false;
|
| 1628 |
+
|
| 1629 |
+
// Save to localStorage
|
| 1630 |
+
try {
|
| 1631 |
+
localStorage.setItem('aiConfig', JSON.stringify(aiConfig));
|
| 1632 |
+
showNotification('Settings saved successfully!');
|
| 1633 |
+
toggleModal(settingsModal);
|
| 1634 |
+
} catch (error) {
|
| 1635 |
+
console.error('Error saving settings:', error);
|
| 1636 |
+
alert('Failed to save settings!');
|
| 1637 |
+
}
|
| 1638 |
+
}
|
| 1639 |
+
|
| 1640 |
+
/**
|
| 1641 |
+
* Start new game handler
|
| 1642 |
+
*/
|
| 1643 |
+
function startNewGameHandler() {
|
| 1644 |
+
if (!newCharName || !newCharClass) return;
|
| 1645 |
+
|
| 1646 |
+
const name = newCharName.value.trim();
|
| 1647 |
+
const charClass = newCharClass.value.trim();
|
| 1648 |
+
|
| 1649 |
+
if (!name || !charClass) {
|
| 1650 |
+
alert('Please enter both character name and class');
|
| 1651 |
+
return;
|
| 1652 |
+
}
|
| 1653 |
+
|
| 1654 |
+
// Create new character
|
| 1655 |
+
gameState.character = {
|
| 1656 |
+
name,
|
| 1657 |
+
class: charClass,
|
| 1658 |
+
level: 1,
|
| 1659 |
+
xp: 0,
|
| 1660 |
+
nextLevel: 100,
|
| 1661 |
+
health: 100,
|
| 1662 |
+
maxHealth: 100,
|
| 1663 |
+
mana: 50,
|
| 1664 |
+
maxMana: 50,
|
| 1665 |
+
str: parseInt(newCharStr?.value) || 10,
|
| 1666 |
+
dex: parseInt(newCharDex?.value) || 10,
|
| 1667 |
+
con: parseInt(newCharCon?.value) || 10,
|
| 1668 |
+
int: parseInt(newCharInt?.value) || 10,
|
| 1669 |
+
wis: parseInt(newCharWis?.value) || 10,
|
| 1670 |
+
cha: parseInt(newCharCha?.value) || 10,
|
| 1671 |
+
statPoints: 0,
|
| 1672 |
+
baseStats: {
|
| 1673 |
+
str: 10,
|
| 1674 |
+
dex: 10,
|
| 1675 |
+
con: 10,
|
| 1676 |
+
int: 10,
|
| 1677 |
+
wis: 10,
|
| 1678 |
+
cha: 10
|
| 1679 |
+
}
|
| 1680 |
+
};
|
| 1681 |
+
|
| 1682 |
+
// Reset other game state
|
| 1683 |
+
gameState.inventory = [
|
| 1684 |
+
{ name: "Rusty Sword", type: "weapon", equipped: true, effect: { str: 1 }, icon: "fa-sword" },
|
| 1685 |
+
{ name: "Health Potion", type: "consumable", quantity: 2, effect: { heal: 20 }, icon: "fa-flask" },
|
| 1686 |
+
{ name: "10 Gold", type: "currency", quantity: 10, icon: "fa-coins" }
|
| 1687 |
+
];
|
| 1688 |
+
|
| 1689 |
+
gameState.equipped = {
|
| 1690 |
+
weapon: "Rusty Sword",
|
| 1691 |
+
armor: null,
|
| 1692 |
+
accessory: null
|
| 1693 |
+
};
|
| 1694 |
+
|
| 1695 |
+
gameState.skills = [
|
| 1696 |
+
{ name: "Sword Mastery", level: 1, xp: 0, nextLevel: 100, description: "Basic proficiency with swords", effect: { str: 1 } }
|
| 1697 |
+
];
|
| 1698 |
+
|
| 1699 |
+
gameState.quests = [];
|
| 1700 |
+
gameState.gameLog = ["> Game started"];
|
| 1701 |
+
|
| 1702 |
+
// Initialize world map
|
| 1703 |
+
initWorldMap();
|
| 1704 |
+
|
| 1705 |
+
// Update all displays
|
| 1706 |
+
updateCharacterDisplay();
|
| 1707 |
+
updateInventory();
|
| 1708 |
+
updateEquippedItems();
|
| 1709 |
+
updateSkillsList();
|
| 1710 |
+
updateGameLog();
|
| 1711 |
+
updateQuestLog();
|
| 1712 |
+
updateMiniMap();
|
| 1713 |
+
|
| 1714 |
+
// Clear chat and add welcome message
|
| 1715 |
+
if (chatLog) chatLog.innerHTML = '';
|
| 1716 |
+
addToChatLog("
|
| 1717 |
</html>
|