craft-bot/playersBoard.go
Timofey.Kovalev 34c5271a62
All checks were successful
continuous-integration/drone/push Build is passing
add logout time and refactor
2021-06-22 20:20:09 +03:00

364 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
p.isOnline = v
return p
}
const (
emojiUp = "\xE2\xAC\x86"
emojiDeaths = "\xF0\x9F\x92\x80"
emojiTime = "\xF0\x9F\x95\x90"
emojiGun = "\xF0\x9F\x94\xAB"
emojiCheck = "\xE2\x9C\x85"
)
func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
botApi := getBotApi(ctx)
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.getKillsByPlayerID(ctx, player.id)
if err != nil {
return err
}
access := "\x1F\x53\x04 offLine"
if p.isOnline {
access = "\x1F\x7E\x02 onLine"
}
level := "-"
if player.level >= 0 {
level = fmt.Sprintf("%d", player.level)
}
d := durafmt.Parse(player.onlineDuration).LimitFirstN(3)
units, err := durafmt.DefaultUnitsCoder.Decode("мес.:мес.,нед.:нед.,дн.:дн.,чс.:чс.,мин.:мин.,cек.:cек.,-:-,-:-")
if err != nil {
panic(err)
}
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)))
if !player.lastLogout.IsZero() && !p.isOnline {
lines = append(lines, fmt.Sprintf("%s Был в сети: *%s*", emojiCheck, player.lastLogout.Format("02 Jan 15:04")))
}
lines = append(lines, "-----")
emojiAch := make([]string, 0, len(p.achievements))
for _, a := range p.achievements {
emojiAch = append(emojiAch, getEmojiByAchievement(a))
}
lines = append(lines, strings.Join(emojiAch, " "))
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
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
}
func (p *playerInfo) deletePlayerInfo(ctx context.Context) error {
botApi := getBotApi(ctx)
conf := getConfig(ctx)
mess := tgbotapi.NewDeleteMessage(conf.ChatID, p.playerMessageID)
_, err := botApi.Send(mess)
if err != nil {
return errors.Wrap(err, "Failed to send message")
}
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
}
func newPlayersBoard() *playersBoard {
return &playersBoard{
players: make([]*playerInfo, 0),
}
}
func (pb *playersBoard) load(ctx context.Context) error {
pb.mx.Lock()
defer pb.mx.Unlock()
db := getDB(ctx)
players, err := db.getPlayers(ctx)
if err != nil {
return err
}
pb.players = make([]*playerInfo, 0, len(players))
for _, p := range players {
pi := &playerInfo{
playerID: p.id,
}
pb.players = append(pb.players, pi)
}
for _, a := range allAchievement {
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) getPlayerBoard(playerID string) *playerInfo {
for i := range pb.players {
if pb.players[i].playerID == playerID {
return pb.players[i]
}
}
pb.mx.Lock()
defer pb.mx.Unlock()
pb.players = append(pb.players, &playerInfo{
playerID: playerID,
})
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) {
log := getLogger(ctx)
<-ctx.Done()
pb.mx.Lock()
defer pb.mx.Unlock()
for _, p := range pb.players {
err := p.deletePlayerInfo(ctx)
if err != nil {
log.Error(err)
}
}
}