372 lines
12 KiB
JavaScript
372 lines
12 KiB
JavaScript
/**
|
|
* 🏛️ STRUCTURE INTERACTION SYSTEM
|
|
* Enables player interaction with structures in the world
|
|
* - Enter buildings
|
|
* - Loot chests
|
|
* - Landmark treasures
|
|
* - Lock/unlock mechanics
|
|
*/
|
|
|
|
class StructureInteractionSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Interaction markers
|
|
this.interactionMarkers = [];
|
|
|
|
// Loot chests (generated per structure)
|
|
this.chests = new Map(); // key: "x,y" → chest data
|
|
|
|
// Active interactions
|
|
this.nearbyStructures = [];
|
|
this.currentInteraction = null;
|
|
|
|
// Loot tables per biome
|
|
this.lootTables = {
|
|
'Grassland': [
|
|
{ item: 'wheat_seeds', min: 5, max: 15, chance: 0.7 },
|
|
{ item: 'wood', min: 10, max: 30, chance: 0.8 },
|
|
{ item: 'gold', min: 20, max: 50, chance: 0.5 },
|
|
{ item: 'iron_ore', min: 1, max: 5, chance: 0.3 }
|
|
],
|
|
'Forest': [
|
|
{ item: 'wood', min: 20, max: 50, chance: 0.9 },
|
|
{ item: 'apple', min: 3, max: 10, chance: 0.6 },
|
|
{ item: 'berry', min: 5, max: 15, chance: 0.7 },
|
|
{ item: 'mushroom', min: 2, max: 8, chance: 0.4 }
|
|
],
|
|
'Desert': [
|
|
{ item: 'gold', min: 50, max: 150, chance: 0.6 },
|
|
{ item: 'ruby', min: 1, max: 3, chance: 0.2 },
|
|
{ item: 'ancient_scroll', min: 1, max: 1, chance: 0.1 },
|
|
{ item: 'cactus_fruit', min: 3, max: 10, chance: 0.5 }
|
|
],
|
|
'Mountain': [
|
|
{ item: 'iron_ore', min: 10, max: 30, chance: 0.8 },
|
|
{ item: 'gold_ore', min: 5, max: 15, chance: 0.6 },
|
|
{ item: 'diamond', min: 1, max: 3, chance: 0.15 },
|
|
{ item: 'stone', min: 20, max: 50, chance: 0.9 }
|
|
],
|
|
'Swamp': [
|
|
{ item: 'herbs', min: 5, max: 20, chance: 0.7 },
|
|
{ item: 'mushroom', min: 10, max: 30, chance: 0.8 },
|
|
{ item: 'slime', min: 5, max: 15, chance: 0.6 },
|
|
{ item: 'ancient_bone', min: 1, max: 5, chance: 0.3 }
|
|
]
|
|
};
|
|
|
|
// Landmark treasure (special rare items)
|
|
this.landmarkTreasures = {
|
|
'ancient_temple': [
|
|
{ item: 'legendary_sword', min: 1, max: 1, chance: 1.0 },
|
|
{ item: 'gold', min: 500, max: 1000, chance: 1.0 },
|
|
{ item: 'ancient_artifact', min: 1, max: 1, chance: 1.0 }
|
|
],
|
|
'great_pyramid': [
|
|
{ item: 'pharaoh_staff', min: 1, max: 1, chance: 1.0 },
|
|
{ item: 'ruby', min: 10, max: 20, chance: 1.0 },
|
|
{ item: 'mummy_wraps', min: 5, max: 10, chance: 1.0 }
|
|
],
|
|
'mountain_peak': [
|
|
{ item: 'titan_hammer', min: 1, max: 1, chance: 1.0 },
|
|
{ item: 'diamond', min: 10, max: 20, chance: 1.0 },
|
|
{ item: 'eagle_feather', min: 1, max: 1, chance: 1.0 }
|
|
],
|
|
'abandoned_city': [
|
|
{ item: 'ancient_key', min: 1, max: 1, chance: 1.0 },
|
|
{ item: 'gold', min: 1000, max: 2000, chance: 1.0 },
|
|
{ item: 'city_map', min: 1, max: 1, chance: 1.0 }
|
|
],
|
|
'dragon_skeleton': [
|
|
{ item: 'dragon_scale', min: 5, max: 10, chance: 1.0 },
|
|
{ item: 'dragon_tooth', min: 1, max: 3, chance: 1.0 },
|
|
{ item: 'dragon_heart', min: 1, max: 1, chance: 1.0 }
|
|
]
|
|
};
|
|
|
|
console.log('🏛️ StructureInteractionSystem initialized');
|
|
}
|
|
|
|
// Generate chests for all structures
|
|
generateChestsForStructures(structureSystem) {
|
|
if (!structureSystem) return;
|
|
|
|
let chestsGenerated = 0;
|
|
|
|
// Regular structures
|
|
for (const structure of structureSystem.structures) {
|
|
// 70% chance to have a chest
|
|
if (Math.random() < 0.7) {
|
|
const chestKey = `${structure.x},${structure.y}`;
|
|
this.chests.set(chestKey, {
|
|
x: structure.x,
|
|
y: structure.y,
|
|
biome: structure.biome,
|
|
opened: false,
|
|
loot: this.generateLoot(structure.biome)
|
|
});
|
|
chestsGenerated++;
|
|
}
|
|
}
|
|
|
|
// Landmarks (always have treasure)
|
|
for (const landmark of structureSystem.landmarks) {
|
|
const chestKey = `${landmark.x},${landmark.y}`;
|
|
this.chests.set(chestKey, {
|
|
x: landmark.x,
|
|
y: landmark.y,
|
|
type: 'landmark',
|
|
landmarkType: landmark.type,
|
|
opened: false,
|
|
loot: this.generateLandmarkTreasure(landmark.type)
|
|
});
|
|
chestsGenerated++;
|
|
}
|
|
|
|
console.log(`✅ Generated ${chestsGenerated} chests (${this.chests.size} total)`);
|
|
}
|
|
|
|
// Generate loot based on biome
|
|
generateLoot(biome) {
|
|
const lootTable = this.lootTables[biome] || this.lootTables['Grassland'];
|
|
const loot = [];
|
|
|
|
for (const entry of lootTable) {
|
|
if (Math.random() < entry.chance) {
|
|
const amount = Math.floor(Math.random() * (entry.max - entry.min + 1)) + entry.min;
|
|
loot.push({
|
|
item: entry.item,
|
|
amount: amount
|
|
});
|
|
}
|
|
}
|
|
|
|
return loot;
|
|
}
|
|
|
|
// Generate landmark treasure (always guaranteed)
|
|
generateLandmarkTreasure(landmarkType) {
|
|
const treasureTable = this.landmarkTreasures[landmarkType];
|
|
if (!treasureTable) return [];
|
|
|
|
const loot = [];
|
|
for (const entry of treasureTable) {
|
|
const amount = Math.floor(Math.random() * (entry.max - entry.min + 1)) + entry.min;
|
|
loot.push({
|
|
item: entry.item,
|
|
amount: amount,
|
|
legendary: true // Mark as legendary loot
|
|
});
|
|
}
|
|
|
|
return loot;
|
|
}
|
|
|
|
// Check for nearby structures
|
|
update(playerX, playerY) {
|
|
this.nearbyStructures = [];
|
|
|
|
// Check all chests
|
|
for (const [key, chest] of this.chests) {
|
|
const dist = Math.sqrt((chest.x - playerX) ** 2 + (chest.y - playerY) ** 2);
|
|
|
|
if (dist < 3 && !chest.opened) {
|
|
this.nearbyStructures.push({
|
|
type: chest.type === 'landmark' ? 'landmark' : 'structure',
|
|
x: chest.x,
|
|
y: chest.y,
|
|
key: key,
|
|
data: chest
|
|
});
|
|
}
|
|
}
|
|
|
|
// Show interaction prompt if nearby
|
|
if (this.nearbyStructures.length > 0 && !this.currentInteraction) {
|
|
this.showInteractionPrompt();
|
|
} else if (this.nearbyStructures.length === 0) {
|
|
this.hideInteractionPrompt();
|
|
}
|
|
}
|
|
|
|
// Show UI prompt to interact
|
|
showInteractionPrompt() {
|
|
if (this.interactionPrompt) return;
|
|
|
|
const nearest = this.nearbyStructures[0];
|
|
const promptText = nearest.type === 'landmark'
|
|
? '⭐ Press E to open LEGENDARY TREASURE'
|
|
: '📦 Press E to open chest';
|
|
|
|
this.interactionPrompt = this.scene.add.text(
|
|
this.scene.cameras.main.centerX,
|
|
this.scene.cameras.main.height - 100,
|
|
promptText,
|
|
{
|
|
fontSize: '24px',
|
|
fontFamily: 'Arial',
|
|
color: nearest.type === 'landmark' ? '#FFD700' : '#ffffff',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 20, y: 10 }
|
|
}
|
|
);
|
|
this.interactionPrompt.setOrigin(0.5);
|
|
this.interactionPrompt.setScrollFactor(0);
|
|
this.interactionPrompt.setDepth(10000);
|
|
}
|
|
|
|
// Hide interaction prompt
|
|
hideInteractionPrompt() {
|
|
if (this.interactionPrompt) {
|
|
this.interactionPrompt.destroy();
|
|
this.interactionPrompt = null;
|
|
}
|
|
}
|
|
|
|
// Player presses E to interact
|
|
interact() {
|
|
if (this.nearbyStructures.length === 0) return;
|
|
|
|
const nearest = this.nearbyStructures[0];
|
|
this.openChest(nearest.key, nearest.data);
|
|
}
|
|
|
|
// Open chest and give loot to player
|
|
openChest(chestKey, chestData) {
|
|
if (chestData.opened) return;
|
|
|
|
// Mark as opened
|
|
chestData.opened = true;
|
|
this.chests.set(chestKey, chestData);
|
|
|
|
// Give loot to player
|
|
const inventory = this.scene.inventorySystem;
|
|
if (!inventory) {
|
|
console.warn('⚠️ InventorySystem not found');
|
|
return;
|
|
}
|
|
|
|
console.log(`📦 Opening chest at (${chestData.x}, ${chestData.y})`);
|
|
|
|
let totalValue = 0;
|
|
for (const lootItem of chestData.loot) {
|
|
if (lootItem.item === 'gold') {
|
|
inventory.gold += lootItem.amount;
|
|
totalValue += lootItem.amount;
|
|
} else {
|
|
inventory.addItem(lootItem.item, lootItem.amount);
|
|
totalValue += lootItem.amount * 10; // Estimate value
|
|
}
|
|
|
|
console.log(` + ${lootItem.amount}x ${lootItem.item}${lootItem.legendary ? ' (LEGENDARY!)' : ''}`);
|
|
}
|
|
|
|
// Show loot notification
|
|
this.showLootNotification(chestData, totalValue);
|
|
|
|
// Play sound
|
|
if (this.scene.soundManager) {
|
|
this.scene.soundManager.beepPickup();
|
|
}
|
|
|
|
// Hide prompt
|
|
this.hideInteractionPrompt();
|
|
}
|
|
|
|
// Show loot notification UI
|
|
showLootNotification(chestData, totalValue) {
|
|
const isLandmark = chestData.type === 'landmark';
|
|
|
|
const notification = this.scene.add.text(
|
|
this.scene.cameras.main.centerX,
|
|
this.scene.cameras.main.centerY - 100,
|
|
isLandmark
|
|
? `⭐ LEGENDARY TREASURE FOUND! ⭐\n${chestData.landmarkType}\nValue: ${totalValue} gold`
|
|
: `📦 Chest Opened!\nValue: ${totalValue} gold`,
|
|
{
|
|
fontSize: isLandmark ? '32px' : '24px',
|
|
fontFamily: 'Arial',
|
|
color: isLandmark ? '#FFD700' : '#ffffff',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 30, y: 20 },
|
|
align: 'center'
|
|
}
|
|
);
|
|
notification.setOrigin(0.5);
|
|
notification.setScrollFactor(0);
|
|
notification.setDepth(10001);
|
|
|
|
// Fade out after 3 seconds
|
|
this.scene.tweens.add({
|
|
targets: notification,
|
|
alpha: 0,
|
|
duration: 1000,
|
|
delay: 2000,
|
|
onComplete: () => notification.destroy()
|
|
});
|
|
|
|
// Particle effect
|
|
if (this.scene.particleEffects && isLandmark) {
|
|
// Golden particles for landmarks
|
|
for (let i = 0; i < 20; i++) {
|
|
const particle = this.scene.add.circle(
|
|
this.scene.cameras.main.centerX + (Math.random() - 0.5) * 100,
|
|
this.scene.cameras.main.centerY - 100,
|
|
5,
|
|
0xFFD700
|
|
);
|
|
particle.setScrollFactor(0);
|
|
particle.setDepth(10000);
|
|
|
|
this.scene.tweens.add({
|
|
targets: particle,
|
|
y: particle.y - 100,
|
|
alpha: 0,
|
|
duration: 1500,
|
|
onComplete: () => particle.destroy()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get statistics
|
|
getStats() {
|
|
const opened = Array.from(this.chests.values()).filter(c => c.opened).length;
|
|
return {
|
|
totalChests: this.chests.size,
|
|
chestsOpened: opened,
|
|
chestsRemaining: this.chests.size - opened
|
|
};
|
|
}
|
|
|
|
// Export/Import for save system
|
|
exportData() {
|
|
const chestsArray = [];
|
|
for (const [key, chest] of this.chests) {
|
|
chestsArray.push({
|
|
key,
|
|
opened: chest.opened
|
|
});
|
|
}
|
|
return { chests: chestsArray };
|
|
}
|
|
|
|
importData(data) {
|
|
if (!data || !data.chests) return;
|
|
|
|
for (const savedChest of data.chests) {
|
|
if (this.chests.has(savedChest.key)) {
|
|
const chest = this.chests.get(savedChest.key);
|
|
chest.opened = savedChest.opened;
|
|
this.chests.set(savedChest.key, chest);
|
|
}
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.hideInteractionPrompt();
|
|
this.interactionMarkers.forEach(m => m.destroy());
|
|
this.chests.clear();
|
|
}
|
|
}
|