big revamp

This commit is contained in:
shoopea 2021-11-06 23:33:16 +08:00
parent 90e584fcff
commit 5fad9f085e
11 changed files with 1245 additions and 351 deletions

227
bot.go
View File

@ -2,60 +2,212 @@ package main
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"time"
tb "gopkg.in/tucnak/telebot.v2"
)
func BotHandlers(b *tb.Bot) {
type Bot struct {
bot *tb.Bot
Config *TelegramConfig
}
b.Handle("/pause", botPause)
b.Handle("/unpause", botUnpause)
func (b *Bot) Start() {
var err error
b.bot, err = tb.NewBot(tb.Settings{
Token: b.Config.Token,
URL: b.Config.URL,
Poller: &tb.LongPoller{Timeout: 10 * time.Second},
})
failError(err, "Bot.Start() : registering bot")
b.Handle(tb.OnPhoto, botPhoto)
b.Handle(tb.OnChannelPost, botChannelPost)
b.Handle(tb.OnQuery, botQuery)
b.Handle(tb.OnText, botText)
b.Handle(tb.OnDocument, botDocument)
b.BotHandlers()
}
b.Start()
func (b *Bot) SendUser(id int64, msg string) {
u := tb.User{
ID: int(id),
}
_, err := b.bot.Send(&u, msg)
logErrorDebug(err, "Bot.SendUser()")
}
func (b *Bot) SendChat(chatID int64, text string) {
opt := tb.SendOptions{
ParseMode: tb.ModeDefault,
}
ch := tb.Chat{
ID: chatID,
}
_, err := b.bot.Send(&ch, text, &opt)
logErrorDebug(err, "Bot.SendChat()")
}
func (b *Bot) BotHandlers() {
b.bot.Handle("/pause", botPause)
b.bot.Handle("/unpause", botUnpause)
b.bot.Handle("/register", botRegister)
b.bot.Handle("/delete", botDelete)
b.bot.Handle("/companies", botCompanies)
b.bot.Handle("/clients", botClients)
b.bot.Handle("/players", botPlayers)
b.bot.Handle(tb.OnPhoto, botPhoto)
b.bot.Handle(tb.OnChannelPost, botChannelPost)
b.bot.Handle(tb.OnQuery, botQuery)
b.bot.Handle(tb.OnText, botText)
b.bot.Handle(tb.OnDocument, botDocument)
go func() {
time.Sleep(time.Second)
b.SendUser(b.Config.AdminID, fmt.Sprintf("Started (%s)", version))
}()
b.bot.Start()
}
func botPause(m *tb.Message) {
forcePaused = true
if !paused {
paused = true
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "pause",
PrintText(m)
for userID, cc := range cfg.Clients {
if userID == m.Sender.ID {
if co, ok := srv.Status.Companies[cc.CompanyID]; ok {
if clt, ok2 := srv.Status.Clients[co.ClientID]; ok2 {
clt.Paused = true
}
_, err := conn.Write(px.Bytes())
logErrorDebug(err, "botPause : conn.Write")
sendChat(-436055948, "Game paused.")
}
}
}
if !srv.Status.Paused {
srv.Pause()
bot.SendChat(bot.Config.ChatID, "Game paused.")
} else {
sendChat(-436055948, "Game already paused.")
bot.SendChat(bot.Config.ChatID, "Game already paused.")
}
return
}
func botUnpause(m *tb.Message) {
forcePaused = false
if paused && len(clients) > 1 {
paused = false
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "unpause",
PrintText(m)
for userID, cc := range cfg.Clients {
if userID == m.Sender.ID {
if co, ok := srv.Status.Companies[cc.CompanyID]; ok {
if clt, ok2 := srv.Status.Clients[co.ClientID]; ok2 {
clt.Paused = false
}
_, err := conn.Write(px.Bytes())
logErrorDebug(err, "botUnpause : conn.Write")
sendChat(-436055948, "Game unpaused.")
} else if len(clients) == 1 {
sendChat(-436055948, "No players, cannot unpause.")
}
}
}
if !srv.NeedPause() {
srv.Unpause()
bot.SendChat(bot.Config.ChatID, "Game unpaused.")
} else {
sendChat(-436055948, "Game already unpaused.")
bot.SendChat(bot.Config.ChatID, fmt.Sprintf("Cannot unpause : %s", srv.NeedPauseReason()))
}
return
}
func botDelete(m *tb.Message) {
logInfoDebug("[%d] %s(%d) | %s(%d) : delete : %s\n", m.ID, m.Chat.Title, m.Chat.ID, m.Sender.Username, m.Sender.ID, m.Text)
if m.Sender.ID == int(bot.Config.AdminID) {
r := regexp.MustCompile("/delete (?P<CompanyID>[0-9]+)")
ID64, _ := strconv.ParseInt(r.ReplaceAllString(m.Text, "${CompanyID}"), 10, 64)
srv.DeleteCompany(uint8(ID64))
bot.SendChat(m.Chat.ID, "Deleting")
} else {
bot.SendChat(m.Chat.ID, "Not authorized to delete")
}
return
}
func botCompanies(m *tb.Message) {
str := "Companies :"
for k, v := range srv.Status.Companies {
str = str + "\r\n" + fmt.Sprintf(" - %s (%d)", v.Name, k)
}
bot.SendChat(m.Chat.ID, str)
}
func botClients(m *tb.Message) {
str := "Clients :"
for k, v := range srv.Status.Clients {
str = str + "\r\n" + fmt.Sprintf(" - %s (%d) : company #%d", v.Name, k, v.CompanyID)
}
bot.SendChat(m.Chat.ID, str)
}
func botPlayers(m *tb.Message) {
online := ""
for _, cc := range cfg.Clients {
if cc.Online {
online = online + fmt.Sprintf(" - @%s (%s) : %s", cc.Username, cc.TimeLeft, srv.Status.Companies[cc.CompanyID].Name) + "\r\n"
}
}
offline := ""
for _, cc := range cfg.Clients {
if !cc.Online {
offline = offline + fmt.Sprintf(" - @%s (%s) : %s", cc.Username, cc.TimeLeft, srv.Status.Companies[cc.CompanyID].Name) + "\r\n"
}
}
str := ""
if len(online) > 0 {
str = str + "Players online :\r\n" + online
}
if len(offline) > 0 {
str = str + "Players offline :\r\n" + offline
}
if str == "" {
str = "No players."
}
bot.SendChat(m.Chat.ID, str)
}
func botRegister(m *tb.Message) {
cc, ok := cfg.Clients[m.Sender.ID]
if !ok {
cc = &ClientConfig{
UserID: m.Sender.ID,
Username: m.Sender.Username,
CompanyID: 255,
}
cfg.Clients[m.Sender.ID] = cc
}
coList := make(map[uint8]struct{})
for coID, _ := range srv.Status.Companies {
coList[coID] = struct{}{}
}
for _, c := range cfg.Clients {
if c.CompanyID != 255 {
if _, ok := coList[c.CompanyID]; !ok {
logInfoAlert("botRegister : %s : no such CompanyID : %d", c.Username, c.CompanyID)
c.CompanyID = 255
} else {
delete(coList, c.CompanyID)
}
}
}
if len(coList) == 0 {
bot.SendChat(m.Chat.ID, "No company to register")
return
}
if len(coList) == 1 {
for id, _ := range coList {
cc.CompanyID = id
days := int(time.Now().Sub(cfg.Game.StartDate).Hours() / 24)
cc.TimeLeft = cfg.Game.StartingAllotment + cfg.Game.DailyAllotment*time.Duration(days)
bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s registered %s (with %s playable)", cc.Username, srv.Status.Companies[cc.CompanyID].Name, cc.TimeLeft))
return
}
}
bot.SendChat(m.Chat.ID, "More than one company unregistered. Wait for bot update (poke @tiennou)")
return
}
func PrintText(m *tb.Message) {
logInfoDebug("[%d] %s(%d) | %s(%d) : %s\n", m.ID, m.Chat.Title, m.Chat.ID, m.Sender.Username, m.Sender.ID, m.Text)
return
@ -75,7 +227,6 @@ func botChannelPost(m *tb.Message) {
PrintText(m)
b, _ := json.Marshal(m)
logInfoDebug("botChannelPost : %s\n", string(b))
// channel posts only
}
@ -87,15 +238,3 @@ func botQuery(q *tb.Query) {
func botText(m *tb.Message) {
PrintText(m)
}
func sendChat(chatID int64, text string) {
opt := tb.SendOptions{
ParseMode: tb.ModeDefault,
}
ch := tb.Chat{
ID: chatID,
}
_, err := bot.Send(&ch, text, &opt)
logErrorDebug(err, "sendChat")
}

118
config.go Normal file
View File

@ -0,0 +1,118 @@
package main
import (
_ "embed"
"encoding/json"
"io/ioutil"
"time"
"github.com/tidwall/pretty"
)
//go:embed config.sample.json
var cfgSample []byte
type ServerConfig struct {
Addr string `json:"addr"`
Passwd string `json:"passwd"`
}
type TelegramConfig struct {
AdminID int64 `json:"admin_id"`
ChatID int64 `json:"chat_id"`
URL string `json:"url"`
Token string `json:"token"`
}
type GameConfig struct {
TimeZone string `json:"timezone"`
DailyAllotment time.Duration `json:"daily_allotment"`
StartingAllotment time.Duration `json:"starting_allotment"`
Threshold time.Duration `json:"threshold"`
StartDate time.Time `json:"start_date"`
}
type ClientConfig struct {
UserID int `json:"user_id"`
Username string `json:"username"`
Online bool `json:"online"`
TimeLeft time.Duration `json:"time_left"`
CompanyID uint8 `json:"company_id`
}
type Config struct {
Server *ServerConfig `json:"server"`
Telegram *TelegramConfig `json:"telegram"`
Game *GameConfig `json:"game"`
Clients map[int]*ClientConfig `json:"clients"`
}
// Init values for a config based on package defaults
func (c *Config) Init() error {
err := json.Unmarshal(cfgSample, &c)
c.Clients = make(map[int]*ClientConfig)
if err != nil {
return err
}
return nil
}
// Load config values from a given file
func (c *Config) Load(path string) error {
err := c.Init()
if err != nil {
return err
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
err = json.Unmarshal(b, &c)
if err != nil {
return err
}
return nil
}
// Save config values to a given file
func (c *Config) Save(path string) error {
b, err := c.Export()
if err != nil {
return nil
}
err = ioutil.WriteFile(path, b, 0644)
return err
}
// Export config values as a bytestream
func (c *Config) Export() ([]byte, error) {
b, err := json.Marshal(c)
if err != nil {
return b, err
}
return pretty.Pretty(b), nil
}
func (c *Config) CompanyIsRegistered(id uint8) bool {
for _, cc := range c.Clients {
if cc.CompanyID == id {
return true
}
}
return false
}
func (c *Config) GetCompanyClient(id uint8) *ClientConfig {
for _, cc := range c.Clients {
if cc.CompanyID == id {
return cc
}
}
return nil
}

18
config.sample.json Normal file
View File

@ -0,0 +1,18 @@
{
"server":{
"addr":"heidi.siteop.biz:3977",
"passwd":"plop"
},
"telegram":{
"admin_id":32173684,
"chat_id":-436055948,
"url":"https://api.telegram.org",
"token":"954090437:AAEMYeUWGluKZRwXi_K3-T23ZVpFoqQAmu0"
},
"game":{
"daily_allotment":1200000000000,
"starting_allotment":6000000000000,
"threshold":600000000000,
"start_date":"2021-11-01T17:22:28+02:00"
}
}

3
generate.go Normal file
View File

@ -0,0 +1,3 @@
package main
//go:generate bash ./version.sh

5
go.mod
View File

@ -2,4 +2,7 @@ module git.siteop.biz/shoopea/gottdad
go 1.16
require gopkg.in/tucnak/telebot.v2 v2.4.0
require (
github.com/tidwall/pretty v1.2.0
gopkg.in/tucnak/telebot.v2 v2.4.0
)

2
go.sum
View File

@ -7,6 +7,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tucnak/telebot.v2 v2.4.0 h1:nOeqOWnOAD3dzbKW+NRumd8zjj5vrWwSa0WRTxvgfag=
gopkg.in/tucnak/telebot.v2 v2.4.0/go.mod h1:BgaIIx50PSRS9pG59JH+geT82cfvoJU/IaI5TJdN3v8=

337
main.go
View File

@ -1,328 +1,55 @@
package main
import (
"bufio"
"encoding/binary"
"fmt"
"net"
"regexp"
"strconv"
"flag"
"time"
tb "gopkg.in/tucnak/telebot.v2"
_ "embed"
)
type Client struct {
ClientID uint32
Name string
Address string
CompanyID uint8
}
var (
bot *tb.Bot
clients map[uint32]*Client
paused bool = true
forcePaused bool = true
pausedBy string
cfg *Config
srv *ServerTTD
bot *Bot
githash string
buildstamp string
commits string
conn net.Conn
configFlag = flag.String("config", "config.json", "config file")
initFlag = flag.Bool("init", false, "init config")
)
func main() {
var err error
clients = make(map[uint32]*Client)
flag.Parse()
conn, err = net.Dial("tcp", "poop.siteop.biz:3977")
failError(err, "net.Dial")
logInfoDebug("Connected to poop.siteop.biz:3977")
//send auth
p := PacketAdminJoin{
Packet: Packet{PType: AdminPacketAdminJoin},
Password: "plop",
AppName: "gottdad",
AppVersion: "alpha",
cfg = &Config{}
if *initFlag {
err = cfg.Init()
failError(err, "Cannot init config")
err = cfg.Save(*configFlag)
failError(err, "Cannot save config")
} else {
err = cfg.Load(*configFlag)
failError(err, "Cannot open config")
}
_, err = conn.Write(p.Bytes())
failError(err, "conn.Write")
r := bufio.NewReader(conn)
b := make([]byte, 0xFFFF)
read := 0
n := 0
logInfoWarn("Starting up (%s) ...", version)
// Registering bot
bot, err = tb.NewBot(tb.Settings{
Token: "954090437:AAEMYeUWGluKZRwXi_K3-T23ZVpFoqQAmu0",
URL: "https://api.telegram.org",
Poller: &tb.LongPoller{Timeout: 10 * time.Second},
})
failError(err, "Registering bot")
logInfoDebug("Connected to Telegram")
go BotHandlers(bot)
u := tb.User{
ID: int(32173684),
bot = &Bot{
Config: cfg.Telegram,
}
bot.Send(&u, fmt.Sprintf("Started (%s-b%s - %s)", githash, commits, buildstamp))
go bot.Start()
time.Sleep(1 * time.Second)
srv = &ServerTTD{
Config: cfg.Server,
Data: &ServerDataTTD{},
Status: &ServerStatusTTD{},
}
go srv.Start()
for {
p := Packet{}
for {
if read >= 3 {
//logInfoDebug("Packet read")
break
time.Sleep(1 * time.Second)
}
n, err = r.Read(b[read:])
logErrorDebug(err, "r.Read")
read += n
//logInfoDebug("Waiting for packet, read %d bytes.", read)
}
p.PLength = binary.LittleEndian.Uint16(b[0:])
p.PType = b[2]
if p.PLength <= 3 {
logInfoAlert("Wrong packet length")
break
}
//logInfoDebug("Waiting for packet data : len : %d / type : %d", p.PLength, p.PType)
for {
if read >= int(p.PLength) {
//logInfoDebug("Data read")
break
}
n, err = r.Read(b[read:])
logErrorDebug(err, "r.Read")
read += n
//logInfoDebug("Waiting for data, read %d/%d bytes.", read, p.PLength)
}
switch p.PType {
case AdminPacketServerProtocol:
sp := PacketServerProtocol{
Packet: p,
}
sp.Read(b[:p.PLength])
logInfoDebug("AdminPacketServerProtocol :\n- ProtocolVersion: %v\n- FurtherData: %v\n- UpdatePacketType: %v\n- FrequenciesAllowed: %b", sp.ProtocolVersion, sp.FurtherData, sp.UpdatePacketType, sp.FrequenciesAllowed)
case AdminPacketServerWelcome:
sp := PacketServerWelcome{
Packet: p,
}
sp.Read(b[:p.PLength])
logInfoDebug("AdminPacketServerWelcome :\n- ServerName: %v\n- OpenTTDVersion: %v\n- Dedicated: %v\n- MapSeed: %x\n- MapLandscape: %v\n- MapStartDate: %v\n- Size: %v x %v", sp.ServerName, sp.OpenTTDVersion, sp.Dedicated, sp.MapSeed, sp.MapLandscape, sp.MapStartDate, sp.MapX, sp.MapY)
px := PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateDate,
UpdateFrequency: AdminFrequencyDaily,
}
_, err = conn.Write(px.Bytes())
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateClientInfo,
UpdateFrequency: AdminFrequencyAutomatic,
}
_, err = conn.Write(px.Bytes())
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateCompanyInfo,
UpdateFrequency: AdminFrequencyAutomatic,
}
_, err = conn.Write(px.Bytes())
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateChat,
UpdateFrequency: AdminFrequencyAutomatic,
}
_, err = conn.Write(px.Bytes())
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateConsole,
UpdateFrequency: AdminFrequencyAutomatic,
}
_, err = conn.Write(px.Bytes())
case AdminPacketServerDate:
sp := PacketServerDate{
Packet: p,
}
sp.Read(b[:p.PLength])
logInfoDebug("AdminPacketServerDate :\n- Date: %d\n- RealDate : %v", sp.Date, toDate(sp.Date))
t := toDate(sp.Date)
if t.Day() == 1 && t.Month() == 1 {
sendChat(-436055948, fmt.Sprintf("Year %d.", t.Year()))
}
paused = false
case AdminPacketServerClientJoin:
sp := PacketServerClientJoin{
Packet: p,
}
sp.Read(b[:p.PLength])
logInfoDebug("AdminPacketServerClientJoin :\n- ClientID: %d", sp.ClientID)
sendChat(-436055948, fmt.Sprintf("%s joining.", clients[sp.ClientID].Name))
case AdminPacketServerClientInfo:
sp := PacketServerClientInfo{
Packet: p,
}
sp.Read(b[:p.PLength])
//logInfoDebug("AdminPacketServerClientInfo :\n- ClientID: %d\n- Address: %s\n- Name: %s\n- Lang: %d\n- Date: %d\n- CompanyID: %d", sp.ClientID, sp.Address, sp.Name, sp.Lang, sp.Date, sp.CompanyID)
clt := Client{
ClientID: sp.ClientID,
Name: sp.Name,
Address: sp.Address,
CompanyID: sp.CompanyID,
}
clients[sp.ClientID] = &clt
logInfoDebug("Clients : %v", clients)
case AdminPacketServerClientError:
sp := PacketServerClientError{
Packet: p,
}
sp.Read(b[:p.PLength])
logInfoDebug("AdminPacketServerClientError :\n- ClientID: %d\n- ErrorID: %d", sp.ClientID, sp.ErrorID)
case AdminPacketServerClientQuit:
sp := PacketServerClientQuit{
Packet: p,
}
sp.Read(b[:p.PLength])
logInfoDebug("AdminPacketServerClientQuit :\n- ClientID: %d", sp.ClientID)
if len(clients) == 2 && !paused {
sendChat(-436055948, fmt.Sprintf("%s leaving. Game paused.", clients[sp.ClientID].Name))
} else {
sendChat(-436055948, fmt.Sprintf("%s leaving.", clients[sp.ClientID].Name))
}
delete(clients, sp.ClientID)
case AdminPacketServerChat:
sp := PacketServerChat{
Packet: p,
}
sp.Read(b[:p.PLength])
if sp.Message == "!unpause" {
logInfoDebug("AdminPacketServerChat : Unpausing")
forcePaused = false
pausedBy = clients[sp.ClientID].Name
} else if sp.Message == "!pause" {
logInfoDebug("AdminPacketServerChat : Pausing")
forcePaused = true
pausedBy = clients[sp.ClientID].Name
} else {
logInfoDebug("AdminPacketServerChat :\n- ActionID: %d\n- DestinationID: %d\n- ClientID: %d\n- Message: %s\n- Amount: %d", sp.ActionID, sp.DestinationID, sp.ClientID, sp.Message, sp.Amount)
}
case AdminPacketServerConsole:
sp := PacketServerConsole{
Packet: p,
}
sp.Read(b[:p.PLength])
ok, err := regexp.MatchString("\\[udp\\] queried from .*", sp.Text)
logErrorDebug(err, "queried from")
if sp.Origin != "net" || ok == false {
logInfoDebug("AdminPacketServerConsole :\n- Origin: %q\n- Text: %s", sp.Origin, sp.Text)
}
case AdminPacketServerRCon:
sp := PacketServerRCon{
Packet: p,
}
sp.Read(b[:p.PLength])
ok, _ := regexp.MatchString("Client #[0-9]+ name: '.*' company: [0-9]+ IP: .*", sp.Output)
if ok {
clt := Client{}
r, _ := regexp.Compile("Client #(?P<ClientID>[0-9]+) name: '(?P<Name>.*)' company: (?P<CompanyID>[0-9]+) IP: (?P<Address>.*)")
ID64, _ := strconv.ParseInt(r.ReplaceAllString(sp.Output, "${ClientID}"), 10, 32)
clt.ClientID = uint32(ID64)
clt.Name = r.ReplaceAllString(sp.Output, "${Name}")
if clt.Name == "" {
clt.Name = "server"
}
ID64, _ = strconv.ParseInt(r.ReplaceAllString(sp.Output, "${CompanyID}"), 10, 8)
clt.CompanyID = uint8(ID64)
clt.Address = r.ReplaceAllString(sp.Output, "${Address}")
clients[clt.ClientID] = &clt
} else {
logInfoDebug("AdminPacketServerRCon :\n- ColorID: %d\n- Output: %s", sp.ColorID, sp.Output)
}
case AdminPacketServerRConEnd:
sp := PacketServerRConEnd{
Packet: p,
}
sp.Read(b[:p.PLength])
if sp.Command == "clients" {
for k, v := range clients {
logInfoDebug("Client[%d] : %s - %d (%s)", k, v.Name, v.CompanyID, v.Address)
}
} else {
logInfoDebug("AdminPacketServerRConEnd :\n- Command: %s", sp.Command)
}
default:
logInfoDebug("Packet fully read : len : %d / type : %d", p.PLength, p.PType)
}
c := make([]byte, 0xFFFF)
copy(c, b[p.PLength:])
b = c
read -= int(p.PLength)
if len(clients) == 0 {
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "clients",
}
_, err = conn.Write(px.Bytes())
}
if !paused && forcePaused {
paused = true
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "pause",
}
_, err = conn.Write(px.Bytes())
logInfoDebug("Force pausing")
if pausedBy != "" {
sendChat(-436055948, fmt.Sprintf("Game paused by %s", pausedBy))
pausedBy = ""
}
}
if paused && !forcePaused && len(clients) > 1 { // server is client #1
paused = false
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "unpause",
}
_, err = conn.Write(px.Bytes())
logInfoDebug("Force unpausing")
if pausedBy != "" {
sendChat(-436055948, fmt.Sprintf("Game unpaused by %s", pausedBy))
pausedBy = ""
} else {
sendChat(-436055948, "Game unpaused")
}
}
if !paused && len(clients) == 1 { // server is client #1
paused = true
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "pause",
}
_, err = conn.Write(px.Bytes())
logInfoDebug("Pausing")
}
}
}

