diff --git a/cardinal/component/effect.go b/cardinal/component/effect.go index 6350ecc..1948121 100644 --- a/cardinal/component/effect.go +++ b/cardinal/component/effect.go @@ -24,12 +24,16 @@ func GetCapacityByEffectType(effectType EffectType) int { } type Effect struct { - Type EffectType `json:"type"` - Amount int `json:"amount"` - Capacity int `json:"capacity"` - Resources []Resource `json:"resources,omitempty"` - BuildingTimeSeconds int `json:"buildingTimeSeconds,omitempty"` - BuildingTimeStartedAt uint64 `json:"buildingTimeStartedAt,omitempty"` + ID string `json:"id"` + Player string `json:"player"` + Type EffectType `json:"type"` + Amount int `json:"amount"` + Capacity int `json:"capacity"` + Resources []Resource `json:"resources,omitempty"` + Position [2]float64 `json:"position,omitempty"` + TargetPosition *[2]float64 `json:"targetPosition,omitempty"` + BuildingTimeSeconds int `json:"buildingTimeSeconds,omitempty"` + BuildingTimeStartedAt uint64 `json:"buildingTimeStartedAt,omitempty"` } func (Effect) Name() string { diff --git a/cardinal/constants/buildings.go b/cardinal/constants/buildings.go index 1aea86d..ec6e15c 100644 --- a/cardinal/constants/buildings.go +++ b/cardinal/constants/buildings.go @@ -1,28 +1,28 @@ package constants const ( - MainTileID = 22 - MainUnitLimit = 5 - MainStorageCapacity = 10 - // MainStorageCapacity = 100 + MainTileID = 22 + MainUnitLimit = 5 + // MainStorageCapacity = 10 + MainStorageCapacity = 100 ) const ( - WoodcutterFarmingSpeed = 5 - // WoodcutterFarmingSpeed = 50 + // WoodcutterFarmingSpeed = 5 + WoodcutterFarmingSpeed = 50 ) const ( QuarryResourcesWoodAmount = 5 - QuarryFarmingStoneSpeed = 5 - // QuarryFarmingStoneSpeed = 50 + // QuarryFarmingStoneSpeed = 5 + QuarryFarmingStoneSpeed = 50 ) const ( FishermanHutResourcesWoodAmount = 10 FishermanHutResourcesStoneAmount = 10 - FishermanHutFarmingFishSpeed = 5 - // FishermanHutFarmingFishSpeed = 50 + // FishermanHutFarmingFishSpeed = 5 + FishermanHutFarmingFishSpeed = 50 ) const ( @@ -35,6 +35,10 @@ const ( ShipyardEffectRaftBuildSeconds = 60 ) +const ( + RaftTravelSpeedPerMinute = 0.4 +) + const ( WarehouseResourcesWoodAmount = 10 WarehouseResourcesFishAmount = 10 @@ -47,4 +51,4 @@ const ( UnitLimitHouseUnitLimit = 15 ) -const ShipwreckDistanceFromIsland = 0.8 +const ShipwreckDistanceFromIsland = 0.4 diff --git a/cardinal/go.mod b/cardinal/go.mod index f0bab8e..081a931 100644 --- a/cardinal/go.mod +++ b/cardinal/go.mod @@ -3,6 +3,7 @@ module oceanus-shard go 1.22.1 require ( + github.com/google/uuid v1.6.0 github.com/rs/zerolog v1.32.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa pkg.world.dev/world-engine/cardinal v1.5.1 @@ -46,7 +47,6 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20230901174712-0191c66da455 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect diff --git a/cardinal/main.go b/cardinal/main.go index 6c7580e..2e21b18 100644 --- a/cardinal/main.go +++ b/cardinal/main.go @@ -53,6 +53,7 @@ func MustInitWorld(w *cardinal.World) { cardinal.RegisterMessage[msg.DeleteBuildingMsg, msg.DeleteBuildingResult](w, "delete-building"), cardinal.RegisterMessage[msg.CreateEffectMsg, msg.CreateEffectResult](w, "create-effect"), cardinal.RegisterMessage[msg.RelocateBuildingMsg, msg.RelocateBuildingResult](w, "relocate-building"), + cardinal.RegisterMessage[msg.SailShipwreckMsg, msg.SailShipWreckResult](w, "sail-shipwreck"), ) // Register queries @@ -74,6 +75,10 @@ func MustInitWorld(w *cardinal.World) { query.GlobalMapRequest, []query.GlobalMapResponse, ](w, "global-map", query.GlobalMap), + cardinal.RegisterQuery[ + query.AllShipsRequest, + map[string]component.Effect, + ](w, "all-ships", query.AllShips), ) // Each system executes deterministically in the order they are added. @@ -88,6 +93,8 @@ func MustInitWorld(w *cardinal.World) { system.CreateEffectSystem, system.EffectsSpawnerSystem, system.RelocateBuildingSystem, + system.StartSailShipwreckSystem, + system.SailShip, )) } diff --git a/cardinal/msg/sail_shipwreck.go b/cardinal/msg/sail_shipwreck.go new file mode 100644 index 0000000..293c796 --- /dev/null +++ b/cardinal/msg/sail_shipwreck.go @@ -0,0 +1,9 @@ +package msg + +type SailShipwreckMsg struct { + Player string `json:"player"` +} + +type SailShipWreckResult struct { + Success bool `json:"success"` +} diff --git a/cardinal/query/all_ships.go b/cardinal/query/all_ships.go new file mode 100644 index 0000000..f14f160 --- /dev/null +++ b/cardinal/query/all_ships.go @@ -0,0 +1,30 @@ +package query + +import ( + comp "oceanus-shard/component" + "oceanus-shard/system" + + "pkg.world.dev/world-engine/cardinal" + "pkg.world.dev/world-engine/cardinal/search/filter" +) + +type AllShipsRequest struct{} + +// AllShips - +func AllShips(world cardinal.WorldContext, _ *AllShipsRequest) (*map[string]comp.Effect, error) { + _, effects, err := system.QueryAllComponents[comp.Effect]( + world, + filter.Component[comp.Player](), + filter.Component[comp.Building](), + filter.Component[comp.Effect](), + ) + + shipsMap := make(map[string]comp.Effect) + for _, effect := range effects { + if effect.Amount > 0 { + shipsMap[effect.ID] = *effect + } + } + + return &shipsMap, err +} diff --git a/cardinal/query/global_map.go b/cardinal/query/global_map.go index d86c3e6..4179ad4 100644 --- a/cardinal/query/global_map.go +++ b/cardinal/query/global_map.go @@ -11,9 +11,10 @@ import ( type GlobalMapRequest struct{} type GlobalMapResponse struct { - Player string `json:"player"` - Island ResourcePoint `json:"island"` - Shipwreck ResourcePoint `json:"shipwreck"` + Player string `json:"player"` + Island ResourcePoint `json:"island"` + Shipwreck ResourcePoint `json:"shipwreck"` + Ships *[]comp.Effect `json:"ships"` } type ResourcePoint struct { diff --git a/cardinal/query/map_state.go b/cardinal/query/map_state.go index 38a790b..adb33cc 100644 --- a/cardinal/query/map_state.go +++ b/cardinal/query/map_state.go @@ -14,26 +14,27 @@ type MapStateRequest struct { } type MapStateResponse struct { - Tiles *[]comp.Tile `json:"tiles"` - Width int `json:"width"` - Height int `json:"height"` + Tiles *[]comp.Tile `json:"tiles"` + Width int `json:"width"` + Height int `json:"height"` + Position [2]float64 `json:"position"` } func PlayerMap(world cardinal.WorldContext, req *MapStateRequest) (*MapStateResponse, error) { - _, playerMap, err := system.QueryPlayerComponent[comp.TileMap]( + playerMapEntityID, playerMap, err := system.QueryPlayerComponent[comp.TileMap]( world, req.Nickname, filter.Component[comp.Player](), filter.Component[comp.TileMap](), ) - if playerMap == nil { return nil, fmt.Errorf("error querying players %s map", req.Nickname) } - + position, _ := cardinal.GetComponent[comp.Position](world, playerMapEntityID) return &MapStateResponse{ - Tiles: playerMap.Tiles, - Width: playerMap.Width, - Height: playerMap.Height, + Tiles: playerMap.Tiles, + Width: playerMap.Width, + Height: playerMap.Height, + Position: position.Island, }, err } diff --git a/cardinal/system/create_building.go b/cardinal/system/create_building.go index e2477c0..46a7007 100644 --- a/cardinal/system/create_building.go +++ b/cardinal/system/create_building.go @@ -3,6 +3,7 @@ package system import ( "fmt" + "github.com/google/uuid" "pkg.world.dev/world-engine/cardinal" "pkg.world.dev/world-engine/cardinal/search/filter" @@ -69,6 +70,9 @@ func CreateBuildingSystem(world cardinal.WorldContext) error { } if building.Effect != nil { + building.Effect.Player = request.Tx.PersonaTag + building.Effect.ID = uuid.New().String() + _ = cardinal.AddComponentTo[comp.Effect](world, buildingEntityID) _ = cardinal.SetComponent(world, buildingEntityID, building.Effect) } diff --git a/cardinal/system/effects_spawner.go b/cardinal/system/effects_spawner.go index 26168f2..360ad62 100644 --- a/cardinal/system/effects_spawner.go +++ b/cardinal/system/effects_spawner.go @@ -47,11 +47,14 @@ func EffectsSpawnerSystem(world cardinal.WorldContext) error { return true } + mapPosition, _ := cardinal.GetComponent[comp.Position](world, playerMapEntityID) + effectComponent.Amount = min( constants.ShipyardEffectRaftCapacity, effectComponent.Amount+1, ) effectComponent.BuildingTimeStartedAt = 0 + effectComponent.Position = mapPosition.Island buildingComponent.Effect = effectComponent tile := &(*playerMap.Tiles)[buildingComponent.TileID] tile.Building = buildingComponent diff --git a/cardinal/system/sail_ship.go b/cardinal/system/sail_ship.go new file mode 100644 index 0000000..3d5f2c7 --- /dev/null +++ b/cardinal/system/sail_ship.go @@ -0,0 +1,64 @@ +package system + +import ( + "math" + "time" + + "pkg.world.dev/world-engine/cardinal" + "pkg.world.dev/world-engine/cardinal/search/filter" + "pkg.world.dev/world-engine/cardinal/types" + + comp "oceanus-shard/component" + "oceanus-shard/constants" +) + +// SailShip - +func SailShip(world cardinal.WorldContext) error { + return cardinal.NewSearch().Entity( + filter.Contains(filter.Component[comp.Building](), filter.Component[comp.Effect]())). + Each(world, func(id types.EntityID) bool { + effectComponent, _ := cardinal.GetComponent[comp.Effect](world, id) + + if effectComponent.Amount == 0 || effectComponent.TargetPosition == nil { + return true + } + + buildingComponent, _ := cardinal.GetComponent[comp.Building](world, id) + raftTravelDistancePerTick := + constants.RaftTravelSpeedPerMinute / time.Minute.Seconds() * constants.TickRate.Seconds() + + buildingComponent.Effect.Position = findPointAtDistance( + buildingComponent.Effect.Position, + *buildingComponent.Effect.TargetPosition, + raftTravelDistancePerTick, + ) + + err := updateEffect(world, id, buildingComponent.Effect) + if err != nil { + return true + } + return true + }) +} + +func findPointAtDistance(start, end [2]float64, distance float64) [2]float64 { + x1, y1 := start[0], start[1] + x2, y2 := end[0], end[1] + + dx := x2 - x1 + dy := y2 - y1 + + length := math.Sqrt(dx*dx + dy*dy) + + if distance > length { + return end + } + + unitDx := dx / length + unitDy := dy / length + + newX := x1 + unitDx*distance + newY := y1 + unitDy*distance + + return [2]float64{newX, newY} +} diff --git a/cardinal/system/start_sail_shipwreck_system.go b/cardinal/system/start_sail_shipwreck_system.go new file mode 100644 index 0000000..61c7d7b --- /dev/null +++ b/cardinal/system/start_sail_shipwreck_system.go @@ -0,0 +1,93 @@ +package system + +import ( + "fmt" + + "pkg.world.dev/world-engine/cardinal" + "pkg.world.dev/world-engine/cardinal/search/filter" + "pkg.world.dev/world-engine/cardinal/types" + + comp "oceanus-shard/component" + "oceanus-shard/msg" +) + +// StartSailShipwreckSystem - +func StartSailShipwreckSystem(world cardinal.WorldContext) error { + return cardinal.EachMessage[msg.SailShipwreckMsg, msg.SailShipWreckResult]( + world, + func(request cardinal.TxData[msg.SailShipwreckMsg]) (msg.SailShipWreckResult, error) { + mapEntityID, playerPosition, _ := QueryPlayerComponent[comp.Position]( + world, + request.Tx.PersonaTag, + filter.Component[comp.Player](), + filter.Component[comp.TileMap](), + ) + + player, _ := cardinal.GetComponent[comp.Player](world, mapEntityID) + + _, targetPlayerPosition, err := QueryPlayerComponent[comp.Position]( + world, + request.Msg.Player, + filter.Component[comp.Player](), + filter.Component[comp.TileMap](), + ) + + if err != nil { + return msg.SailShipWreckResult{Success: false}, err + } + + if playerPosition == nil { + return msg.SailShipWreckResult{Success: false}, + fmt.Errorf("failed to sail shipwreck, this player did not have tilemap") + } + + if player.Nickname != request.Tx.PersonaTag { + return msg.SailShipWreckResult{Success: false}, fmt.Errorf("can't sail another player building") + } + err = createSailData(world, request.Tx.PersonaTag, playerPosition, targetPlayerPosition) + if err != nil { + return msg.SailShipWreckResult{Success: false}, err + } + return msg.SailShipWreckResult{Success: true}, err + }) +} + +func createSailData( + world cardinal.WorldContext, + personaTag string, + playerPosition *comp.Position, + targetPlayerPosition *comp.Position) error { + playerBuildingsEntityIDs, playerBuildings, _ := QueryAllPlayerComponents[comp.Building]( + world, + personaTag, + filter.Component[comp.Player](), + filter.Component[comp.Building](), + ) + + var shipyard *comp.Building + var shipyardEntityID types.EntityID + for playerBuildingID, playerBuilding := range playerBuildings { + if playerBuilding.Type != comp.Shipyard { + continue + } + if playerBuilding.Effect.Amount == 0 { + continue + } + if playerBuilding.Effect.Position != playerPosition.Island { + continue + } + shipyard = playerBuilding + shipyardEntityID = playerBuildingsEntityIDs[playerBuildingID] + } + + if shipyard == nil { + return fmt.Errorf("player didn't have ships on base") + } + shipyardEffect := shipyard.Effect + shipyardEffect.TargetPosition = &targetPlayerPosition.Shipwreck + err := updateEffect(world, shipyardEntityID, shipyard.Effect) + if err != nil { + return err + } + return err +} diff --git a/cardinal/system/utils.go b/cardinal/system/utils.go index 1ddb609..8be5eb3 100644 --- a/cardinal/system/utils.go +++ b/cardinal/system/utils.go @@ -186,3 +186,28 @@ func GetTotalPlayersAmount(world cardinal.WorldContext) (int, error) { } return totalPlayers, searchErr } + +func updateEffect(world cardinal.WorldContext, buildingEntityID types.EntityID, effect *comp.Effect) error { + building, _ := cardinal.GetComponent[comp.Building](world, buildingEntityID) + player, _ := cardinal.GetComponent[comp.Player](world, buildingEntityID) + mapEntityID, playerMap, _ := QueryPlayerComponent[comp.TileMap]( + world, + player.Nickname, + filter.Component[comp.Player](), + filter.Component[comp.TileMap](), + ) + + building.Effect = effect + tile := &(*playerMap.Tiles)[building.TileID] + tile.Building = building + if err := cardinal.SetComponent(world, buildingEntityID, effect); err != nil { + return err + } + if err := cardinal.SetComponent(world, buildingEntityID, building); err != nil { + return err + } + if err := cardinal.SetComponent(world, mapEntityID, playerMap); err != nil { + return err + } + return nil +}