Become the leader if you meet the criteria
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

217 lines
6.1KB

  1. // NGnius 2020-02-27
  2. package main
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "flag"
  7. "fmt"
  8. "io/ioutil"
  9. "net/http"
  10. "os"
  11. "os/signal"
  12. "path/filepath"
  13. )
  14. const (
  15. defaultPassword = ""
  16. defaultEntryURL = "http://localhost:1337/record"
  17. defaultPort = "9000"
  18. defaultDir = "criterias"
  19. )
  20. var (
  21. // cli params
  22. password string
  23. entryURL string
  24. port string
  25. dir string
  26. // internal
  27. server *http.Server
  28. client *http.Client
  29. isClosing bool
  30. referenceCriteria map[string]Criteria
  31. )
  32. // json structs
  33. // NewEntryJSON a new entry to be saved to a leaderboard
  34. type NewEntryJSON struct { // from leadercraft-s
  35. Score int64
  36. PlayerID int64
  37. BoardID int64
  38. Password string
  39. }
  40. // Criteria a set of values that must be met
  41. type Criteria struct {
  42. Location [2][3]float64 // (min coords (x,y,z), max coords (x,y,z))
  43. Time int64 // time since start of game (seconds)
  44. GameID int64 // game/board id in leadercraft-s and in Gamecraft (workshop ID?)
  45. PlayerID int64 // player id in leadercraft-s and in Gamecraft (steam ID)
  46. Coefficient float64 // coefficient for calculating the score
  47. Complete bool // game has been completed
  48. Points int64 // points scored in game
  49. ScoreMode string // score calculation mode
  50. }
  51. func (c *Criteria) Meets(c2 *Criteria) bool {
  52. meets := false
  53. if c2.Location[0][0] != 0.0 && c2.Location[0][1] != 0.0 && c2.Location[0][2] != 0.0 &&
  54. c2.Location[1][0] != 0.0 && c2.Location[1][1] != 0.0 && c2.Location[1][2] != 0.0 {
  55. // criteria is location-based
  56. meets = c.Location[0][0] >= c2.Location[0][0] && c.Location[0][1] >= c2.Location[0][1] && c.Location[0][2] >= c2.Location[0][2]
  57. meets = meets && c.Location[1][0] <= c2.Location[1][0] && c.Location[1][1] <= c2.Location[1][1] && c.Location[1][2] <= c2.Location[1][2]
  58. return meets
  59. }
  60. if c2.Complete {
  61. return c.Complete && (c.Points >= c2.Points)
  62. }
  63. return meets
  64. }
  65. func (c *Criteria) Score(c2 *Criteria) int64 {
  66. if c2.ScoreMode == "time" {
  67. time := float64(c.Time)
  68. if time < 1 {
  69. time = 1
  70. }
  71. return int64(c2.Coefficient / float64(c.Time))
  72. }
  73. if c2.ScoreMode == "points" {
  74. return int64(c2.Coefficient * float64(c.Points))
  75. }
  76. return 0
  77. }
  78. func init() {
  79. flag.StringVar(&password, "entry-pwd", defaultPassword, "Password for new entry POST requests")
  80. flag.StringVar(&entryURL, "url", defaultEntryURL, "URL for new entry POST requests")
  81. flag.StringVar(&port, "port", defaultPort, "Port to listen on")
  82. flag.StringVar(&dir, "dir", defaultDir, "Working directory")
  83. }
  84. func main() {
  85. flag.Parse()
  86. serverMux := http.NewServeMux()
  87. serverMux.HandleFunc("/criteria", criteriaHandler)
  88. signalChan := make(chan os.Signal)
  89. signal.Notify(signalChan, os.Interrupt)
  90. go func() {
  91. s := <-signalChan
  92. fmt.Println("Received terminate signal " + s.String())
  93. isClosing = true
  94. server.Close()
  95. }()
  96. server = &http.Server{
  97. Addr: ":" + port,
  98. Handler: serverMux,
  99. }
  100. client = &http.Client{}
  101. fmt.Println("Starting on " + server.Addr)
  102. err := server.ListenAndServe()
  103. if err != nil && !isClosing {
  104. fmt.Println(err)
  105. }
  106. }
  107. // criteria POST request handler
  108. // this also sends a new entry to leadercraft-s when the criteria is met
  109. func criteriaHandler(w http.ResponseWriter, r *http.Request) {
  110. if r.Method != "POST" {
  111. w.WriteHeader(405)
  112. return
  113. }
  114. authHeader := r.Header.Get("Authorization")
  115. w.Header().Add("Content-Type", "application/json")
  116. //w.Header().Add("Access-Control-Allow-Origin", "*")
  117. data, readErr := ioutil.ReadAll(r.Body)
  118. if readErr != nil {
  119. // body could not be read properly
  120. w.WriteHeader(500)
  121. return
  122. }
  123. reqCriteria := &Criteria{}
  124. unmarshErr := json.Unmarshal(data, reqCriteria)
  125. if unmarshErr != nil {
  126. // body could not be interpreted as json
  127. w.WriteHeader(400)
  128. return
  129. }
  130. if reqCriteria.GameID < 2 {
  131. //fmt.Println("404 -- GameID too low")
  132. w.WriteHeader(404)
  133. return
  134. }
  135. criteriaFilename := filepath.Join(dir, fmt.Sprintf("criteria-%d.json", reqCriteria.GameID))
  136. //fmt.Println(criteriaFilename)
  137. realCriteria := &Criteria{}
  138. realCriteriaF, openErr := os.Open(criteriaFilename)
  139. if openErr != nil {
  140. // file not found (or not accessible)
  141. //fmt.Println("404 -- criteria file not accessible: %s", openErr.Error())
  142. w.WriteHeader(404)
  143. return
  144. }
  145. realCritData, realCritReadErr := ioutil.ReadAll(realCriteriaF)
  146. if realCritReadErr != nil {
  147. // internal read error
  148. w.WriteHeader(500)
  149. return
  150. }
  151. unmarshCritErr := json.Unmarshal(realCritData, realCriteria)
  152. if unmarshCritErr != nil {
  153. // criteria file is invalid json
  154. //fmt.Printf("404 -- Invalid criteria file json: %s", unmarshCritErr.Error())
  155. w.WriteHeader(404)
  156. return
  157. }
  158. // TODO check if criteria matches
  159. if !reqCriteria.Meets(realCriteria) {
  160. // if criteria does not match, stop
  161. //fmt.Println("400 -- Criteria does not meet required criteria")
  162. w.WriteHeader(400)
  163. return
  164. }
  165. // if criteria matches, send new entry to leadercraft-s
  166. entry := NewEntryJSON{
  167. Score: reqCriteria.Score(realCriteria),
  168. PlayerID: reqCriteria.PlayerID,
  169. BoardID: realCriteria.GameID,
  170. Password: password,
  171. }
  172. echoData, marshErr := json.Marshal(entry)
  173. if marshErr != nil {
  174. //fmt.Println("500 -- Unable to marshal entry into JSON for leaderboard-s endpoint")
  175. w.WriteHeader(500)
  176. return
  177. }
  178. echoBody := bytes.NewReader(echoData)
  179. entryReq, reqErr := http.NewRequest("POST", entryURL, echoBody)
  180. if reqErr != nil {
  181. // malformed request parameters
  182. //fmt.Println("500 -- Malformed request detected during initialization")
  183. w.WriteHeader(500)
  184. return
  185. }
  186. entryReq.Header.Add("Authorization", authHeader)
  187. entryReq.Header.Add("Content-Type", "application/json")
  188. echoResp, postErr := client.Do(entryReq)
  189. if postErr != nil {
  190. // bad communication or malformed request
  191. //fmt.Println("500 -- Bad communication for leadercraft-s")
  192. w.WriteHeader(500)
  193. return
  194. }
  195. // echo new entry request response to original sender
  196. w.WriteHeader(echoResp.StatusCode)
  197. echoRespData, echoReadErr := ioutil.ReadAll(echoResp.Body)
  198. if echoReadErr != nil {
  199. // body read error (should never occur)
  200. //fmt.Println("!!! Error reading response body from leadercraft-s")
  201. return
  202. }
  203. w.Write(echoRespData)
  204. }