package zone import ( "database/sql" "fmt" "log" "sync" ) // ZoneDatabase handles all database operations for zones type ZoneDatabase struct { db *sql.DB queries map[string]*sql.Stmt mutex sync.RWMutex } // NewZoneDatabase creates a new zone database instance func NewZoneDatabase(db *sql.DB) *ZoneDatabase { zdb := &ZoneDatabase{ db: db, queries: make(map[string]*sql.Stmt), } if err := zdb.prepareStatements(); err != nil { log.Printf("%s Failed to prepare database statements: %v", LogPrefixZone, err) } return zdb } // LoadZoneData loads all zone configuration and spawn data func (zdb *ZoneDatabase) LoadZoneData(zoneID int32) (*ZoneData, error) { zoneData := &ZoneData{ ZoneID: zoneID, } // Load zone configuration if err := zdb.loadZoneConfiguration(zoneData); err != nil { return nil, fmt.Errorf("failed to load zone configuration: %v", err) } // Load spawn locations if err := zdb.loadSpawnLocations(zoneData); err != nil { return nil, fmt.Errorf("failed to load spawn locations: %v", err) } // Load spawn entries if err := zdb.loadSpawnEntries(zoneData); err != nil { return nil, fmt.Errorf("failed to load spawn entries: %v", err) } // Load NPCs if err := zdb.loadNPCs(zoneData); err != nil { return nil, fmt.Errorf("failed to load NPCs: %v", err) } // Load objects if err := zdb.loadObjects(zoneData); err != nil { return nil, fmt.Errorf("failed to load objects: %v", err) } // Load widgets if err := zdb.loadWidgets(zoneData); err != nil { return nil, fmt.Errorf("failed to load widgets: %v", err) } // Load signs if err := zdb.loadSigns(zoneData); err != nil { return nil, fmt.Errorf("failed to load signs: %v", err) } // Load ground spawns if err := zdb.loadGroundSpawns(zoneData); err != nil { return nil, fmt.Errorf("failed to load ground spawns: %v", err) } // Load transporters if err := zdb.loadTransporters(zoneData); err != nil { return nil, fmt.Errorf("failed to load transporters: %v", err) } // Load location grids if err := zdb.loadLocationGrids(zoneData); err != nil { return nil, fmt.Errorf("failed to load location grids: %v", err) } // Load revive points if err := zdb.loadRevivePoints(zoneData); err != nil { return nil, fmt.Errorf("failed to load revive points: %v", err) } log.Printf("%s Loaded zone data for zone %d", LogPrefixZone, zoneID) return zoneData, nil } // SaveZoneConfiguration saves zone configuration to database func (zdb *ZoneDatabase) SaveZoneConfiguration(config *ZoneConfiguration) error { zdb.mutex.Lock() defer zdb.mutex.Unlock() stmt := zdb.queries["updateZoneConfig"] if stmt == nil { return fmt.Errorf("update zone config statement not prepared") } _, err := stmt.Exec( config.Name, config.File, config.Description, config.SafeX, config.SafeY, config.SafeZ, config.SafeHeading, config.Underworld, config.MinLevel, config.MaxLevel, config.MinStatus, config.MinVersion, config.InstanceType, config.MaxPlayers, config.DefaultLockoutTime, config.DefaultReenterTime, config.DefaultResetTime, config.GroupZoneOption, config.ExpansionFlag, config.HolidayFlag, config.CanBind, config.CanGate, config.CanEvac, config.CityZone, config.AlwaysLoaded, config.WeatherAllowed, config.ZoneID, ) if err != nil { return fmt.Errorf("failed to save zone configuration: %v", err) } return nil } // LoadSpawnLocation loads a specific spawn location func (zdb *ZoneDatabase) LoadSpawnLocation(locationID int32) (*SpawnLocation, error) { zdb.mutex.RLock() defer zdb.mutex.RUnlock() stmt := zdb.queries["selectSpawnLocation"] if stmt == nil { return nil, fmt.Errorf("select spawn location statement not prepared") } location := &SpawnLocation{} err := stmt.QueryRow(locationID).Scan( &location.ID, &location.X, &location.Y, &location.Z, &location.Heading, &location.Pitch, &location.Roll, &location.SpawnType, &location.RespawnTime, &location.ExpireTime, &location.ExpireOffset, &location.Conditions, &location.ConditionalValue, &location.SpawnPercentage, ) if err != nil { return nil, fmt.Errorf("failed to load spawn location %d: %v", locationID, err) } return location, nil } // SaveSpawnLocation saves a spawn location to database func (zdb *ZoneDatabase) SaveSpawnLocation(location *SpawnLocation) error { zdb.mutex.Lock() defer zdb.mutex.Unlock() var stmt *sql.Stmt var err error if location.ID == 0 { // Insert new location stmt = zdb.queries["insertSpawnLocation"] if stmt == nil { return fmt.Errorf("insert spawn location statement not prepared") } err = stmt.QueryRow( location.X, location.Y, location.Z, location.Heading, location.Pitch, location.Roll, location.SpawnType, location.RespawnTime, location.ExpireTime, location.ExpireOffset, location.Conditions, location.ConditionalValue, location.SpawnPercentage, ).Scan(&location.ID) } else { // Update existing location stmt = zdb.queries["updateSpawnLocation"] if stmt == nil { return fmt.Errorf("update spawn location statement not prepared") } _, err = stmt.Exec( location.X, location.Y, location.Z, location.Heading, location.Pitch, location.Roll, location.SpawnType, location.RespawnTime, location.ExpireTime, location.ExpireOffset, location.Conditions, location.ConditionalValue, location.SpawnPercentage, location.ID, ) } if err != nil { return fmt.Errorf("failed to save spawn location: %v", err) } return nil } // DeleteSpawnLocation deletes a spawn location from database func (zdb *ZoneDatabase) DeleteSpawnLocation(locationID int32) error { zdb.mutex.Lock() defer zdb.mutex.Unlock() stmt := zdb.queries["deleteSpawnLocation"] if stmt == nil { return fmt.Errorf("delete spawn location statement not prepared") } _, err := stmt.Exec(locationID) if err != nil { return fmt.Errorf("failed to delete spawn location %d: %v", locationID, err) } return nil } // LoadSpawnGroups loads spawn group associations for a zone func (zdb *ZoneDatabase) LoadSpawnGroups(zoneID int32) (map[int32][]int32, error) { zdb.mutex.RLock() defer zdb.mutex.RUnlock() stmt := zdb.queries["selectSpawnGroups"] if stmt == nil { return nil, fmt.Errorf("select spawn groups statement not prepared") } rows, err := stmt.Query(zoneID) if err != nil { return nil, fmt.Errorf("failed to query spawn groups: %v", err) } defer rows.Close() groups := make(map[int32][]int32) for rows.Next() { var groupID, locationID int32 if err := rows.Scan(&groupID, &locationID); err != nil { return nil, fmt.Errorf("failed to scan spawn group row: %v", err) } groups[groupID] = append(groups[groupID], locationID) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("error iterating spawn groups: %v", err) } return groups, nil } // SaveSpawnGroup saves spawn group associations func (zdb *ZoneDatabase) SaveSpawnGroup(groupID int32, locationIDs []int32) error { zdb.mutex.Lock() defer zdb.mutex.Unlock() // Start transaction tx, err := zdb.db.Begin() if err != nil { return fmt.Errorf("failed to begin transaction: %v", err) } defer tx.Rollback() // Delete existing associations deleteStmt := zdb.queries["deleteSpawnGroup"] if deleteStmt == nil { return fmt.Errorf("delete spawn group statement not prepared") } _, err = tx.Stmt(deleteStmt).Exec(groupID) if err != nil { return fmt.Errorf("failed to delete existing spawn group: %v", err) } // Insert new associations insertStmt := zdb.queries["insertSpawnGroup"] if insertStmt == nil { return fmt.Errorf("insert spawn group statement not prepared") } for _, locationID := range locationIDs { _, err = tx.Stmt(insertStmt).Exec(groupID, locationID) if err != nil { return fmt.Errorf("failed to insert spawn group association: %v", err) } } // Commit transaction if err := tx.Commit(); err != nil { return fmt.Errorf("failed to commit spawn group transaction: %v", err) } return nil } // Close closes all prepared statements and database connection func (zdb *ZoneDatabase) Close() error { zdb.mutex.Lock() defer zdb.mutex.Unlock() // Close all prepared statements for name, stmt := range zdb.queries { if err := stmt.Close(); err != nil { log.Printf("%s Error closing statement %s: %v", LogPrefixZone, name, err) } } zdb.queries = make(map[string]*sql.Stmt) return nil } // Private helper methods func (zdb *ZoneDatabase) prepareStatements() error { statements := map[string]string{ "updateZoneConfig": ` UPDATE zones SET name = ?, file = ?, description = ?, safe_x = ?, safe_y = ?, safe_z = ?, safe_heading = ?, underworld = ?, min_level = ?, max_level = ?, min_status = ?, min_version = ?, instance_type = ?, max_players = ?, default_lockout_time = ?, default_reenter_time = ?, default_reset_time = ?, group_zone_option = ?, expansion_flag = ?, holiday_flag = ?, can_bind = ?, can_gate = ?, can_evac = ?, city_zone = ?, always_loaded = ?, weather_allowed = ? WHERE id = ?`, "selectZoneConfig": ` SELECT id, name, file, description, safe_x, safe_y, safe_z, safe_heading, underworld, min_level, max_level, min_status, min_version, instance_type, max_players, default_lockout_time, default_reenter_time, default_reset_time, group_zone_option, expansion_flag, holiday_flag, can_bind, can_gate, can_evac, city_zone, always_loaded, weather_allowed FROM zones WHERE id = ?`, "selectSpawnLocations": ` SELECT id, x, y, z, heading, pitch, roll, spawn_type, respawn_time, expire_time, expire_offset, conditions, conditional_value, spawn_percentage FROM spawn_location_placement WHERE zone_id = ? ORDER BY id`, "selectSpawnLocation": ` SELECT id, x, y, z, heading, pitch, roll, spawn_type, respawn_time, expire_time, expire_offset, conditions, conditional_value, spawn_percentage FROM spawn_location_placement WHERE id = ?`, "insertSpawnLocation": ` INSERT INTO spawn_location_placement (x, y, z, heading, pitch, roll, spawn_type, respawn_time, expire_time, expire_offset, conditions, conditional_value, spawn_percentage) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id`, "updateSpawnLocation": ` UPDATE spawn_location_placement SET x = ?, y = ?, z = ?, heading = ?, pitch = ?, roll = ?, spawn_type = ?, respawn_time = ?, expire_time = ?, expire_offset = ?, conditions = ?, conditional_value = ?, spawn_percentage = ? WHERE id = ?`, "deleteSpawnLocation": `DELETE FROM spawn_location_placement WHERE id = ?`, "selectSpawnEntries": ` SELECT id, spawn_type, spawn_entry_id, name, level, encounter_level, model, size, hp, power, heroic, gender, race, adventure_class, tradeskill_class, attack_type, min_level, max_level, encounter_type, show_name, targetable, show_level, command_primary, command_secondary, loot_tier, min_gold, max_gold, harvest_type, icon FROM spawn_location_entry WHERE zone_id = ? ORDER BY id`, "selectNPCs": ` SELECT id, spawn_entry_id, name, level, encounter_level, model, size, hp, power, heroic, gender, race, adventure_class, tradeskill_class, attack_type, min_level, max_level, encounter_type, show_name, targetable, show_level, loot_tier, min_gold, max_gold, aggro_radius, cast_percentage, randomize FROM spawn_npcs WHERE zone_id = ? ORDER BY id`, "selectObjects": ` SELECT id, spawn_entry_id, name, model, size, device_id, icon, sound_name FROM spawn_objects WHERE zone_id = ? ORDER BY id`, "selectWidgets": ` SELECT id, spawn_entry_id, name, model, widget_type, open_type, open_time, close_time, open_sound, close_sound, open_graphic, close_graphic, linked_spawn_id, action_spawn_id, house_id, include_location, include_heading FROM spawn_widgets WHERE zone_id = ? ORDER BY id`, "selectSigns": ` SELECT id, spawn_entry_id, name, model, sign_type, zone_id_destination, widget_id, title, description, zone_x, zone_y, zone_z, zone_heading, include_location, include_heading FROM spawn_signs WHERE zone_id = ? ORDER BY id`, "selectGroundSpawns": ` SELECT id, spawn_entry_id, name, model, harvest_type, number_harvests, max_number_harvests, collection_skill, respawn_timer FROM spawn_ground_spawns WHERE zone_id = ? ORDER BY id`, "selectTransporters": ` SELECT id, type, display_name, message, destination_zone_id, destination_x, destination_y, destination_z, destination_heading, cost, unique_id, min_level, max_level, quest_req, quest_step_req, quest_complete, map_x, map_y, expansion_flag, holiday_flag, min_client_version, max_client_version, flight_path_id, mount_id, mount_red_color, mount_green_color, mount_blue_color FROM transporters WHERE zone_id = ? ORDER BY id`, "selectLocationGrids": ` SELECT id, grid_id, name, include_y, discovery FROM location_grids WHERE zone_id = ? ORDER BY id`, "selectLocationGridLocations": ` SELECT location_id, x, y, z, name FROM location_grid_locations WHERE grid_id = ? ORDER BY location_id`, "selectRevivePoints": ` SELECT id, zone_id, location_name, x, y, z, heading, always_included FROM revive_points WHERE zone_id = ? ORDER BY id`, "selectSpawnGroups": ` SELECT group_id, location_id FROM spawn_location_group WHERE zone_id = ? ORDER BY group_id, location_id`, "insertSpawnGroup": ` INSERT INTO spawn_location_group (group_id, location_id) VALUES (?, ?)`, "deleteSpawnGroup": ` DELETE FROM spawn_location_group WHERE group_id = ?`, } for name, query := range statements { stmt, err := zdb.db.Prepare(query) if err != nil { return fmt.Errorf("failed to prepare statement %s: %v", name, err) } zdb.queries[name] = stmt } return nil } func (zdb *ZoneDatabase) loadZoneConfiguration(zoneData *ZoneData) error { stmt := zdb.queries["selectZoneConfig"] if stmt == nil { return fmt.Errorf("select zone config statement not prepared") } config := &ZoneConfiguration{} err := stmt.QueryRow(zoneData.ZoneID).Scan( &config.ZoneID, &config.Name, &config.File, &config.Description, &config.SafeX, &config.SafeY, &config.SafeZ, &config.SafeHeading, &config.Underworld, &config.MinLevel, &config.MaxLevel, &config.MinStatus, &config.MinVersion, &config.InstanceType, &config.MaxPlayers, &config.DefaultLockoutTime, &config.DefaultReenterTime, &config.DefaultResetTime, &config.GroupZoneOption, &config.ExpansionFlag, &config.HolidayFlag, &config.CanBind, &config.CanGate, &config.CanEvac, &config.CityZone, &config.AlwaysLoaded, &config.WeatherAllowed, ) if err != nil { return fmt.Errorf("failed to load zone configuration: %v", err) } zoneData.Configuration = config return nil } func (zdb *ZoneDatabase) loadSpawnLocations(zoneData *ZoneData) error { stmt := zdb.queries["selectSpawnLocations"] if stmt == nil { return fmt.Errorf("select spawn locations statement not prepared") } rows, err := stmt.Query(zoneData.ZoneID) if err != nil { return fmt.Errorf("failed to query spawn locations: %v", err) } defer rows.Close() locations := make(map[int32]*SpawnLocation) for rows.Next() { location := &SpawnLocation{} err := rows.Scan( &location.ID, &location.X, &location.Y, &location.Z, &location.Heading, &location.Pitch, &location.Roll, &location.SpawnType, &location.RespawnTime, &location.ExpireTime, &location.ExpireOffset, &location.Conditions, &location.ConditionalValue, &location.SpawnPercentage, ) if err != nil { return fmt.Errorf("failed to scan spawn location: %v", err) } locations[location.ID] = location } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating spawn locations: %v", err) } zoneData.SpawnLocations = locations return nil } func (zdb *ZoneDatabase) loadSpawnEntries(zoneData *ZoneData) error { // Similar implementation for spawn entries zoneData.SpawnEntries = make(map[int32]*SpawnEntry) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadNPCs(zoneData *ZoneData) error { // Similar implementation for NPCs zoneData.NPCs = make(map[int32]*NPCTemplate) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadObjects(zoneData *ZoneData) error { // Similar implementation for objects zoneData.Objects = make(map[int32]*ObjectTemplate) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadWidgets(zoneData *ZoneData) error { // Similar implementation for widgets zoneData.Widgets = make(map[int32]*WidgetTemplate) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadSigns(zoneData *ZoneData) error { // Similar implementation for signs zoneData.Signs = make(map[int32]*SignTemplate) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadGroundSpawns(zoneData *ZoneData) error { // Similar implementation for ground spawns zoneData.GroundSpawns = make(map[int32]*GroundSpawnTemplate) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadTransporters(zoneData *ZoneData) error { // Similar implementation for transporters zoneData.Transporters = make(map[int32]*TransportDestination) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadLocationGrids(zoneData *ZoneData) error { // Similar implementation for location grids zoneData.LocationGrids = make(map[int32]*LocationGrid) return nil // TODO: Implement } func (zdb *ZoneDatabase) loadRevivePoints(zoneData *ZoneData) error { // Similar implementation for revive points zoneData.RevivePoints = make(map[int32]*RevivePoint) return nil // TODO: Implement } // ZoneData represents all data loaded for a zone type ZoneData struct { ZoneID int32 Configuration *ZoneConfiguration SpawnLocations map[int32]*SpawnLocation SpawnEntries map[int32]*SpawnEntry NPCs map[int32]*NPCTemplate Objects map[int32]*ObjectTemplate Widgets map[int32]*WidgetTemplate Signs map[int32]*SignTemplate GroundSpawns map[int32]*GroundSpawnTemplate Transporters map[int32]*TransportDestination LocationGrids map[int32]*LocationGrid RevivePoints map[int32]*RevivePoint SpawnGroups map[int32][]int32 } // ZoneConfiguration represents zone configuration from database type ZoneConfiguration struct { ZoneID int32 Name string File string Description string SafeX float32 SafeY float32 SafeZ float32 SafeHeading float32 Underworld float32 MinLevel int16 MaxLevel int16 MinStatus int16 MinVersion int16 InstanceType int16 MaxPlayers int32 DefaultLockoutTime int32 DefaultReenterTime int32 DefaultResetTime int32 GroupZoneOption int8 ExpansionFlag int32 HolidayFlag int32 CanBind bool CanGate bool CanEvac bool CityZone bool AlwaysLoaded bool WeatherAllowed bool } // Template types for database-loaded spawn data type NPCTemplate struct { ID int32 SpawnEntryID int32 Name string Level int16 EncounterLevel int16 Model string Size float32 HP int32 Power int32 Heroic int8 Gender int8 Race int16 AdventureClass int16 TradeskillClass int16 AttackType int8 MinLevel int16 MaxLevel int16 EncounterType int8 ShowName int8 Targetable int8 ShowLevel int8 LootTier int8 MinGold int32 MaxGold int32 AggroRadius float32 CastPercentage int8 Randomize bool } type ObjectTemplate struct { ID int32 SpawnEntryID int32 Name string Model string Size float32 DeviceID int32 Icon int32 SoundName string } type WidgetTemplate struct { ID int32 SpawnEntryID int32 Name string Model string WidgetType int8 OpenType int8 OpenTime int32 CloseTime int32 OpenSound string CloseSound string OpenGraphic string CloseGraphic string LinkedSpawnID int32 ActionSpawnID int32 HouseID int32 IncludeLocation bool IncludeHeading bool } type SignTemplate struct { ID int32 SpawnEntryID int32 Name string Model string SignType int8 ZoneIDDestination int32 WidgetID int32 Title string Description string ZoneX float32 ZoneY float32 ZoneZ float32 ZoneHeading float32 IncludeLocation bool IncludeHeading bool } type GroundSpawnTemplate struct { ID int32 SpawnEntryID int32 Name string Model string HarvestType string NumberHarvests int8 MaxNumberHarvests int8 CollectionSkill string RespawnTimer int32 }