455 lines
13 KiB
JavaScript
455 lines
13 KiB
JavaScript
/**
|
|
* NPC PRIVACY & HOME DECORATION SYSTEM
|
|
* Part of: Game Systems Expansion
|
|
* Created: January 4, 2026
|
|
*
|
|
* Features:
|
|
* - Hobby-based automatic interior generation
|
|
* - Door lock system (heart-based access)
|
|
* - NPC home visit mechanics
|
|
* - Relationship effects from visits
|
|
* - Privacy violations & consequences
|
|
*/
|
|
|
|
class NPCPrivacySystem {
|
|
constructor(game) {
|
|
this.game = game;
|
|
this.player = game.player;
|
|
|
|
// Privacy settings
|
|
this.privacyLevels = {
|
|
PUBLIC: 0, // Anyone can enter (living room, kitchen)
|
|
FRIENDLY: 3, // 3+ hearts required
|
|
PRIVATE: 5, // 5+ hearts required (bedroom)
|
|
INTIMATE: 8, // 8+ hearts required (special rooms)
|
|
LOCKED: 10 // 10 hearts or marriage required
|
|
};
|
|
|
|
// Visit tracking
|
|
this.visitHistory = {};
|
|
this.lastVisitTimes = {};
|
|
|
|
// NPC home decorations (generated based on hobby)
|
|
this.npcHomes = {};
|
|
}
|
|
|
|
/**
|
|
* Generate NPC home interior based on hobby
|
|
*/
|
|
generateNPCHome(npcId) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
if (!npc) return null;
|
|
|
|
const hobby = npc.hobby;
|
|
|
|
// Base home structure
|
|
const home = {
|
|
npcId: npcId,
|
|
rooms: {
|
|
living_room: {
|
|
name: 'Living Room',
|
|
privacyLevel: this.privacyLevels.PUBLIC,
|
|
objects: this.generateLivingRoomObjects(hobby)
|
|
},
|
|
kitchen: {
|
|
name: 'Kitchen',
|
|
privacyLevel: this.privacyLevels.PUBLIC,
|
|
objects: this.generateKitchenObjects(hobby)
|
|
},
|
|
bedroom: {
|
|
name: 'Bedroom',
|
|
privacyLevel: this.privacyLevels.PRIVATE,
|
|
objects: this.generateBedroomObjects(hobby)
|
|
},
|
|
hobby_room: {
|
|
name: this.getHobbyRoomName(hobby),
|
|
privacyLevel: this.privacyLevels.FRIENDLY,
|
|
objects: this.generateHobbyRoomObjects(hobby)
|
|
}
|
|
},
|
|
visitCount: 0,
|
|
lastVisit: null
|
|
};
|
|
|
|
// Store home
|
|
this.npcHomes[npcId] = home;
|
|
|
|
return home;
|
|
}
|
|
|
|
/**
|
|
* Generate living room objects
|
|
*/
|
|
generateLivingRoomObjects(hobby) {
|
|
const base = [
|
|
'interior_table_small',
|
|
'interior_bookshelf',
|
|
'interior_gothic_lantern'
|
|
];
|
|
|
|
// Hobby-specific additions
|
|
const hobbyAdditions = {
|
|
fishing: ['mounted_fish', 'fishing_net'],
|
|
farming: ['grain_sack', 'tool_rack'],
|
|
cooking: ['recipe_shelf'],
|
|
reading: ['bookshelf', 'reading_chair'],
|
|
music: ['guitar_stand', 'music_sheets'],
|
|
crafting: ['crafting_workshop']
|
|
};
|
|
|
|
return [...base, ...(hobbyAdditions[hobby] || [])];
|
|
}
|
|
|
|
/**
|
|
* Generate kitchen objects
|
|
*/
|
|
generateKitchenObjects(hobby) {
|
|
const base = [
|
|
'interior_kitchen_stove',
|
|
'interior_kitchen_counter'
|
|
];
|
|
|
|
if (hobby === 'cooking') {
|
|
base.push(
|
|
'interior_kitchen_fridge',
|
|
'interior_kitchen_sink',
|
|
'interior_recipe_shelf'
|
|
);
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
/**
|
|
* Generate bedroom objects
|
|
*/
|
|
generateBedroomObjects(hobby) {
|
|
const base = [
|
|
'interior_bed_wooden',
|
|
'interior_wardrobe',
|
|
'interior_chest_locked'
|
|
];
|
|
|
|
// Personal items based on hobby
|
|
const hobbyItems = {
|
|
zombie_worker: ['brain_jar', 'work_uniform'],
|
|
baker: ['flour_workspace', 'dough_tools'],
|
|
barber: ['dreadlock_kit', 'styling_tools'],
|
|
fisherman: ['tackle_box', 'fish_collection']
|
|
};
|
|
|
|
return [...base, ...(hobbyItems[hobby] || [])];
|
|
}
|
|
|
|
/**
|
|
* Generate hobby room objects
|
|
*/
|
|
generateHobbyRoomObjects(hobby) {
|
|
const hobbyRooms = {
|
|
fishing: [
|
|
'fishing_rod_rack',
|
|
'bait_storage',
|
|
'fish_tank',
|
|
'mounted_trophy_fish'
|
|
],
|
|
zombie_worker: [
|
|
'brain_jar',
|
|
'work_bench',
|
|
'tool_storage',
|
|
'miner_equipment'
|
|
],
|
|
baker: [
|
|
'flour_workspace',
|
|
'mixing_bowls',
|
|
'bread_storage',
|
|
'recipe_collection'
|
|
],
|
|
alchemy: [
|
|
'interior_alchemy_bottle',
|
|
'interior_brewing_cauldron',
|
|
'interior_chemistry_set',
|
|
'interior_potion_shelf'
|
|
],
|
|
crafting: [
|
|
'interior_crafting_workshop',
|
|
'interior_tool_rack',
|
|
'material_storage',
|
|
'blueprint_table'
|
|
],
|
|
reading: [
|
|
'interior_bookshelf',
|
|
'reading_chair',
|
|
'ancient_manuscripts',
|
|
'writing_desk'
|
|
]
|
|
};
|
|
|
|
return hobbyRooms[hobby] || ['generic_workspace'];
|
|
}
|
|
|
|
/**
|
|
* Get hobby room name
|
|
*/
|
|
getHobbyRoomName(hobby) {
|
|
const names = {
|
|
fishing: 'Fishing Den',
|
|
zombie_worker: 'Worker\'s Quarters',
|
|
baker: 'Baking Workshop',
|
|
alchemy: 'Alchemy Lab',
|
|
crafting: 'Craft Room',
|
|
reading: 'Library',
|
|
music: 'Music Studio',
|
|
farming: 'Storage Shed'
|
|
};
|
|
|
|
return names[hobby] || 'Hobby Room';
|
|
}
|
|
|
|
/**
|
|
* Attempt to enter NPC room
|
|
*/
|
|
attemptEntry(npcId, roomId) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
if (!npc) {
|
|
return { success: false, message: 'NPC not found!' };
|
|
}
|
|
|
|
// Generate home if doesn't exist
|
|
if (!this.npcHomes[npcId]) {
|
|
this.generateNPCHome(npcId);
|
|
}
|
|
|
|
const home = this.npcHomes[npcId];
|
|
const room = home.rooms[roomId];
|
|
|
|
if (!room) {
|
|
return { success: false, message: 'Room not found!' };
|
|
}
|
|
|
|
// Check privacy level
|
|
const playerHearts = this.player.getRelationshipHearts(npcId);
|
|
const requiredHearts = room.privacyLevel;
|
|
|
|
if (playerHearts < requiredHearts) {
|
|
// Privacy violation!
|
|
return this.handlePrivacyViolation(npcId, roomId, requiredHearts);
|
|
}
|
|
|
|
// Allowed entry
|
|
return this.handleSuccessfulEntry(npcId, roomId);
|
|
}
|
|
|
|
/**
|
|
* Handle privacy violation
|
|
*/
|
|
handlePrivacyViolation(npcId, roomId, requiredHearts) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
|
|
// Relationship penalty
|
|
const penalty = (requiredHearts - this.player.getRelationshipHearts(npcId)) * 20;
|
|
npc.addRelationshipPoints(-penalty);
|
|
|
|
// NPC reaction
|
|
const reactions = [
|
|
"Hey! That's private!",
|
|
"What are you doing in my room?!",
|
|
"Get out! This is MY space!",
|
|
"I can't believe you just walked in here...",
|
|
"Privacy, please!"
|
|
];
|
|
|
|
const reaction = Phaser.Utils.Array.GetRandom(reactions);
|
|
|
|
this.game.showDialogue(npc.name, reaction, {
|
|
mood: 'angry',
|
|
animation: 'shocked'
|
|
});
|
|
|
|
this.game.showMessage(
|
|
`${npc.name} is upset! -${penalty} relationship points`
|
|
);
|
|
|
|
// Player gets kicked out
|
|
this.game.player.teleportToLocation('outside_' + npcId + '_home');
|
|
|
|
return {
|
|
success: false,
|
|
privacyViolation: true,
|
|
penalty: penalty,
|
|
requiredHearts: requiredHearts,
|
|
currentHearts: this.player.getRelationshipHearts(npcId)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Handle successful entry
|
|
*/
|
|
handleSuccessfulEntry(npcId, roomId) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
const home = this.npcHomes[npcId];
|
|
|
|
// Track visit
|
|
if (!this.visitHistory[npcId]) {
|
|
this.visitHistory[npcId] = [];
|
|
}
|
|
|
|
const visit = {
|
|
roomId: roomId,
|
|
timestamp: this.game.time.currentTime,
|
|
timeOfDay: this.game.time.getTimeOfDay()
|
|
};
|
|
|
|
this.visitHistory[npcId].push(visit);
|
|
this.lastVisitTimes[npcId] = this.game.time.currentTime;
|
|
|
|
home.visitCount++;
|
|
home.lastVisit = this.game.time.currentTime;
|
|
|
|
// Relationship effects based on visit
|
|
this.applyVisitEffects(npcId, roomId, visit);
|
|
|
|
// Enter room scene
|
|
this.game.scene.start('NPCRoomScene', {
|
|
npcId: npcId,
|
|
roomId: roomId,
|
|
room: home.rooms[roomId]
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
room: home.rooms[roomId],
|
|
npc: npc
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Apply relationship effects from visit
|
|
*/
|
|
applyVisitEffects(npcId, roomId, visit) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
const timeOfDay = visit.timeOfDay;
|
|
|
|
// Visit frequency check
|
|
const recentVisits = this.visitHistory[npcId].filter(v => {
|
|
const hoursSince = (this.game.time.currentTime - v.timestamp) / 3600;
|
|
return hoursSince < 24; // Last 24 hours
|
|
});
|
|
|
|
if (recentVisits.length > 3) {
|
|
// Visiting TOO much = annoying
|
|
npc.addRelationshipPoints(-10);
|
|
this.game.showMessage(
|
|
`${npc.name} seems a bit annoyed by frequent visits...`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Time of day effects
|
|
if (timeOfDay === 'night' && roomId === 'bedroom') {
|
|
// Visiting bedroom at night = awkward
|
|
npc.addRelationshipPoints(-5);
|
|
this.game.showDialogue(
|
|
npc.name,
|
|
"It's quite late... Is everything okay?",
|
|
{ mood: 'concerned' }
|
|
);
|
|
} else if (timeOfDay === 'morning' && roomId === 'kitchen') {
|
|
// Morning kitchen visit = breakfast together!
|
|
npc.addRelationshipPoints(5);
|
|
this.game.showDialogue(
|
|
npc.name,
|
|
"Good morning! Care to join me for breakfast?",
|
|
{ mood: 'happy' }
|
|
);
|
|
} else if (roomId === 'hobby_room') {
|
|
// Showing interest in hobby = bonus points!
|
|
npc.addRelationshipPoints(10);
|
|
this.game.showDialogue(
|
|
npc.name,
|
|
"I'm glad you're interested in my hobby!",
|
|
{ mood: 'excited' }
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get visit statistics for NPC
|
|
*/
|
|
getVisitStats(npcId) {
|
|
const visits = this.visitHistory[npcId] || [];
|
|
|
|
// Count visits per room
|
|
const roomCounts = {};
|
|
visits.forEach(visit => {
|
|
roomCounts[visit.roomId] = (roomCounts[visit.roomId] || 0) + 1;
|
|
});
|
|
|
|
// Average visits per day
|
|
const daysSinceFirstVisit = visits.length > 0
|
|
? (this.game.time.currentTime - visits[0].timestamp) / 86400
|
|
: 0;
|
|
|
|
const avgVisitsPerDay = daysSinceFirstVisit > 0
|
|
? visits.length / daysSinceFirstVisit
|
|
: 0;
|
|
|
|
return {
|
|
totalVisits: visits.length,
|
|
roomCounts: roomCounts,
|
|
avgVisitsPerDay: avgVisitsPerDay,
|
|
lastVisit: this.lastVisitTimes[npcId],
|
|
favoriteRoom: Object.keys(roomCounts).reduce((a, b) =>
|
|
roomCounts[a] > roomCounts[b] ? a : b, null)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if player can access special room
|
|
*/
|
|
canAccessSpecialRoom(npcId, roomId) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
if (!npc) return false;
|
|
|
|
const home = this.npcHomes[npcId];
|
|
if (!home) return false;
|
|
|
|
const room = home.rooms[roomId];
|
|
if (!room) return false;
|
|
|
|
const playerHearts = this.player.getRelationshipHearts(npcId);
|
|
|
|
// Special case: marriage allows LOCKED access
|
|
if (room.privacyLevel === this.privacyLevels.LOCKED) {
|
|
return this.player.spouse === npcId;
|
|
}
|
|
|
|
return playerHearts >= room.privacyLevel;
|
|
}
|
|
|
|
/**
|
|
* Get NPC home UI data
|
|
*/
|
|
getNPCHomeUIData(npcId) {
|
|
if (!this.npcHomes[npcId]) {
|
|
this.generateNPCHome(npcId);
|
|
}
|
|
|
|
const home = this.npcHomes[npcId];
|
|
const npc = this.game.npcs.get(npcId);
|
|
|
|
return {
|
|
npc: npc,
|
|
home: home,
|
|
accessibleRooms: Object.keys(home.rooms).filter(roomId =>
|
|
this.canAccessSpecialRoom(npcId, roomId)
|
|
),
|
|
lockedRooms: Object.keys(home.rooms).filter(roomId =>
|
|
!this.canAccessSpecialRoom(npcId, roomId)
|
|
),
|
|
visitStats: this.getVisitStats(npcId)
|
|
};
|
|
}
|
|
}
|
|
|
|
|