craft-bot/playersBoard.go
Timofey.Kovalev 4d5b25a761
All checks were successful
continuous-integration/drone/push Build is passing
add achievements
2021-06-22 02:34:22 +03:00

358 lines
6.9 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"
)
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 := "\xE2\x9D\x8C offLine"
if p.isOnline {
access = "\xE2\x9C\x85 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)))
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)
}
}
}