gottdad/ttd.go

953 lines
29 KiB
Go

package main
import (
"bufio"
"encoding/binary"
"fmt"
"net"
"os"
"regexp"
"strconv"
"time"
)
type ClientTTD struct {
ClientID uint32
ClientExtlID int64
Name string
Address string
CompanyID uint8
Paused bool
LastSeen time.Time
}
type CompanyTTD struct {
CompanyID uint8
CompanyExtlID int64
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()
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "companies",
}
err = s.Send(px.Bytes())
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,
ClientExtlID: 4294967296,
CompanyID: 255,
}
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 {
if co, ok := s.Status.Companies[sp.CompanyID]; ok {
if co.ClientID == 0 {
bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("Company '%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 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,
CompanyExtlID: 256,
FirstSeen: time.Now(),
ClientID: 0,
}
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,
CompanyExtlID: 256,
FirstSeen: time.Now(),
ClientID: 0,
}
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 := &CompanyTTD{
CompanyID: sp.CompanyID,
CompanyExtlID: 256,
FirstSeen: time.Now(),
ClientID: 0,
}
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.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 AdminPacketServerCompanyEconomy:
sp := PacketServerCompanyEconomy{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : PacketServerCompanyEconomy :\n- CompanyID: %d\n- M: %d\tL: %d\tI: %d\n- Delivered now: %d\tLast: %d\tPrevious: %d\n- Performance last: %d\t Previous: %d\n- Value last: %d\t Previous: %d", sp.CompanyID, sp.Money, sp.Loan, sp.Income, sp.DeliveredCargoThisQuarter, sp.DeliveredCargoLastQuarter, sp.DeliveredCargoPreviousQuarter, sp.PerformanceLastQuarter, sp.PerformancePreviousQuarter, sp.CompanyValueLastQuarter, sp.CompanyValuePreviousQuarter)
if cfg.StatsMonthly == nil {
cfg.StatsMonthly = make(map[int]map[string]*StatMonthly)
}
if cfg.CompanyIsRegistered(sp.CompanyID) {
cc := cfg.GetCompanyClient(sp.CompanyID)
_, ok := cfg.StatsMonthly[cc.UserID]
if !ok {
cfg.StatsMonthly[cc.UserID] = make(map[string]*StatMonthly)
}
stats, ok := cfg.StatsMonthly[cc.UserID][s.Status.GameDate.Format("20060102")]
if !ok {
stats = &StatMonthly{
CompanyID: sp.CompanyID,
Date: s.Status.GameDate,
}
cfg.StatsMonthly[cc.UserID][s.Status.GameDate.Format("20060102")] = stats
}
stats.Money = int64(sp.Money)
stats.Loan = int64(sp.Loan)
stats.Income = sp.Income
stats.DeliveredCargoThisQuarter = int(sp.DeliveredCargoThisQuarter)
stats.DeliveredCargoLastQuarter = int(sp.DeliveredCargoLastQuarter)
stats.DeliveredCargoPreviousQuarter = int(sp.DeliveredCargoPreviousQuarter)
stats.PerformanceLastQuarter = int(sp.PerformanceLastQuarter)
stats.PerformancePreviousQuarter = int(sp.PerformancePreviousQuarter)
stats.CompanyValueLastQuarter = int64(sp.CompanyValueLastQuarter)
stats.CompanyValuePreviousQuarter = int64(sp.CompanyValuePreviousQuarter)
}
case AdminPacketServerCompanyStats:
sp := PacketServerCompanyStats{
Packet: p,
}
sp.Read(buffer[:p.PLength])
logInfoDebug("Server.Poll() : PacketServerCompanyStats :\n- CompanyID: %d\n- Vehicles T: %d\tL: %d\tB: %d\tP: %d\tS: %d\n- Stations T: %d\tL: %d\tB: %d\tP: %d\tS: %d", sp.CompanyID, sp.Trains, sp.Lorries, sp.Busses, sp.Planes, sp.Ships, sp.TrainStations, sp.LorryStations, sp.BusStops, sp.Airports, sp.Harbours)
if cfg.StatsMonthly == nil {
cfg.StatsMonthly = make(map[int]map[string]*StatMonthly)
}
if cfg.CompanyIsRegistered(sp.CompanyID) {
cc := cfg.GetCompanyClient(sp.CompanyID)
_, ok := cfg.StatsMonthly[cc.UserID]
if !ok {
cfg.StatsMonthly[cc.UserID] = make(map[string]*StatMonthly)
}
stats, ok := cfg.StatsMonthly[cc.UserID][s.Status.GameDate.Format("20060102")]
if !ok {
stats = &StatMonthly{
CompanyID: sp.CompanyID,
Date: s.Status.GameDate,
}
cfg.StatsMonthly[cc.UserID][s.Status.GameDate.Format("20060102")] = stats
}
stats.Trains = int(sp.Trains)
stats.TrainStations = int(sp.TrainStations)
stats.Busses = int(sp.Busses)
stats.BusStops = int(sp.BusStops)
stats.Lorries = int(sp.Lorries)
stats.LorryStations = int(sp.LorryStations)
stats.Planes = int(sp.Planes)
stats.Airports = int(sp.Airports)
stats.Ships = int(sp.Ships)
stats.Harbours = int(sp.Harbours)
}
case AdminPacketServerChat:
sp := PacketServerChat{
Packet: p,
}
sp.Read(buffer[:p.PLength])
if sp.Message == "!unpause" || sp.Message == "unpause" {
logInfoDebug("Server.Poll() : AdminPacketServerChat : Unpausing")
clt := s.Status.Clients[sp.ClientID]
clt.Paused = false
s.Unpause()
} else if sp.Message == "!pause" || 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
}
r := regexp.MustCompile("^\\[admin\\] Rcon command from 'gottdad' \\(.*\\): (?P<Command>.*)$")
if r.MatchString(sp.Text) {
command := r.ReplaceAllString(sp.Text, "${Command}")
if sp.Origin == "net" && (command == "clients" || command == "companies") {
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])
r := regexp.MustCompile("#:(?P<CompanyID>[0-9]+)\\((?P<Color>[a-zA-Z]+)\\) Company Name: '(?P<CompanyName>.+)'.*Money: (?P<Money>[0-9]+).*Loan: (?P<Loan>[0-9]*).*Value: (?P<Value>[0-9]+).* \\(T:(?P<Train>[0-9]+), R:(?P<Road>[0-9]+), P:(?P<Plane>[0-9]+), S:(?P<Ship>[0-9]+)\\).*$")
r2 := regexp.MustCompile("Client #(?P<ClientID>[0-9]+).*name: '(?P<ClientName>.+)'.*company: (?P<CompanyID>[0-9]+)( )+IP: .*$")
if r.MatchString(sp.Output) {
coID := uint8(0)
for _, co := range srv.Status.Companies {
if co.Name == r.ReplaceAllString(sp.Output, "${CompanyName}") {
co.CompanyExtlID, _ = strconv.ParseInt(r.ReplaceAllString(sp.Output, "${CompanyID}"), 10, 64)
coID = co.CompanyID
break
}
}
if cfg.StatsDaily == nil {
cfg.StatsDaily = make(map[int]map[string]*StatDaily)
}
if cfg.CompanyIsRegistered(coID) {
cc := cfg.GetCompanyClient(coID)
cc.Color = r.ReplaceAllString(sp.Output, "${Color}")
_, ok := cfg.StatsDaily[cc.UserID]
if !ok {
cfg.StatsDaily[cc.UserID] = make(map[string]*StatDaily)
}
stats, ok := cfg.StatsDaily[cc.UserID][s.Status.GameDate.Format("20060102")]
if !ok {
stats = &StatDaily{
CompanyID: coID,
Date: s.Status.GameDate,
}
cfg.StatsDaily[cc.UserID][s.Status.GameDate.Format("20060102")] = stats
}
stats.Money, _ = strconv.ParseInt(r.ReplaceAllString(sp.Output, "${Money}"), 10, 64)
stats.Loan, _ = strconv.ParseInt(r.ReplaceAllString(sp.Output, "${Loan}"), 10, 64)
stats.Value, _ = strconv.ParseInt(r.ReplaceAllString(sp.Output, "${Value}"), 10, 64)
stats.Train, _ = strconv.Atoi(r.ReplaceAllString(sp.Output, "${Train}"))
stats.Road, _ = strconv.Atoi(r.ReplaceAllString(sp.Output, "${Road}"))
stats.Plane, _ = strconv.Atoi(r.ReplaceAllString(sp.Output, "${Plane}"))
stats.Ship, _ = strconv.Atoi(r.ReplaceAllString(sp.Output, "${Ship}"))
}
} else if r2.MatchString(sp.Output) {
for _, c := range srv.Status.Clients {
if c.Name == r2.ReplaceAllString(sp.Output, "${ClientName}") {
c.ClientExtlID, _ = strconv.ParseInt(r2.ReplaceAllString(sp.Output, "${ClientID}"), 10, 64)
break
}
}
} else {
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":
case "companies":
case "pause":
case "unpause":
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")
for _, c := range srv.Status.Clients {
if c.ClientExtlID == 4294967296 {
px2 := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "clients",
}
err = s.Send(px2.Bytes())
logErrorDebug(err, "Server.UpdateClients() : Send(PacketAdminPoll) : AdminPacketAdminRCon")
break
}
}
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")
for _, co := range srv.Status.Companies {
if co.CompanyExtlID == 256 {
px2 := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: "companies",
}
err = s.Send(px2.Bytes())
logErrorDebug(err, "Server.UpdateClients() : Send(PacketAdminPoll) : AdminPacketAdminRCon")
break
} else if !co.Protected {
for _, cc := range cfg.Clients {
if cc.CompanyID == co.CompanyID {
s.SetPasswd(co.CompanyExtlID, cc.Passwd)
co.Protected = true
break
}
}
if !co.Protected {
s.SetPasswd(co.CompanyExtlID, "blabla")
}
}
}
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)
if clt.CompanyID != 255 && cfg.CompanyIsRegistered(clt.CompanyID) {
cc := cfg.GetCompanyClient(clt.CompanyID)
if cc.Online {
cc.Online = false
}
}
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()
if !cfg.Game.Started {
s.Data.LastClientCompute = t
return
}
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 {
msg := fmt.Sprintf("@%s got not time left. Grace period of %s.", cc.Username, cfg.Game.Threshold)
bot.SendChat(cfg.Telegram.ChatID, msg)
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: fmt.Sprintf("say \"%s\"", msg),
}
srv.Send(px.Bytes())
}
if (cc.TimeLeft+cfg.Game.Threshold) > 0 && (cc.TimeLeft+cfg.Game.Threshold) < diff {
msg := fmt.Sprintf("@%s got not time left. Grace period over.", cc.Username)
bot.SendChat(cfg.Telegram.ChatID, msg)
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: fmt.Sprintf("say \"%s\"", msg),
}
srv.Send(px.Bytes())
}
cc.TimeLeft = cc.TimeLeft - diff
}
}
}
s.Data.LastClientCompute = t
return
}
func (s *ServerTTD) DeleteCompany(id uint8) {
if co, ok := s.Status.Companies[id]; ok {
logInfoDebug("Server.DeleteCompany : deleting #%d", id)
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: fmt.Sprintf("reset_company %d", co.CompanyExtlID),
}
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()
//bot.SendChat(cfg.Telegram.ChatID, "Game is paused.")
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()
//bot.SendChat(cfg.Telegram.ChatID, "Game is unpaused.")
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 !cfg.Game.Started {
return "game not started"
}
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())
failError(err, "Server.Initialize() : Send(AdminUpdateDate)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateClientInfo,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
failError(err, "Server.Initialize() : Send(AdminUpdateClientInfo)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateCompanyInfo,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
failError(err, "Server.Initialize() : Send(AdminUpdateCompanyInfo)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateCompanyEconomy,
UpdateFrequency: AdminFrequencyMonthly,
}
err = s.Send(px.Bytes())
failError(err, "Server.Initialize() : Send(AdminUpdateCompanyEconomy)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateCompanyStats,
UpdateFrequency: AdminFrequencyMonthly,
}
err = s.Send(px.Bytes())
failError(err, "Server.Initialize() : Send(AdminUpdateCompanyStats)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateChat,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
failError(err, "Server.Initialize() : Send(AdminUpdateChat)")
px = PacketAdminUpdateFrequency{
Packet: Packet{PType: AdminPacketAdminUpdateFrequency},
UpdateType: AdminUpdateConsole,
UpdateFrequency: AdminFrequencyAutomatic,
}
err = s.Send(px.Bytes())
failError(err, "Server.Initialize() : Send(AdminUpdateConsole)")
s.Status.Initialized = true
s.UpdateCompanies()
s.UpdateClients()
}
func (s *ServerTTD) SetPasswd(id int64, passwd string) {
px := PacketAdminRCon{
Packet: Packet{PType: AdminPacketAdminRCon},
Command: fmt.Sprintf("company_pw %d %s", id, passwd),
}
err := s.Send(px.Bytes())
logErrorDebug(err, "Server.SetPasswd() : Send(AdminPacketAdminRCon)")
return
}