diff options
author | Julien Dessaux | 2025-02-21 00:58:45 +0100 |
---|---|---|
committer | Julien Dessaux | 2025-02-21 00:58:45 +0100 |
commit | e887213affbf2b1e6804d5d2cd86417c881249ba (patch) | |
tree | f28bd776cde0f2674484f18598069e9c0342b542 /golang | |
parent | [golang] refactored the api client to reduce the amount of db pointer shuffli... (diff) | |
download | spacetraders-e887213affbf2b1e6804d5d2cd86417c881249ba.tar.gz spacetraders-e887213affbf2b1e6804d5d2cd86417c881249ba.tar.bz2 spacetraders-e887213affbf2b1e6804d5d2cd86417c881249ba.zip |
[golang] finished implementing procument contracts
Diffstat (limited to '')
-rw-r--r-- | golang/pkg/agent/contracting.go | 2 | ||||
-rw-r--r-- | golang/pkg/agent/navigate.go | 167 | ||||
-rw-r--r-- | golang/pkg/agent/trading.go | 36 | ||||
-rw-r--r-- | golang/pkg/agent/utils.go | 9 | ||||
-rw-r--r-- | golang/pkg/agent/visit.go | 8 | ||||
-rw-r--r-- | golang/pkg/api/contracts.go | 3 | ||||
-rw-r--r-- | golang/pkg/api/ships.go | 12 | ||||
-rw-r--r-- | golang/pkg/api/systems.go | 23 | ||||
-rw-r--r-- | golang/pkg/api/utils.go | 15 | ||||
-rw-r--r-- | golang/pkg/database/agents.go | 2 | ||||
-rw-r--r-- | golang/pkg/database/db.go | 1 | ||||
-rw-r--r-- | golang/pkg/model/cargo.go | 6 | ||||
-rw-r--r-- | golang/pkg/model/inventory.go | 2 | ||||
-rw-r--r-- | golang/pkg/model/ship.go | 12 | ||||
-rw-r--r-- | golang/pkg/model/shipyard.go | 10 |
15 files changed, 252 insertions, 56 deletions
diff --git a/golang/pkg/agent/contracting.go b/golang/pkg/agent/contracting.go index 4e476a6..dcc7815 100644 --- a/golang/pkg/agent/contracting.go +++ b/golang/pkg/agent/contracting.go @@ -72,7 +72,7 @@ func (a *agent) runProcurement(contract *model.Contract, ship *model.Ship) error } } // deliver the goods - if err := a.client.Navigate(ship, deliver.DestinationSymbol); err != nil { + if err := a.navigate(ship, deliver.DestinationSymbol); err != nil { return fmt.Errorf("failed to navigate to %s: %w", deliver.DestinationSymbol, err) } if err := a.client.Deliver(contract, ship); err != nil { diff --git a/golang/pkg/agent/navigate.go b/golang/pkg/agent/navigate.go new file mode 100644 index 0000000..14d2e93 --- /dev/null +++ b/golang/pkg/agent/navigate.go @@ -0,0 +1,167 @@ +package agent + +import ( + "container/heap" + "fmt" + "log/slog" + "math" + "slices" + + "git.adyxax.org/adyxax/spacetraders/golang/pkg/api" + "git.adyxax.org/adyxax/spacetraders/golang/pkg/model" +) + +func (a *agent) navigate(ship *model.Ship, waypointSymbol string) error { + if ship.Nav.WaypointSymbol == waypointSymbol { + return nil + } + if ship.Fuel.Capacity == 0 { + if err := a.client.Navigate(ship, waypointSymbol); err != nil { + return fmt.Errorf("failed to navigate to %s: %w", waypointSymbol, err) + } + } + path, cost, err := a.shortestPath(ship.Nav.WaypointSymbol, waypointSymbol, ship.Fuel.Capacity) + if err != nil { + return fmt.Errorf("failed to compute shortest path: %w", err) + } + slog.Debug("shortest path", "origin", ship.Nav.WaypointSymbol, "destination", waypointSymbol, "path", path, "cost", cost) + for _, next := range path { + if err := a.client.Refuel(ship); err != nil { + return fmt.Errorf("failed to refuel: %w", err) + } + if err := a.client.Navigate(ship, next); err != nil { + return fmt.Errorf("failed to navigate to %s: %w", next, err) + } + } + return nil +} + +func (a *agent) shortestPath(origin string, destination string, fuelCapacity int) ([]string, int, error) { + if fuelCapacity == 0 { // Probes + fuelCapacity = math.MaxInt + } + systemSymbol := api.WaypointSymbolToSystemSymbol(origin) + waypoints, err := a.client.ListWaypointsInSystem(systemSymbol) + if err != nil { + return nil, 0, fmt.Errorf("failed to list waypoints in system %s: %w", systemSymbol, err) + } + backtrace := make(map[string]string) // backtrace map with the shortest path from one point to another + costs := make(map[string]int) // cost to reach each waypoint from the origin. cost = distance + 1 + costs[origin] = 0 + unvisited := make(map[string]*model.Waypoint) + for i := range waypoints { + symbol := waypoints[i].Symbol + costs[symbol] = math.MaxInt + unvisited[waypoints[i].Symbol] = &waypoints[i] + // We need to know which waypoints allow refueling + waypoints[i].Traits = slices.DeleteFunc(waypoints[i].Traits, func(trait model.Common) bool { + if trait.Symbol == "MARKETPLACE" { + return false + } + return true + }) + if len(waypoints[i].Traits) > 0 { + market, err := a.client.GetMarket(symbol, a.ships) + if err != nil { + return nil, 0, fmt.Errorf("failed to get market %s: %w", symbol, err) + } + market.Exchange = slices.DeleteFunc(market.Exchange, func(item model.Common) bool { + if item.Symbol == "FUEL" { + return false + } + return true + }) + market.Exports = slices.DeleteFunc(market.Exports, func(item model.Common) bool { + if item.Symbol == "FUEL" { + return false + } + return true + }) + if len(market.Exchange) == 0 && len(market.Exports) == 0 { + waypoints[i].Traits = nil + } + } + } + costs[origin] = 0 + pq := make(PriorityQueue, 1) + pq[0] = &Node{ + waypointSymbol: origin, + } + heap.Init(&pq) +outer: + for pq.Len() > 0 { + node := heap.Pop(&pq).(*Node) + waypoint, ok := unvisited[node.waypointSymbol] + if !ok { // already visited + continue + } + delete(unvisited, node.waypointSymbol) + for _, candidate := range unvisited { + fuel := int(math.Floor(math.Sqrt(float64(distance2(waypoint, candidate))))) + 1 + if fuel > fuelCapacity { + continue + } + cost := node.cost + fuel + if cost < costs[candidate.Symbol] { + backtrace[candidate.Symbol] = node.waypointSymbol + costs[candidate.Symbol] = cost + if candidate.Symbol == destination { + break outer + } + if len(candidate.Traits) > 0 { + heap.Push(&pq, &Node{ + cost: cost, + waypointSymbol: candidate.Symbol, + }) + } + } + } + } + path := []string{destination} + step, ok := backtrace[destination] + if !ok { + slog.Debug("shortest path failure", "backtraces", backtrace, "costs", costs) + return nil, 0, fmt.Errorf("no path exists between origin and destination with the given fuel capacity") + } + for step != origin { + path = append([]string{step}, path...) + step = backtrace[step] + } + return path, costs[destination], nil +} + +// Priority queue implementation with container/heap +type Node struct { + cost int + waypointSymbol string + index int // needed by the heap.Interface methods +} + +type PriorityQueue []*Node + +func (pq PriorityQueue) Len() int { return len(pq) } +func (pq PriorityQueue) Less(i, j int) bool { return pq[i].cost < pq[j].cost } +func (pq PriorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] + pq[i].index = i + pq[j].index = j +} +func (pq *PriorityQueue) Push(x any) { + n := len(*pq) + item := x.(*Node) + item.index = n + *pq = append(*pq, item) +} +func (pq *PriorityQueue) Pop() any { + old := *pq + n := len(old) + item := old[n-1] + old[n-1] = nil // don't stop the GC from reclaiming the item eventually + item.index = -1 // for safety + *pq = old[0 : n-1] + return item +} +func (pq *PriorityQueue) Update(n *Node, cost int) { + n.cost = cost + heap.Fix(pq, n.index) +} diff --git a/golang/pkg/agent/trading.go b/golang/pkg/agent/trading.go index b900f7a..349b839 100644 --- a/golang/pkg/agent/trading.go +++ b/golang/pkg/agent/trading.go @@ -36,7 +36,7 @@ func (a *agent) buyTradeGood(ship *model.Ship, tradeGoodToBuy string) error { // find the closest place to buy TODO waypoint, err := a.client.GetWaypoint(ship.Nav.WaypointSymbol) if err != nil { - return fmt.Errorf("failed to get nav waypoint %s: %w", ship.Nav.WaypointSymbol, err) + return fmt.Errorf("failed to get waypoint %s: %w", ship.Nav.WaypointSymbol, err) } waypoints := make([]model.Waypoint, 0) for i := range markets { @@ -48,10 +48,13 @@ func (a *agent) buyTradeGood(ship *model.Ship, tradeGoodToBuy string) error { } sortByDistanceFrom(waypoint, waypoints) // Go there and refresh our market data - if err := a.client.Navigate(ship, waypoints[0].Symbol); err != nil { + if err := a.navigate(ship, waypoints[0].Symbol); err != nil { return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err) } - market, err := a.client.GetMarket(waypoints[0].Symbol) + if err := a.client.Dock(ship); err != nil { + return fmt.Errorf("failed to dock: %w", err) + } + market, err := a.client.GetMarket(waypoints[0].Symbol, a.ships) if err != nil { return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err) } @@ -72,11 +75,13 @@ func (a *agent) buyTradeGood(ship *model.Ship, tradeGoodToBuy string) error { func (a *agent) sellEverythingExcept(ship *model.Ship, keep string) error { // First lets see what we need to sell - cargo := ship.Cargo.Inventory - cargo = slices.DeleteFunc(cargo, func(inventory model.Inventory) bool { - return inventory.Symbol == keep - }) - if len(cargo) == 0 { + var inventoryItems []model.InventoryItem + for _, item := range ship.Cargo.Inventory { + if item.Symbol != keep { + inventoryItems = append(inventoryItems, item) + } + } + if len(inventoryItems) == 0 { return nil } // list markets would buy our goods @@ -85,9 +90,9 @@ func (a *agent) sellEverythingExcept(ship *model.Ship, keep string) error { return fmt.Errorf("failed to list markets in system %s: %w", ship.Nav.SystemSymbol, err) } markets = slices.DeleteFunc(markets, func(market model.Market) bool { - for _, item := range market.Imports { - for _, cargoItem := range cargo { - if item.Symbol == cargoItem.Symbol { + for _, marketItem := range market.Imports { + for _, inventoryItem := range inventoryItems { + if marketItem.Symbol == inventoryItem.Symbol { return false } } @@ -112,15 +117,18 @@ func (a *agent) sellEverythingExcept(ship *model.Ship, keep string) error { } sortByDistanceFrom(waypoint, waypoints) // Go there and refresh our market data - if err := a.client.Navigate(ship, waypoints[0].Symbol); err != nil { + if err := a.navigate(ship, waypoints[0].Symbol); err != nil { return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err) } - market, err := a.client.GetMarket(waypoints[0].Symbol) + if err := a.client.Dock(ship); err != nil { + return fmt.Errorf("failed to dock: %w", err) + } + market, err := a.client.GetMarket(waypoints[0].Symbol, a.ships) if err != nil { return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err) } // sell everything we can - for _, cargoItem := range cargo { + for _, cargoItem := range inventoryItems { units := cargoItem.Units for _, tradeGood := range market.TradeGoods { if tradeGood.Type == "IMPORT" && tradeGood.Symbol == cargoItem.Symbol { diff --git a/golang/pkg/agent/utils.go b/golang/pkg/agent/utils.go index 7aa9c7b..86f428d 100644 --- a/golang/pkg/agent/utils.go +++ b/golang/pkg/agent/utils.go @@ -47,7 +47,7 @@ func (a *agent) listMarketsInSystem(systemSymbol string) ([]model.Market, error) } var markets []model.Market for i := range waypoints { - market, err := a.client.GetMarket(waypoints[i].Symbol) + market, err := a.client.GetMarket(waypoints[i].Symbol, a.ships) if err != nil { return nil, fmt.Errorf("failed to get market %s: %w", waypoints[i].Symbol, err) } @@ -63,7 +63,7 @@ func (a *agent) listShipyardsInSystem(systemSymbol string) ([]model.Shipyard, er } var shipyards []model.Shipyard for i := range waypoints { - shipyard, err := a.client.GetShipyard(waypoints[i].Symbol) + shipyard, err := a.client.GetShipyard(waypoints[i].Symbol, a.ships) if err != nil { return nil, fmt.Errorf("failed to get shipyard %s: %w", waypoints[i].Symbol, err) } @@ -86,6 +86,9 @@ func (a *agent) sendShipToShipyardThatSells(ship *model.Ship, shipType string) e } return true }) + if len(shipyards) == 0 { + return fmt.Errorf("no shipyards sells that ship type") + } // sort by cheapest slices.SortFunc(shipyards, func(a, b model.Shipyard) int { aPrice := math.MaxInt @@ -104,7 +107,7 @@ func (a *agent) sendShipToShipyardThatSells(ship *model.Ship, shipType string) e } return cmp.Compare(aPrice, bPrice) }) - if err := a.client.Navigate(ship, shipyards[0].Symbol); err != nil { + if err := a.navigate(ship, shipyards[0].Symbol); err != nil { return fmt.Errorf("failed to navigate to %s: %w", shipyards[0].Symbol, err) } return nil diff --git a/golang/pkg/agent/visit.go b/golang/pkg/agent/visit.go index e420186..38a9859 100644 --- a/golang/pkg/agent/visit.go +++ b/golang/pkg/agent/visit.go @@ -14,7 +14,7 @@ func (a *agent) visitAllShipyards(ship *model.Ship) error { } shipyards = slices.DeleteFunc(shipyards, func(shipyard model.Shipyard) bool { // filter out shipyards for which we already have ships prices - if shipyard.Ships != nil { + if len(shipyard.Ships) > 0 { return true } // filter out shipyards for which a ship is either present or inbound @@ -36,16 +36,16 @@ func (a *agent) visitAllShipyards(ship *model.Ship) error { waypoints = append(waypoints, *waypoint) } sortByDistanceFrom(waypoint, waypoints) - if err := a.client.Navigate(ship, waypoints[0].Symbol); err != nil { + if err := a.navigate(ship, waypoints[0].Symbol); err != nil { return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err) } - if _, err := a.client.GetShipyard(waypoints[0].Symbol); err != nil { + if _, err := a.client.GetShipyard(waypoints[0].Symbol, a.ships); err != nil { return fmt.Errorf("failed to get shipyard %s: %w", waypoints[0].Symbol, err) } // If this waypoint is also a marketplace, get its data for _, trait := range waypoints[0].Traits { if trait.Symbol == "MARKETPLACE" { - if _, err := a.client.GetMarket(waypoints[0].Symbol); err != nil { + if _, err := a.client.GetMarket(waypoints[0].Symbol, a.ships); err != nil { return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err) } break diff --git a/golang/pkg/api/contracts.go b/golang/pkg/api/contracts.go index fd20cce..bf5ab9c 100644 --- a/golang/pkg/api/contracts.go +++ b/golang/pkg/api/contracts.go @@ -30,6 +30,9 @@ func (c *Client) Accept(contract *model.Contract) error { } func (c *Client) Deliver(contract *model.Contract, ship *model.Ship) error { + if err := c.Dock(ship); err != nil { + return fmt.Errorf("failed to dock: %w", err) + } deliver := contract.Terms.Deliver[0] var units int for _, cargoItem := range ship.Cargo.Inventory { diff --git a/golang/pkg/api/ships.go b/golang/pkg/api/ships.go index c3b7074..fe1770b 100644 --- a/golang/pkg/api/ships.go +++ b/golang/pkg/api/ships.go @@ -9,7 +9,7 @@ import ( "git.adyxax.org/adyxax/spacetraders/golang/pkg/model" ) -func (c *Client) dock(s *model.Ship) error { +func (c *Client) Dock(s *model.Ship) error { if s.Nav.Status == "DOCKED" { return nil } @@ -41,8 +41,6 @@ func (c *Client) Navigate(s *model.Ship, waypointSymbol string) error { if err := c.orbit(s); err != nil { return fmt.Errorf("failed to orbit: %w", err) } - // TODO shortest path - // TODO go refuel if necessary uriRef := url.URL{Path: path.Join("my/ships", s.Symbol, "navigate")} type navigateRequest struct { WaypointSymbol string `json:"waypointSymbol"` @@ -96,7 +94,7 @@ func (c *Client) orbit(s *model.Ship) error { } func (c *Client) Purchase(s *model.Ship, cargoItem string, units int) error { - if err := c.dock(s); err != nil { + if err := c.Dock(s); err != nil { return fmt.Errorf("failed to dock: %w", err) } uriRef := url.URL{Path: path.Join("my/ships", s.Symbol, "purchase")} @@ -123,11 +121,11 @@ func (c *Client) Purchase(s *model.Ship, cargoItem string, units int) error { return nil } -func (c *Client) refuel(s *model.Ship, db *database.DB) error { +func (c *Client) Refuel(s *model.Ship) error { if s.Fuel.Current == s.Fuel.Capacity { return nil } - if err := c.dock(s); err != nil { + if err := c.Dock(s); err != nil { return fmt.Errorf("failed to dock: %w", err) } uriRef := url.URL{Path: path.Join("my/ships", s.Symbol, "refuel")} @@ -151,7 +149,7 @@ func (c *Client) refuel(s *model.Ship, db *database.DB) error { } func (c *Client) Sell(s *model.Ship, cargoItem string, units int) error { - if err := c.dock(s); err != nil { + if err := c.Dock(s); err != nil { return fmt.Errorf("failed to dock: %w", err) } uriRef := url.URL{Path: path.Join("my/ships", s.Symbol, "sell")} diff --git a/golang/pkg/api/systems.go b/golang/pkg/api/systems.go index 2cdfeca..3dfec67 100644 --- a/golang/pkg/api/systems.go +++ b/golang/pkg/api/systems.go @@ -8,10 +8,12 @@ import ( "git.adyxax.org/adyxax/spacetraders/golang/pkg/model" ) -func (c *Client) GetMarket(waypointSymbol string) (*model.Market, error) { - if market, err := c.db.LoadMarket(waypointSymbol); err == nil && market != nil { - // TODO check last updated time - return market, nil +func (c *Client) GetMarket(waypointSymbol string, ships []model.Ship) (*model.Market, error) { + if !isThereAShipDockerAtWaypoint(waypointSymbol, ships) { + if market, err := c.db.LoadMarket(waypointSymbol); err == nil && market != nil { + // TODO check last updated time + return market, nil + } } systemSymbol := WaypointSymbolToSystemSymbol(waypointSymbol) uriRef := url.URL{Path: path.Join("systems", systemSymbol, "waypoints", waypointSymbol, "market")} @@ -25,11 +27,12 @@ func (c *Client) GetMarket(waypointSymbol string) (*model.Market, error) { return &market, nil } -func (c *Client) GetShipyard(waypointSymbol string) (*model.Shipyard, error) { - if shipyard, err := c.db.LoadShipyard(waypointSymbol); err == nil && shipyard != nil && - (shipyard.Ships != nil) { // TODO || !IsThereAShipAtWaypoint(waypoint)) { - // TODO check last updated time - return shipyard, nil +func (c *Client) GetShipyard(waypointSymbol string, ships []model.Ship) (*model.Shipyard, error) { + if !isThereAShipDockerAtWaypoint(waypointSymbol, ships) { + if shipyard, err := c.db.LoadShipyard(waypointSymbol); err == nil && shipyard != nil { + // TODO check last updated time + return shipyard, nil + } } systemSymbol := WaypointSymbolToSystemSymbol(waypointSymbol) uriRef := url.URL{Path: path.Join("systems", systemSymbol, "waypoints", waypointSymbol, "shipyard")} @@ -76,7 +79,7 @@ func (c *Client) GetWaypoint(waypointSymbol string) (*model.Waypoint, error) { } func (c *Client) ListWaypointsInSystem(systemSymbol string) ([]model.Waypoint, error) { - if waypoints, err := c.db.LoadWaypointsInSystem(systemSymbol); err == nil && waypoints != nil { + if waypoints, err := c.db.LoadWaypointsInSystem(systemSymbol); err == nil && len(waypoints) > 0 { // TODO check last updated time return waypoints, nil } diff --git a/golang/pkg/api/utils.go b/golang/pkg/api/utils.go index 7dfee5c..ba85cb9 100644 --- a/golang/pkg/api/utils.go +++ b/golang/pkg/api/utils.go @@ -1,6 +1,19 @@ package api -import "strings" +import ( + "strings" + + "git.adyxax.org/adyxax/spacetraders/golang/pkg/model" +) + +func isThereAShipDockerAtWaypoint(symbol string, ships []model.Ship) bool { + for _, ship := range ships { + if ship.Nav.WaypointSymbol == symbol && ship.Nav.Status == "DOCKED" { + return true + } + } + return false +} func WaypointSymbolToSystemSymbol(symbol string) string { return strings.Join(strings.Split(symbol, "-")[:2], "-") diff --git a/golang/pkg/database/agents.go b/golang/pkg/database/agents.go index e2580a9..20da396 100644 --- a/golang/pkg/database/agents.go +++ b/golang/pkg/database/agents.go @@ -12,7 +12,7 @@ func (db *DB) SaveAgent(agent *model.Agent) error { if err != nil { return fmt.Errorf("failed to marshal agent: %w", err) } - if _, err := db.Exec(`INSERT INTO agents VALUES data = (json(?));`, data); err != nil { + if _, err := db.Exec(`INSERT INTO agents(data) VALUES (json(?));`, data); err != nil { return fmt.Errorf("failed to insert agent data: %w", err) } return nil diff --git a/golang/pkg/database/db.go b/golang/pkg/database/db.go index f094039..4ce09c4 100644 --- a/golang/pkg/database/db.go +++ b/golang/pkg/database/db.go @@ -95,6 +95,7 @@ func (db *DB) Reset() error { _, err := db.Exec(strings.Join([]string{ "DELETE FROM agents;", "DELETE FROM markets;", + "DELETE FROM shipyards;", "DELETE FROM systems;", "DELETE FROM tokens;", "DELETE FROM transactions;", diff --git a/golang/pkg/model/cargo.go b/golang/pkg/model/cargo.go index bf58204..7bd8db8 100644 --- a/golang/pkg/model/cargo.go +++ b/golang/pkg/model/cargo.go @@ -1,7 +1,7 @@ package model type Cargo struct { - Capacity int `json:"capacity"` - Inventory []Inventory `json:"inventory"` - Units int `json:"units"` + Capacity int `json:"capacity"` + Inventory []InventoryItem `json:"inventory"` + Units int `json:"units"` } diff --git a/golang/pkg/model/inventory.go b/golang/pkg/model/inventory.go index d685250..dfb28f8 100644 --- a/golang/pkg/model/inventory.go +++ b/golang/pkg/model/inventory.go @@ -1,6 +1,6 @@ package model -type Inventory struct { +type InventoryItem struct { //Description string `json:"description"` Units int `json:"units"` //Name string `json:"name"` diff --git a/golang/pkg/model/ship.go b/golang/pkg/model/ship.go index 73244d4..6bdfa81 100644 --- a/golang/pkg/model/ship.go +++ b/golang/pkg/model/ship.go @@ -3,14 +3,14 @@ package model type Ship struct { Cargo *Cargo `json:"cargo"` Cooldown *Cooldown `json:"cooldown"` - //// crew - //// engine - //// frame + // crew + // engine + // frame Fuel *Fuel `json:"fuel"` - //// modules - //// mounts + // modules + // mounts Nav *Nav `json:"nav"` - //// reactor + // reactor //registration: Registration; Symbol string `json:"symbol"` } diff --git a/golang/pkg/model/shipyard.go b/golang/pkg/model/shipyard.go index 2ba2e95..4140d44 100644 --- a/golang/pkg/model/shipyard.go +++ b/golang/pkg/model/shipyard.go @@ -3,11 +3,11 @@ package model import "time" type Shipyard struct { - ModificationFee int `json:"modificationFee"` - Symbol string `json:"symbol"` - ShipTypes []CommonType `json:"shipTypes"` - Transactions []ShipyardTransaction `json:"transactions"` - Ships []ShipyardShip `json:"ships"` + ModificationFee int `json:"modificationFee"` + Symbol string `json:"symbol"` + ShipTypes []CommonType `json:"shipTypes"` + //Transactions []ShipyardTransaction `json:"transactions"` + Ships []ShipyardShip `json:"ships"` } type ShipyardShip struct { |