add achievements
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Timofey.Kovalev
2021-06-22 02:34:22 +03:00
parent 3302634f61
commit 4d5b25a761
4 changed files with 249 additions and 96 deletions

View File

@ -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,43 +106,48 @@ 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"),
),
)
for _, a := range p.achievements {
emojiAch = append(emojiAch, getEmojiByAchievement(a))
}
mess.ReplyMarkup = &ikm
lines = append(lines, strings.Join(emojiAch, " "))
_, err := botApi.Send(mess)
if err != nil {
return errors.Wrap(err, "Failed to update message")
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
}
return nil
m, err := botApi.Send(c)
if err != nil {
return errors.Wrap(err, "Failed to send message")
}
p.playerMessageID = m.MessageID
p.messageText = text
}
mess := tgbotapi.NewMessage(conf.ChatID, test)
mess.ParseMode = tgbotapi.ModeMarkdown
m, err := botApi.Send(mess)
if err != nil {
return errors.Wrap(err, "Failed to send message")
}
p.playerMessageID = m.MessageID
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) {