package model import ( "database/sql" "time" ) // ─── User ──────────────────────────────────────────────────────────────────── type User struct { ID int32 Username string PasswordHash string CreatedAt time.Time DeletedAt sql.NullTime } // ─── Airport ───────────────────────────────────────────────────────────────── type Airport struct { ID uint32 Name string UserID uint32 OriginCountryID uint16 Cash uint64 CurrentPassengers uint32 MaxPassengerCapacity uint32 CurrentFuel uint32 MaxFuelCapacity uint32 FuelGenerationRate uint32 PassengerGenerationRate uint32 ResourcesLastUpdatedAt time.Time IsAvailable bool IsCentral bool PlayerLevel uint32 ExperiencePoints uint64 StorageCap uint32 OperationCap uint32 IsDeleted bool } // CentralAirport is a system-owned destination port (one per continent), shown // in the Fleet dispatch picker. Joined with its owning faction for flavour. type CentralAirport struct { AirportID uint32 Continent string Name string FactionName string LeaderName string } // FacilityStatus is the occupancy of one operation-building class (a runway or // service bay of a given aircraft size): how many exist vs. how many are busy. type FacilityStatus struct { OperationType string // "runway" | "service" Size string // "small" | "medium" | "large" | "huge" Total int Busy int } // Faction is the NPC owner of a continent's central port — lore / future content. type Faction struct { ID uint32 Name string Continent string LeaderName string Background string UserID int32 CentralAirportID uint32 } // ─── Country ───────────────────────────────────────────────────────────────── type Country struct { ID uint16 Name string } // ─── Plane (template) ──────────────────────────────────────────────────────── type Plane struct { ID uint32 Name string MinLevel uint32 OperationCostFuel uint32 OperationCostCash uint32 MaxPassengers sql.NullInt32 TravelTimeInSeconds uint32 OperationType string // "passenger" | "cargo" AircraftSize string // "small" | "medium" | "large" | "huge" AircraftType string // "plane" | "helicopter" | "seaplane" | "spaceship" BuyingCostCash uint32 CompletedFlightExperience uint32 CompletedFlightCashReward sql.NullInt32 CompletedFlightCargoReward sql.NullInt32 BoardingTimeSec uint32 FuelingTimeSec uint32 } // ─── Player Plane ───────────────────────────────────────────────────────────── type PlayerPlane struct { ID uint32 AirportID uint32 PlaneID uint32 AssignedHangarID uint32 CurrentBuildingID sql.NullInt32 Status string // "idle"|"maintenance"|"boarding"|"taxiing"|"flying"|"unloading" UpdatedAt time.Time PurchasedAt time.Time FlightStartTime sql.NullTime DestinationAirportID sql.NullInt32 DestinationName sql.NullString // Joined from planes PlaneName string AircraftSize string AircraftType string OperationType string TravelTimeSec uint32 FuelCost uint32 BuyingCost uint32 MaxPassengers sql.NullInt32 CashReward sql.NullInt32 ExpReward uint32 BoardingTimeSec uint32 FuelingTimeSec uint32 // RemainingSec is the seconds left in the current timed phase, computed // server-side (MySQL NOW()) in GetFleet so the countdown shares the exact // clock used by the landing / service-advance checks. Avoids Go-vs-MySQL // clock skew that made planes land early. RemainingSec int } // TimeRemainingSeconds returns seconds left in the current timed phase // (boarding, taxiing/fueling, or flying). The value is computed in SQL // (see GetFleet) so it agrees with the DB-side landing check. func (p *PlayerPlane) TimeRemainingSeconds() int { if p.RemainingSec < 0 { return 0 } return p.RemainingSec } // ─── Leveling ───────────────────────────────────────────────────────────────── // // XP is cumulative. Advancing from level L to L+1 costs 500*(L+1) XP, so: // L0→L1 = 500, L1→L2 = 1000, L2→L3 = 1500 … // Total XP to reach the start of level L = 500 * L*(L+1)/2. // XPToNextLevel is the XP span of the current level (level → level+1). func XPToNextLevel(level uint32) int { return 500 * (int(level) + 1) } // cumulativeXP is the total XP required to be at the start of `level`. func cumulativeXP(level uint32) uint64 { l := uint64(level) return 500 * l * (l + 1) / 2 } // LevelForXP returns the level that the given total XP corresponds to. func LevelForXP(xp uint64) uint32 { level := uint32(0) for cumulativeXP(level+1) <= xp { level++ } return level } // XPIntoLevel returns how much of the current level's span has been earned. func XPIntoLevel(level uint32, xp uint64) int { base := cumulativeXP(level) if xp < base { return 0 } return int(xp - base) } // ─── Building ───────────────────────────────────────────────────────────────── type Building struct { ID uint32 Name string MinLevel uint32 ConstructionCostCash uint32 UpgradeTier uint8 BuildingType string // storage Capacity sql.NullInt32 StorageType sql.NullString StorageSubtype sql.NullString // operation OperationSize sql.NullString OperationType sql.NullString // passive PassiveType sql.NullString PassiveRate sql.NullInt32 } // AirportBuilding is an instance owned by a player type AirportBuilding struct { ID uint32 AirportID uint32 BuildingID uint32 Level uint32 // Joined from buildings Name string BuildingType string ConstructionCostCash uint32 OperationSize sql.NullString OperationType sql.NullString Capacity sql.NullInt32 StorageType sql.NullString } // ─── Product ────────────────────────────────────────────────────────────────── type Product struct { ID uint16 Name string } // ─── Contract ──────────────────────────────────────────────────────────────── type Contract struct { ID uint32 Title string RequiredProductID uint16 RequiredAmount uint32 CurrentAmountDelivered uint32 IsCompleted bool ExpiresAt time.Time // Joined ProductName string } func (c *Contract) ProgressPct() int { if c.RequiredAmount == 0 { return 100 } return int((c.CurrentAmountDelivered * 100) / c.RequiredAmount) } // ─── App state helpers ──────────────────────────────────────────────────────── // Tab indices const ( TabAirport = 0 TabFleet = 1 TabBuildings = 2 TabContracts = 3 TabShop = 4 ) var TabNames = []string{"✈ Airport", "🛩 Fleet", "🏗 Buildings", "📋 Contracts", "🛒 Shop"}