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