managarten/games/whopixels/js/scenes/RPGScene.js
Wuesteon d36b321d9d style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
2025-11-27 18:33:16 +01:00

1210 lines
34 KiB
JavaScript

class RPGScene extends Phaser.Scene {
constructor() {
super({ key: 'RPGScene' });
}
preload() {
// Wir verwenden die in BootScene erstellten Texturen
}
create() {
console.log('RPGScene create wird aufgerufen');
// Initialisiere NPC-Status
this.initNPCState();
// Spielwelt erstellen
this.createWorld();
// Spieler erstellen
this.createPlayer();
// NPCs erstellen
this.createNPCs();
// Kamera einrichten
this.cameras.main.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);
this.cameras.main.startFollow(this.player, true);
// Steuerung einrichten
this.cursors = this.input.keyboard.createCursorKeys();
// UI erstellen
this.createUI();
console.log('RPGScene create abgeschlossen');
}
createWorld() {
// Einfache Welt mit Kacheln erstellen
this.map = {
widthInPixels: 440, // Angepasste Spielfeldgröße
heightInPixels: 440,
tileWidth: 40,
tileHeight: 40,
};
// Hintergrund
this.add
.tileSprite(0, 0, this.map.widthInPixels, this.map.heightInPixels, 'background')
.setOrigin(0, 0)
.setScale(1.0); // Doppelte Skalierung für größeres Bild
// Erstelle eine Gruppe für Hindernisse
this.obstacles = this.physics.add.staticGroup();
// Definiere die Tile-Typen
const tileTypes = [
{ key: 'tile_grass', isObstacle: false },
{ key: 'tile_grass_flower', isObstacle: false },
{ key: 'tile_dirt', isObstacle: false },
{ key: 'tile_dirt_stone', isObstacle: false },
{ key: 'tile_stone_wall', isObstacle: true },
{ key: 'tile_stone_wall_flower', isObstacle: true },
];
// Größe des Spielfelds (angepasst für das 440x440 Spielfeld)
const gridSize = 11; // 11x11 Raster für 440x440 Pixel (40 Pixel pro Kachel)
// Erstelle das Spielfeld
for (let y = 0; y < gridSize; y++) {
for (let x = 0; x < gridSize; x++) {
let tileType;
// Erstelle Muster für die Welt
if (x === 0 || y === 0 || x === gridSize - 1 || y === gridSize - 1) {
// Tür in der oberen Mauer für NPCs
if (y === 0 && x === Math.floor(gridSize / 2)) {
// Tür (kein Hindernis)
tileType = tileTypes[2]; // Erde als Tür
} else {
// Steinwände am Rand
tileType = Math.random() < 0.3 ? tileTypes[5] : tileTypes[4]; // Steinwand oder Steinwand mit Blumen
}
} else {
// Innerer Bereich - Mischung aus Gras und Erde
const rand = Math.random();
if (rand < 0.4) {
tileType = tileTypes[0]; // Gras
} else if (rand < 0.7) {
tileType = tileTypes[1]; // Gras mit Blumen
} else if (rand < 0.9) {
tileType = tileTypes[2]; // Erde
} else {
tileType = tileTypes[3]; // Erde mit Steinen
}
}
// Erstelle das Tile mit doppelter Größe
const tile = this.add.image(x * 40 + 20, y * 40 + 20, tileType.key);
tile.setScale(2.0); // Doppelte Skalierung
// Wenn es ein Hindernis ist, füge es zur Hindernisgruppe hinzu
if (tileType.isObstacle) {
// Erstelle ein unsichtbares Rechteck für die Kollision
const obstacle = this.add.rectangle(
x * 40 + 20,
y * 40 + 20,
40 * 2,
40 * 2 // Doppelte Größe für Kollision
);
// Füge das Hindernis zur Gruppe hinzu
this.obstacles.add(obstacle);
}
}
}
}
createPlayer() {
// Spieler in der Mitte der Karte platzieren
this.player = this.physics.add.sprite(
this.map.widthInPixels / 2, // Mitte der Karte
this.map.heightInPixels / 2,
'player_down' // Verwende die neue Textur
);
// Spieler-Größe anpassen - doppelte Skalierung
this.player.setScale(2.4); // Doppelte Skalierung (1.2 * 2)
// Kollisionen mit Hindernissen
this.physics.add.collider(this.player, this.obstacles);
// Spieler-Grenzen setzen
this.player.setCollideWorldBounds(true);
}
createUI() {
// Zurück-Button
const backButton = this.add
.text(10, 10, 'Zurück zum Menü', {
fontSize: '18px',
fill: '#fff',
backgroundColor: '#4a4a4a',
padding: { x: 10, y: 5 },
})
.setScrollFactor(0)
.setInteractive();
backButton.on('pointerover', () => {
backButton.setStyle({ fill: '#ff0' });
});
backButton.on('pointerout', () => {
backButton.setStyle({ fill: '#fff' });
});
backButton.on('pointerdown', () => {
this.scene.start('MainMenuScene');
});
// Spielanleitung
this.add
.text(10, 50, 'Pfeiltasten zum Bewegen', {
fontSize: '16px',
fill: '#fff',
backgroundColor: 'rgba(0,0,0,0.5)',
padding: { x: 5, y: 2 },
})
.setScrollFactor(0);
}
createNPCs() {
console.log('createNPCs wird aufgerufen');
// Importiere die NPC-Charaktere aus der npc_characters.js-Datei
try {
// Versuche, die NPC-Charaktere aus der externen Datei zu laden
this.npcCharacters = window.npcCharacters || [];
if (!this.npcCharacters || this.npcCharacters.length === 0) {
console.error(
'Keine NPC-Charaktere gefunden! Stelle sicher, dass npc_characters.js geladen wurde.'
);
// Fallback zu einigen Standard-Charakteren
this.npcCharacters = [
{
id: 1,
name: 'Leonardo da Vinci',
personality: 'Ein vielseitiger Universalgelehrter der Renaissance.',
hint: 'Meine Skizzenbücher enthalten Flugmaschinen und anatomische Studien.',
},
{
id: 2,
name: 'Nikola Tesla',
personality: 'Ein exzentrischer Elektroingenieur mit visionären Ideen.',
hint: 'Meine Arbeiten mit Wechselstrom revolutionierten die Energienutzung.',
},
];
}
} catch (error) {
console.error('Fehler beim Laden der NPC-Charaktere:', error);
// Fallback zu einigen Standard-Charakteren
this.npcCharacters = [
{
id: 1,
name: 'Leonardo da Vinci',
personality: 'Ein vielseitiger Universalgelehrter der Renaissance.',
hint: 'Meine Skizzenbücher enthalten Flugmaschinen und anatomische Studien.',
},
{
id: 2,
name: 'Nikola Tesla',
personality: 'Ein exzentrischer Elektroingenieur mit visionären Ideen.',
hint: 'Meine Arbeiten mit Wechselstrom revolutionierten die Energienutzung.',
},
];
}
console.log('NPC-Charaktere geladen:', this.npcCharacters.length);
// Array für alle NPCs im Spiel
this.npcs = [];
// Aktiver NPC (der, mit dem der Spieler gerade interagiert)
this.npc = null;
// Speichere den Status der NPCs
this.npcState = {
isInConversation: false,
isWaitingForResponse: false,
identityRevealed: false,
discoveredNPCs: [],
currentNpcIndex: -1,
};
// Dialog-Box für den NPC
this.npcDialog = this.add.text(0, 0, 'Hallo! Ich bin ein NPC.\nDrücke E zum Sprechen.', {
fontSize: '12px',
fill: '#fff',
backgroundColor: '#000',
padding: { x: 5, y: 5 },
wordWrap: { width: 200 },
});
this.npcDialog.setVisible(false);
// Interaktions-Prompt
this.interactionPrompt = this.add.text(0, 0, 'Drücke E zum Sprechen', {
fontSize: '10px',
fill: '#fff',
backgroundColor: '#000',
padding: { x: 3, y: 3 },
});
this.interactionPrompt.setVisible(false);
// Erstelle den ersten NPC
this.createNewNPC();
}
// Methode zum Erstellen eines neuen NPCs
createNewNPC() {
console.log('createNewNPC wird aufgerufen');
// Initialisiere npcState, wenn es noch nicht existiert
if (!this.npcState) {
this.initNPCState();
}
// Debug-Ausgabe der bereits entdeckten NPCs
console.log('Bereits entdeckte NPCs:', this.npcState.discoveredNPCs);
// Wähle einen zufälligen NPC-Charakter, der noch nicht entdeckt wurde
// UND der nicht der aktuelle NPC ist (falls vorhanden)
let availableCharacters = this.npcCharacters.filter(
(char) => !this.npcState.discoveredNPCs.includes(char.id)
);
// Wenn es einen aktuellen NPC gibt, stelle sicher, dass wir nicht denselben Charakter erneut auswählen
if (this.npc && this.npc.characterId) {
availableCharacters = availableCharacters.filter((char) => char.id !== this.npc.characterId);
console.log(
`Aktueller NPC ${this.npc.characterName} (ID: ${this.npc.characterId}) wird aus der Auswahl ausgeschlossen.`
);
}
console.log('Verfügbare Charaktere:', availableCharacters.length);
availableCharacters.forEach((char) => {
console.log(`- ${char.name} (ID: ${char.id})`);
});
// Wenn keine Charaktere mehr verfügbar sind, verwende alle Charaktere außer dem aktuellen
if (availableCharacters.length === 0) {
console.log('Keine neuen Charaktere verfügbar, verwende alle außer dem aktuellen.');
availableCharacters = this.npcCharacters.filter((char) => {
if (this.npc && this.npc.characterId) {
return char.id !== this.npc.characterId;
}
return true;
});
if (availableCharacters.length === 0) {
console.log('Keine Charaktere verfügbar!');
return null;
}
}
const randomIndex = Math.floor(Math.random() * availableCharacters.length);
const selectedCharacter = availableCharacters[randomIndex];
console.log(
'Ausgewählter Charakter:',
selectedCharacter.name,
'(ID:',
selectedCharacter.id,
')'
);
console.log('Persönlichkeit:', selectedCharacter.personality);
// Feste Position in der Mitte des Bildschirms
// Positioniere den NPC an der Tür in der oberen Mauer
const doorX = Math.floor(this.map.widthInPixels / 2);
const doorY = 40; // Knapp unterhalb der oberen Mauer (angepasst für 440x440 Spielfeld)
// Verwende die Türposition
const x = doorX;
const y = doorY;
console.log(`NPC wird an der Tür (${x}, ${y}) erstellt`);
// Erstelle den NPC
const newNpc = this.physics.add.sprite(x, y, 'npc_down');
newNpc.setScale(2.4); // Doppelte Skalierung (1.2 * 2)
// NPC ist im Anonymitätsmodus (komplett schwarz)
newNpc.setTint(0x000000);
// Speichere die Charakter-ID im NPC-Objekt
newNpc.characterId = selectedCharacter.id;
newNpc.characterName = selectedCharacter.name;
newNpc.characterPersonality = selectedCharacter.personality;
// Animation: NPC läuft durch die Tür herein
// Starte an der Türposition
newNpc.y = doorY;
// Animiere den NPC, damit er durch die Tür hereinläuft
this.tweens.add({
targets: newNpc,
y: this.map.heightInPixels / 2, // Zielposition in der Mitte des Spielfelds
duration: 2000,
ease: 'Linear', // Gleichmäßige Bewegung für einen Laufeffekt
onUpdate: () => {
// Aktualisiere die Position des Debug-Textes während der Animation
if (newNpc.debugText) {
newNpc.debugText.x = newNpc.x;
newNpc.debugText.y = newNpc.y + 20;
}
// Wechsle zwischen verschiedenen Frames, um eine Laufanimation zu simulieren
if (Math.floor(Date.now() / 150) % 2 === 0) {
newNpc.setTexture('npc_down');
} else {
// Wenn es eine alternative Textur gibt, verwende diese
// Ansonsten bleibt es bei 'npc_down'
if (this.textures.exists('npc_down_walk')) {
newNpc.setTexture('npc_down_walk');
}
}
},
});
// Füge einen Debug-Text unter dem NPC hinzu
// Bei anonymen NPCs nur "Anonym" anzeigen
const debugText = this.add.text(x, y + 20, 'Anonym', {
fontSize: '10px',
fontFamily: 'Arial',
fill: '#ffffff',
stroke: '#000000',
strokeThickness: 2,
align: 'center',
});
debugText.setOrigin(0.5, 0);
newNpc.debugText = debugText; // Speichere die Referenz im NPC-Objekt
// Kollisionen mit Hindernissen
if (this.obstacles) {
this.physics.add.collider(newNpc, this.obstacles);
}
// Kollision mit dem Spieler
if (this.player) {
this.physics.add.collider(newNpc, this.player, this.showInteractionPrompt, null, this);
}
// Füge den NPC zum Array hinzu
this.npcs.push(newNpc);
// Setze den aktuellen NPC
this.npc = newNpc;
this.npcState.currentNpcIndex = this.npcs.length - 1;
console.log(`Neuer NPC erstellt: ${selectedCharacter.name} (ID: ${selectedCharacter.id})`);
return newNpc;
}
// Methode zum Erstellen eines Test-NPCs an einer festen Position
createTestNPC() {
console.log('createTestNPC wird aufgerufen');
// Feste Position in der Nähe des Spielers
const x = 400;
const y = 400;
// Erstelle den NPC
const testNpc = this.physics.add.sprite(x, y, 'npc_down');
testNpc.setScale(1.2);
// NPC ist im Anonymitätsmodus (komplett schwarz)
testNpc.setTint(0x000000);
// Speichere die Charakter-ID im NPC-Objekt
testNpc.characterId = 1;
testNpc.characterName = 'Test NPC';
testNpc.characterPersonality = 'Ein Test-NPC zum Testen der Anzeige';
// Kollisionen mit Hindernissen
if (this.obstacles) {
this.physics.add.collider(testNpc, this.obstacles);
}
// Kollision mit dem Spieler
if (this.player) {
this.physics.add.collider(testNpc, this.player, this.showInteractionPrompt, null, this);
}
// Füge den NPC zum Array hinzu
if (!this.npcs) {
this.npcs = [];
}
this.npcs.push(testNpc);
// Setze den aktuellen NPC
this.npc = testNpc;
if (this.npcState) {
this.npcState.currentNpcIndex = this.npcs.length - 1;
}
console.log(`Test-NPC erstellt an Position ${x}, ${y}`);
return testNpc;
}
// Methode zum Initialisieren der UI-Elemente
createUI() {
// Interaktions-Prompt
this.interactionPrompt.setVisible(false);
// Chat-Eingabe erstellen
this.createChatInput();
// Interaktions-Taste (E) für Dialog
this.interactKey = this.input.keyboard.addKey('E');
// NPC-Bewegung
this.time.addEvent({
delay: 3000, // Alle 3 Sekunden
callback: this.moveNPC,
callbackScope: this,
loop: true,
});
}
// Methode zum Initialisieren des NPC-Status
initNPCState() {
console.log('initNPCState wird aufgerufen');
// NPC-Status initialisieren
this.npcState = {
isInConversation: false,
lastMessage: '',
isWaitingForResponse: false,
identityRevealed: false,
discoveredNPCs: [], // Liste der bereits entdeckten NPCs (IDs)
currentNpcIndex: -1,
};
// Debug-Ausgabe der NPC-Charaktere
if (this.npcCharacters) {
console.log('Verfügbare NPC-Charaktere:');
this.npcCharacters.forEach((char) => {
console.log(`- ${char.name} (ID: ${char.id})`);
});
}
console.log('NPC-Status initialisiert');
}
moveNPC() {
// Zufällige Bewegung des NPCs
if (!this.npc) return;
// Stoppe vorherige Bewegung
this.npc.setVelocity(0);
// 30% Chance, dass der NPC sich bewegt
if (Math.random() < 0.3) {
const speed = 50;
const direction = Math.floor(Math.random() * 4); // 0: up, 1: right, 2: down, 3: left
switch (direction) {
case 0: // up
this.npc.setVelocityY(-speed);
this.npc.setTexture('npc_up');
break;
case 1: // right
this.npc.setVelocityX(speed);
this.npc.setTexture('npc_down'); // Wir haben nur up/down Texturen
break;
case 2: // down
this.npc.setVelocityY(speed);
this.npc.setTexture('npc_down');
break;
case 3: // left
this.npc.setVelocityX(-speed);
this.npc.setTexture('npc_up'); // Wir haben nur up/down Texturen
break;
}
// Stoppe die Bewegung nach 1-2 Sekunden
this.time.delayedCall(
1000 + Math.random() * 1000,
() => {
if (this.npc) this.npc.setVelocity(0);
},
[],
this
);
}
}
createChatInput() {
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const padding = 20;
const chatWidth = width - padding * 2;
const chatHeight = 250; // Erhöhte Höhe für mehr Platz
// Chat-Hintergrund mit abgerundeten Ecken und Schatten
this.chatBackground = this.add.graphics();
this.chatBackground.fillStyle(0x1a1a2a, 0.9); // Dunkleres Blau mit höherer Opazität
this.chatBackground.fillRoundedRect(
padding,
height - chatHeight - padding,
chatWidth,
chatHeight,
10 // Abgerundete Ecken
);
// Füge einen Rahmen hinzu
this.chatBackground.lineStyle(2, 0x4a6fa5, 1); // Blauer Rahmen
this.chatBackground.strokeRoundedRect(
padding,
height - chatHeight - padding,
chatWidth,
chatHeight,
10
);
this.chatBackground.setScrollFactor(0);
this.chatBackground.setVisible(false);
// Titel für den Chat
this.chatTitle = this.add.text(
width / 2,
height - chatHeight - padding + 20,
'Gespräch mit NPC',
{
fontSize: '18px',
fontFamily: 'Arial',
fontStyle: 'bold',
fill: '#ffffff',
align: 'center',
}
);
this.chatTitle.setOrigin(0.5, 0.5);
this.chatTitle.setScrollFactor(0);
this.chatTitle.setVisible(false);
// NPC-Antwortbereich mit besserem Styling
this.npcResponse = this.add.text(padding + 15, height - chatHeight - padding + 50, '', {
fontSize: '16px',
fontFamily: 'Arial',
fill: '#e0e0ff', // Helleres Blau für bessere Lesbarkeit
padding: { x: 10, y: 10 },
wordWrap: { width: chatWidth - 50 },
lineSpacing: 6,
});
this.npcResponse.setScrollFactor(0);
this.npcResponse.setVisible(false);
// Trennlinie zwischen Antwort und Eingabe
this.chatDivider = this.add.graphics();
this.chatDivider.lineStyle(1, 0x4a6fa5, 0.8); // Blauer Trennstrich
this.chatDivider.lineBetween(padding + 15, height - 90, width - padding - 15, height - 90);
this.chatDivider.setScrollFactor(0);
this.chatDivider.setVisible(false);
// Chat-Eingabefeld mit besserem Styling
const inputBg = this.add.graphics();
inputBg.fillStyle(0x2a2a3a, 1); // Dunklerer Hintergrund für Eingabefeld
inputBg.fillRoundedRect(
padding + 15,
height - 70,
chatWidth - 230, // Platz für Buttons lassen
40,
5
);
inputBg.setScrollFactor(0);
inputBg.setVisible(false);
this.inputBackground = inputBg;
this.chatInput = this.add.text(padding + 25, height - 65, 'Tippe deine Nachricht hier ein...', {
fontSize: '16px',
fontFamily: 'Arial',
fill: '#bbbbbb', // Hellgrau für Platzhaltertext
padding: { x: 5, y: 5 },
});
this.chatInput.setScrollFactor(0);
this.chatInput.setVisible(false);
// Chat-Senden-Button mit besserem Styling
const sendBg = this.add.graphics();
sendBg.fillStyle(0x4a6fa5, 1); // Blauer Button
sendBg.fillRoundedRect(width - padding - 100, height - 70, 85, 40, 5);
sendBg.setScrollFactor(0);
sendBg.setVisible(false);
this.sendBackground = sendBg;
this.chatSendButton = this.add.text(width - padding - 57.5, height - 50, 'Senden', {
fontSize: '16px',
fontFamily: 'Arial',
fontStyle: 'bold',
fill: '#ffffff',
});
this.chatSendButton.setOrigin(0.5, 0.5);
this.chatSendButton.setScrollFactor(0);
this.chatSendButton.setVisible(false);
this.chatSendButton.setInteractive({ useHandCursor: true });
this.chatSendButton.on('pointerdown', () => this.sendChatMessage());
// Hover-Effekt für den Senden-Button
this.chatSendButton.on('pointerover', () => {
sendBg.clear();
sendBg.fillStyle(0x5a7fb5, 1); // Hellerer Blau bei Hover
sendBg.fillRoundedRect(width - padding - 100, height - 70, 85, 40, 5);
});
this.chatSendButton.on('pointerout', () => {
sendBg.clear();
sendBg.fillStyle(0x4a6fa5, 1); // Normales Blau
sendBg.fillRoundedRect(width - padding - 100, height - 70, 85, 40, 5);
});
// X-Icon zum Schließen in der oberen rechten Ecke
const closeIconSize = 24;
const closeIconPadding = 10;
// Runder Hintergrund für das X-Icon
const closeIconBg = this.add.graphics();
closeIconBg.fillStyle(0x8a4a4a, 0.7); // Halbtransparentes Rot
closeIconBg.fillCircle(
width - padding - closeIconPadding,
height - chatHeight - padding + closeIconPadding + closeIconSize / 2,
closeIconSize / 2
);
closeIconBg.setScrollFactor(0);
closeIconBg.setVisible(false);
this.cancelBackground = closeIconBg;
// X-Icon erstellen mit Linien
const closeIcon = this.add.graphics();
// Zeichne das X
closeIcon.lineStyle(3, 0xffffff, 1);
// Erste Linie des X (von oben links nach unten rechts)
closeIcon.lineBetween(
width - padding - closeIconPadding - closeIconSize / 3,
height - chatHeight - padding + closeIconPadding + closeIconSize / 3,
width - padding - closeIconPadding + closeIconSize / 3,
height - chatHeight - padding + closeIconPadding + (closeIconSize * 2) / 3
);
// Zweite Linie des X (von oben rechts nach unten links)
closeIcon.lineBetween(
width - padding - closeIconPadding + closeIconSize / 3,
height - chatHeight - padding + closeIconPadding + closeIconSize / 3,
width - padding - closeIconPadding - closeIconSize / 3,
height - chatHeight - padding + closeIconPadding + (closeIconSize * 2) / 3
);
closeIcon.setScrollFactor(0);
closeIcon.setVisible(false);
this.chatCancelButton = closeIcon;
// Interaktiver Bereich für das X-Icon
const closeHitArea = this.add.rectangle(
width - padding - closeIconPadding,
height - chatHeight - padding + closeIconPadding + closeIconSize / 2,
closeIconSize * 1.5,
closeIconSize * 1.5
);
closeHitArea.setScrollFactor(0);
closeHitArea.setVisible(false); // Unsichtbarer Klickbereich
closeHitArea.setInteractive({ useHandCursor: true });
closeHitArea.on('pointerdown', () => this.closeChatInput());
this.closeHitArea = closeHitArea;
// Hover-Effekt für das X-Icon
closeHitArea.on('pointerover', () => {
closeIconBg.clear();
closeIconBg.fillStyle(0x9a5a5a, 0.9); // Helleres, weniger transparentes Rot bei Hover
closeIconBg.fillCircle(
width - padding - closeIconPadding,
height - chatHeight - padding + closeIconPadding + closeIconSize / 2,
(closeIconSize / 2) * 1.1 // Leicht größer bei Hover
);
});
closeHitArea.on('pointerout', () => {
closeIconBg.clear();
closeIconBg.fillStyle(0x8a4a4a, 0.7); // Normales Rot
closeIconBg.fillCircle(
width - padding - closeIconPadding,
height - chatHeight - padding + closeIconPadding + closeIconSize / 2,
closeIconSize / 2
);
});
// Aktiviere Tastatureingabe
this.userInput = '';
this.input.keyboard.on('keydown', this.handleKeyInput, this);
// Letzte NPC-Antwort
this.lastNpcResponse = '';
// Konversationsverlauf für das LLM
this.conversationHistory = [];
}
showInteractionPrompt() {
// Wenn der Spieler mit dem NPC kollidiert
if (!this.npc || !this.player) return;
// Zeige Interaktions-Prompt über dem NPC
this.interactionPrompt.setPosition(
this.npc.x - this.interactionPrompt.width / 2,
this.npc.y - 40
);
this.interactionPrompt.setVisible(true);
// Verstecke den Prompt nach 2 Sekunden
this.time.delayedCall(
2000,
() => {
this.interactionPrompt.setVisible(false);
},
[],
this
);
}
talkToNPC() {
// Wenn der Spieler mit dem NPC spricht
if (!this.npc || !this.player || this.npcState.isInConversation) return;
// Stoppe die Bewegung des Spielers und des NPCs
this.player.setVelocity(0);
this.npc.setVelocity(0);
// Drehe den NPC zum Spieler
if (this.player.x < this.npc.x) {
this.npc.setTexture('npc_up'); // Links (wir haben nur up/down)
} else {
this.npc.setTexture('npc_down'); // Rechts
}
// Starte die Konversation
this.npcState.isInConversation = true;
// Starte direkt den Chat ohne Sprechblase über dem NPC
this.lastNpcResponse = 'Verhüllt von Zeit,\nwer könnt es sein?';
// Zeige Chat-Eingabe direkt
this.openChatInput();
}
openChatInput() {
// Aktiviere Chat-Eingabe
this.chatBackground.setVisible(true);
this.chatTitle.setVisible(true);
this.inputBackground.setVisible(true);
this.chatInput.setVisible(true);
this.sendBackground.setVisible(true);
this.chatSendButton.setVisible(true);
this.cancelBackground.setVisible(true);
this.chatCancelButton.setVisible(true);
this.closeHitArea.setVisible(true); // Klickbereich für X-Icon
this.npcResponse.setVisible(true);
this.chatDivider.setVisible(true);
// Setze Eingabe zurück
this.userInput = '';
this.chatInput.setText('Tippe deine Nachricht hier ein...');
this.chatInput.setStyle({ fill: '#bbbbbb' }); // Platzhaltertext in Grau
// Zeige letzte NPC-Antwort an
this.npcResponse.setText(this.lastNpcResponse || 'Sprich mit dem NPC...');
// Aktualisiere den Titel mit dem NPC-Namen
this.chatTitle.setText('Gespräch mit Unbekanntem');
}
closeChatInput() {
// Deaktiviere Chat-Eingabe
this.chatBackground.setVisible(false);
this.chatTitle.setVisible(false);
this.inputBackground.setVisible(false);
this.chatInput.setVisible(false);
this.sendBackground.setVisible(false);
this.chatSendButton.setVisible(false);
this.cancelBackground.setVisible(false);
this.chatCancelButton.setVisible(false);
this.closeHitArea.setVisible(false); // Klickbereich für X-Icon
this.npcResponse.setVisible(false);
this.chatDivider.setVisible(false);
// Beende die Konversation
this.npcState.isInConversation = false;
this.npcDialog.setVisible(false);
}
handleKeyInput(event) {
// Wenn keine Chat-Eingabe aktiv ist, ignoriere Tastatureingabe
if (!this.chatInput.visible) return;
// Enter-Taste zum Senden
if (event.keyCode === 13) {
// Enter
this.sendChatMessage();
return;
}
// Escape-Taste zum Abbrechen
if (event.keyCode === 27) {
// Escape
this.closeChatInput();
return;
}
// Backspace zum Löschen
if (event.keyCode === 8 && this.userInput.length > 0) {
// Backspace
this.userInput = this.userInput.slice(0, -1);
}
// Normale Tasteneingabe
else if (event.keyCode >= 32 && event.keyCode <= 126) {
// Druckbare Zeichen
this.userInput += event.key;
}
// Aktualisiere Anzeige
if (this.userInput.length === 0) {
this.chatInput.setText('Tippe deine Nachricht hier ein...');
this.chatInput.setStyle({ fill: '#bbbbbb' }); // Platzhaltertext in Grau
} else {
this.chatInput.setText(this.userInput);
this.chatInput.setStyle({ fill: '#ffffff' }); // Aktiver Text in Weiß
// Visuelles Feedback beim Tippen
this.tweens.add({
targets: this.inputBackground,
alpha: 0.7,
duration: 50,
yoyo: true,
ease: 'Power1',
});
}
}
updateNpcResponse(response) {
// Aktualisiere die letzte NPC-Antwort
this.lastNpcResponse = response;
// Aktualisiere die Anzeige, wenn sichtbar
if (this.npcResponse.visible) {
this.npcResponse.setText(response);
}
}
// Methode, die aufgerufen wird, wenn der Spieler die Identität des NPCs erraten hat
revealIdentity() {
// Markiere, dass die Identität aufgedeckt wurde
this.npcState.identityRevealed = true;
// Füge den aktuellen NPC zur Liste der entdeckten NPCs hinzu
if (this.npc && this.npc.characterId) {
// Prüfe, ob der NPC bereits in der Liste ist, um Duplikate zu vermeiden
if (!this.npcState.discoveredNPCs.includes(this.npc.characterId)) {
this.npcState.discoveredNPCs.push(this.npc.characterId);
console.log(`NPC ${this.npc.characterName} (ID: ${this.npc.characterId}) wurde entdeckt!`);
console.log('Aktualisierte Liste der entdeckten NPCs:', this.npcState.discoveredNPCs);
} else {
console.log(
`NPC ${this.npc.characterName} (ID: ${this.npc.characterId}) wurde bereits zuvor entdeckt.`
);
}
}
// Spiele einen Soundeffekt ab (falls vorhanden)
// this.sound.play('reveal');
// Entferne die schwarze Einfärbung, um den NPC normal anzuzeigen
this.npc.clearTint();
// Aktualisiere den Debug-Text mit dem richtigen Namen ohne ID
if (this.npc.debugText) {
this.npc.debugText.setText(this.npc.characterName);
this.npc.debugText.setStyle({
fontSize: '12px',
fontFamily: 'Arial',
fontStyle: 'bold',
fill: '#ffff00', // Gelb für aufgedeckte NPCs
stroke: '#000000',
strokeThickness: 3,
align: 'center',
});
}
// Kurzer gelber Blitz-Effekt
this.npc.setTint(0xffff00);
this.time.delayedCall(300, () => {
if (this.npcState.identityRevealed) {
this.npc.clearTint(); // Zur normalen Farbe zurückkehren
}
});
// Erstelle einen Partikeleffekt um den NPC
const particles = this.add.particles('particle'); // Du musst ein Partikel-Sprite laden
// Erstelle einen Emitter für den Partikeleffekt
if (particles.createEmitter) {
const emitter = particles.createEmitter({
x: this.npc.x,
y: this.npc.y,
speed: { min: 50, max: 100 },
angle: { min: 0, max: 360 },
scale: { start: 0.5, end: 0 },
blendMode: 'ADD',
lifespan: 1000,
gravityY: 0,
});
// Stoppe den Emitter nach 2 Sekunden
this.time.delayedCall(2000, () => {
emitter.stop();
});
} else {
console.warn('Particles not available or not properly loaded');
}
// Aktualisiere den Chat-Titel, um den NPC-Namen anzuzeigen
if (this.chatTitle && this.chatTitle.visible) {
this.chatTitle.setText(`Gespräch mit ${this.npc.characterName}`);
}
// Zeige eine spezielle Nachricht an
const revealText = this.add.text(
this.cameras.main.width / 2,
this.cameras.main.height / 3,
`Du hast ${this.npc.characterName} entlarvt!`,
{
fontSize: '24px',
fontFamily: 'Arial',
fontStyle: 'bold',
fill: '#ffff00',
stroke: '#000000',
strokeThickness: 4,
align: 'center',
}
);
revealText.setOrigin(0.5);
revealText.setScrollFactor(0);
// Blende die Nachricht nach einigen Sekunden aus
this.tweens.add({
targets: revealText,
alpha: 0,
duration: 2000,
delay: 3000,
onComplete: () => {
revealText.destroy();
// Erstelle nach kurzer Verzögerung einen neuen NPC
this.time.delayedCall(1000, () => {
// Erstelle einen neuen NPC nur, wenn noch nicht alle entdeckt wurden
const newNpc = this.createNewNPC();
if (newNpc) {
// Zeige eine Benachrichtigung an
const newNpcText = this.add.text(
this.cameras.main.width / 2,
this.cameras.main.height / 3,
'Ein neuer geheimnisvoller NPC ist erschienen!',
{
fontSize: '20px',
fontFamily: 'Arial',
fontStyle: 'bold',
fill: '#ffffff',
stroke: '#000000',
strokeThickness: 3,
align: 'center',
}
);
newNpcText.setOrigin(0.5);
newNpcText.setScrollFactor(0);
// Blende die Nachricht nach einigen Sekunden aus
this.tweens.add({
targets: newNpcText,
alpha: 0,
duration: 1500,
delay: 2500,
onComplete: () => newNpcText.destroy(),
});
}
});
},
});
}
async sendChatMessage() {
// Wenn keine Nachricht eingegeben wurde
if (this.userInput.length === 0 || this.npcState.isWaitingForResponse) return;
const message = this.userInput;
this.userInput = '';
// Füge die Nachricht des Spielers zum Konversationsverlauf hinzu (für das LLM)
this.conversationHistory.push({
type: 'user',
message: message,
});
// Zeige "Nachricht wird gesendet"-Status
this.chatInput.setText('Nachricht wird gesendet...');
this.npcState.isWaitingForResponse = true;
try {
// Sende Anfrage an den Server mit der Konversationshistorie
const response = await fetch('http://localhost:3000/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
conversationHistory: this.conversationHistory,
characterName: this.npc ? this.npc.characterName : null,
characterPersonality: this.npc ? this.npc.characterPersonality : null,
}),
});
const data = await response.json();
// Verarbeite die Antwort
let npcResponse = 'Entschuldigung, ich habe dich nicht verstanden.';
if (data.response) {
npcResponse = data.response;
// Füge die NPC-Antwort zum Konversationsverlauf hinzu (für das LLM)
this.conversationHistory.push({
type: 'npc',
message: npcResponse,
});
// Prüfe, ob die Identität aufgedeckt wurde
if (data.identityRevealed) {
console.log('Identität aufgedeckt!');
// Spiele eine Animation ab oder führe eine spezielle Aktion aus
this.revealIdentity();
}
}
// Aktualisiere die NPC-Antwort
this.updateNpcResponse(npcResponse);
// Füge die Antwort des NPCs zum Konversationsverlauf hinzu (für das LLM)
this.conversationHistory.push({
type: 'npc',
message: npcResponse,
});
} catch (error) {
console.error('Fehler beim Senden der Nachricht:', error);
// Aktualisiere die NPC-Antwort mit Fehlermeldung
this.updateNpcResponse('Entschuldigung, ich kann gerade nicht antworten.');
// Füge die Fehlermeldung zum Konversationsverlauf hinzu (für das LLM)
this.conversationHistory.push({
type: 'npc',
message: 'Entschuldigung, ich kann gerade nicht antworten.',
});
// Fehlermeldung wird nur im Chat-Fenster angezeigt
}
// Setze Status zurück
this.npcState.isWaitingForResponse = false;
this.chatInput.setText('Tippe deine Nachricht hier ein...');
}
update() {
// Spielerbewegung
this.handlePlayerMovement();
// Aktualisiere die Position des Dialogs, wenn er sichtbar ist
if (this.npcDialog && this.npcDialog.visible && this.npc) {
this.npcDialog.setPosition(this.npc.x - 100, this.npc.y - 50);
}
// Aktualisiere die Position des Interaktions-Prompts
if (this.interactionPrompt && this.interactionPrompt.visible && this.npc) {
this.interactionPrompt.setPosition(this.npc.x - 50, this.npc.y - 30);
}
// Aktualisiere die Position der Debug-Texte für alle NPCs
if (this.npcs) {
this.npcs.forEach((npc) => {
if (npc.debugText) {
npc.debugText.setPosition(npc.x, npc.y + 20);
}
});
}
}
handlePlayerMovement() {
if (!this.player) return;
const speed = 160;
// Horizontal
if (this.cursors.left.isDown) {
this.player.setVelocityX(-speed);
this.player.setTexture('player_left');
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(speed);
this.player.setTexture('player_right');
} else {
this.player.setVelocityX(0);
}
// Vertikal
if (this.cursors.up.isDown) {
this.player.setVelocityY(-speed);
if (!this.cursors.left.isDown && !this.cursors.right.isDown) {
this.player.setTexture('player_up');
}
} else if (this.cursors.down.isDown) {
this.player.setVelocityY(speed);
if (!this.cursors.left.isDown && !this.cursors.right.isDown) {
this.player.setTexture('player_down');
}
} else {
this.player.setVelocityY(0);
}
// Interaktion mit NPCs prüfen, wenn E gedrückt wird
if (
this.interactKey &&
Phaser.Input.Keyboard.JustDown(this.interactKey) &&
this.npcs &&
this.npcs.length > 0
) {
// Prüfe die Distanz zu allen NPCs
let closestNPC = null;
let closestDistance = 100; // Maximale Interaktionsdistanz
for (let i = 0; i < this.npcs.length; i++) {
const npc = this.npcs[i];
const distance = Phaser.Math.Distance.Between(this.player.x, this.player.y, npc.x, npc.y);
console.log(`Abstand zu NPC ${i}: ${distance}`);
// Wenn dieser NPC näher ist als der bisher nächste und in Reichweite
if (distance < closestDistance) {
closestDistance = distance;
closestNPC = npc;
this.npcState.currentNpcIndex = i;
}
}
// Wenn ein NPC in Reichweite ist
if (closestNPC) {
console.log(
`Interaktion mit NPC an Position ${closestNPC.x}, ${closestNPC.y}, Abstand: ${closestDistance}`
);
this.npc = closestNPC; // Setze den aktuellen NPC
this.talkToNPC();
}
}
}
}