This commit is contained in:
parent
3302634f61
commit
4d5b25a761
43
dataBase.go
43
dataBase.go
@ -68,7 +68,11 @@ type Player struct {
|
||||
level int
|
||||
}
|
||||
|
||||
func fetchPlayer(r *sql.Rows, p *Player) error {
|
||||
type scannable interface {
|
||||
Scan(dest ...interface{}) error
|
||||
}
|
||||
|
||||
func fetchPlayer(r scannable, p *Player) error {
|
||||
var (
|
||||
err error
|
||||
lastOnline string
|
||||
@ -119,73 +123,56 @@ func (db *dbLayer) getPlayers(ctx context.Context) ([]Player, error) {
|
||||
func (db *dbLayer) getPlayerByID(ctx context.Context, id string) (*Player, error) {
|
||||
query := "SELECT id, name, last_login, online_duration, deaths, level FROM players WHERE id = $1"
|
||||
|
||||
rows, err := db.db.QueryContext(ctx, query, id)
|
||||
if err != nil {
|
||||
row := db.db.QueryRowContext(ctx, query, id)
|
||||
if err := row.Err(); err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
var p Player
|
||||
|
||||
err = fetchPlayer(rows, &p)
|
||||
err := fetchPlayer(row, &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *dbLayer) getPlayerByName(ctx context.Context, name string) (*Player, error) {
|
||||
query := "SELECT id, name, last_login, online_duration, deaths, level FROM players WHERE name = $1 LIMIT 1"
|
||||
|
||||
rows, err := db.db.QueryContext(ctx, query, name)
|
||||
if err != nil {
|
||||
row := db.db.QueryRowContext(ctx, query, name)
|
||||
if err := row.Err(); err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
var p Player
|
||||
|
||||
err = fetchPlayer(rows, &p)
|
||||
err := fetchPlayer(row, &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *dbLayer) getKillsByOPlayerID(ctx context.Context, playerID string) (int, error) {
|
||||
func (db *dbLayer) getKillsByPlayerID(ctx context.Context, playerID string) (int, error) {
|
||||
query := "SELECT SUM(count) FROM killings WHERE player_id = $1 GROUP BY player_id"
|
||||
|
||||
rows, err := db.db.QueryContext(ctx, query, playerID)
|
||||
if err != nil {
|
||||
row := db.db.QueryRowContext(ctx, query, playerID)
|
||||
if err := row.Err(); err != nil {
|
||||
return 0, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
var killsNum int
|
||||
|
||||
err = rows.Scan(&killsNum)
|
||||
err := row.Scan(&killsNum)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Failed to fetch data base")
|
||||
}
|
||||
|
||||
return killsNum, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (db *dbLayer) createPlayer(ctx context.Context, name string, lastOnline time.Time) (*Player, error) {
|
||||
|
19
handlers.go
19
handlers.go
@ -49,7 +49,7 @@ func (c *joinCommand) run(ctx context.Context) {
|
||||
log := getLogger(ctx)
|
||||
pb := getDPlayersBoard(ctx)
|
||||
|
||||
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s вошел в матрицу", c.name))
|
||||
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s подключился", c.name))
|
||||
m, err := botApi.Send(mess)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@ -77,7 +77,9 @@ func (c *joinCommand) run(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
err = pb.getPlayerBoard(p.id).setOnline(true).updatePlayerInfo(ctx)
|
||||
pb.getPlayerBoard(p.id).setOnline(true)
|
||||
|
||||
err = pb.update(ctx, achievementVeryLongOnline)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
@ -95,7 +97,7 @@ func (c *quitCommand) run(ctx context.Context) {
|
||||
log := getLogger(ctx)
|
||||
pb := getDPlayersBoard(ctx)
|
||||
|
||||
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s покинул матрицу", c.name))
|
||||
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s отключился", c.name))
|
||||
m, err := botApi.Send(mess)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@ -115,7 +117,9 @@ func (c *quitCommand) run(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = pb.getPlayerBoard(p.id).setOnline(false).updatePlayerInfo(ctx)
|
||||
pb.getPlayerBoard(p.id).setOnline(false)
|
||||
|
||||
err = pb.update(ctx, achievementVeryLongOnline)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
@ -143,7 +147,7 @@ func (c *deathCommand) run(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = pb.getPlayerBoard(p.id).updatePlayerInfo(ctx)
|
||||
err = pb.update(ctx, achievementDeathless, achievementBestFeeder)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
@ -182,7 +186,7 @@ func (c *killCommand) run(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = pb.getPlayerBoard(p.id).updatePlayerInfo(ctx)
|
||||
err = pb.update(ctx, achievementMaxFrags, achievementPeaceable)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
@ -210,9 +214,8 @@ func (c *changeLevelCommand) run(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = pb.getPlayerBoard(p.id).updatePlayerInfo(ctx)
|
||||
err = pb.update(ctx, achievementMaxLevel)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
245
playersBoard.go
245
playersBoard.go
@ -3,17 +3,61 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/hako/durafmt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type achievement uint8
|
||||
|
||||
const (
|
||||
_ achievement = iota
|
||||
achievementBestFeeder
|
||||
achievementVeryLongOnline
|
||||
achievementDeathless
|
||||
achievementPeaceable
|
||||
achievementMaxFrags
|
||||
achievementMaxLevel
|
||||
)
|
||||
|
||||
func getEmojiByAchievement(ach achievement) string {
|
||||
switch ach {
|
||||
case achievementBestFeeder:
|
||||
return "\xF0\x9F\x91\xBB"
|
||||
case achievementVeryLongOnline:
|
||||
return "\xF0\x9F\xA4\xAA"
|
||||
case achievementDeathless:
|
||||
return "\xF0\x9F\x98\x8E"
|
||||
case achievementPeaceable:
|
||||
return "\xF0\x9F\x98\x87"
|
||||
case achievementMaxFrags:
|
||||
return "\xF0\x9F\x98\x88"
|
||||
case achievementMaxLevel:
|
||||
return "\xF0\x9F\xA7\x99\xE2\x80\x8D\xE2\x99\x82\xEF\xB8\x8F"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var allAchievement = []achievement{
|
||||
achievementBestFeeder,
|
||||
achievementVeryLongOnline,
|
||||
achievementDeathless,
|
||||
achievementPeaceable,
|
||||
achievementMaxFrags,
|
||||
achievementMaxLevel,
|
||||
}
|
||||
|
||||
type playerInfo struct {
|
||||
playerID string
|
||||
playerMessageID int
|
||||
isOnline bool
|
||||
achievements []achievement
|
||||
messageText string
|
||||
}
|
||||
|
||||
func (p *playerInfo) setOnline(v bool) *playerInfo {
|
||||
@ -33,23 +77,18 @@ func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
|
||||
conf := getConfig(ctx)
|
||||
db := getDB(ctx)
|
||||
|
||||
lines := make([]string, 0, 10)
|
||||
|
||||
player, err := db.getPlayerByID(ctx, p.playerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kills, err := db.getKillsByOPlayerID(ctx, player.id)
|
||||
kills, err := db.getKillsByPlayerID(ctx, player.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
test := `*%s* | %s
|
||||
%s Уровень: *%s*
|
||||
%s Смертей: *%d*
|
||||
%s Время в игре: %s
|
||||
%s Фрагов: %d
|
||||
`
|
||||
|
||||
access := "\xE2\x9D\x8C offLine"
|
||||
if p.isOnline {
|
||||
access = "\xE2\x9C\x85 onLine"
|
||||
@ -67,42 +106,47 @@ func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
test = fmt.Sprintf(test, player.name, access,
|
||||
emojiUp, level,
|
||||
emojiDeaths, player.deaths,
|
||||
emojiTime, d.Format(units),
|
||||
emojiGun, kills,
|
||||
)
|
||||
lines = append(lines, fmt.Sprintf("*%s* | %s", player.name, access))
|
||||
lines = append(lines, fmt.Sprintf("%s Уровень: *%s*", emojiUp, level))
|
||||
lines = append(lines, fmt.Sprintf("%s Смертей: *%d*", emojiDeaths, player.deaths))
|
||||
lines = append(lines, fmt.Sprintf("%s Фрагов: *%d*", emojiGun, kills))
|
||||
lines = append(lines, fmt.Sprintf("%s Время в игре: *%s*", emojiTime, d.Format(units)))
|
||||
lines = append(lines, "-----")
|
||||
|
||||
if p.playerMessageID != 0 {
|
||||
mess := tgbotapi.NewEditMessageText(conf.ChatID, p.playerMessageID, test)
|
||||
mess.ParseMode = tgbotapi.ModeMarkdown
|
||||
emojiAch := make([]string, 0, len(p.achievements))
|
||||
|
||||
ikm := tgbotapi.NewInlineKeyboardMarkup(
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("Подробнее", "additional"),
|
||||
),
|
||||
)
|
||||
|
||||
mess.ReplyMarkup = &ikm
|
||||
|
||||
_, err := botApi.Send(mess)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to update message")
|
||||
for _, a := range p.achievements {
|
||||
emojiAch = append(emojiAch, getEmojiByAchievement(a))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
lines = append(lines, strings.Join(emojiAch, " "))
|
||||
|
||||
mess := tgbotapi.NewMessage(conf.ChatID, test)
|
||||
text := strings.Join(lines, "\n")
|
||||
|
||||
if text != p.messageText {
|
||||
|
||||
var c tgbotapi.Chattable
|
||||
|
||||
if p.playerMessageID == 0 {
|
||||
mess := tgbotapi.NewMessage(conf.ChatID, text)
|
||||
mess.ParseMode = tgbotapi.ModeMarkdown
|
||||
|
||||
m, err := botApi.Send(mess)
|
||||
c = mess
|
||||
} else {
|
||||
mess := tgbotapi.NewEditMessageText(conf.ChatID, p.playerMessageID, text)
|
||||
mess.ParseMode = tgbotapi.ModeMarkdown
|
||||
|
||||
c = mess
|
||||
}
|
||||
|
||||
m, err := botApi.Send(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to send message")
|
||||
}
|
||||
|
||||
p.playerMessageID = m.MessageID
|
||||
p.messageText = text
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -120,14 +164,32 @@ func (p *playerInfo) deletePlayerInfo(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *playerInfo) setAchievement(ach achievement) {
|
||||
for _, a := range p.achievements {
|
||||
if a == ach {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
p.achievements = append(p.achievements, ach)
|
||||
}
|
||||
|
||||
func (p *playerInfo) unsetAchievement(ach achievement) {
|
||||
for i, a := range p.achievements {
|
||||
if a == ach {
|
||||
p.achievements = append(p.achievements[:i], p.achievements[:i+1]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type playersBoard struct {
|
||||
mx sync.Mutex
|
||||
players []playerInfo
|
||||
players []*playerInfo
|
||||
}
|
||||
|
||||
func newPlayersBoard() *playersBoard {
|
||||
return &playersBoard{
|
||||
players: make([]playerInfo, 0),
|
||||
players: make([]*playerInfo, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,19 +204,28 @@ func (pb *playersBoard) load(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
pb.players = make([]playerInfo, 0, len(players))
|
||||
pb.players = make([]*playerInfo, 0, len(players))
|
||||
|
||||
for _, p := range players {
|
||||
pi := playerInfo{
|
||||
pi := &playerInfo{
|
||||
playerID: p.id,
|
||||
}
|
||||
|
||||
err := pi.updatePlayerInfo(ctx)
|
||||
pb.players = append(pb.players, pi)
|
||||
}
|
||||
|
||||
for _, a := range allAchievement {
|
||||
err := pb.updateAchievements(ctx, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pb.players = append(pb.players, pi)
|
||||
for _, p := range pb.players {
|
||||
err := p.updatePlayerInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -163,18 +234,110 @@ func (pb *playersBoard) load(ctx context.Context) error {
|
||||
func (pb *playersBoard) getPlayerBoard(playerID string) *playerInfo {
|
||||
for i := range pb.players {
|
||||
if pb.players[i].playerID == playerID {
|
||||
return &pb.players[i]
|
||||
return pb.players[i]
|
||||
}
|
||||
}
|
||||
|
||||
pb.mx.Lock()
|
||||
defer pb.mx.Unlock()
|
||||
|
||||
pb.players = append(pb.players, playerInfo{
|
||||
pb.players = append(pb.players, &playerInfo{
|
||||
playerID: playerID,
|
||||
})
|
||||
|
||||
return &pb.players[len(pb.players)-1]
|
||||
return pb.players[len(pb.players)-1]
|
||||
}
|
||||
|
||||
func (pb *playersBoard) updateAchievements(ctx context.Context, ach achievement) error {
|
||||
db := getDB(ctx)
|
||||
|
||||
var value int
|
||||
|
||||
playerIDs := make([]string, 0, len(pb.players))
|
||||
|
||||
setValue := func(v int, playerID string) {
|
||||
if value < v {
|
||||
value = v
|
||||
|
||||
playerIDs = []string{playerID}
|
||||
} else if value == v {
|
||||
playerIDs = append(playerIDs, playerID)
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range pb.players {
|
||||
player, err := db.getPlayerByID(ctx, p.playerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch ach {
|
||||
case achievementBestFeeder:
|
||||
setValue(player.deaths, p.playerID)
|
||||
|
||||
case achievementVeryLongOnline:
|
||||
setValue(int(player.onlineDuration/time.Second), p.playerID)
|
||||
|
||||
case achievementDeathless:
|
||||
if player.deaths == 0 {
|
||||
playerIDs = append(playerIDs, p.playerID)
|
||||
}
|
||||
|
||||
case achievementPeaceable:
|
||||
kills, err := db.getKillsByPlayerID(ctx, p.playerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setValue(int(^uint(0)>>1)-kills, p.playerID)
|
||||
|
||||
case achievementMaxFrags:
|
||||
kills, err := db.getKillsByPlayerID(ctx, p.playerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setValue(kills, p.playerID)
|
||||
|
||||
case achievementMaxLevel:
|
||||
setValue(player.level, p.playerID)
|
||||
}
|
||||
}
|
||||
|
||||
mainLoop:
|
||||
for _, p := range pb.players {
|
||||
for _, pid := range playerIDs {
|
||||
if p.playerID == pid {
|
||||
p.setAchievement(ach)
|
||||
continue mainLoop
|
||||
}
|
||||
}
|
||||
|
||||
p.unsetAchievement(ach)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb *playersBoard) update(ctx context.Context, as ...achievement) error {
|
||||
pb.mx.Lock()
|
||||
defer pb.mx.Unlock()
|
||||
|
||||
for _, a := range as {
|
||||
err := pb.updateAchievements(ctx, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range pb.players {
|
||||
err := p.updatePlayerInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb *playersBoard) run(ctx context.Context) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user