diff --git a/database/accounts/service.go b/database/accounts/service.go index aa085ec..3d39131 100755 --- a/database/accounts/service.go +++ b/database/accounts/service.go @@ -22,6 +22,7 @@ import ( "database/sql" "errors" "fmt" + "log" "time" "github.com/google/uuid" @@ -72,6 +73,7 @@ func (s *Service) Register(ctx context.Context, username, password string) (dbmo return s.queries.CreatePlayer(ctx, dbmodels.CreatePlayerParams{ Username: username, PasswordHash: hash, + Pang: 20000, // TODO }) } @@ -261,6 +263,42 @@ func (s *Service) getConsumablesWith(ctx context.Context, tx *dbmodels.Queries, }, nil } +func (s *Service) setConsumablesWith(ctx context.Context, tx *dbmodels.Queries, playerID int64, newSlots [10]uint32) (dbmodels.Player, error) { + ret, err := tx.SetPlayerConsumables(ctx, dbmodels.SetPlayerConsumablesParams{ + PlayerID: playerID, + Slot0TypeID: int64(newSlots[0]), + Slot1TypeID: int64(newSlots[1]), + Slot2TypeID: int64(newSlots[2]), + Slot3TypeID: int64(newSlots[3]), + Slot4TypeID: int64(newSlots[4]), + Slot5TypeID: int64(newSlots[5]), + Slot6TypeID: int64(newSlots[6]), + Slot7TypeID: int64(newSlots[7]), + Slot8TypeID: int64(newSlots[8]), + Slot9TypeID: int64(newSlots[9]), + }) + if err != nil { + return dbmodels.Player{}, err + } + + return ret, nil +} + +func (s *Service) updateConsumables(player *dbmodels.GetPlayerRow, ret dbmodels.Player) { + if player != nil { + player.Slot0TypeID = ret.Slot0TypeID + player.Slot1TypeID = ret.Slot1TypeID + player.Slot2TypeID = ret.Slot2TypeID + player.Slot3TypeID = ret.Slot3TypeID + player.Slot4TypeID = ret.Slot4TypeID + player.Slot5TypeID = ret.Slot5TypeID + player.Slot6TypeID = ret.Slot6TypeID + player.Slot7TypeID = ret.Slot7TypeID + player.Slot8TypeID = ret.Slot8TypeID + player.Slot9TypeID = ret.Slot9TypeID + } +} + func (s *Service) decrementConsumableQuantityWith(ctx context.Context, tx *dbmodels.Queries, playerID, itemTypeID int64) (int64, error) { items, err := tx.GetItemsByTypeID(ctx, dbmodels.GetItemsByTypeIDParams{ PlayerID: playerID, @@ -282,13 +320,16 @@ func (s *Service) decrementConsumableQuantityWith(ctx context.Context, tx *dbmod } else if quantity > 1 { _, err := tx.SetItemQuantity(ctx, dbmodels.SetItemQuantityParams{ PlayerID: playerID, + ItemID: item.ItemID, Quantity: sql.NullInt64{Int64: quantity - 1, Valid: true}, }) + log.Printf("decrement consumable quantity: %d to %d", quantity, quantity-1) if err != nil { return 0, err } return item.ItemID, nil } else { + log.Printf("remove consumable") return item.ItemID, tx.RemoveItemFromInventory(ctx, dbmodels.RemoveItemFromInventoryParams{ PlayerID: playerID, ItemID: item.ItemID, @@ -310,7 +351,7 @@ func (s *Service) incrementConsumableQuantityWith(ctx context.Context, tx *dbmod _, err = tx.AddItemToInventory(ctx, dbmodels.AddItemToInventoryParams{ PlayerID: playerID, ItemTypeID: itemTypeID, - Quantity: sql.NullInt64{Valid: true, Int64: 1}, + Quantity: sql.NullInt64{Valid: true, Int64: amount}, }) if err != nil { return fmt.Errorf("adding item to inventory: %w", err) @@ -325,6 +366,7 @@ func (s *Service) incrementConsumableQuantityWith(ctx context.Context, tx *dbmod quantity := item.Quantity.Int64 _, err = tx.SetItemQuantity(ctx, dbmodels.SetItemQuantityParams{ PlayerID: playerID, + ItemID: item.ItemID, Quantity: sql.NullInt64{Int64: quantity + amount, Valid: true}, }) return err @@ -342,30 +384,8 @@ func (s *Service) SetConsumables(ctx context.Context, playerID int64, newSlots [ defer tx.Rollback() queries := s.queries.WithTx(tx) - oldSlots, err := s.getConsumablesWith(ctx, queries, playerID) - if err != nil { - return err - } - - // Inefficient, but should be OK for just 10 item slots. - for i := range newSlots { - // Only act if the old slot != the new slot. - if oldSlots[i] != newSlots[i] { - // If the old slot had something, increment it back to the inventory. - if oldSlots[i] != 0 { - if err := s.incrementConsumableQuantityWith(ctx, queries, playerID, int64(oldSlots[i]), 1); err != nil { - return err - } - } - // If the new slot has something, decrement it from the inventory. - if newSlots[i] != 0 { - if _, err := s.decrementConsumableQuantityWith(ctx, queries, playerID, int64(newSlots[i])); err != nil { - return fmt.Errorf("set consumables: %w", err) - } - } - } - } + // TODO: check that there is enough in the inventory ret, err := queries.SetPlayerConsumables(ctx, dbmodels.SetPlayerConsumablesParams{ PlayerID: playerID, @@ -508,6 +528,76 @@ func (s *Service) PurchaseItem(ctx context.Context, playerID, pangTotal, pointTo return newCurrency, nil } +func (s *Service) UseItem(ctx context.Context, playerID, itemTypeID int64, player *dbmodels.GetPlayerRow) error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + queries := s.queries.WithTx(tx) + + _, err = s.decrementConsumableQuantityWith(ctx, queries, playerID, itemTypeID) + if err != nil { + return err + } + + slots, err := s.getConsumablesWith(ctx, queries, playerID) + if err != nil { + return err + } + for i := 9; i >= 0; i-- { + if slots[i] == uint32(itemTypeID) { + slots[i] = 0 + break + } + } + ret, err := s.setConsumablesWith(ctx, queries, playerID, slots) + if err != nil { + return err + } + + err = tx.Commit() + if err != nil { + return err + } + + s.updateConsumables(player, ret) + + return nil +} + +func (s *Service) AddPang(ctx context.Context, playerID, pang int64) (int64, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return 0, err + } + defer tx.Rollback() + + queries := s.queries.WithTx(tx) + + currency, err := queries.GetPlayerCurrency(ctx, playerID) + if err != nil { + return 0, err + } + + newCurrency, err := queries.SetPlayerCurrency(ctx, dbmodels.SetPlayerCurrencyParams{ + PlayerID: playerID, + Pang: currency.Pang + pang, + Points: currency.Points, + }) + if err != nil { + return 0, err + } + + err = tx.Commit() + if err != nil { + return 0, err + } + + return newCurrency.Pang, nil +} + type DecorationTypeIDs struct { BackgroundTypeID uint32 FrameTypeID uint32 diff --git a/game/model/room.go b/game/model/room.go index 3b75626..0d984f2 100644 --- a/game/model/room.go +++ b/game/model/room.go @@ -121,6 +121,7 @@ type RoomState struct { GamePhase GamePhase ShotSync *ShotSyncData + HoleInfo *HoleInfo ActiveConnID uint32 } @@ -163,5 +164,16 @@ type RoomInfoPlayer struct { type ShotSyncData struct { ActiveConnID uint32 X, Y, Z float32 - Unknown [22]byte + Unknown1 [3]byte + Pang uint32 + BonusPang uint32 + Unknown2 [11]byte +} + +type HoleInfo struct { + Par uint8 + TeeX float32 + TeeZ float32 + PinX float32 + PinZ float32 } diff --git a/game/packet/client.go b/game/packet/client.go index a376b80..4529fd3 100755 --- a/game/packet/client.go +++ b/game/packet/client.go @@ -398,7 +398,7 @@ type ClientPauseGame struct { type ClientHoleEnd struct { ClientMessage_ - // TODO + Stats pangya.PlayerStats } // ClientSetIdleStatus sets whether or not the client is idle in a room. diff --git a/game/packet/server.go b/game/packet/server.go index be9c69c..c1f207d 100755 --- a/game/packet/server.go +++ b/game/packet/server.go @@ -68,7 +68,7 @@ var ServerMessageTable = common.NewMessageTable(map[uint16]ServerMessage{ 0x00A1: &ServerUserInfo{}, 0x00A3: &ServerPlayerLoadProgress{}, 0x00C4: &ServerRoomAction{}, - 0x00C8: &ServerPangPurchaseData{}, + 0x00C8: &ServerPangBalanceData{}, 0x00CC: &ServerRoomShotEnd{}, 0x00F1: &ServerMessageConnect{}, 0x00F5: &ServerMultiplayerJoined{}, @@ -299,8 +299,8 @@ type ServerRoomAction struct { gamemodel.RoomAction } -// ServerPangPurchaseData is sent after a pang purchase succeeds. -type ServerPangPurchaseData struct { +// ServerPangBalanceData is sent after a pang purchase succeeds. +type ServerPangBalanceData struct { ServerMessage_ PangsRemaining uint64 PangsSpent uint64 @@ -522,10 +522,10 @@ type ServerRoomItemUseAnnounce struct { type ServerRoomSetWind struct { ServerMessage_ - Wind uint8 - Unknown uint8 - Unknown2 uint16 - Reset bool `struct:"bool"` + Wind uint8 + Unknown uint8 + Heading uint16 + Reset bool `struct:"bool"` } type ServerRoomUserTypingAnnounce struct { @@ -568,7 +568,7 @@ type PlayerGameResult struct { type ServerRoomFinishGame struct { ServerMessage_ NumPlayers uint8 - Results []PlayerGameResult `struct:"sizefrom=NumPlayers"` + Standings []PlayerGameResult `struct:"sizefrom=NumPlayers"` } type ServerPurchaseItemResponse struct { @@ -638,9 +638,8 @@ type UpdateRewardUnknownData struct { } type UpdatePangBalanceData struct { - Unknown uint32 - PangAmount uint32 - Unknown2 uint32 + Status uint32 + PangAmount uint64 } type ServerMoneyUpdate struct { diff --git a/game/room/event.go b/game/room/event.go index fe4c45c..28ba9bf 100644 --- a/game/room/event.go +++ b/game/room/event.go @@ -190,6 +190,7 @@ type RoomGameTurnEnd struct { type RoomGameHoleEnd struct { roomEvent ConnID uint32 + Stats pangya.PlayerStats } type RoomGameShotSync struct { diff --git a/game/room/lobby.go b/game/room/lobby.go index bfac69d..150ec8c 100644 --- a/game/room/lobby.go +++ b/game/room/lobby.go @@ -25,6 +25,7 @@ import ( "github.com/pangbox/server/common" "github.com/pangbox/server/common/actor" + "github.com/pangbox/server/database/accounts" gamemodel "github.com/pangbox/server/game/model" gamepacket "github.com/pangbox/server/game/packet" log "github.com/sirupsen/logrus" @@ -34,9 +35,10 @@ import ( type Lobby struct { actor.Base[LobbyEvent] - logger *log.Entry - storage *Storage - players *orderedmap.OrderedMap[uint32, *LobbyPlayer] + logger *log.Entry + storage *Storage + players *orderedmap.OrderedMap[uint32, *LobbyPlayer] + accounts *accounts.Service } type LobbyPlayer struct { @@ -45,11 +47,12 @@ type LobbyPlayer struct { Joined time.Time } -func NewLobby(ctx context.Context, logger *log.Entry) *Lobby { +func NewLobby(ctx context.Context, logger *log.Entry, accounts *accounts.Service) *Lobby { lobby := &Lobby{ - logger: logger, - storage: new(Storage), - players: orderedmap.New[uint32, *LobbyPlayer](), + logger: logger, + storage: new(Storage), + players: orderedmap.New[uint32, *LobbyPlayer](), + accounts: accounts, } lobby.TryStart(ctx, lobby.task) return lobby @@ -150,7 +153,7 @@ func (l *Lobby) handleEvent(ctx context.Context, t *actor.Task[LobbyEvent], msg func (l *Lobby) lobbyRoomCreate(ctx context.Context, e *LobbyRoomCreate) (*Room, error) { room := l.storage.NewRoom(ctx) - room.Start(ctx, e.Room, l) + room.Start(ctx, e.Room, l, l.accounts) e.Room.RoomNumber = room.Number() l.broadcast(ctx, &gamepacket.ServerRoomList{ Count: 1, diff --git a/game/room/room.go b/game/room/room.go index 3372469..72ccc93 100644 --- a/game/room/room.go +++ b/game/room/room.go @@ -26,19 +26,22 @@ import ( "github.com/pangbox/server/common" "github.com/pangbox/server/common/actor" + "github.com/pangbox/server/database/accounts" gamemodel "github.com/pangbox/server/game/model" gamepacket "github.com/pangbox/server/game/packet" "github.com/pangbox/server/pangya" log "github.com/sirupsen/logrus" orderedmap "github.com/wk8/go-ordered-map/v2" + "golang.org/x/exp/slices" "golang.org/x/sync/errgroup" ) type Room struct { actor.Base[RoomEvent] - state gamemodel.RoomState - players *orderedmap.OrderedMap[uint32, RoomPlayer] - lobby *Lobby + state gamemodel.RoomState + players *orderedmap.OrderedMap[uint32, RoomPlayer] + lobby *Lobby + accounts *accounts.Service } type PlayerGameState struct { @@ -53,9 +56,13 @@ type RoomPlayer struct { Conn *gamepacket.ServerConn PlayerData pangya.PlayerData GameState *PlayerGameState + Pang uint64 + BonusPang uint64 + Stroke int8 + Score int32 } -func (r *Room) Start(ctx context.Context, state gamemodel.RoomState, lobby *Lobby) bool { +func (r *Room) Start(ctx context.Context, state gamemodel.RoomState, lobby *Lobby, accounts *accounts.Service) bool { return r.TryStart(ctx, func(ctx context.Context, t *actor.Task[RoomEvent]) error { r.state.Active = true r.state.Open = true @@ -73,6 +80,7 @@ func (r *Room) Start(ctx context.Context, state gamemodel.RoomState, lobby *Lobb r.state.GamePhase = gamemodel.LobbyPhase r.players = orderedmap.New[uint32, RoomPlayer]() r.lobby = lobby + r.accounts = accounts return r.task(ctx, t) }) } @@ -318,10 +326,10 @@ func (r *Room) handlePlayerLeave(ctx context.Context, event RoomPlayerLeave) err func (r *Room) handleRoomAction(ctx context.Context, event RoomAction) error { if event.Action.Rotation != nil { - if player, ok := r.players.Get(event.ConnID); ok { - player.Entry.Angle = event.Action.Rotation.Z + if pair := r.players.GetPair(event.ConnID); pair != nil { + pair.Value.Entry.Angle = event.Action.Rotation.Z return r.broadcast(ctx, &gamepacket.ServerRoomAction{ - ConnID: player.Entry.ConnID, + ConnID: pair.Value.Entry.ConnID, RoomAction: event.Action, }) } @@ -330,11 +338,11 @@ func (r *Room) handleRoomAction(ctx context.Context, event RoomAction) error { } func (r *Room) handleRoomPlayerIdle(ctx context.Context, event RoomPlayerIdle) error { - if player, ok := r.players.Get(event.ConnID); ok { + if pair := r.players.GetPair(event.ConnID); pair != nil { if event.Idle { - player.Entry.StatusFlags |= gamemodel.RoomStateAway + pair.Value.Entry.StatusFlags |= gamemodel.RoomStateAway } else { - player.Entry.StatusFlags &^= gamemodel.RoomStateAway + pair.Value.Entry.StatusFlags &^= gamemodel.RoomStateAway } // TODO: not sure what to broadcast return r.broadcastPlayerList(ctx) @@ -343,16 +351,16 @@ func (r *Room) handleRoomPlayerIdle(ctx context.Context, event RoomPlayerIdle) e } func (r *Room) handleRoomPlayerReady(ctx context.Context, event RoomPlayerReady) error { - if player, ok := r.players.Get(event.ConnID); ok { + if pair := r.players.GetPair(event.ConnID); pair != nil { state := byte(0) if event.Ready { - player.Entry.StatusFlags |= gamemodel.RoomStateReady + pair.Value.Entry.StatusFlags |= gamemodel.RoomStateReady } else { - player.Entry.StatusFlags &^= gamemodel.RoomStateReady + pair.Value.Entry.StatusFlags &^= gamemodel.RoomStateReady state = 1 } return r.broadcast(ctx, &gamepacket.ServerPlayerReady{ - ConnID: player.Entry.ConnID, + ConnID: pair.Value.Entry.ConnID, State: state, }) } @@ -360,18 +368,18 @@ func (r *Room) handleRoomPlayerReady(ctx context.Context, event RoomPlayerReady) } func (r *Room) handleRoomPlayerKick(ctx context.Context, event RoomPlayerKick) error { - player, ok := r.players.Get(event.ConnID) - if !ok { + pair := r.players.GetPair(event.ConnID) + if pair == nil { return nil } - subject, ok := r.players.Get(event.KickConnID) - if !ok { + subject := r.players.GetPair(event.KickConnID) + if subject == nil { return nil } - if player.Entry.ConnID != r.state.OwnerConnID { + if pair.Value.Entry.ConnID != r.state.OwnerConnID { return nil } - return r.removePlayer(ctx, subject.Entry.ConnID) + return r.removePlayer(ctx, subject.Value.Entry.ConnID) } func (r *Room) handleRoomSettingsChange(ctx context.Context, event RoomSettingsChange) error { @@ -431,6 +439,9 @@ func (r *Room) handleRoomStartGame(ctx context.Context, event RoomStartGame) err } r.state.CurrentHole = 1 for i, pair := 0, r.players.Oldest(); pair != nil; pair = pair.Next() { + // Clear ready status. + pair.Value.Entry.StatusFlags &^= gamemodel.RoomStateReady + player := pair.Value gameInit.Full.Players[i] = gamepacket.GamePlayer{ Number: uint16(i + 1), @@ -473,8 +484,8 @@ func (r *Room) handleRoomLoadingProgress(ctx context.Context, event RoomLoadingP } func (r *Room) handleRoomGameReady(ctx context.Context, event RoomGameReady) error { - if player, ok := r.players.Get(event.ConnID); ok { - player.GameState.GameReady = true + if pair := r.players.GetPair(event.ConnID); pair != nil { + pair.Value.GameState.GameReady = true } if r.checkGameReady() { r.startHole(ctx) @@ -543,8 +554,8 @@ func (r *Room) handleRoomGameTurn(ctx context.Context, event RoomGameTurn) error } func (r *Room) handleRoomGameTurnEnd(ctx context.Context, event RoomGameTurnEnd) error { - if player, ok := r.players.Get(event.ConnID); ok { - player.GameState.TurnEnd = true + if pair := r.players.GetPair(event.ConnID); pair != nil { + pair.Value.GameState.TurnEnd = true } if r.checkTurnEnd() { r.broadcast(ctx, &gamepacket.ServerRoomShotEnd{ @@ -572,25 +583,56 @@ func (r *Room) handleRoomGameTurnEnd(ctx context.Context, event RoomGameTurnEnd) r.state.CurrentHole++ if r.state.CurrentHole > r.state.NumHoles { for pair := r.players.Oldest(); pair != nil; pair = pair.Next() { - // TODO: - // - Need to actually calculate values for real. r.broadcast(ctx, &gamepacket.ServerEvent{ Type: gamepacket.GameEndEvent, Data: gamepacket.ChatMessage{ Nickname: common.ToPString(pair.Value.Entry.Nickname), }, GameEnd: &gamepacket.GameEnd{ - Score: 0, - Pang: 0, + Score: pair.Value.Score, + Pang: pair.Value.Pang, }, }) results := &gamepacket.ServerRoomFinishGame{ NumPlayers: uint8(r.players.Len()), - Results: make([]gamepacket.PlayerGameResult, r.players.Len()), + Standings: make([]gamepacket.PlayerGameResult, r.players.Len()), } for i, pair := 0, r.players.Oldest(); pair != nil; pair = pair.Next() { - results.Results[i].ConnID = pair.Value.Entry.ConnID - results.Results[i].Place = uint8(i + 1) + results.Standings[i].ConnID = pair.Value.Entry.ConnID + results.Standings[i].Pang = pair.Value.Pang + results.Standings[i].Score = int8(pair.Value.Score) + results.Standings[i].BonusPang = pair.Value.BonusPang + + newPang, err := r.accounts.AddPang(ctx, int64(pair.Value.Entry.PlayerID), int64(pair.Value.Pang+pair.Value.BonusPang)) + if err != nil { + log.WithError(err).Error("failed giving game-ending pang") + } + + if err := pair.Value.Conn.SendMessage(ctx, &gamepacket.ServerPangBalanceData{PangsRemaining: uint64(newPang)}); err != nil { + log.WithError(err).Error("failed informing player of game-ending pang") + } + + // reset game state now + pair.Value.Score = 0 + pair.Value.Pang = 0 + pair.Value.BonusPang = 0 + pair.Value.GameState.HoleEnd = false + pair.Value.GameState.ShotSync = nil + + i++ + } + slices.SortFunc(results.Standings, func(a, b gamepacket.PlayerGameResult) bool { + return a.Score < b.Score + }) + results.Standings[0].Place = 1 + for i := 1; i < len(results.Standings); i++ { + if results.Standings[i-1].Score == results.Standings[i].Score { + // If tie: use placement of tied player(s) + results.Standings[i].Place = results.Standings[i-1].Place + } else { + // If not tie: use position in standing as placement + results.Standings[i].Place = uint8(i + 1) + } } r.broadcast(ctx, results) r.state.Open = true @@ -615,8 +657,10 @@ func (r *Room) handleRoomGameTurnEnd(ctx context.Context, event RoomGameTurnEnd) } func (r *Room) handleRoomGameHoleEnd(ctx context.Context, event RoomGameHoleEnd) error { - if player, ok := r.players.Get(event.ConnID); ok { - player.GameState.HoleEnd = true + if pair := r.players.GetPair(event.ConnID); pair != nil { + pair.Value.GameState.HoleEnd = true + pair.Value.Score += int32(pair.Value.Stroke) - int32(r.state.HoleInfo.Par) + pair.Value.Stroke = 0 } return nil } @@ -630,13 +674,20 @@ func (r *Room) handleRoomGameShotSync(ctx context.Context, event RoomGameShotSyn log.Warningf("Shot sync mismatch: %#v vs %#v", r.state.ShotSync, syncData) } } - if player, ok := r.players.Get(event.ConnID); ok { - player.GameState.ShotSync = r.state.ShotSync + if pair := r.players.GetPair(event.ConnID); pair != nil { + pair.Value.GameState.ShotSync = r.state.ShotSync } if r.checkShotSync() { r.broadcast(ctx, &gamepacket.ServerRoomShotSync{ Data: *r.state.ShotSync, }) + if pair := r.players.GetPair(r.state.ShotSync.ActiveConnID); pair != nil { + pair.Value.Pang += uint64(r.state.ShotSync.Pang) + pair.Value.BonusPang += uint64(r.state.ShotSync.Pang) + pair.Value.Stroke++ + } else { + log.WithField("ConnID", r.state.ShotSync.ActiveConnID).Warn("couldn't find conn") + } for pair := r.players.Oldest(); pair != nil; pair = pair.Next() { pair.Value.GameState.ShotSync = nil } @@ -646,7 +697,13 @@ func (r *Room) handleRoomGameShotSync(ctx context.Context, event RoomGameShotSyn } func (r *Room) handleRoomGameHoleInfo(ctx context.Context, event RoomGameHoleInfo) error { - fmt.Printf("%#v\n", event) + r.state.HoleInfo = &gamemodel.HoleInfo{ + Par: event.Par, + TeeX: event.TeeX, + TeeZ: event.TeeZ, + PinX: event.PinX, + PinZ: event.PinZ, + } return nil } @@ -690,11 +747,19 @@ func (r *Room) startHole(ctx context.Context) error { r.broadcast(ctx, &gamepacket.ServerRoomSetWeather{ Weather: 0, }) + // Allow wind up to 12m, but make it increasingly unlikely. + wind := rand.Intn(12) + if wind >= 11 { + wind = rand.Intn(12) + } + if wind >= 10 { + wind = rand.Intn(12) + } r.broadcast(ctx, &gamepacket.ServerRoomSetWind{ - Wind: 10, - Unknown: 0, - Unknown2: 0x98, - Reset: true, + Wind: uint8(wind), + Unknown: 0, + Heading: uint16(rand.Intn(256)), + Reset: true, }) // TODO: select player based on proper order r.state.ActiveConnID = r.players.Oldest().Key @@ -737,15 +802,15 @@ func (r *Room) startHole(ctx context.Context) error { } func (r *Room) removePlayer(ctx context.Context, connID uint32) error { - if player, ok := r.players.Get(connID); ok { - err := player.Conn.SendMessage(ctx, &gamepacket.ServerRoomLeave{ + if pair := r.players.GetPair(connID); pair != nil { + err := pair.Value.Conn.SendMessage(ctx, &gamepacket.ServerRoomLeave{ RoomNumber: -1, }) r.broadcast(ctx, &gamepacket.ServerRoomCensus{ Type: byte(gamepacket.ListRemove), Unknown: -1, ListRemove: &gamepacket.RoomCensusListRemove{ - ConnID: player.Entry.ConnID, + ConnID: pair.Value.Entry.ConnID, }, }) r.players.Delete(connID) @@ -753,7 +818,7 @@ func (r *Room) removePlayer(ctx context.Context, connID uint32) error { ConnID: connID, RoomNumber: -1, }) - if r.state.OwnerConnID == player.Entry.ConnID && r.players.Len() > 0 { + if r.state.OwnerConnID == pair.Value.Entry.ConnID && r.players.Len() > 0 { newOwner := r.players.Oldest() newOwner.Value.Entry.StatusFlags |= gamemodel.RoomStateMaster r.state.OwnerConnID = newOwner.Value.Entry.ConnID diff --git a/game/server/conn.go b/game/server/conn.go index da65cc7..db49ddb 100755 --- a/game/server/conn.go +++ b/game/server/conn.go @@ -253,10 +253,18 @@ func (c *Conn) Handle(ctx context.Context) error { Club: t.Club, }) case *gamepacket.ClientShotItemUse: - c.currentRoom.Send(ctx, room.RoomGameShotItemUse{ - ConnID: c.connID, - ItemTypeID: t.ItemTypeID, - }) + if c.currentRoom == nil { + break + } + err := c.s.accountsService.UseItem(ctx, c.session.PlayerID, int64(t.ItemTypeID), &c.player) + if err != nil { + log.WithError(err).Error("using item failed") + } else { + c.currentRoom.Send(ctx, room.RoomGameShotItemUse{ + ConnID: c.connID, + ItemTypeID: t.ItemTypeID, + }) + } case *gamepacket.ClientUserTypingIndicator: c.currentRoom.Send(ctx, room.RoomGameTypingIndicator{ ConnID: c.connID, @@ -276,6 +284,7 @@ func (c *Conn) Handle(ctx context.Context) error { case *gamepacket.ClientHoleEnd: c.currentRoom.Send(ctx, room.RoomGameHoleEnd{ ConnID: c.connID, + Stats: t.Stats, }) case *gamepacket.ClientGameEnd: // TODO @@ -623,7 +632,7 @@ func (c *Conn) Handle(ctx context.Context) error { return fmt.Errorf("purchasing item %v: %w", item.ItemTypeID, err) } } - if err := c.SendMessage(ctx, &gamepacket.ServerPangPurchaseData{ + if err := c.SendMessage(ctx, &gamepacket.ServerPangBalanceData{ PangsRemaining: uint64(newCurrency.Pang), PangsSpent: uint64(pangTotal), }); err != nil { diff --git a/game/server/server.go b/game/server/server.go index dbcdefc..02ca663 100755 --- a/game/server/server.go +++ b/game/server/server.go @@ -71,7 +71,7 @@ func New(opts Options) *Server { // Listen listens for connections on a given address and blocks indefinitely. func (s *Server) Listen(ctx context.Context, addr string) error { - s.lobby = room.NewLobby(ctx, s.logger) + s.lobby = room.NewLobby(ctx, s.logger, s.accountsService) return s.baseServer.Listen(s.logger, addr, func(logger *log.Entry, socket net.Conn) error { conn := Conn{ ServerConn: common.NewServerConn( diff --git a/gameconfig/config.go b/gameconfig/config.go index 9875951..fa7b6c4 100644 --- a/gameconfig/config.go +++ b/gameconfig/config.go @@ -25,6 +25,7 @@ func init() { type Provider interface { GetCharacterDefaults(id uint8) CharacterDefaults GetDefaultClubSetTypeID() uint32 + GetDefaultPang() uint64 } type CharacterDefaults struct { @@ -35,11 +36,13 @@ type CharacterDefaults struct { type Manifest struct { CharacterDefaults []CharacterDefaults `json:"CharacterDefaults"` DefaultClubSetTypeID uint32 `json:"DefaultClubSetTypeID"` + DefaultPang uint64 `json:"DefaultPang"` } type configFileProvider struct { characterDefaults map[uint8]CharacterDefaults defaultClubSetTypeID uint32 + defaultPang uint64 } func Default() Provider { @@ -74,6 +77,7 @@ func FromManifest(manifest Manifest) Provider { provider := &configFileProvider{ characterDefaults: make(map[uint8]CharacterDefaults), defaultClubSetTypeID: manifest.DefaultClubSetTypeID, + defaultPang: manifest.DefaultPang, } for _, defaults := range manifest.CharacterDefaults { provider.characterDefaults[defaults.CharacterID] = defaults @@ -88,3 +92,7 @@ func (c *configFileProvider) GetCharacterDefaults(id uint8) CharacterDefaults { func (c *configFileProvider) GetDefaultClubSetTypeID() uint32 { return c.defaultClubSetTypeID } + +func (c *configFileProvider) GetDefaultPang() uint64 { + return c.defaultPang +} diff --git a/gameconfig/default.json b/gameconfig/default.json index b8009cb..26e4efa 100644 --- a/gameconfig/default.json +++ b/gameconfig/default.json @@ -1,4 +1,5 @@ { + "DefaultPang": 20000, "DefaultClubSetTypeID": 268435553, "CharacterDefaults": [] } diff --git a/gen/dbmodels/player.sql.go b/gen/dbmodels/player.sql.go index 705071a..cbf681d 100644 --- a/gen/dbmodels/player.sql.go +++ b/gen/dbmodels/player.sql.go @@ -14,9 +14,10 @@ const createPlayer = `-- name: CreatePlayer :one INSERT INTO player ( username, nickname, - password_hash + password_hash, + pang ) VALUES ( - ?, ?, ? + ?, ?, ?, ? ) RETURNING player_id, username, nickname, password_hash, pang, points, rank, ball_type_id, mascot_type_id, slot0_type_id, slot1_type_id, slot2_type_id, slot3_type_id, slot4_type_id, slot5_type_id, slot6_type_id, slot7_type_id, slot8_type_id, slot9_type_id, caddie_id, club_id, background_id, frame_id, sticker_id, slot_id, cut_in_id, title_id, poster0_id, poster1_id, character_id ` @@ -25,10 +26,16 @@ type CreatePlayerParams struct { Username string Nickname sql.NullString PasswordHash string + Pang int64 } func (q *Queries) CreatePlayer(ctx context.Context, arg CreatePlayerParams) (Player, error) { - row := q.db.QueryRowContext(ctx, createPlayer, arg.Username, arg.Nickname, arg.PasswordHash) + row := q.db.QueryRowContext(ctx, createPlayer, + arg.Username, + arg.Nickname, + arg.PasswordHash, + arg.Pang, + ) var i Player err := row.Scan( &i.PlayerID, diff --git a/pangya/player.go b/pangya/player.go index 2ecb607..02c0018 100755 --- a/pangya/player.go +++ b/pangya/player.go @@ -57,17 +57,9 @@ type PlayerStats struct { Level byte Pang uint64 TotalScore int32 - Difficulty1Score uint8 //Not 100% sure on these - Difficulty2Score uint8 - Difficulty3Score uint8 - Difficulty4Score uint8 - Difficulty5Score uint8 + DifficultyScore [5]uint8 UnknownFlag uint8 //possibly total? - BestPang1 uint64 - BestPang2 uint64 - BestPang3 uint64 - BestPang4 uint64 - BestPang5 uint64 + BestPang [5]uint64 BestPangTotal uint64 GamesPlayed uint32 TeamHole uint32 diff --git a/queries/player.sql b/queries/player.sql index b06da28..6ea5311 100644 --- a/queries/player.sql +++ b/queries/player.sql @@ -38,9 +38,10 @@ LIMIT 1; INSERT INTO player ( username, nickname, - password_hash + password_hash, + pang ) VALUES ( - ?, ?, ? + ?, ?, ?, ? ) RETURNING *;