136
packet.go
View File

@ -109,6 +109,11 @@ type PacketAdminUpdateFrequency struct {
UpdateFrequency uint16
}
type PacketAdminPoll struct {
Packet
UpdateType uint8
UpdateID uint32
}
type PacketServerDate struct {
Packet
Date uint32
@ -134,12 +139,52 @@ type PacketServerClientInfo struct {
CompanyID uint8
}
type PacketServerClientUpdate struct {
Packet
ClientID uint32
Name string
CompanyID uint8
}
type PacketServerClientError struct {
Packet
ClientID uint32
ErrorID uint8
}
type PacketServerCompanyNew struct {
Packet
CompanyID uint8
}
type PacketServerCompanyInfo struct {
Packet
CompanyID uint8
Name string
President string
Color uint8
Inauguration uint32
AI bool
Protected bool
Bankruptcies uint8
}
type PacketServerCompanyUpdate struct {
Packet
CompanyID uint8
Name string
President string
Color uint8
Protected bool
Bankruptcies uint8
}
type PacketServerCompanyRemove struct {
Packet
CompanyID uint8
Reason uint8
}
type PacketServerChat struct {
Packet
ActionID uint8
@ -206,6 +251,18 @@ func (p *PacketAdminUpdateFrequency) Bytes() []byte {
return buf.Bytes()
}
func (p *PacketAdminPoll) Bytes() []byte {
buf := new(bytes.Buffer)
p.PLength = 8
binary.Write(buf, binary.LittleEndian, p.PLength)
binary.Write(buf, binary.LittleEndian, p.PType)
binary.Write(buf, binary.LittleEndian, p.UpdateType)
binary.Write(buf, binary.LittleEndian, p.UpdateID)
return buf.Bytes()
}
func (p *PacketServerWelcome) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
@ -285,6 +342,18 @@ func (p *PacketServerClientInfo) Read(b []byte) {
p.CompanyID = uint8(c)
}
func (p *PacketServerClientUpdate) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
bs := make([]byte, 4)
_, _ = r.Read(bs)
p.ClientID = binary.LittleEndian.Uint32(bs[0:])
p.Name, _ = r.ReadString(0)
p.Name = p.Name[:len(p.Name)-1]
c, _ := r.ReadByte()
p.CompanyID = uint8(c)
}
func (p *PacketServerClientError) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
@ -303,6 +372,73 @@ func (p *PacketServerClientQuit) Read(b []byte) {
p.ClientID = binary.LittleEndian.Uint32(bs[0:])
}
func (p *PacketServerCompanyNew) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
c, _ := r.ReadByte()
p.CompanyID = uint8(c)
}
func (p *PacketServerCompanyInfo) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
c, _ := r.ReadByte()
p.CompanyID = uint8(c)
p.Name, _ = r.ReadString(0)
p.Name = p.Name[:len(p.Name)-1]
p.President, _ = r.ReadString(0)
p.President = p.Name[:len(p.Name)-1]
c, _ = r.ReadByte()
p.Color = uint8(c)
c, _ = r.ReadByte()
if c > 0 {
p.Protected = true
} else {
p.Protected = false
}
bs := make([]byte, 4)
_, _ = r.Read(bs)
p.Inauguration = binary.LittleEndian.Uint32(bs[0:])
c, _ = r.ReadByte()
if c > 0 {
p.AI = true
} else {
p.AI = false
}
c, _ = r.ReadByte()
p.Bankruptcies = uint8(c)
}
func (p *PacketServerCompanyUpdate) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
c, _ := r.ReadByte()
p.CompanyID = uint8(c)
p.Name, _ = r.ReadString(0)
p.Name = p.Name[:len(p.Name)-1]
p.President, _ = r.ReadString(0)
p.President = p.Name[:len(p.Name)-1]
c, _ = r.ReadByte()
p.Color = uint8(c)
c, _ = r.ReadByte()
if c > 0 {
p.Protected = true
} else {
p.Protected = false
}
c, _ = r.ReadByte()
p.Bankruptcies = uint8(c)
}
func (p *PacketServerCompanyRemove) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)
c, _ := r.ReadByte()
p.CompanyID = uint8(c)
c, _ = r.ReadByte()
p.Reason = uint8(c)
}
func (p *PacketServerChat) Read(b []byte) {
r := bufio.NewReader(bytes.NewReader(b))
r.Discard(3)

729
ttd.go Normal file
View File

@ -0,0 +1,729 @@
package main
import (
"bufio"
"encoding/binary"
"fmt"
"net"
"os"
"regexp"
"strconv"
"time"
)
type ClientTTD struct {
ClientID uint32
Name string
Address string
CompanyID uint8
Paused bool
LastSeen time.Time
}
type CompanyTTD struct {
CompanyID uint8
Name string
Protected bool
ClientID uint32
FirstSeen time.Time
LastSeen time.Time
}
type ServerTTD struct {
Config *ServerConfig
Status *ServerStatusTTD
Data *ServerDataTTD
}
type ServerStatusTTD struct {
Connected bool
Paused bool
UpdateCompanies time.Time
UpdateClients time.Time
UpdateDate time.Time
Initialized bool
GameDate time.Time
Clients map[uint32]*ClientTTD
Companies map[uint8]*CompanyTTD
}
type ServerDataTTD struct {
LastClientCompute time.Time
Conn net.Conn
Stop chan struct{}
}
var updateHeartBeat = 5 * time.Second
func (s *ServerTTD) Connect() (err error) {
t := time.Now()
s.Status.Connected = false
s.Data.Conn, err = net.Dial("tcp", s.Config.Addr)
logErrorDebug(err, "Server.Connect() : net.Dial")
if err != nil {
return err
}
logInfoDebug("Server.Connect() : Connected to " + s.Config.Addr)
s.Data.LastClientCompute = time.Now()
s.Status.Connected = true
s.Status.Initialized = false
s.Status.UpdateDate = t
s.Status.UpdateClients = t
s.Status.UpdateCompanies = t
s.Status.Clients = make(map[uint32]*ClientTTD)
s.Status.Companies = make(map[uint8]*CompanyTTD)
return err
}
func (s *ServerTTD) Send(b []byte) (err error) {
if !s.Status.Connected {
return fmt.Errorf("not connected")
}
_, err = s.Data.Conn.Write(b)
return err
}
func (s *ServerTTD) Start() {
for {
err := s.Connect()
if err != nil {
time.Sleep(5 * time.Second)
}
break
}
//send auth
p := PacketAdminJoin{
Packet: Packet{PType: AdminPacketAdminJoin},
Password: s.Config.Passwd,
AppName: "gottdad",
AppVersion: version,
}
err := s.Send(p.Bytes())
failError(err, "Server.Start() : Cannot send authentication packet")
stopPoll := make(chan struct{})
go s.Poll(stopPoll)
stopHeartBeat := make(chan struct{})
go s.HeartBeat(stopHeartBeat)
for {
select {
// call to stop polling
case <-s.Data.Stop:
close(stopPoll)
close(stopHeartBeat)
return
}
}
}
// Stop gracefully shuts the poller down.
func (s *ServerTTD) Stop() {
s.Data.Stop <- struct{}{}
}
func (s *ServerTTD) HeartBeat(stop chan struct{}) {
for {
select {
case <-stop:
return
default:
}
s.UpdateDate()
s.UpdateClients()
s.UpdateCompanies()
s.PruneClients()
s.PruneCompanies()
s.ComputeClientTime()
if !s.Status.Paused && s.NeedPause() {
s.Pause()
} else if s.Status.Paused && !s.NeedPause() {
s.Unpause()
}
cfg.Save(*configFlag)
time.Sleep(updateHeartBeat)
}
}
func (s *ServerTTD) Poll(stop chan struct{}) {
var err error
reader := bufio.NewReader(s.Data.Conn)
buffer := make([]byte, 0xFFFF)
var n, read int
for {
select {
case <-stop:
return
default:
}
p := Packet{}
for {
if read >= 3 {
//logInfoDebug("Server.Poll() : packet read")
break
}
n, err = reader.Read(buffer[read:])
logErrorDebug(err, "Server.Poll() : reader.Read")
read += n
//logInfoDebug("Server.Poll() : waiting for packet, read %d bytes.", read)
}
p.PLength = binary.LittleEndian.Uint16(buffer[0:])
p.PType = buffer[2]
if p.PLength <= 3 {
logInfoAlert("Server.Poll() : wrong packet length")
break
}
//logInfoDebug("Server.Poll() : waiting for packet data : len : %d / type : %d", p.PLength, p.PType)
for {
if read >= int(p.PLength) {
//logInfoDebug("Server.Poll() : data read")
break
}
n, err = reader.Read(buffer[read:])
logErrorDebug(err, "Server.Poll() : reader.Read")
read += n
//logInfoDebug("Server.Poll() : waiting for data, read %d/%d bytes.", read, p.PLength)
}
switch p.PType {
case AdminPacketServerProtocol:
sp := PacketServerProtocol{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : AdminPacketServerProtocol :\n- ProtocolVersion: %v\n- FurtherData: %v\n- UpdatePacketType: %v\n- FrequenciesAllowed: %b", sp.ProtocolVersion, sp.FurtherData, sp.UpdatePacketType, sp.FrequenciesAllowed)
case AdminPacketServerWelcome:
sp := PacketServerWelcome{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : AdminPacketServerWelcome :\n- ServerName: %v\n- OpenTTDVersion: %v\n- Dedicated: %v\n- MapSeed: %x\n- MapLandscape: %v\n- MapStartDate: %v\n- Size: %v x %v", sp.ServerName, sp.OpenTTDVersion, sp.Dedicated, sp.MapSeed, sp.MapLandscape, sp.MapStartDate, sp.MapX, sp.MapY)
s.Initialize()
case AdminPacketServerDate:
sp := PacketServerDate{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : AdminPacketServerDate :\n- Date: %d\n- RealDate : %v", sp.Date, toDate(sp.Date))
gameDate := toDate(sp.Date)
if gameDate.Day() == 1 && gameDate.Month() == 1 && gameDate != s.Status.GameDate {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("Year %d.", gameDate.Year()))
}
s.Status.GameDate = gameDate
s.Status.UpdateDate = time.Now()
case AdminPacketServerClientJoin:
sp := PacketServerClientJoin{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : AdminPacketServerClientJoin :\n- ClientID: %d", sp.ClientID)
case AdminPacketServerClientInfo:
sp := PacketServerClientInfo{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : AdminPacketServerClientInfo :\n- ClientID: %d\n- Address: %s\n- Name: %s\n- Lang: %d\n- Date: %d\n- CompanyID: %d", sp.ClientID, sp.Address, sp.Name, sp.Lang, sp.Date, sp.CompanyID)
clt := &ClientTTD{
ClientID: sp.ClientID,
}
if _, ok := s.Status.Clients[sp.ClientID]; ok {
clt = s.Status.Clients[sp.ClientID]
} else {
s.Status.Clients[sp.ClientID] = clt
}
clt.Address = sp.Address
clt.Name = sp.Name
clt.CompanyID = sp.CompanyID
clt.LastSeen = time.Now()
if clt.CompanyID != 255 {
if co, ok := s.Status.Companies[clt.CompanyID]; ok {
if co.ClientID == 0 {
co.ClientID = clt.ClientID
}
} else {
//FIXME company doesn't exist ?
}
if cfg.CompanyIsRegistered(clt.CompanyID) {
cc := cfg.GetCompanyClient(clt.CompanyID)
if !cc.Online {
cc.Online = true
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s now playing.", cc.Username))
}
}
}
case AdminPacketServerClientUpdate:
sp := PacketServerClientUpdate{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : AdminPacketServerClientUpdate :\n- ClientID: %d\n- Name: %s\n- CompanyID: %d", sp.ClientID, sp.Name, sp.CompanyID)
clt := s.Status.Clients[sp.ClientID]
clt.Name = sp.Name
if sp.CompanyID != 255 {
if cfg.CompanyIsRegistered(sp.CompanyID) {
cc := cfg.GetCompanyClient(sp.CompanyID)
cc.Online = true
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s playing.", cc.Username))
} else {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("%s unclaimed. Please /register to claim the company", s.Status.Companies[sp.CompanyID].Name))
}
} else if clt.CompanyID != 255 && cfg.CompanyIsRegistered(clt.CompanyID) {
cc := cfg.GetCompanyClient(clt.CompanyID)
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s taking a break.", cc.Username))
cc.Online = false
}
clt.LastSeen = time.Now()
clt.CompanyID = sp.CompanyID
s.Unpause()
s.Pause()
case AdminPacketServerClientError:
sp := PacketServerClientError{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : AdminPacketServerClientError :\n- ClientID: %d\n- ErrorID: %d", sp.ClientID, sp.ErrorID)
case AdminPacketServerClientQuit:
sp := PacketServerClientQuit{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : AdminPacketServerClientQuit :\n- ClientID: %d", sp.ClientID)
if len(s.Status.Clients) == 2 && !srv.Status.Paused {
if cfg.CompanyIsRegistered(s.Status.Clients[sp.ClientID].CompanyID) {
cc := cfg.GetCompanyClient(s.Status.Clients[sp.ClientID].CompanyID)
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s leaving. Game paused.", cc.Username))
cc.Online = false
} else {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("%s leaving. Game paused.", s.Status.Clients[sp.ClientID].Name))
}
} else {
if cfg.CompanyIsRegistered(s.Status.Clients[sp.ClientID].CompanyID) {
cc := cfg.GetCompanyClient(s.Status.Clients[sp.ClientID].CompanyID)
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s leaving.", cc.Username))
cc.Online = false
} else {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("%s leaving.", s.Status.Clients[sp.ClientID].Name))
}
}
delete(s.Status.Clients, sp.ClientID)
case AdminPacketServerCompanyNew:
sp := PacketServerCompanyNew{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : PacketServerCompanyNew :\n- CompanyID: %d", sp.CompanyID)
c := &CompanyTTD{
CompanyID: sp.CompanyID,
FirstSeen: time.Now(),
}
s.Status.Companies[sp.CompanyID] = c
case AdminPacketServerCompanyInfo:
sp := PacketServerCompanyInfo{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : PacketServerCompanyInfo :\n- CompanyID: %d\n- Name: %s\n- President: %s\n- Protected: %t\n- Inauguration: %d\n- AI: %t", sp.CompanyID, sp.Name, sp.President, sp.Protected, sp.Inauguration, sp.AI)
c := &CompanyTTD{
CompanyID: sp.CompanyID,
FirstSeen: time.Now(),
}
if _, ok := s.Status.Companies[sp.CompanyID]; !ok {
s.Status.Companies[sp.CompanyID] = c
} else {
c = s.Status.Companies[sp.CompanyID]
}
c.Name = sp.Name
c.LastSeen = time.Now()
c.Protected = sp.Protected
case AdminPacketServerCompanyUpdate:
sp := PacketServerCompanyUpdate{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : PacketServerCompanyUpdate :\n- CompanyID: %d\n- Name: %s\n- President: %s\n- Protected: %t", sp.CompanyID, sp.Name, sp.President, sp.Protected)
c := s.Status.Companies[sp.CompanyID]
c.Name = sp.Name
c.FirstSeen = time.Now()
c.LastSeen = time.Now()
c.Protected = sp.Protected
case AdminPacketServerCompanyRemove:
sp := PacketServerCompanyRemove{
Packet: p,
}
sp.Read(buffer[:p.PLength])
//logInfoDebug("Server.Poll() : PacketServerCompanyRemove :\n- CompanyID: %d\n- Reason: %d", sp.CompanyID, sp.Reason)
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("Company #%d deleted (%s)", sp.CompanyID, s.Status.Companies[sp.CompanyID].Name))
delete(s.Status.Companies, sp.CompanyID)
case AdminPacketServerChat:
sp := PacketServerChat{
Packet: p,
}
sp.Read(buffer[:p.PLength])
if sp.Message == "!unpause" {
logInfoDebug("Server.Poll() : AdminPacketServerChat : Unpausing")
clt := s.Status.Clients[sp.ClientID]
clt.Paused = false
s.Unpause()
} else if sp.Message == "!pause" {
logInfoDebug("Server.Poll() : AdminPacketServerChat : Pausing")
clt := s.Status.Clients[sp.ClientID]
clt.Paused = true
s.Pause()
} else {
logInfoDebug("Server.Poll() : AdminPacketServerChat :\n- ActionID: %d\n- DestinationID: %d\n- ClientID: %d\n- Message: %s\n- Amount: %d", sp.ActionID, sp.DestinationID, sp.ClientID, sp.Message, sp.Amount)
}
case AdminPacketServerConsole:
sp := PacketServerConsole{
Packet: p,
}
sp.Read(buffer[:p.PLength])
udp_queried_from, err := regexp.MatchString("\\[udp\\] queried from .*", sp.Text)
logErrorDebug(err, "Server.Poll() : queried from")
if udp_queried_from && sp.Origin == "net" {
break
}
joined_company, err := regexp.MatchString("\\*\\*\\* .* has joined company #[0-9]+", sp.Text)
logErrorDebug(err, "Server.Poll() : joined company")
if joined_company && sp.Origin == "console" {
r := regexp.MustCompile("\\*\\*\\* (?P<Client>.*) has joined company #(?P<CompanyID>[0-9]+)")
clientName := r.ReplaceAllString(sp.Text, "${Client}")
ID8, _ := strconv.ParseInt(r.ReplaceAllString(sp.Text, "${CompanyID}"), 10, 8)
companyID := uint8(ID8) - 1
for _, clt := range s.Status.Clients {
if clt.Name == clientName {
clt.CompanyID = companyID
}
}
break
}
joined_spectators, err := regexp.MatchString("\\*\\*\\* .* has joined spectators", sp.Text)
if joined_spectators && sp.Origin == "console" {
r := regexp.MustCompile("\\*\\*\\* (?P<Client>.*) has joined spectators")
clientName := r.ReplaceAllString(sp.Text, "${Client}")
for _, clt := range s.Status.Clients {
if clt.Name == clientName {
clt.CompanyID = 255
}
}
break
}
command, err := regexp.MatchString("\\[admin\\] Rcon command from 'gottdad' \\(.*\\): .*", sp.Text)
if command && sp.Origin == "net" {
break
}
logInfoDebug("Server.Poll() : AdminPacketServerConsole :\n- Origin: %q\n- Text: %s", sp.Origin, sp.Text)
case AdminPacketServerRCon:
sp := PacketServerRCon{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : AdminPacketServerRCon :\n- ColorID: %d\n- Output: %s", sp.ColorID, sp.Output)
case AdminPacketServerRConEnd:
sp := PacketServerRConEnd{
Packet: p,
}
sp.Read(buffer[:p.PLength])
switch sp.Command {
case "clients":
for k, v := range s.Status.Clients {
logInfoDebug("Server.Poll() : Client[%d] : %s - %d (%s)", k, v.Name, v.CompanyID, v.Address)
}
case "companies":
for k, v := range s.Status.Companies {
logInfoDebug("Server.Poll() : Company[%d] : %s", k, v.Name)
}
case "pause":
default:
logInfoDebug("Server.Poll() : AdminPacketServerRConEnd :\n- Command: %s", sp.Command)
}
default:
logInfoDebug("Server.Poll() : Packet fully read : len : %d / type : %d", p.PLength, p.PType)
os.Exit(0)
}
c := make([]byte, 0xFFFF)
copy(c, buffer[p.PLength:])
buffer = c
read -= int(p.PLength)
}
}
func (s *ServerTTD) UpdateDate() {
//logInfoDebug("Server.UpdateDate")
px := PacketAdminPoll{
Packet: Packet{PType: AdminPacketAdminPoll},
UpdateType: AdminUpdateDate,
UpdateID: 0,
}
err := s.Send(px.Bytes())
logErrorDebug(err, "Server.UpdateDate() : Send(PacketAdminPoll) : AdminUpdateDate")
s.Status.UpdateDate = time.Now()
}
func (s *ServerTTD) UpdateClients() {
//logInfoDebug("Server.UpdateClients")
px := PacketAdminPoll{
Packet: Packet{PType: AdminPacketAdminPoll},
UpdateType: AdminUpdateClientInfo,
UpdateID: uint32(4294967295),
}
err := s.Send(px.Bytes())
logErrorDebug(err, "Server.UpdateClients() : Send(PacketAdminPoll) : AdminUpdateClientInfo")
s.Status.UpdateClients = time.Now()
}
func (s *ServerTTD) UpdateCompanies() {
//logInfoDebug("Server.UpdateCompanies")
px := PacketAdminPoll{
Packet: Packet{PType: AdminPacketAdminPoll},
UpdateType: AdminUpdateCompanyInfo,
UpdateID: uint32(4294967295),
}
err := s.Send(px.Bytes())
logErrorDebug(err, "Server.UpdateCompanies() : Send(UpdateCompanies) : AdminUpdateClientInfo")
s.Status.UpdateCompanies = time.Now()
}
func (s *ServerTTD) PruneClients() {
for cltID, clt := range s.Status.Clients {
if clt.LastSeen.Add(2 * updateHeartBeat).Before(time.Now()) {
logInfoDebug("ServerTTD.PruneClients : deleting client #%d", cltID)
delete(s.Status.Clients, cltID)
}
}
}
func (s *ServerTTD) PruneCompanies() {
for coID, co := range s.Status.Companies {
if co.LastSeen.Add(2 * updateHeartBeat).Before(time.Now()) {
logInfoDebug("ServerTTD.PruneCompanies : deleting company #%d", coID)
delete(s.Status.Companies, coID)
}
}
}
func (s *ServerTTD) ComputeClientTime() {
t := time.Now()
daysNow := int(t.Sub(cfg.Game.StartDate).Hours() / 24)
daysLast := int(s.Data.LastClientCompute.Sub(cfg.Game.StartDate).Hours() / 24)
if daysLast != daysNow {
bot.SendChat(cfg.Telegram.ChatID, "New daily allotment available.")
logInfoDebug("Server.ComputeClientTime : newDay : now : %d vs %d : last", daysNow, daysLast)
for _, cc := range cfg.Clients {
cc.TimeLeft = cc.TimeLeft + cfg.Game.DailyAllotment
}
}
if !s.Status.Paused {
diff := t.Sub(s.Data.LastClientCompute)
for _, cc := range cfg.Clients {
if cc.Online {
if cc.TimeLeft > 0 && cc.TimeLeft < diff {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s got not time left. Grace period of %s.", cc.Username, cfg.Game.Threshold))
}
if (cc.TimeLeft+cfg.Game.Threshold) > 0 && (cc.TimeLeft+cfg.Game.Threshold) < diff {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s got not time left. Grace period over.", cc.Username))
}
cc.TimeLeft = cc.TimeLeft - diff
}
}
}
s.Data.LastClientCompute = t
return
}
func (s *ServerTTD) DeleteCompany(id uint8) {
if _, ok := s.Status.Companies[id]; ok {
logInfoDebug("Server.DeleteCompany : deleting #%d", id)
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: fmt.Sprintf("reset_company %d", id),
}
s.Send(px.Bytes())
px = PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "companies",
}
s.Send(px.Bytes())
} else {
logInfoDebug("Server.DeleteCompany : cannot find company #%d", id)
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "companies",
}
s.Send(px.Bytes())
}
}
func (s *ServerTTD) Pause() {
if s.Status.Paused {
return
}
if !s.NeedPause() {
return
}
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "pause",
}
err := s.Send(px.Bytes())
s.ComputeClientTime()
s.Status.Paused = true
logErrorDebug(err, "Server.Pause : Send()")
logInfoDebug("Server.Pause : pausing")
}
func (s *ServerTTD) Unpause() {
if !s.Status.Paused {
return
}
if s.NeedPause() {
return
}
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "unpause",
}
err := s.Send(px.Bytes())
s.ComputeClientTime()
s.Status.Paused = false
logErrorDebug(err, "Server.Unpause : Send()")
logInfoDebug("Server.Unpause : unpausing")
}
func (s *ServerTTD) NeedPause() bool {
if !s.Status.Initialized {
logInfoDebug("Server.NeedPause : not initialized yet")
return false
}
reason := s.NeedPauseReason()
if reason == "" {
return false
} else {
//logInfoDebug("Server.NeedPause : %s", reason)
return true
}
}
func (s *ServerTTD) NeedPauseReason() string {
if !s.Status.Initialized {
return ""
}
dupl := make(map[uint8]struct{})
online := 0
for cltID, clt := range s.Status.Clients {
if cltID != 1 && clt.CompanyID != 255 {
online++
if _, ok := s.Status.Companies[clt.CompanyID]; !ok {
return "company unidentified"
}
}
if clt.Paused {
return "user paused"
}
if _, ok := dupl[clt.CompanyID]; ok && clt.CompanyID != 255 {
return "more than one user in company"
}
dupl[clt.CompanyID] = struct{}{}
}
for coID, co := range s.Status.Companies {
if !co.Protected {
return "company unprotected"
}
if !cfg.CompanyIsRegistered(coID) {
return "company unregistered"
}
}
for _, cc := range cfg.Clients {
if cc.Online && (cc.TimeLeft+cfg.Game.Threshold) < 0 {
return "no time left"
}
}
if online == 0 {
return "no players online"
}
return ""
}
func (s *ServerTTD) Initialize() {
var err error
if s.Status.Initialized {
return
}
px := PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateDate,
UpdateFrequency: AdminFrequencyDaily,
}
err = s.Send(px.Bytes())
logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateDate)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateClientInfo,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateClientInfo)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateCompanyInfo,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateCompanyInfo)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateChat,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateChat)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateConsole,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateConsole)")
s.Status.Initialized = true
s.UpdateCompanies()
s.UpdateClients()
}

6
version.go Normal file
View File

@ -0,0 +1,6 @@
// Code generated by version.sh (@generated) DO NOT EDIT.
package main
var githash = "90e584f"
var buildstamp = "2021-11-06_15:32:44"
var commits = "156"
var version = "90e584f-b156 - 2021-11-06_15:32:44"

13
version.sh Normal file
View File

@ -0,0 +1,13 @@
# Get the version.
githash=`git rev-parse --short HEAD`
buildstamp=`date -u '+%Y-%m-%d_%H:%M:%S'`
commits=`git rev-list --count master`
# Write out the package.
cat << EOF > version.go
// Code generated by version.sh (@generated) DO NOT EDIT.
package main
var githash = "$githash"
var buildstamp = "$buildstamp"
var commits = "$commits"
var version = "$githash-b$commits - $buildstamp"
EOF