/entrylist_ini.go

https://github.com/JustaPenguin/assetto-server-manager · Go · 356 lines · 264 code · 80 blank · 12 comment · 42 complexity · c14a2c0634c73e34d5c03392365fd978 MD5 · raw file

  1. package servermanager
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "path/filepath"
  7. "regexp"
  8. "sort"
  9. "strings"
  10. "github.com/cj123/ini"
  11. "github.com/google/uuid"
  12. "github.com/sirupsen/logrus"
  13. )
  14. const (
  15. AnyCarModel = "any_car_model"
  16. entryListFilename = "entry_list.ini"
  17. driverSwapEntrantSeparator = ";"
  18. maxEntryListSize = 255
  19. )
  20. type EntryList map[string]*Entrant
  21. // Write the EntryList to the server location
  22. func (e EntryList) Write() error {
  23. setupDirectory := filepath.Join(ServerInstallPath, "setups")
  24. // belt and braces check to make sure setup file exists
  25. for _, entrant := range e.AsSlice() {
  26. if entrant.FixedSetup != "" {
  27. if _, err := os.Stat(filepath.Join(setupDirectory, entrant.FixedSetup)); os.IsNotExist(err) {
  28. return err
  29. }
  30. }
  31. }
  32. for i, entrant := range e.AsSlice() {
  33. entrant.PitBox = i
  34. }
  35. f := ini.NewFile([]ini.DataSource{nil}, ini.LoadOptions{
  36. IgnoreInlineComment: true,
  37. })
  38. // making and throwing away a default section due to the utter insanity of ini or assetto. i don't know which.
  39. _, err := f.NewSection("DEFAULT")
  40. if err != nil {
  41. return err
  42. }
  43. for _, v := range e.AsSlice() {
  44. s, err := f.NewSection(fmt.Sprintf("CAR_%d", v.PitBox))
  45. if err != nil {
  46. return err
  47. }
  48. err = s.ReflectFrom(&v)
  49. if err != nil {
  50. return err
  51. }
  52. }
  53. return f.SaveTo(filepath.Join(ServerInstallPath, ServerConfigPath, entryListFilename))
  54. }
  55. func (e EntryList) ReadString() (string, error) {
  56. content, err := ioutil.ReadFile(filepath.Join(ServerInstallPath, ServerConfigPath, entryListFilename))
  57. if err != nil {
  58. return "", err
  59. }
  60. return string(content), nil
  61. }
  62. // Add an Entrant to the EntryList
  63. func (e EntryList) AddToBackOfGrid(entrant *Entrant) {
  64. e.AddInPitBox(entrant, len(e))
  65. }
  66. // AddInPitBox adds an Entrant in a specific pitbox - overwriting any entrant that was in that pitbox previously.
  67. func (e EntryList) AddInPitBox(entrant *Entrant, pitBox int) {
  68. pitBoxKey := fmt.Sprintf("CAR_%d", pitBox)
  69. if existingEntrant, ok := e[pitBoxKey]; ok {
  70. logrus.Warnf("Car already present in pitbox: %d! Driver: %s (%s) in %s will be overwritten!", pitBox, existingEntrant.Name, existingEntrant.GUID, existingEntrant.Model)
  71. }
  72. entrant.PitBox = pitBox
  73. e[pitBoxKey] = entrant
  74. }
  75. // Remove an Entrant from the EntryList
  76. func (e EntryList) Delete(entrant *Entrant) {
  77. for k, v := range e {
  78. if v == entrant {
  79. delete(e, k)
  80. return
  81. }
  82. }
  83. }
  84. func (e EntryList) AsSlice() []*Entrant {
  85. var entrants []*Entrant
  86. for _, x := range e {
  87. entrants = append(entrants, x)
  88. }
  89. // note: pitbox sorting here is crucial
  90. sort.Slice(entrants, func(i, j int) bool {
  91. return entrants[i].PitBox < entrants[j].PitBox
  92. })
  93. return entrants
  94. }
  95. func (e EntryList) AlphaSlice() []*Entrant {
  96. var entrants []*Entrant
  97. for _, x := range e {
  98. entrants = append(entrants, x)
  99. }
  100. sort.Slice(entrants, func(i, j int) bool {
  101. return entrants[i].Name < entrants[j].Name
  102. })
  103. return entrants
  104. }
  105. func (e EntryList) PrettyList() []*Entrant {
  106. var entrants []*Entrant
  107. numOpenSlots := 0
  108. for _, x := range e {
  109. if x.GUID == "" {
  110. numOpenSlots++
  111. continue
  112. }
  113. if x.Model == AnyCarModel {
  114. continue
  115. }
  116. entrants = append(entrants, x)
  117. }
  118. sort.Slice(entrants, func(i, j int) bool {
  119. return entrants[i].Name < entrants[j].Name
  120. })
  121. entrants = append(entrants, &Entrant{
  122. Name: fmt.Sprintf("%d open slots", numOpenSlots),
  123. GUID: "OPEN_SLOTS",
  124. })
  125. return entrants
  126. }
  127. func (e EntryList) Entrants() string {
  128. var entrants []string
  129. numOpenSlots := 0
  130. for _, x := range e {
  131. if x.Name == "" {
  132. numOpenSlots++
  133. } else {
  134. entrants = append(entrants, driverName(x.Name))
  135. }
  136. }
  137. if numOpenSlots > 0 {
  138. entrants = append(entrants, fmt.Sprintf("%d open slots", numOpenSlots))
  139. }
  140. return strings.Join(entrants, ", ")
  141. }
  142. func (e EntryList) FindEntrantByInternalUUID(internalUUID uuid.UUID) *Entrant {
  143. for _, entrant := range e {
  144. if entrant.InternalUUID == internalUUID {
  145. return entrant
  146. }
  147. }
  148. return &Entrant{}
  149. }
  150. // CarIDs returns a unique list of car IDs used in the EntryList
  151. func (e EntryList) CarIDs() []string {
  152. cars := make(map[string]bool)
  153. for _, entrant := range e {
  154. cars[entrant.Model] = true
  155. }
  156. var out []string
  157. for car := range cars {
  158. out = append(out, car)
  159. }
  160. return out
  161. }
  162. // returns the greatest ballast set on any entrant
  163. func (e EntryList) FindGreatestBallast() int {
  164. var greatest int
  165. for _, entrant := range e {
  166. if entrant.Ballast > greatest {
  167. greatest = entrant.Ballast
  168. }
  169. }
  170. return greatest
  171. }
  172. func NewEntrant() *Entrant {
  173. return &Entrant{
  174. InternalUUID: uuid.New(),
  175. }
  176. }
  177. type Entrant struct {
  178. InternalUUID uuid.UUID `ini:"-"`
  179. PitBox int `ini:"-"`
  180. Name string `ini:"DRIVERNAME"`
  181. Team string `ini:"TEAM"`
  182. GUID string `ini:"GUID"`
  183. Model string `ini:"MODEL"`
  184. Skin string `ini:"SKIN"`
  185. Ballast int `ini:"BALLAST"`
  186. SpectatorMode int `ini:"SPECTATOR_MODE"`
  187. Restrictor int `ini:"RESTRICTOR"`
  188. FixedSetup string `ini:"FIXED_SETUP"`
  189. TransferTeamPoints bool `ini:"-" json:"-"`
  190. OverwriteAllEvents bool `ini:"-" json:"-"`
  191. IsPlaceHolder bool `ini:"-"`
  192. }
  193. func (e Entrant) ID() string {
  194. if e.GUID != "" {
  195. return e.GUID
  196. }
  197. return e.Name
  198. }
  199. func (e *Entrant) OverwriteProperties(other *Entrant) {
  200. e.FixedSetup = other.FixedSetup
  201. e.Restrictor = other.Restrictor
  202. e.SpectatorMode = other.SpectatorMode
  203. e.Ballast = other.Ballast
  204. e.Skin = other.Skin
  205. e.PitBox = other.PitBox
  206. }
  207. func (e *Entrant) SwapProperties(other *Entrant, entrantRemainedInClass bool) {
  208. if entrantRemainedInClass {
  209. e.Model, other.Model = other.Model, e.Model
  210. e.Skin, other.Skin = other.Skin, e.Skin
  211. e.FixedSetup, other.FixedSetup = other.FixedSetup, e.FixedSetup
  212. e.Restrictor, other.Restrictor = other.Restrictor, e.Restrictor
  213. e.Ballast, other.Ballast = other.Ballast, e.Ballast
  214. }
  215. e.Team, other.Team = other.Team, e.Team
  216. e.InternalUUID, other.InternalUUID = other.InternalUUID, e.InternalUUID
  217. e.PitBox, other.PitBox = other.PitBox, e.PitBox
  218. }
  219. func (e *Entrant) AssignFromResult(result *SessionResult, car *SessionCar) {
  220. e.Name = result.DriverName
  221. e.Team = car.Driver.Team
  222. e.GUID = result.DriverGUID
  223. e.Model = result.CarModel
  224. e.Skin = car.Skin
  225. e.Restrictor = car.Restrictor
  226. e.Ballast = car.BallastKG
  227. }
  228. func (e *Entrant) AsSessionCar() *SessionCar {
  229. return &SessionCar{
  230. BallastKG: e.Ballast,
  231. CarID: e.PitBox,
  232. Driver: SessionDriver{
  233. GUID: e.GUID,
  234. GuidsList: []string{e.GUID},
  235. Name: e.Name,
  236. Team: e.Team,
  237. },
  238. Model: e.Model,
  239. Restrictor: e.Restrictor,
  240. Skin: e.Skin,
  241. }
  242. }
  243. func (e *Entrant) AsSessionResult() *SessionResult {
  244. return &SessionResult{
  245. BallastKG: e.Ballast,
  246. CarID: e.PitBox,
  247. CarModel: e.Model,
  248. DriverGUID: e.GUID,
  249. DriverName: e.Name,
  250. Restrictor: e.Restrictor,
  251. }
  252. }
  253. var guidCleanupRegex = regexp.MustCompile(`[^0-9]+`)
  254. func CleanGUIDs(guids []string) []string {
  255. var cleaned []string
  256. for _, guid := range guids {
  257. g := guidCleanupRegex.ReplaceAllLiteralString(guid, "")
  258. if len(g) > 0 {
  259. cleaned = append(cleaned, g)
  260. }
  261. }
  262. return cleaned
  263. }
  264. // NormaliseEntrantGUID takes a guid which may have driverSwapEntrantSeparators in it,
  265. // sorts all GUIDs in the string and then rejoins them by driverSwapEntrantSeparator
  266. func NormaliseEntrantGUID(guid string) string {
  267. split := CleanGUIDs(strings.Split(guid, driverSwapEntrantSeparator))
  268. sort.Strings(split)
  269. return strings.Join(split, driverSwapEntrantSeparator)
  270. }
  271. // NormaliseEntrantGUIDs takes a list of guids, sorts them and joins them by driverSwapEntrantSeparator
  272. func NormaliseEntrantGUIDs(guids []string) string {
  273. guids = CleanGUIDs(guids)
  274. sort.Strings(guids)
  275. return strings.Join(guids, driverSwapEntrantSeparator)
  276. }