@ -209,6 +209,23 @@ class HikeMapDB {
this . db . exec ( ` ALTER TABLE monster_skills ADD COLUMN custom_name TEXT ` ) ;
} catch ( e ) { /* Column already exists */ }
// Migration: Add home base and death system columns to rpg_stats
try {
this . db . exec ( ` ALTER TABLE rpg_stats ADD COLUMN home_base_lat REAL ` ) ;
} catch ( e ) { /* Column already exists */ }
try {
this . db . exec ( ` ALTER TABLE rpg_stats ADD COLUMN home_base_lng REAL ` ) ;
} catch ( e ) { /* Column already exists */ }
try {
this . db . exec ( ` ALTER TABLE rpg_stats ADD COLUMN last_home_set TEXT ` ) ;
} catch ( e ) { /* Column already exists */ }
try {
this . db . exec ( ` ALTER TABLE rpg_stats ADD COLUMN is_dead INTEGER DEFAULT 0 ` ) ;
} catch ( e ) { /* Column already exists */ }
try {
this . db . exec ( ` ALTER TABLE rpg_stats ADD COLUMN home_base_icon TEXT DEFAULT '00' ` ) ;
} catch ( e ) { /* Column already exists */ }
// Game settings table - key/value store for game configuration
this . db . exec ( `
CREATE TABLE IF NOT EXISTS game_settings (
@ -468,7 +485,8 @@ class HikeMapDB {
// RPG Stats methods
getRpgStats ( userId ) {
const stmt = this . db . prepare ( `
SELECT character_name , race , class , level , xp , hp , max_hp , mp , max_mp , atk , def , accuracy , dodge , unlocked_skills
SELECT character_name , race , class , level , xp , hp , max_hp , mp , max_mp , atk , def , accuracy , dodge , unlocked_skills ,
home_base_lat , home_base_lng , last_home_set , is_dead , home_base_icon
FROM rpg_stats WHERE user_id = ?
` );
return stmt . get ( userId ) ;
@ -525,8 +543,8 @@ class HikeMapDB {
saveRpgStats ( userId , stats ) {
const stmt = this . db . prepare ( `
INSERT INTO rpg_stats ( user_id , character_name , race , class , level , xp , hp , max_hp , mp , max_mp , atk , def , accuracy , dodge , unlocked_skills , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , datetime ( 'now' ) )
INSERT INTO rpg_stats ( user_id , character_name , race , class , level , xp , hp , max_hp , mp , max_mp , atk , def , accuracy , dodge , unlocked_skills , home_base_lat , home_base_lng , last_home_set , is_dead , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , datetime ( 'now' ) )
ON CONFLICT ( user_id ) DO UPDATE SET
character_name = COALESCE ( excluded . character_name , rpg_stats . character_name ) ,
race = COALESCE ( excluded . race , rpg_stats . race ) ,
@ -542,6 +560,10 @@ class HikeMapDB {
accuracy = excluded . accuracy ,
dodge = excluded . dodge ,
unlocked_skills = COALESCE ( excluded . unlocked_skills , rpg_stats . unlocked_skills ) ,
home_base_lat = COALESCE ( excluded . home_base_lat , rpg_stats . home_base_lat ) ,
home_base_lng = COALESCE ( excluded . home_base_lng , rpg_stats . home_base_lng ) ,
last_home_set = COALESCE ( excluded . last_home_set , rpg_stats . last_home_set ) ,
is_dead = COALESCE ( excluded . is_dead , rpg_stats . is_dead ) ,
updated_at = datetime ( 'now' )
` );
// Convert unlockedSkills array to JSON string for storage
@ -561,10 +583,94 @@ class HikeMapDB {
stats . def || 8 ,
stats . accuracy || 90 ,
stats . dodge || 10 ,
unlockedSkillsJson
unlockedSkillsJson ,
stats . homeBaseLat || null ,
stats . homeBaseLng || null ,
stats . lastHomeSet || null ,
stats . isDead !== undefined ? ( stats . isDead ? 1 : 0 ) : null
) ;
}
// Set home base location
setHomeBase ( userId , lat , lng ) {
const stmt = this . db . prepare ( `
UPDATE rpg_stats SET
home_base_lat = ? ,
home_base_lng = ? ,
last_home_set = datetime ( 'now' ) ,
updated_at = datetime ( 'now' )
WHERE user_id = ?
` );
return stmt . run ( lat , lng , userId ) ;
}
// Update home base icon
setHomeBaseIcon ( userId , iconId ) {
const stmt = this . db . prepare ( `
UPDATE rpg_stats SET
home_base_icon = ? ,
updated_at = datetime ( 'now' )
WHERE user_id = ?
` );
return stmt . run ( iconId , userId ) ;
}
// Check if user can set home base (once per day)
canSetHomeBase ( userId ) {
const stmt = this . db . prepare ( `
SELECT last_home_set FROM rpg_stats WHERE user_id = ?
` );
const result = stmt . get ( userId ) ;
if ( ! result || ! result . last_home_set ) return true ;
const lastSet = new Date ( result . last_home_set ) ;
const now = new Date ( ) ;
const hoursSince = ( now - lastSet ) / ( 1000 * 60 * 60 ) ;
return hoursSince >= 24 ;
}
// Handle player death
handlePlayerDeath ( userId , xpPenaltyPercent = 10 ) {
// Get current stats to calculate XP penalty
const stats = this . getRpgStats ( userId ) ;
if ( ! stats ) return null ;
// Calculate XP loss - can't drop below current level threshold
const currentLevel = stats . level ;
const levelThresholds = [ 0 , 100 , 250 , 500 , 800 , 1200 ] ; // XP needed for each level
const minXp = levelThresholds [ currentLevel - 1 ] || 0 ;
const xpLoss = Math . floor ( stats . xp * ( xpPenaltyPercent / 100 ) ) ;
const newXp = Math . max ( minXp , stats . xp - xpLoss ) ;
const stmt = this . db . prepare ( `
UPDATE rpg_stats SET
is_dead = 1 ,
hp = 0 ,
xp = ? ,
updated_at = datetime ( 'now' )
WHERE user_id = ?
` );
stmt . run ( newXp , userId ) ;
return { xpLost : stats . xp - newXp , newXp } ;
}
// Respawn player at home base
respawnPlayer ( userId ) {
const stats = this . getRpgStats ( userId ) ;
if ( ! stats ) return null ;
const stmt = this . db . prepare ( `
UPDATE rpg_stats SET
is_dead = 0 ,
hp = max_hp ,
mp = max_mp ,
updated_at = datetime ( 'now' )
WHERE user_id = ?
` );
return stmt . run ( userId ) ;
}
// Monster entourage methods
getMonsterEntourage ( userId ) {
const stmt = this . db . prepare ( `
@ -611,6 +717,11 @@ class HikeMapDB {
return stmt . run ( userId , monsterId ) ;
}
clearMonsterEntourage ( userId ) {
const stmt = this . db . prepare ( ` DELETE FROM monster_entourage WHERE user_id = ? ` ) ;
return stmt . run ( userId ) ;
}
// Monster type methods
getAllMonsterTypes ( enabledOnly = true ) {
const stmt = enabledOnly
@ -1193,7 +1304,9 @@ class HikeMapDB {
seedDefaultSettings ( ) {
const defaults = {
monsterSpawnInterval : 30000 ,
monsterSpawnInterval : 20000 , // Timer interval in ms (20 seconds)
monsterSpawnChance : 50 , // Percent chance per interval (50%)
monsterSpawnDistance : 10 , // Meters player must move for new spawns (10m)
maxMonstersPerPlayer : 10 ,
xpMultiplier : 1.0 ,
combatEnabled : true