733 lines
21 KiB
Go
733 lines
21 KiB
Go
package housing
|
|
|
|
import (
|
|
"context"
|
|
"eq2emu/internal/database"
|
|
"eq2emu/internal/packets"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// House represents a purchasable house type/zone
|
|
type House struct {
|
|
mu sync.RWMutex
|
|
ID int32
|
|
Name string
|
|
CostCoins int64
|
|
CostStatus int64
|
|
UpkeepCoins int64
|
|
UpkeepStatus int64
|
|
VaultSlots int8
|
|
Alignment int8
|
|
GuildLevel int8
|
|
ZoneID int32
|
|
ExitZoneID int32
|
|
ExitX float32
|
|
ExitY float32
|
|
ExitZ float32
|
|
ExitHeading float32
|
|
}
|
|
|
|
// CharacterHouse represents a house owned by a character
|
|
type CharacterHouse struct {
|
|
mu sync.RWMutex
|
|
UniqueID int64
|
|
CharacterID int32
|
|
HouseID int32
|
|
InstanceID int32
|
|
UpkeepDue time.Time
|
|
EscrowCoins int64
|
|
EscrowStatus int64
|
|
Status int8
|
|
Settings CharacterHouseSettings
|
|
AccessList map[int32]HouseAccess
|
|
Items []HouseItem
|
|
History []HouseHistory
|
|
}
|
|
|
|
// CharacterHouseSettings represents house configuration
|
|
type CharacterHouseSettings struct {
|
|
HouseName string
|
|
VisitPermission int8
|
|
PublicNote string
|
|
PrivateNote string
|
|
AllowFriends bool
|
|
AllowGuild bool
|
|
RequireApproval bool
|
|
ShowOnDirectory bool
|
|
AllowDecoration bool
|
|
}
|
|
|
|
// HouseAccess represents access permissions for a character
|
|
type HouseAccess struct {
|
|
CharacterID int32
|
|
PlayerName string
|
|
AccessLevel int8
|
|
Permissions int32
|
|
GrantedBy int32
|
|
GrantedDate time.Time
|
|
ExpiresDate time.Time
|
|
Notes string
|
|
}
|
|
|
|
// HouseItem represents an item placed in a house
|
|
type HouseItem struct {
|
|
ID int64
|
|
ItemID int32
|
|
CharacterID int32
|
|
X float32
|
|
Y float32
|
|
Z float32
|
|
Heading float32
|
|
PitchX float32
|
|
PitchY float32
|
|
RollX float32
|
|
RollY float32
|
|
PlacedDate time.Time
|
|
Quantity int32
|
|
Condition int8
|
|
}
|
|
|
|
// HouseHistory represents a transaction history entry
|
|
type HouseHistory struct {
|
|
Timestamp time.Time
|
|
Amount int64
|
|
Status int64
|
|
Reason string
|
|
Name string
|
|
CharacterID int32
|
|
Type int
|
|
}
|
|
|
|
// HousingManager manages the housing system
|
|
type HousingManager struct {
|
|
mu sync.RWMutex
|
|
db *database.Database
|
|
houses map[int32]*House // Available house types
|
|
characterHouses map[int64]*CharacterHouse // All character houses by unique ID
|
|
characterIndex map[int32][]*CharacterHouse // Character houses by character ID
|
|
logger Logger
|
|
config HousingConfig
|
|
}
|
|
|
|
// Logger interface for housing system logging
|
|
type Logger interface {
|
|
LogInfo(system, format string, args ...interface{})
|
|
LogError(system, format string, args ...interface{})
|
|
LogDebug(system, format string, args ...interface{})
|
|
LogWarning(system, format string, args ...interface{})
|
|
}
|
|
|
|
// PlayerManager interface for player operations
|
|
type PlayerManager interface {
|
|
CanPlayerAffordHouse(characterID int32, coinCost, statusCost int64) (bool, error)
|
|
DeductPlayerCoins(characterID int32, amount int64) error
|
|
DeductPlayerStatus(characterID int32, amount int64) error
|
|
GetPlayerAlignment(characterID int32) (int8, error)
|
|
GetPlayerGuildLevel(characterID int32) (int8, error)
|
|
}
|
|
|
|
// HousingConfig contains housing system configuration
|
|
type HousingConfig struct {
|
|
EnableUpkeep bool
|
|
EnableForeclosure bool
|
|
UpkeepGracePeriod int32
|
|
MaxHousesPerPlayer int
|
|
EnableStatistics bool
|
|
}
|
|
|
|
// NewHousingManager creates a new housing manager
|
|
func NewHousingManager(db *database.Database, logger Logger, config HousingConfig) *HousingManager {
|
|
return &HousingManager{
|
|
db: db,
|
|
houses: make(map[int32]*House),
|
|
characterHouses: make(map[int64]*CharacterHouse),
|
|
characterIndex: make(map[int32][]*CharacterHouse),
|
|
logger: logger,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// Initialize loads house data and starts background processes
|
|
func (hm *HousingManager) Initialize(ctx context.Context) error {
|
|
hm.mu.Lock()
|
|
defer hm.mu.Unlock()
|
|
|
|
// Load available house types from database
|
|
houseData, err := hm.loadHousesFromDB(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load houses: %w", err)
|
|
}
|
|
|
|
for _, house := range houseData {
|
|
hm.houses[house.ID] = house
|
|
}
|
|
|
|
hm.logger.LogInfo("housing", "Loaded %d house types", len(hm.houses))
|
|
|
|
// Start upkeep processing if enabled
|
|
if hm.config.EnableUpkeep {
|
|
go hm.processUpkeepLoop(ctx)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetHouse returns a house type by ID
|
|
func (hm *HousingManager) GetHouse(houseID int32) (*House, bool) {
|
|
hm.mu.RLock()
|
|
defer hm.mu.RUnlock()
|
|
house, exists := hm.houses[houseID]
|
|
return house, exists
|
|
}
|
|
|
|
// GetAvailableHouses returns all available house types
|
|
func (hm *HousingManager) GetAvailableHouses() []*House {
|
|
hm.mu.RLock()
|
|
defer hm.mu.RUnlock()
|
|
|
|
houses := make([]*House, 0, len(hm.houses))
|
|
for _, house := range hm.houses {
|
|
houses = append(houses, house)
|
|
}
|
|
return houses
|
|
}
|
|
|
|
// GetCharacterHouses returns all houses owned by a character
|
|
func (hm *HousingManager) GetCharacterHouses(characterID int32) ([]*CharacterHouse, error) {
|
|
hm.mu.RLock()
|
|
houses, exists := hm.characterIndex[characterID]
|
|
hm.mu.RUnlock()
|
|
|
|
if !exists {
|
|
// If no database, return empty list
|
|
if hm.db == nil {
|
|
return []*CharacterHouse{}, nil
|
|
}
|
|
|
|
// Load from database
|
|
houses, err := hm.loadCharacterHousesFromDB(context.Background(), characterID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load character houses: %w", err)
|
|
}
|
|
|
|
hm.mu.Lock()
|
|
hm.characterIndex[characterID] = houses
|
|
for _, house := range houses {
|
|
hm.characterHouses[house.UniqueID] = house
|
|
}
|
|
hm.mu.Unlock()
|
|
|
|
return houses, nil
|
|
}
|
|
|
|
return houses, nil
|
|
}
|
|
|
|
// PurchaseHouse handles house purchase requests
|
|
func (hm *HousingManager) PurchaseHouse(ctx context.Context, characterID int32, houseID int32, playerManager PlayerManager) (*CharacterHouse, error) {
|
|
// Get house type
|
|
house, exists := hm.GetHouse(houseID)
|
|
if !exists {
|
|
return nil, fmt.Errorf("house type %d not found", houseID)
|
|
}
|
|
|
|
// Check if player can afford it
|
|
canAfford, err := playerManager.CanPlayerAffordHouse(characterID, house.CostCoins, house.CostStatus)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check affordability: %w", err)
|
|
}
|
|
if !canAfford {
|
|
return nil, fmt.Errorf("insufficient funds")
|
|
}
|
|
|
|
// Check alignment requirement
|
|
if house.Alignment != AlignmentAny {
|
|
alignment, err := playerManager.GetPlayerAlignment(characterID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get player alignment: %w", err)
|
|
}
|
|
if alignment != house.Alignment {
|
|
return nil, fmt.Errorf("alignment requirement not met")
|
|
}
|
|
}
|
|
|
|
// Check guild level requirement
|
|
if house.GuildLevel > 0 {
|
|
guildLevel, err := playerManager.GetPlayerGuildLevel(characterID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get guild level: %w", err)
|
|
}
|
|
if guildLevel < house.GuildLevel {
|
|
return nil, fmt.Errorf("guild level requirement not met")
|
|
}
|
|
}
|
|
|
|
// Check max houses limit
|
|
currentHouses, err := hm.GetCharacterHouses(characterID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get current houses: %w", err)
|
|
}
|
|
if len(currentHouses) >= hm.config.MaxHousesPerPlayer {
|
|
return nil, fmt.Errorf("maximum number of houses reached")
|
|
}
|
|
|
|
// Deduct costs
|
|
if err := playerManager.DeductPlayerCoins(characterID, house.CostCoins); err != nil {
|
|
return nil, fmt.Errorf("failed to deduct coins: %w", err)
|
|
}
|
|
if err := playerManager.DeductPlayerStatus(characterID, house.CostStatus); err != nil {
|
|
return nil, fmt.Errorf("failed to deduct status: %w", err)
|
|
}
|
|
|
|
// Create character house
|
|
characterHouse := &CharacterHouse{
|
|
CharacterID: characterID,
|
|
HouseID: houseID,
|
|
InstanceID: hm.generateInstanceID(),
|
|
UpkeepDue: time.Now().Add(7 * 24 * time.Hour), // Weekly upkeep
|
|
EscrowCoins: 0,
|
|
EscrowStatus: 0,
|
|
Status: HouseStatusActive,
|
|
Settings: CharacterHouseSettings{
|
|
VisitPermission: VisitPermissionFriends,
|
|
AllowFriends: true,
|
|
ShowOnDirectory: true,
|
|
},
|
|
AccessList: make(map[int32]HouseAccess),
|
|
Items: []HouseItem{},
|
|
History: []HouseHistory{{
|
|
Timestamp: time.Now(),
|
|
Amount: house.CostCoins,
|
|
Status: house.CostStatus,
|
|
Reason: "House Purchase",
|
|
CharacterID: characterID,
|
|
Type: TransactionPurchase,
|
|
}},
|
|
}
|
|
|
|
// Save to database if database is available
|
|
if hm.db != nil {
|
|
if err := hm.saveCharacterHouseToDBInternal(ctx, characterHouse); err != nil {
|
|
return nil, fmt.Errorf("failed to save house: %w", err)
|
|
}
|
|
}
|
|
|
|
// Add to memory
|
|
hm.mu.Lock()
|
|
hm.characterHouses[characterHouse.UniqueID] = characterHouse
|
|
hm.characterIndex[characterID] = append(hm.characterIndex[characterID], characterHouse)
|
|
hm.mu.Unlock()
|
|
|
|
hm.logger.LogInfo("housing", "Character %d purchased house type %d", characterID, houseID)
|
|
return characterHouse, nil
|
|
}
|
|
|
|
// SendHousePurchasePacket sends a house purchase packet to a client
|
|
func (hm *HousingManager) SendHousePurchasePacket(characterID int32, clientVersion int32, house *House) error {
|
|
def, exists := packets.GetPacket("PlayerHousePurchase")
|
|
if !exists {
|
|
return fmt.Errorf("PlayerHousePurchase packet definition not found")
|
|
}
|
|
|
|
builder := packets.NewPacketBuilder(def, uint32(clientVersion), 0)
|
|
|
|
packetData := map[string]any{
|
|
"house_name": house.Name,
|
|
"house_id": uint64(house.ID),
|
|
"spawn_id": uint32(0),
|
|
"purchase_coins": house.CostCoins,
|
|
"purchase_status": house.CostStatus,
|
|
"upkeep_coins": house.UpkeepCoins,
|
|
"upkeep_status": house.UpkeepStatus,
|
|
"vendor_vault_slots": house.VaultSlots,
|
|
"additional_reqs": fmt.Sprintf("Alignment: %s", AlignmentNames[house.Alignment]),
|
|
"enable_buy": 1,
|
|
}
|
|
|
|
packet, err := builder.Build(packetData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build packet: %w", err)
|
|
}
|
|
|
|
// TODO: Send packet to client when client interface is available
|
|
_ = packet
|
|
hm.logger.LogDebug("housing", "Built house purchase packet for character %d", characterID)
|
|
return nil
|
|
}
|
|
|
|
// SendCharacterHousesPacket sends a character's house list to client
|
|
func (hm *HousingManager) SendCharacterHousesPacket(characterID int32, clientVersion int32) error {
|
|
houses, err := hm.GetCharacterHouses(characterID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get character houses: %w", err)
|
|
}
|
|
|
|
def, exists := packets.GetPacket("CharacterHousingList")
|
|
if !exists {
|
|
return fmt.Errorf("CharacterHousingList packet definition not found")
|
|
}
|
|
|
|
builder := packets.NewPacketBuilder(def, uint32(clientVersion), 0)
|
|
|
|
houseArray := make([]map[string]any, len(houses))
|
|
for i, house := range houses {
|
|
houseType, exists := hm.GetHouse(house.HouseID)
|
|
houseName := "Unknown"
|
|
if exists {
|
|
houseName = houseType.Name
|
|
}
|
|
|
|
houseArray[i] = map[string]any{
|
|
"house_id": uint64(house.UniqueID),
|
|
"zone": house.HouseID,
|
|
"house_city": "Qeynos", // Default city
|
|
"house_address": house.Settings.HouseName,
|
|
"house_description": fmt.Sprintf("Upkeep due: %s", house.UpkeepDue.Format("2006-01-02")),
|
|
"index": i,
|
|
}
|
|
|
|
if house.Settings.HouseName == "" {
|
|
houseArray[i]["house_address"] = houseName
|
|
}
|
|
}
|
|
|
|
packetData := map[string]any{
|
|
"num_houses": len(houses),
|
|
"house_array": houseArray,
|
|
}
|
|
|
|
packet, err := builder.Build(packetData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build packet: %w", err)
|
|
}
|
|
|
|
// TODO: Send packet to client when client interface is available
|
|
_ = packet
|
|
hm.logger.LogDebug("housing", "Built housing list packet for character %d (%d houses)", characterID, len(houses))
|
|
return nil
|
|
}
|
|
|
|
// PayUpkeep handles upkeep payment for a house
|
|
func (hm *HousingManager) PayUpkeep(ctx context.Context, houseUniqueID int64, playerManager PlayerManager) error {
|
|
hm.mu.RLock()
|
|
characterHouse, exists := hm.characterHouses[houseUniqueID]
|
|
hm.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return fmt.Errorf("character house %d not found", houseUniqueID)
|
|
}
|
|
|
|
house, exists := hm.GetHouse(characterHouse.HouseID)
|
|
if !exists {
|
|
return fmt.Errorf("house type %d not found", characterHouse.HouseID)
|
|
}
|
|
|
|
// Check if player can afford upkeep
|
|
canAfford, err := playerManager.CanPlayerAffordHouse(characterHouse.CharacterID, house.UpkeepCoins, house.UpkeepStatus)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check affordability: %w", err)
|
|
}
|
|
if !canAfford {
|
|
return fmt.Errorf("insufficient funds for upkeep")
|
|
}
|
|
|
|
// Deduct upkeep costs
|
|
if err := playerManager.DeductPlayerCoins(characterHouse.CharacterID, house.UpkeepCoins); err != nil {
|
|
return fmt.Errorf("failed to deduct upkeep coins: %w", err)
|
|
}
|
|
if err := playerManager.DeductPlayerStatus(characterHouse.CharacterID, house.UpkeepStatus); err != nil {
|
|
return fmt.Errorf("failed to deduct upkeep status: %w", err)
|
|
}
|
|
|
|
// Update upkeep due date
|
|
characterHouse.mu.Lock()
|
|
characterHouse.UpkeepDue = time.Now().Add(7 * 24 * time.Hour) // Next week
|
|
characterHouse.History = append(characterHouse.History, HouseHistory{
|
|
Timestamp: time.Now(),
|
|
Amount: house.UpkeepCoins,
|
|
Status: house.UpkeepStatus,
|
|
Reason: "Upkeep Payment",
|
|
CharacterID: characterHouse.CharacterID,
|
|
Type: TransactionUpkeep,
|
|
})
|
|
characterHouse.mu.Unlock()
|
|
|
|
// Save to database if database is available
|
|
if hm.db != nil {
|
|
if err := hm.saveCharacterHouseToDBInternal(ctx, characterHouse); err != nil {
|
|
return fmt.Errorf("failed to save upkeep payment: %w", err)
|
|
}
|
|
}
|
|
|
|
hm.logger.LogInfo("housing", "Paid upkeep for house %d (coins: %d, status: %d)",
|
|
houseUniqueID, house.UpkeepCoins, house.UpkeepStatus)
|
|
return nil
|
|
}
|
|
|
|
// processUpkeepLoop runs upkeep processing in background
|
|
func (hm *HousingManager) processUpkeepLoop(ctx context.Context) {
|
|
ticker := time.NewTicker(1 * time.Hour)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
hm.logger.LogInfo("housing", "Stopping upkeep processing")
|
|
return
|
|
case <-ticker.C:
|
|
if err := hm.processUpkeep(ctx); err != nil {
|
|
hm.logger.LogError("housing", "Failed to process upkeep: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// processUpkeep processes houses due for upkeep
|
|
func (hm *HousingManager) processUpkeep(ctx context.Context) error {
|
|
hm.mu.RLock()
|
|
houses := make([]*CharacterHouse, 0)
|
|
now := time.Now()
|
|
cutoff := now.Add(24 * time.Hour) // Houses due in next 24 hours
|
|
|
|
for _, house := range hm.characterHouses {
|
|
if house.UpkeepDue.Before(cutoff) && house.Status == HouseStatusActive {
|
|
houses = append(houses, house)
|
|
}
|
|
}
|
|
hm.mu.RUnlock()
|
|
|
|
hm.logger.LogInfo("housing", "Processing upkeep for %d houses", len(houses))
|
|
|
|
for _, house := range houses {
|
|
if house.UpkeepDue.Before(now) {
|
|
// Mark as upkeep due
|
|
house.mu.Lock()
|
|
house.Status = HouseStatusUpkeepDue
|
|
house.mu.Unlock()
|
|
|
|
hm.logger.LogWarning("housing", "House %d upkeep is overdue", house.UniqueID)
|
|
|
|
// If enabled, could handle foreclosure here
|
|
if hm.config.EnableForeclosure {
|
|
gracePeriod := time.Duration(hm.config.UpkeepGracePeriod) * time.Second
|
|
if house.UpkeepDue.Add(gracePeriod).Before(now) {
|
|
house.mu.Lock()
|
|
house.Status = HouseStatusForeclosed
|
|
house.mu.Unlock()
|
|
hm.logger.LogInfo("housing", "House %d foreclosed due to unpaid upkeep", house.UniqueID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateInstanceID generates a unique instance ID for houses
|
|
func (hm *HousingManager) generateInstanceID() int32 {
|
|
// Simple implementation - in production would use proper ID generation
|
|
return int32(time.Now().Unix())
|
|
}
|
|
|
|
// Database operations (internal)
|
|
|
|
func (hm *HousingManager) loadHousesFromDB(ctx context.Context) ([]*House, error) {
|
|
query := `
|
|
SELECT id, name, cost_coins, cost_status, upkeep_coins, upkeep_status,
|
|
vault_slots, alignment, guild_level, zone_id, exit_zone_id,
|
|
exit_x, exit_y, exit_z, exit_heading
|
|
FROM character_house_zones
|
|
ORDER BY id
|
|
`
|
|
|
|
rows, err := hm.db.Query(query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query houses: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
houses := make([]*House, 0)
|
|
for rows.Next() {
|
|
house := &House{}
|
|
err := rows.Scan(
|
|
&house.ID, &house.Name, &house.CostCoins, &house.CostStatus,
|
|
&house.UpkeepCoins, &house.UpkeepStatus, &house.VaultSlots,
|
|
&house.Alignment, &house.GuildLevel, &house.ZoneID, &house.ExitZoneID,
|
|
&house.ExitX, &house.ExitY, &house.ExitZ, &house.ExitHeading,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan house: %w", err)
|
|
}
|
|
houses = append(houses, house)
|
|
}
|
|
|
|
return houses, nil
|
|
}
|
|
|
|
func (hm *HousingManager) loadCharacterHousesFromDB(ctx context.Context, characterID int32) ([]*CharacterHouse, error) {
|
|
query := `
|
|
SELECT unique_id, char_id, house_id, instance_id, upkeep_due,
|
|
escrow_coins, escrow_status, status, house_name,
|
|
visit_permission, public_note, private_note, allow_friends,
|
|
allow_guild, require_approval, show_on_directory, allow_decoration
|
|
FROM character_houses
|
|
WHERE char_id = ?
|
|
ORDER BY unique_id
|
|
`
|
|
|
|
rows, err := hm.db.Query(query, characterID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query character houses: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
houses := make([]*CharacterHouse, 0)
|
|
for rows.Next() {
|
|
house := &CharacterHouse{
|
|
AccessList: make(map[int32]HouseAccess),
|
|
Items: []HouseItem{},
|
|
History: []HouseHistory{},
|
|
}
|
|
|
|
err := rows.Scan(
|
|
&house.UniqueID, &house.CharacterID, &house.HouseID, &house.InstanceID,
|
|
&house.UpkeepDue, &house.EscrowCoins, &house.EscrowStatus, &house.Status,
|
|
&house.Settings.HouseName, &house.Settings.VisitPermission,
|
|
&house.Settings.PublicNote, &house.Settings.PrivateNote,
|
|
&house.Settings.AllowFriends, &house.Settings.AllowGuild,
|
|
&house.Settings.RequireApproval, &house.Settings.ShowOnDirectory,
|
|
&house.Settings.AllowDecoration,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan character house: %w", err)
|
|
}
|
|
|
|
houses = append(houses, house)
|
|
}
|
|
|
|
return houses, nil
|
|
}
|
|
|
|
func (hm *HousingManager) saveCharacterHouseToDBInternal(ctx context.Context, house *CharacterHouse) error {
|
|
if house.UniqueID == 0 {
|
|
// Insert new house
|
|
query := `
|
|
INSERT INTO character_houses (
|
|
char_id, house_id, instance_id, upkeep_due, escrow_coins,
|
|
escrow_status, status, house_name, visit_permission,
|
|
public_note, private_note, allow_friends, allow_guild,
|
|
require_approval, show_on_directory, allow_decoration
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`
|
|
|
|
result, err := hm.db.Exec(query,
|
|
house.CharacterID, house.HouseID, house.InstanceID, house.UpkeepDue,
|
|
house.EscrowCoins, house.EscrowStatus, house.Status,
|
|
house.Settings.HouseName, house.Settings.VisitPermission,
|
|
house.Settings.PublicNote, house.Settings.PrivateNote,
|
|
house.Settings.AllowFriends, house.Settings.AllowGuild,
|
|
house.Settings.RequireApproval, house.Settings.ShowOnDirectory,
|
|
house.Settings.AllowDecoration,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert character house: %w", err)
|
|
}
|
|
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get insert ID: %w", err)
|
|
}
|
|
house.UniqueID = id
|
|
} else {
|
|
// Update existing house
|
|
query := `
|
|
UPDATE character_houses SET
|
|
upkeep_due = ?, escrow_coins = ?, escrow_status = ?, status = ?,
|
|
house_name = ?, visit_permission = ?, public_note = ?, private_note = ?,
|
|
allow_friends = ?, allow_guild = ?, require_approval = ?,
|
|
show_on_directory = ?, allow_decoration = ?
|
|
WHERE unique_id = ?
|
|
`
|
|
|
|
_, err := hm.db.Exec(query,
|
|
house.UpkeepDue, house.EscrowCoins, house.EscrowStatus, house.Status,
|
|
house.Settings.HouseName, house.Settings.VisitPermission,
|
|
house.Settings.PublicNote, house.Settings.PrivateNote,
|
|
house.Settings.AllowFriends, house.Settings.AllowGuild,
|
|
house.Settings.RequireApproval, house.Settings.ShowOnDirectory,
|
|
house.Settings.AllowDecoration, house.UniqueID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update character house: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the housing manager
|
|
func (hm *HousingManager) Shutdown(ctx context.Context) error {
|
|
hm.logger.LogInfo("housing", "Shutting down housing manager")
|
|
// Any cleanup would go here
|
|
return nil
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
// FormatUpkeepDue formats upkeep due date for display
|
|
func FormatUpkeepDue(upkeepDue time.Time) string {
|
|
now := time.Now()
|
|
|
|
if upkeepDue.Before(now) {
|
|
duration := now.Sub(upkeepDue)
|
|
days := int(duration.Hours() / 24)
|
|
if days == 0 {
|
|
return "Overdue (today)"
|
|
}
|
|
return fmt.Sprintf("Overdue (%d days)", days)
|
|
} else {
|
|
duration := upkeepDue.Sub(now)
|
|
days := int(duration.Hours() / 24)
|
|
if days == 0 {
|
|
return "Due today"
|
|
}
|
|
return fmt.Sprintf("Due in %d days", days)
|
|
}
|
|
}
|
|
|
|
// FormatCurrency formats currency amounts for display
|
|
func FormatCurrency(amount int64) string {
|
|
if amount < 0 {
|
|
return fmt.Sprintf("-%s", FormatCurrency(-amount))
|
|
}
|
|
|
|
if amount >= 10000 { // 1 gold = 10000 copper
|
|
gold := amount / 10000
|
|
remainder := amount % 10000
|
|
if remainder == 0 {
|
|
return fmt.Sprintf("%dg", gold)
|
|
} else {
|
|
silver := remainder / 100
|
|
copper := remainder % 100
|
|
if copper == 0 {
|
|
return fmt.Sprintf("%dg %ds", gold, silver)
|
|
} else {
|
|
return fmt.Sprintf("%dg %ds %dc", gold, silver, copper)
|
|
}
|
|
}
|
|
} else if amount >= 100 {
|
|
silver := amount / 100
|
|
copper := amount % 100
|
|
if copper == 0 {
|
|
return fmt.Sprintf("%ds", silver)
|
|
} else {
|
|
return fmt.Sprintf("%ds %dc", silver, copper)
|
|
}
|
|
} else {
|
|
return fmt.Sprintf("%dc", amount)
|
|
}
|
|
}
|
|
|