953 lines
29 KiB
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
|
|
}
|