fix zone
This commit is contained in:
parent
cc49aac689
commit
8f8dbefece
@ -1,31 +1,25 @@
|
||||
package zone
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// ZoneDatabase handles all database operations for zones
|
||||
type ZoneDatabase struct {
|
||||
db *sql.DB
|
||||
queries map[string]*sql.Stmt
|
||||
mutex sync.RWMutex
|
||||
conn *sqlite.Conn
|
||||
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),
|
||||
func NewZoneDatabase(conn *sqlite.Conn) *ZoneDatabase {
|
||||
return &ZoneDatabase{
|
||||
conn: conn,
|
||||
}
|
||||
|
||||
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
|
||||
@ -98,40 +92,46 @@ 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")
|
||||
}
|
||||
query := `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 = ?`
|
||||
|
||||
_, 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,
|
||||
)
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
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)
|
||||
@ -145,33 +145,40 @@ func (zdb *ZoneDatabase) LoadSpawnLocation(locationID int32) (*SpawnLocation, er
|
||||
zdb.mutex.RLock()
|
||||
defer zdb.mutex.RUnlock()
|
||||
|
||||
stmt := zdb.queries["selectSpawnLocation"]
|
||||
if stmt == nil {
|
||||
return nil, fmt.Errorf("select spawn location statement not prepared")
|
||||
}
|
||||
query := `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 = ?`
|
||||
|
||||
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,
|
||||
)
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{locationID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
location.ID = int32(stmt.ColumnInt64(0))
|
||||
location.X = float32(stmt.ColumnFloat(1))
|
||||
location.Y = float32(stmt.ColumnFloat(2))
|
||||
location.Z = float32(stmt.ColumnFloat(3))
|
||||
location.Heading = float32(stmt.ColumnFloat(4))
|
||||
location.Pitch = float32(stmt.ColumnFloat(5))
|
||||
location.Roll = float32(stmt.ColumnFloat(6))
|
||||
location.SpawnType = int8(stmt.ColumnInt64(7))
|
||||
location.RespawnTime = int32(stmt.ColumnInt64(8))
|
||||
location.ExpireTime = int32(stmt.ColumnInt64(9))
|
||||
location.ExpireOffset = int32(stmt.ColumnInt64(10))
|
||||
location.Conditions = int8(stmt.ColumnInt64(11))
|
||||
location.ConditionalValue = int32(stmt.ColumnInt64(12))
|
||||
location.SpawnPercentage = float32(stmt.ColumnFloat(13))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load spawn location %d: %v", locationID, err)
|
||||
}
|
||||
|
||||
if location.ID == 0 {
|
||||
return nil, fmt.Errorf("spawn location %d not found", locationID)
|
||||
}
|
||||
|
||||
return location, nil
|
||||
}
|
||||
|
||||
@ -180,58 +187,67 @@ 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")
|
||||
}
|
||||
query := `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`
|
||||
|
||||
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)
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
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,
|
||||
},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
location.ID = int32(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert spawn location: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Update existing location
|
||||
stmt = zdb.queries["updateSpawnLocation"]
|
||||
if stmt == nil {
|
||||
return fmt.Errorf("update spawn location statement not prepared")
|
||||
query := `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 = ?`
|
||||
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
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 update spawn location: %v", err)
|
||||
}
|
||||
|
||||
_, 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
|
||||
@ -242,12 +258,10 @@ 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)
|
||||
query := `DELETE FROM spawn_location_placement WHERE id = ?`
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{locationID},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete spawn location %d: %v", locationID, err)
|
||||
}
|
||||
@ -260,30 +274,23 @@ 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()
|
||||
query := `SELECT group_id, location_id
|
||||
FROM spawn_location_group WHERE zone_id = ?
|
||||
ORDER BY group_id, location_id`
|
||||
|
||||
groups := make(map[int32][]int32)
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{zoneID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
groupID := int32(stmt.ColumnInt64(0))
|
||||
locationID := int32(stmt.ColumnInt64(1))
|
||||
groups[groupID] = append(groups[groupID], locationID)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load spawn groups: %v", err)
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
@ -294,288 +301,134 @@ func (zdb *ZoneDatabase) SaveSpawnGroup(groupID int32, locationIDs []int32) erro
|
||||
zdb.mutex.Lock()
|
||||
defer zdb.mutex.Unlock()
|
||||
|
||||
// Start transaction
|
||||
tx, err := zdb.db.Begin()
|
||||
// Use transaction for atomic operations
|
||||
endFn, err := sqlitex.ImmediateTransaction(zdb.conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %v", err)
|
||||
return fmt.Errorf("failed to start transaction: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
defer endFn(&err)
|
||||
|
||||
// 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)
|
||||
deleteQuery := `DELETE FROM spawn_location_group WHERE group_id = ?`
|
||||
err = sqlitex.Execute(zdb.conn, deleteQuery, &sqlitex.ExecOptions{
|
||||
Args: []any{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")
|
||||
}
|
||||
|
||||
insertQuery := `INSERT INTO spawn_location_group (group_id, location_id) VALUES (?, ?)`
|
||||
for _, locationID := range locationIDs {
|
||||
_, err = tx.Stmt(insertStmt).Exec(groupID, locationID)
|
||||
err = sqlitex.Execute(zdb.conn, insertQuery, &sqlitex.ExecOptions{
|
||||
Args: []any{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
|
||||
// Close closes the 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
|
||||
return nil // Connection is managed externally
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
query := `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 = ?`
|
||||
|
||||
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,
|
||||
)
|
||||
found := false
|
||||
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{zoneData.ZoneID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
found = true
|
||||
config.ZoneID = int32(stmt.ColumnInt64(0))
|
||||
config.Name = stmt.ColumnText(1)
|
||||
config.File = stmt.ColumnText(2)
|
||||
config.Description = stmt.ColumnText(3)
|
||||
config.SafeX = float32(stmt.ColumnFloat(4))
|
||||
config.SafeY = float32(stmt.ColumnFloat(5))
|
||||
config.SafeZ = float32(stmt.ColumnFloat(6))
|
||||
config.SafeHeading = float32(stmt.ColumnFloat(7))
|
||||
config.Underworld = float32(stmt.ColumnFloat(8))
|
||||
config.MinLevel = int16(stmt.ColumnInt64(9))
|
||||
config.MaxLevel = int16(stmt.ColumnInt64(10))
|
||||
config.MinStatus = int16(stmt.ColumnInt64(11))
|
||||
config.MinVersion = int16(stmt.ColumnInt64(12))
|
||||
config.InstanceType = int16(stmt.ColumnInt64(13))
|
||||
config.MaxPlayers = int32(stmt.ColumnInt64(14))
|
||||
config.DefaultLockoutTime = int32(stmt.ColumnInt64(15))
|
||||
config.DefaultReenterTime = int32(stmt.ColumnInt64(16))
|
||||
config.DefaultResetTime = int32(stmt.ColumnInt64(17))
|
||||
config.GroupZoneOption = int8(stmt.ColumnInt64(18))
|
||||
config.ExpansionFlag = int32(stmt.ColumnInt64(19))
|
||||
config.HolidayFlag = int32(stmt.ColumnInt64(20))
|
||||
config.CanBind = stmt.ColumnInt64(21) != 0
|
||||
config.CanGate = stmt.ColumnInt64(22) != 0
|
||||
config.CanEvac = stmt.ColumnInt64(23) != 0
|
||||
config.CityZone = stmt.ColumnInt64(24) != 0
|
||||
config.AlwaysLoaded = stmt.ColumnInt64(25) != 0
|
||||
config.WeatherAllowed = stmt.ColumnInt64(26) != 0
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load zone configuration: %v", err)
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("zone configuration not found for zone %d", zoneData.ZoneID)
|
||||
}
|
||||
|
||||
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()
|
||||
query := `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`
|
||||
|
||||
locations := make(map[int32]*SpawnLocation)
|
||||
err := sqlitex.Execute(zdb.conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{zoneData.ZoneID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
location := &SpawnLocation{
|
||||
ID: int32(stmt.ColumnInt64(0)),
|
||||
X: float32(stmt.ColumnFloat(1)),
|
||||
Y: float32(stmt.ColumnFloat(2)),
|
||||
Z: float32(stmt.ColumnFloat(3)),
|
||||
Heading: float32(stmt.ColumnFloat(4)),
|
||||
Pitch: float32(stmt.ColumnFloat(5)),
|
||||
Roll: float32(stmt.ColumnFloat(6)),
|
||||
SpawnType: int8(stmt.ColumnInt64(7)),
|
||||
RespawnTime: int32(stmt.ColumnInt64(8)),
|
||||
ExpireTime: int32(stmt.ColumnInt64(9)),
|
||||
ExpireOffset: int32(stmt.ColumnInt64(10)),
|
||||
Conditions: int8(stmt.ColumnInt64(11)),
|
||||
ConditionalValue: int32(stmt.ColumnInt64(12)),
|
||||
SpawnPercentage: float32(stmt.ColumnFloat(13)),
|
||||
}
|
||||
locations[location.ID] = location
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load spawn locations: %v", err)
|
||||
}
|
||||
|
||||
zoneData.SpawnLocations = locations
|
||||
@ -583,57 +436,57 @@ func (zdb *ZoneDatabase) loadSpawnLocations(zoneData *ZoneData) error {
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadSpawnEntries(zoneData *ZoneData) error {
|
||||
// Similar implementation for spawn entries
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.SpawnEntries = make(map[int32]*SpawnEntry)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadNPCs(zoneData *ZoneData) error {
|
||||
// Similar implementation for NPCs
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.NPCs = make(map[int32]*NPCTemplate)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadObjects(zoneData *ZoneData) error {
|
||||
// Similar implementation for objects
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.Objects = make(map[int32]*ObjectTemplate)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadWidgets(zoneData *ZoneData) error {
|
||||
// Similar implementation for widgets
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.Widgets = make(map[int32]*WidgetTemplate)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadSigns(zoneData *ZoneData) error {
|
||||
// Similar implementation for signs
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.Signs = make(map[int32]*SignTemplate)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadGroundSpawns(zoneData *ZoneData) error {
|
||||
// Similar implementation for ground spawns
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.GroundSpawns = make(map[int32]*GroundSpawnTemplate)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadTransporters(zoneData *ZoneData) error {
|
||||
// Similar implementation for transporters
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.Transporters = make(map[int32]*TransportDestination)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadLocationGrids(zoneData *ZoneData) error {
|
||||
// Similar implementation for location grids
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.LocationGrids = make(map[int32]*LocationGrid)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zdb *ZoneDatabase) loadRevivePoints(zoneData *ZoneData) error {
|
||||
// Similar implementation for revive points
|
||||
// Placeholder implementation - initialize empty map
|
||||
zoneData.RevivePoints = make(map[int32]*RevivePoint)
|
||||
return nil // TODO: Implement
|
||||
return nil
|
||||
}
|
||||
|
||||
// ZoneData represents all data loaded for a zone
|
||||
@ -775,3 +628,7 @@ type GroundSpawnTemplate struct {
|
||||
CollectionSkill string
|
||||
RespawnTimer int32
|
||||
}
|
||||
|
||||
// Types are already defined in interfaces.go
|
||||
|
||||
// All types are defined in interfaces.go
|
@ -121,7 +121,11 @@ func (mm *MobMovementManager) Process() error {
|
||||
func (mm *MobMovementManager) AddMovementSpawn(spawnID int32) {
|
||||
mm.mutex.Lock()
|
||||
defer mm.mutex.Unlock()
|
||||
mm.addMovementSpawnInternal(spawnID)
|
||||
}
|
||||
|
||||
// addMovementSpawnInternal adds a spawn to movement tracking without acquiring lock
|
||||
func (mm *MobMovementManager) addMovementSpawnInternal(spawnID int32) {
|
||||
if _, exists := mm.movementSpawns[spawnID]; exists {
|
||||
return // Already tracking
|
||||
}
|
||||
@ -131,7 +135,10 @@ func (mm *MobMovementManager) AddMovementSpawn(spawnID int32) {
|
||||
return
|
||||
}
|
||||
|
||||
x, y, z, heading := spawn.GetPosition()
|
||||
x := spawn.GetX()
|
||||
y := spawn.GetY()
|
||||
z := spawn.GetZ()
|
||||
heading := spawn.GetHeading()
|
||||
|
||||
mm.movementSpawns[spawnID] = &MovementState{
|
||||
SpawnID: spawnID,
|
||||
@ -234,7 +241,10 @@ func (mm *MobMovementManager) EvadeCombat(spawnID int32) error {
|
||||
|
||||
// Get spawn's original position (would need to be stored somewhere)
|
||||
// For now, use current position as placeholder
|
||||
x, y, z, heading := spawn.GetPosition()
|
||||
x := spawn.GetX()
|
||||
y := spawn.GetY()
|
||||
z := spawn.GetZ()
|
||||
heading := spawn.GetHeading()
|
||||
|
||||
command := &MovementCommand{
|
||||
Type: MovementCommandEvadeCombat,
|
||||
@ -257,7 +267,7 @@ func (mm *MobMovementManager) QueueCommand(spawnID int32, command *MovementComma
|
||||
|
||||
// Ensure spawn is being tracked
|
||||
if _, exists := mm.movementSpawns[spawnID]; !exists {
|
||||
mm.AddMovementSpawn(spawnID)
|
||||
mm.addMovementSpawnInternal(spawnID)
|
||||
}
|
||||
|
||||
// Add command to queue
|
||||
@ -373,7 +383,9 @@ func (mm *MobMovementManager) processMovementCommand(spawn *spawn.Spawn, state *
|
||||
}
|
||||
|
||||
func (mm *MobMovementManager) processMoveTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
|
||||
currentX, currentY, currentZ, currentHeading := spawn.GetPosition()
|
||||
currentX := spawn.GetX()
|
||||
currentY := spawn.GetY()
|
||||
currentZ := spawn.GetZ()
|
||||
|
||||
// Calculate distance to target
|
||||
distanceToTarget := Distance3D(currentX, currentY, currentZ, command.TargetX, command.TargetY, command.TargetZ)
|
||||
@ -421,7 +433,10 @@ func (mm *MobMovementManager) processMoveTo(spawn *spawn.Spawn, state *MovementS
|
||||
newHeading := CalculateHeading(currentX, currentY, command.TargetX, command.TargetY)
|
||||
|
||||
// Update spawn position
|
||||
spawn.SetPosition(newX, newY, newZ, newHeading)
|
||||
spawn.SetX(newX)
|
||||
spawn.SetY(newY, false)
|
||||
spawn.SetZ(newZ)
|
||||
spawn.SetHeadingFromFloat(newHeading)
|
||||
|
||||
// Update state
|
||||
state.LastPosition.Set(newX, newY, newZ, newHeading)
|
||||
@ -441,7 +456,10 @@ func (mm *MobMovementManager) processSwimTo(spawn *spawn.Spawn, state *MovementS
|
||||
|
||||
func (mm *MobMovementManager) processTeleportTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
|
||||
// Instant teleport
|
||||
spawn.SetPosition(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)
|
||||
spawn.SetX(command.TargetX)
|
||||
spawn.SetY(command.TargetY, false)
|
||||
spawn.SetZ(command.TargetZ)
|
||||
spawn.SetHeadingFromFloat(command.TargetHeading)
|
||||
|
||||
// Update state
|
||||
state.LastPosition.Set(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)
|
||||
@ -458,14 +476,14 @@ func (mm *MobMovementManager) processTeleportTo(spawn *spawn.Spawn, state *Movem
|
||||
}
|
||||
|
||||
func (mm *MobMovementManager) processRotateTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
|
||||
currentX, currentY, currentZ, currentHeading := spawn.GetPosition()
|
||||
currentHeading := spawn.GetHeading()
|
||||
|
||||
// Calculate heading difference
|
||||
headingDiff := HeadingDifference(currentHeading, command.TargetHeading)
|
||||
|
||||
// Check if we've reached the target heading
|
||||
if abs(headingDiff) <= 1.0 { // Close enough threshold
|
||||
spawn.SetPosition(currentX, currentY, currentZ, command.TargetHeading)
|
||||
spawn.SetHeadingFromFloat(command.TargetHeading)
|
||||
mm.zone.markSpawnChanged(spawn.GetID())
|
||||
return true, nil
|
||||
}
|
||||
@ -490,7 +508,7 @@ func (mm *MobMovementManager) processRotateTo(spawn *spawn.Spawn, state *Movemen
|
||||
|
||||
// Apply rotation
|
||||
newHeading := NormalizeHeading(currentHeading + rotation)
|
||||
spawn.SetPosition(currentX, currentY, currentZ, newHeading)
|
||||
spawn.SetHeadingFromFloat(newHeading)
|
||||
|
||||
// Update state
|
||||
state.LastPosition.Heading = newHeading
|
||||
@ -526,7 +544,9 @@ func (mm *MobMovementManager) getNextCommand(spawnID int32) *MovementCommand {
|
||||
}
|
||||
|
||||
func (mm *MobMovementManager) checkStuck(spawn *spawn.Spawn, state *MovementState) bool {
|
||||
currentX, currentY, currentZ, _ := spawn.GetPosition()
|
||||
currentX := spawn.GetX()
|
||||
currentY := spawn.GetY()
|
||||
currentZ := spawn.GetZ()
|
||||
|
||||
// Check if spawn has moved significantly since last update
|
||||
if state.LastPosition != nil {
|
||||
@ -546,7 +566,10 @@ func (mm *MobMovementManager) handleStuck(spawn *spawn.Spawn, state *MovementSta
|
||||
// Get or create stuck info
|
||||
stuckInfo, exists := mm.stuckSpawns[spawnID]
|
||||
if !exists {
|
||||
currentX, currentY, currentZ, currentHeading := spawn.GetPosition()
|
||||
currentX := spawn.GetX()
|
||||
currentY := spawn.GetY()
|
||||
currentZ := spawn.GetZ()
|
||||
currentHeading := spawn.GetHeading()
|
||||
stuckInfo = &StuckInfo{
|
||||
Position: NewPosition(currentX, currentY, currentZ, currentHeading),
|
||||
StuckCount: 0,
|
||||
@ -588,8 +611,6 @@ func (mm *MobMovementManager) handleStuckWithRun(spawn *spawn.Spawn, state *Move
|
||||
}
|
||||
|
||||
// Try a slightly different path
|
||||
currentX, currentY, currentZ, _ := spawn.GetPosition()
|
||||
|
||||
// Add some randomness to the movement
|
||||
offsetX := float32((time.Now().UnixNano()%100 - 50)) / 50.0 * 2.0
|
||||
offsetY := float32((time.Now().UnixNano()%100 - 50)) / 50.0 * 2.0
|
||||
@ -607,7 +628,10 @@ func (mm *MobMovementManager) handleStuckWithRun(spawn *spawn.Spawn, state *Move
|
||||
|
||||
func (mm *MobMovementManager) handleStuckWithWarp(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
|
||||
// Teleport directly to target
|
||||
spawn.SetPosition(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)
|
||||
spawn.SetX(command.TargetX)
|
||||
spawn.SetY(command.TargetY, false)
|
||||
spawn.SetZ(command.TargetZ)
|
||||
spawn.SetHeadingFromFloat(command.TargetHeading)
|
||||
mm.zone.markSpawnChanged(spawn.GetID())
|
||||
|
||||
log.Printf("%s Warped stuck spawn %d to target", LogPrefixMovement, spawn.GetID())
|
||||
@ -617,7 +641,10 @@ func (mm *MobMovementManager) handleStuckWithWarp(spawn *spawn.Spawn, state *Mov
|
||||
func (mm *MobMovementManager) handleStuckWithEvade(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
|
||||
// Return to original position (evade)
|
||||
if stuckInfo.Position != nil {
|
||||
spawn.SetPosition(stuckInfo.Position.X, stuckInfo.Position.Y, stuckInfo.Position.Z, stuckInfo.Position.Heading)
|
||||
spawn.SetX(stuckInfo.Position.X)
|
||||
spawn.SetY(stuckInfo.Position.Y, false)
|
||||
spawn.SetZ(stuckInfo.Position.Z)
|
||||
spawn.SetHeadingFromFloat(stuckInfo.Position.Heading)
|
||||
mm.zone.markSpawnChanged(spawn.GetID())
|
||||
|
||||
log.Printf("%s Evaded stuck spawn %d to original position", LogPrefixMovement, spawn.GetID())
|
||||
|
@ -260,7 +260,7 @@ func ValidatePath(path *Path) error {
|
||||
}
|
||||
|
||||
if totalDistance > MaxPathDistance {
|
||||
return fmt.Errorf("path is too long: %.2f > %.2f", totalDistance, MaxPathDistance)
|
||||
return fmt.Errorf("path is too long: %.2f > %d", totalDistance, MaxPathDistance)
|
||||
}
|
||||
|
||||
path.Distance = totalDistance
|
||||
|
@ -2,6 +2,7 @@ package zone
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// Position represents a 3D position with heading
|
||||
@ -421,10 +422,10 @@ func InterpolateHeading(from, to, t float32) float32 {
|
||||
// GetRandomPositionInRadius generates a random position within the given radius
|
||||
func GetRandomPositionInRadius(centerX, centerY, centerZ float32, radius float32) *Position {
|
||||
// Generate random angle
|
||||
angle := float32(math.Random() * 2 * math.Pi)
|
||||
angle := float32(rand.Float64() * 2 * math.Pi)
|
||||
|
||||
// Generate random distance (uniform distribution in circle)
|
||||
distance := float32(math.Sqrt(math.Random())) * radius
|
||||
distance := float32(math.Sqrt(rand.Float64())) * radius
|
||||
|
||||
// Calculate new position
|
||||
x := centerX + distance*float32(math.Cos(float64(angle)))
|
||||
|
@ -33,7 +33,7 @@ const (
|
||||
const (
|
||||
RegionFileExtension = ".region"
|
||||
WaterVolumeType = "watervol"
|
||||
WaterRegionType = "waterregion"
|
||||
WaterRegionString = "waterregion"
|
||||
WaterRegion2Type = "water_region"
|
||||
OceanType = "ocean"
|
||||
WaterType = "water"
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewRegionManager creates a new region manager for a zone
|
||||
@ -30,7 +29,6 @@ func (rm *RegionManager) GetRegionMap(version int32) RegionMap {
|
||||
|
||||
// ReturnRegionType returns the region type at the given location for a client version
|
||||
func (rm *RegionManager) ReturnRegionType(location [3]float32, gridID int32, version int32) WaterRegionType {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
atomic.AddInt64(&rm.regionChecks, 1)
|
||||
// Update average check time (simplified moving average)
|
||||
|
@ -3,7 +3,6 @@ package region
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewRegionMapRange creates a new region map range for a zone
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/common"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
@ -74,6 +73,7 @@ type ZoneServer struct {
|
||||
// Player and client management
|
||||
clients []Client
|
||||
numPlayers int32
|
||||
maxPlayers int32
|
||||
incomingClients int32
|
||||
lifetimeClientCount int32
|
||||
|
||||
@ -138,20 +138,20 @@ type ZoneServer struct {
|
||||
tradeskillMgr TradeskillManager
|
||||
|
||||
// Timers
|
||||
aggroTimer *common.Timer
|
||||
charsheetChanges *common.Timer
|
||||
clientSave *common.Timer
|
||||
locationProxTimer *common.Timer
|
||||
movementTimer *common.Timer
|
||||
regenTimer *common.Timer
|
||||
respawnTimer *common.Timer
|
||||
shutdownTimer *common.Timer
|
||||
spawnRangeTimer *common.Timer
|
||||
spawnUpdateTimer *common.Timer
|
||||
syncGameTimer *common.Timer
|
||||
trackingTimer *common.Timer
|
||||
weatherTimer *common.Timer
|
||||
widgetTimer *common.Timer
|
||||
aggroTimer *time.Timer
|
||||
charsheetChanges *time.Timer
|
||||
clientSave *time.Timer
|
||||
locationProxTimer *time.Timer
|
||||
movementTimer *time.Timer
|
||||
regenTimer *time.Timer
|
||||
respawnTimer *time.Timer
|
||||
shutdownTimer *time.Timer
|
||||
spawnRangeTimer *time.Timer
|
||||
spawnUpdateTimer *time.Timer
|
||||
syncGameTimer *time.Timer
|
||||
trackingTimer *time.Timer
|
||||
weatherTimer *time.Timer
|
||||
widgetTimer *time.Timer
|
||||
|
||||
// Proximity systems
|
||||
playerProximities map[int32]*PlayerProximity
|
||||
@ -405,7 +405,7 @@ const (
|
||||
SpawnScriptHealthChanged
|
||||
SpawnScriptRandomChat
|
||||
SpawnScriptConversation
|
||||
SpawnScriptTimer
|
||||
SpawnScriptTimerEvent
|
||||
SpawnScriptCustom
|
||||
SpawnScriptHailedBusy
|
||||
SpawnScriptCastedOn
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// ZoneManager manages all active zones in the server
|
||||
@ -14,7 +14,7 @@ type ZoneManager struct {
|
||||
zones map[int32]*ZoneServer
|
||||
zonesByName map[string]*ZoneServer
|
||||
instanceZones map[int32]*ZoneServer
|
||||
db *database.Database
|
||||
db *sqlite.Conn
|
||||
config *ZoneManagerConfig
|
||||
shutdownSignal chan struct{}
|
||||
isShuttingDown bool
|
||||
@ -39,7 +39,7 @@ type ZoneManagerConfig struct {
|
||||
}
|
||||
|
||||
// NewZoneManager creates a new zone manager
|
||||
func NewZoneManager(config *ZoneManagerConfig, db *database.Database) *ZoneManager {
|
||||
func NewZoneManager(config *ZoneManagerConfig, db *sqlite.Conn) *ZoneManager {
|
||||
if config.ProcessInterval == 0 {
|
||||
config.ProcessInterval = time.Millisecond * 100 // 10 FPS default
|
||||
}
|
||||
@ -136,7 +136,7 @@ func (zm *ZoneManager) LoadZone(zoneID int32) (*ZoneServer, error) {
|
||||
}
|
||||
|
||||
// Load zone data from database
|
||||
zoneDB := NewZoneDatabase(zm.db.DB)
|
||||
zoneDB := NewZoneDatabase(zm.db)
|
||||
zoneData, err := zoneDB.LoadZoneData(zoneID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load zone data: %v", err)
|
||||
@ -234,7 +234,7 @@ func (zm *ZoneManager) CreateInstance(baseZoneID int32, instanceType InstanceTyp
|
||||
}
|
||||
|
||||
// Load base zone data
|
||||
zoneDB := NewZoneDatabase(zm.db.DB)
|
||||
zoneDB := NewZoneDatabase(zm.db)
|
||||
zoneData, err := zoneDB.LoadZoneData(baseZoneID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load base zone data: %v", err)
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/common"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
@ -55,6 +54,7 @@ func (zs *ZoneServer) Initialize(config *ZoneServerConfig) error {
|
||||
// Set zone limits
|
||||
zs.minimumLevel = config.MinLevel
|
||||
zs.maximumLevel = config.MaxLevel
|
||||
zs.maxPlayers = config.MaxPlayers
|
||||
|
||||
// Set safe coordinates
|
||||
zs.safeX = config.SafeX
|
||||
@ -304,7 +304,9 @@ func (zs *ZoneServer) GetSpawnsByRange(x, y, z, maxRange float32) []*spawn.Spawn
|
||||
maxRangeSquared := maxRange * maxRange
|
||||
|
||||
for _, spawn := range zs.spawnList {
|
||||
spawnX, spawnY, spawnZ, _ := spawn.GetPosition()
|
||||
spawnX := spawn.GetX()
|
||||
spawnY := spawn.GetY()
|
||||
spawnZ := spawn.GetZ()
|
||||
distSquared := Distance3DSquared(x, y, z, spawnX, spawnY, spawnZ)
|
||||
|
||||
if distSquared <= maxRangeSquared {
|
||||
@ -467,6 +469,11 @@ func (zs *ZoneServer) GetNumPlayers() int32 {
|
||||
|
||||
// GetMaxPlayers returns the maximum number of players allowed
|
||||
func (zs *ZoneServer) GetMaxPlayers() int32 {
|
||||
// Use configured max players if set, otherwise use instance/default values
|
||||
if zs.maxPlayers > 0 {
|
||||
return zs.maxPlayers
|
||||
}
|
||||
|
||||
if zs.isInstance {
|
||||
// Instance zones have different limits based on type
|
||||
switch zs.instanceType {
|
||||
@ -525,20 +532,20 @@ func (zs *ZoneServer) GetWatchdogTime() int32 {
|
||||
// Private helper methods
|
||||
|
||||
func (zs *ZoneServer) initializeTimers() error {
|
||||
zs.aggroTimer = common.NewTimer(AggroCheckInterval)
|
||||
zs.charsheetChanges = common.NewTimer(CharsheetUpdateInterval)
|
||||
zs.clientSave = common.NewTimer(ClientSaveInterval)
|
||||
zs.locationProxTimer = common.NewTimer(LocationProximityInterval)
|
||||
zs.movementTimer = common.NewTimer(MovementUpdateInterval)
|
||||
zs.regenTimer = common.NewTimer(DefaultTimerInterval)
|
||||
zs.respawnTimer = common.NewTimer(RespawnCheckInterval)
|
||||
zs.shutdownTimer = common.NewTimer(0) // Disabled by default
|
||||
zs.spawnRangeTimer = common.NewTimer(SpawnRangeUpdateInterval)
|
||||
zs.spawnUpdateTimer = common.NewTimer(DefaultTimerInterval)
|
||||
zs.syncGameTimer = common.NewTimer(DefaultTimerInterval)
|
||||
zs.trackingTimer = common.NewTimer(TrackingUpdateInterval)
|
||||
zs.weatherTimer = common.NewTimer(WeatherUpdateInterval)
|
||||
zs.widgetTimer = common.NewTimer(WidgetUpdateInterval)
|
||||
zs.aggroTimer = time.NewTimer(AggroCheckInterval)
|
||||
zs.charsheetChanges = time.NewTimer(CharsheetUpdateInterval)
|
||||
zs.clientSave = time.NewTimer(ClientSaveInterval)
|
||||
zs.locationProxTimer = time.NewTimer(LocationProximityInterval)
|
||||
zs.movementTimer = time.NewTimer(MovementUpdateInterval)
|
||||
zs.regenTimer = time.NewTimer(DefaultTimerInterval)
|
||||
zs.respawnTimer = time.NewTimer(RespawnCheckInterval)
|
||||
zs.shutdownTimer = time.NewTimer(0) // Disabled by default
|
||||
zs.spawnRangeTimer = time.NewTimer(SpawnRangeUpdateInterval)
|
||||
zs.spawnUpdateTimer = time.NewTimer(DefaultTimerInterval)
|
||||
zs.syncGameTimer = time.NewTimer(DefaultTimerInterval)
|
||||
zs.trackingTimer = time.NewTimer(TrackingUpdateInterval)
|
||||
zs.weatherTimer = time.NewTimer(WeatherUpdateInterval)
|
||||
zs.widgetTimer = time.NewTimer(WidgetUpdateInterval)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -662,17 +669,26 @@ func (zs *ZoneServer) processSpawns() {
|
||||
}
|
||||
|
||||
func (zs *ZoneServer) processTimers() {
|
||||
// Check and process all timers
|
||||
if zs.aggroTimer.Check() {
|
||||
// Check and process all timers using non-blocking selects
|
||||
select {
|
||||
case <-zs.aggroTimer.C:
|
||||
zs.processAggroChecks()
|
||||
zs.aggroTimer.Reset(AggroCheckInterval)
|
||||
default:
|
||||
}
|
||||
|
||||
if zs.respawnTimer.Check() {
|
||||
select {
|
||||
case <-zs.respawnTimer.C:
|
||||
zs.processRespawns()
|
||||
zs.respawnTimer.Reset(RespawnCheckInterval)
|
||||
default:
|
||||
}
|
||||
|
||||
if zs.widgetTimer.Check() {
|
||||
select {
|
||||
case <-zs.widgetTimer.C:
|
||||
zs.processWidgets()
|
||||
zs.widgetTimer.Reset(WidgetUpdateInterval)
|
||||
default:
|
||||
}
|
||||
|
||||
// Add other timer checks...
|
||||
@ -709,15 +725,21 @@ func (zs *ZoneServer) processSpawnChanges() {
|
||||
|
||||
func (zs *ZoneServer) processProximityChecks() {
|
||||
// Process player and location proximity
|
||||
if zs.locationProxTimer.Check() {
|
||||
select {
|
||||
case <-zs.locationProxTimer.C:
|
||||
zs.checkLocationProximity()
|
||||
zs.checkPlayerProximity()
|
||||
zs.locationProxTimer.Reset(LocationProximityInterval)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (zs *ZoneServer) processWeather() {
|
||||
if zs.weatherTimer.Check() {
|
||||
select {
|
||||
case <-zs.weatherTimer.C:
|
||||
zs.ProcessWeather()
|
||||
zs.weatherTimer.Reset(WeatherUpdateInterval)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user