1
0

inworld.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. // This deals with calls coming from Second Life or OpenSimulator.
  2. // it's essentially a RESTful thingy
  3. package main
  4. import (
  5. _ "github.com/go-sql-driver/mysql"
  6. "crypto/md5"
  7. "database/sql"
  8. "encoding/hex"
  9. "fmt"
  10. "net/http"
  11. "strconv"
  12. "strings"
  13. )
  14. // GetMD5Hash takes a string which is to be encoded using MD5 and returns a string with the hex-encoded MD5 sum.
  15. // Got this from https://gist.github.com/sergiotapia/8263278
  16. func GetMD5Hash(text string) string {
  17. hasher := md5.New()
  18. hasher.Write([]byte(text))
  19. return hex.EncodeToString(hasher.Sum(nil))
  20. }
  21. // updateInventory updates the inventory of the object (object key will come in the headers).
  22. func updateInventory(w http.ResponseWriter, r *http.Request) {
  23. // get all parameters in array
  24. err := r.ParseForm()
  25. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed:", err)
  26. if r.Form.Get("signature") != "" && r.Header.Get("X-Secondlife-Object-Key") != "" {
  27. signature := GetMD5Hash(r.Header.Get("X-Secondlife-Object-Key") + r.Form.Get("timestamp") + ":" + LSLSignaturePIN)
  28. if signature != r.Form.Get("signature") {
  29. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature does not match - hack attempt?")
  30. return
  31. }
  32. // open database connection and see if we can update the inventory for this object
  33. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  34. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Connect failed: %s\n", err)
  35. defer db.Close()
  36. stmt, err := db.Prepare("REPLACE INTO Inventory (`UUID`, `Name`, `Type`, `Permissions`, `LastUpdate`) VALUES (?,?,?,?,?)");
  37. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace prepare failed:", err)
  38. defer stmt.Close()
  39. _, err = stmt.Exec(
  40. r.Header.Get("X-Secondlife-Object-Key"),
  41. r.Form.Get("name"),
  42. r.Form.Get("itemType"),
  43. r.Form.Get("permissions"),
  44. r.Form.Get("timestamp"),
  45. )
  46. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace exec failed:", err)
  47. //_, err := res.RowsAffected()
  48. //checkErr(err)
  49. w.WriteHeader(http.StatusOK)
  50. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  51. fmt.Fprintf(w, "%s successfully updated!", r.Header.Get("X-Secondlife-Object-Key"))
  52. return
  53. } else {
  54. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature not found")
  55. return
  56. }
  57. /*
  58. fmt.Fprintf(w, "Root URL is: %s\n", updateInventory()) // send data to client side
  59. r.ParseForm() // parse arguments, you have to call this by yourself
  60. Log.Debug(r.Form) // print form information in server side
  61. Log.Debug("header connection: ", r.Header.Get("Connection"))
  62. Log.Debug("all headers:")
  63. for k, v := range r.Header {
  64. Log.Debug("key:", k)
  65. Log.Debug("val:", strings.Join(v, ""))
  66. }
  67. Log.Debug("path", r.URL.Path)
  68. Log.Debug("scheme", r.URL.Scheme)
  69. lLog.Debugr.Form["url-long"])
  70. for k, v := range r.Form {
  71. Log.Debug("key:", k)
  72. Log.Debug("val:", strings.Join(v, ""))
  73. }
  74. if r.Form["signature"] != nil {
  75. fmt.Fprintf(w, "Signature is %s\n", r.Form.Get("signature"))
  76. }
  77. */
  78. }
  79. // updateSensor updates the Obstacles database with an additional object found by the sensors.
  80. func updateSensor(w http.ResponseWriter, r *http.Request) {
  81. if r.Header.Get("X-Secondlife-Object-Key") != "" {
  82. err := r.ParseForm()
  83. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed:", err)
  84. // open database connection and see if we can update the inventory for this object
  85. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  86. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Connect failed:", err)
  87. defer db.Close()
  88. stmt, err := db.Prepare("REPLACE INTO Obstacles (`UUID`, `Name`, `BotKey`, `BotName`, `Type`, `Origin`, `Position`, `Rotation`, `Velocity`, `Phantom`, `Prims`, `BBHi`, `BBLo`, `LastUpdate`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
  89. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace prepare failed:", err)
  90. defer stmt.Close()
  91. _, err = stmt.Exec(
  92. r.Form.Get("key"),
  93. r.Form.Get("name"),
  94. r.Header.Get("X-Secondlife-Object-Key"),
  95. r.Header.Get("X-Secondlife-Object-Name"),
  96. r.Form.Get("type"),
  97. r.Form.Get("origin"),
  98. strings.Trim(r.Form.Get("pos"), "<>()"),
  99. strings.Trim(r.Form.Get("rot"), "<>()"),
  100. strings.Trim(r.Form.Get("vel"), "<>()"),
  101. r.Form.Get("phantom"),
  102. r.Form.Get("prims"),
  103. strings.Trim(r.Form.Get("bbhi"), "<>()"),
  104. strings.Trim(r.Form.Get("bblo"), "<>()"),
  105. r.Form.Get("timestamp"),
  106. )
  107. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace exec failed:", err)
  108. //_, err := res.RowsAffected()
  109. //checkErr(err)
  110. w.WriteHeader(http.StatusOK)
  111. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  112. reply := r.Header.Get("X-Secondlife-Owner-Name") + " sent us:\n" +
  113. "Key: " + r.Form.Get("key") + " Name: " + r.Form.Get("name")+ "\n" +
  114. "Position: " + r.Form.Get("pos") + " Rotation: " + r.Form.Get("rot") + "\n" +
  115. "Type: " + r.Form.Get("type") + "\n" + "Origin: " + r.Form.Get("origin") + "\n" +
  116. "Velocity: " + r.Form.Get("vel") +
  117. " Phantom: " + r.Form.Get("phantom") +
  118. " Prims: " + r.Form.Get("prims") + "\n" +
  119. "BB high: " + r.Form.Get("bbhi") +
  120. " BB low: " + r.Form.Get("bblo") + "\n" +
  121. "Timestamp: " + r.Form.Get("timestamp")
  122. fmt.Fprint(w, reply)
  123. return
  124. } else {
  125. logErrHTTP(w, http.StatusForbidden, funcName() + ": Not called from within the virtual world.")
  126. return
  127. }
  128. }
  129. // registerPosition saves a HTTP URL for a single object, making it persistent.
  130. // POST parameters:
  131. // permURL: a permanent URL from llHTTPServer
  132. // signature: to make spoofing harder
  133. // timestamp: in-world timestamp retrieved with llGetTimestamp()
  134. func registerPosition(w http.ResponseWriter, r *http.Request) {
  135. // get all parameters in array
  136. err := r.ParseForm()
  137. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed:", err)
  138. // Log.Debug("Received: ", r) // we know this works well (20170725)
  139. if r.Header.Get("X-Secondlife-Object-Key") == "" {
  140. // Log.Debugf("Got '%s'\n", r.Header["X-Secondlife-Object-Key"])
  141. logErrHTTP(w, http.StatusForbidden, funcName() + ": Not called from within the virtual world.")
  142. return
  143. }
  144. if r.Form.Get("signature") != "" {
  145. // if we don't have the permURL to store, registering this object is pointless
  146. if r.Form["permURL"] == nil {
  147. logErrHTTP(w, http.StatusForbidden, funcName() + ": No PermURL specified")
  148. return
  149. }
  150. signature := GetMD5Hash(r.Header.Get("X-Secondlife-Object-Key") + r.Form.Get("timestamp") + ":" + LSLSignaturePIN)
  151. /* Log.Debugf("%s: Calculating signature and comparing with what we got: Object Key: '%s' Timestamp: '%s' PIN: '%s' LSL signature: '%s' Our signature: %s\n",
  152. funcName(),
  153. r.Header.Get("X-Secondlife-Object-Key"),
  154. r.Form.Get("timestamp"),
  155. LSLSignaturePIN,
  156. r.Form.Get("signature"),
  157. signature) */
  158. if signature != r.Form.Get("signature") {
  159. logErrHTTP(w, http.StatusServiceUnavailable, funcName() + ": Signature does not match - hack attempt?")
  160. return
  161. }
  162. // open database connection and see if we can update the inventory for this object
  163. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  164. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Connect failed:", err)
  165. defer db.Close()
  166. stmt, err := db.Prepare("REPLACE INTO Positions (`UUID`, `Name`, `PermURL`, `Location`, `Position`, `Rotation`, `Velocity`, `OwnerKey`, `OwnerName`, `ObjectType`, `ObjectClass`, `RateEnergy`, `RateMoney`, `RateHappiness`, `LastUpdate`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
  167. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace prepare failed: %s\n", err)
  168. defer stmt.Close()
  169. _, err = stmt.Exec(
  170. r.Header.Get("X-Secondlife-Object-Key"),
  171. r.Header.Get("X-Secondlife-Object-Name"),
  172. r.Form.Get("permURL"),
  173. r.Header.Get("X-Secondlife-Region"),
  174. strings.Trim(r.Header.Get("X-Secondlife-Local-Position"), "<>()"),
  175. strings.Trim(r.Header.Get("X-Secondlife-Local-Rotation"), "<>()"),
  176. strings.Trim(r.Header.Get("X-Secondlife-Local-Velocity"), "<>()"),
  177. r.Header.Get("X-Secondlife-Owner-Key"),
  178. r.Header.Get("X-Secondlife-Owner-Name"),
  179. r.Form.Get("objecttype"),
  180. r.Form.Get("objectclass"),
  181. r.Form.Get("rateenergy"),
  182. r.Form.Get("ratemoney"),
  183. r.Form.Get("ratehappiness"),
  184. r.Form.Get("timestamp"),
  185. )
  186. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace exec failed:", err)
  187. //_, err := res.RowsAffected()
  188. //checkErr(err)
  189. w.WriteHeader(http.StatusOK)
  190. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  191. fmt.Fprintf(w, "'%s' successfully updated!", r.Header.Get("X-Secondlife-Object-Name"))
  192. // Log.Debugf("These are the headers I got: %v\nAnd these are the parameters %v\n", r.Header, r.Form)
  193. return
  194. } else {
  195. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature not found")
  196. return
  197. }
  198. }
  199. // registerAgent saves a HTTP URL for a single agent, making it persistent.
  200. // POST parameters:
  201. // permURL: a permanent URL from llHTTPServer
  202. // signature: to make spoofing harder
  203. // timestamp: in-world timestamp retrieved with llGetTimestamp()
  204. // request: currently only delete (to remove entry from database when the bot dies)
  205. func registerAgent(w http.ResponseWriter, r *http.Request) {
  206. // get all parameters in array
  207. err := r.ParseForm()
  208. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed:", err)
  209. if r.Header.Get("X-Secondlife-Object-Key") == "" {
  210. // Log.Debugf("Got '%s'\n", r.Header["X-Secondlife-Object-Key"])
  211. logErrHTTP(w, http.StatusForbidden, funcName() + ": Only in-world requests allowed.")
  212. return
  213. }
  214. if r.Form.Get("signature") == "" {
  215. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature not found")
  216. return
  217. }
  218. signature := GetMD5Hash(r.Header.Get("X-Secondlife-Object-Key") + r.Form.Get("timestamp") + ":" + LSLSignaturePIN)
  219. if signature != r.Form.Get("signature") {
  220. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature does not match - hack attempt?")
  221. return
  222. }
  223. // open database connection and see if we can update the inventory for this object
  224. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  225. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Connect failed:", err)
  226. defer db.Close()
  227. if r.Form.Get("permURL") != "" { // bot registration
  228. stmt, err := db.Prepare("REPLACE INTO Agents (`UUID`, `Name`, `OwnerKey`, `OwnerName`, `PermURL`, `Location`, `Position`, `Rotation`, `Velocity`, `Energy`, `Money`, `Happiness`, `Class`, `SubType`, `LastUpdate`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
  229. checkErrPanicHTTP(w, http.StatusServiceUnavailable, "Replace prepare failed: %s\n", err)
  230. defer stmt.Close()
  231. _, err = stmt.Exec(
  232. r.Header.Get("X-Secondlife-Object-Key"),
  233. r.Header.Get("X-Secondlife-Object-Name"),
  234. r.Header.Get("X-Secondlife-Owner-Key"),
  235. r.Header.Get("X-Secondlife-Owner-Name"),
  236. r.Form.Get("permURL"),
  237. r.Header.Get("X-Secondlife-Region"),
  238. strings.Trim(r.Header.Get("X-Secondlife-Local-Position"), "<>()"),
  239. strings.Trim(r.Header.Get("X-Secondlife-Local-Rotation"), "<>()"),
  240. strings.Trim(r.Header.Get("X-Secondlife-Local-Velocity"), "<>()"),
  241. r.Form.Get("energy"),
  242. r.Form.Get("money"),
  243. r.Form.Get("happiness"),
  244. r.Form.Get("class"),
  245. r.Form.Get("subtype"),
  246. r.Form.Get("timestamp"),
  247. )
  248. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace exec failed:", err)
  249. w.WriteHeader(http.StatusOK)
  250. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  251. replyText := "'" + r.Header.Get("X-Secondlife-Object-Name") +
  252. "' successfully updated object for NPC '" +
  253. r.Header.Get("X-Secondlife-Owner-Name") + "' (" +
  254. r.Header.Get("X-Secondlife-Owner-Key") + "), energy=" +
  255. r.Form.Get("energy") + ", money=" +
  256. r.Form.Get("money") + ", happiness=" +
  257. r.Form.Get("happiness") + ", class=" +
  258. r.Form.Get("class") + ", subtype=" +
  259. r.Form.Get("subtype") + "."
  260. fmt.Fprint(w, replyText)
  261. // log.Printf(replyText) // debug
  262. } else if r.Form.Get("request") == "delete" { // other requests, currently only deletion
  263. stmt, err := db.Prepare("DELETE FROM Agents WHERE UUID=?")
  264. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Delete agent prepare failed:", err)
  265. defer stmt.Close()
  266. _, err = stmt.Exec(r.Form.Get("npc"))
  267. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Delete agent exec failed:", err)
  268. w.WriteHeader(http.StatusOK)
  269. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  270. fmt.Fprintf(w, "'%s' successfully deleted.", r.Form.Get("npc"))
  271. return
  272. }
  273. }
  274. // configureCube Support scripts for remote startup configuration for the cubes.
  275. // This basically gives the lists of options (e.g. energy, happiness; classes of NPCs, etc.) so
  276. // that we don't need to hardcode them
  277. func configureCube(w http.ResponseWriter, r *http.Request) {
  278. logErrHTTP(w, http.StatusNotImplemented, funcName() + ": configureCube not implemented")
  279. // return
  280. }
  281. // processCube is called when an agent sits on the cube; it will update the agent's money/energy/happiness.
  282. // Once everything is updated, the agent will get kicked out of the cube after a certain time has elapsed. This happens in-world.
  283. // We might also animate the avatar depending on its class, subclass, etc.
  284. func processCube(w http.ResponseWriter, r *http.Request) {
  285. err := r.ParseForm()
  286. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed:", err)
  287. if r.Header.Get("X-Secondlife-Object-Key") == "" {
  288. logErrHTTP(w, http.StatusForbidden, funcName() + ": Only in-world requests allowed.")
  289. return
  290. }
  291. if r.Form.Get("signature") == "" {
  292. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature not found")
  293. return
  294. }
  295. signature := GetMD5Hash(r.Header.Get("X-Secondlife-Object-Key") + r.Form.Get("timestamp") + ":" + LSLSignaturePIN)
  296. if signature != r.Form.Get("signature") {
  297. logErrHTTP(w, http.StatusForbidden, funcName() + ": Signature does not match - hack attempt?")
  298. return
  299. }
  300. if r.Form.Get("avatar") == NullUUID { // happens when standing up, we return without giving errors
  301. w.WriteHeader(http.StatusOK)
  302. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  303. fmt.Fprintf(w, "Got an avatar/NPC standing up")
  304. sendMessageToBrowser("status", "success", "Got an avatar/NPC standing up", "")
  305. return
  306. }
  307. // all checks fine, now let's get the agent data from the database:
  308. // allegedly, we get avatar=[UUID] — all the rest ought to be on the database (20170807)
  309. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  310. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Connect failed:", err)
  311. defer db.Close()
  312. Log.Debug("processCube called, avatar UUID is", r.Form.Get("avatar"), "cube UUID is", r.Header.Get("X-Secondlife-Object-Key"))
  313. var agent AgentType
  314. err = db.QueryRow("SELECT * FROM Agents WHERE OwnerKey=?", r.Form.Get("avatar")).Scan(
  315. &agent.UUID,
  316. &agent.Name,
  317. &agent.OwnerName,
  318. &agent.OwnerKey,
  319. &agent.Location,
  320. &agent.Position,
  321. &agent.Rotation,
  322. &agent.Velocity,
  323. &agent.Energy,
  324. &agent.Money,
  325. &agent.Happiness,
  326. &agent.Class,
  327. &agent.SubType,
  328. &agent.PermURL,
  329. &agent.LastUpdate,
  330. &agent.BestPath,
  331. &agent.SecondBestPath,
  332. &agent.CurrentTarget,
  333. )
  334. if err != nil || !agent.UUID.Valid {
  335. // this can happen when an avatar sits on the object, or a NPC that never got a chance to register, or even because of bugs
  336. // or cubes which did not have llSitTarget() initialised (20170811)
  337. checkErrHTTP(w, http.StatusNotFound, funcName() + " Agent UUID not found in database or invalid:", err)
  338. return
  339. }
  340. // get information on this cube
  341. var cube PositionType
  342. err = db.QueryRow("SELECT * FROM Positions WHERE UUID=?", r.Header.Get("X-Secondlife-Object-Key")).Scan(
  343. &cube.PermURL,
  344. &cube.UUID,
  345. &cube.Name,
  346. &cube.OwnerName,
  347. &cube.Location,
  348. &cube.Position,
  349. &cube.Rotation,
  350. &cube.Velocity,
  351. &cube.LastUpdate,
  352. &cube.OwnerKey,
  353. &cube.ObjectType,
  354. &cube.ObjectClass,
  355. &cube.RateEnergy,
  356. &cube.RateMoney,
  357. &cube.RateHappiness,
  358. )
  359. if err != nil || !cube.UUID.Valid {
  360. checkErrPanicHTTP(w, http.StatusNotFound, funcName() + " This cube's UUID was not found in database or is invalid! We should reset it in-world. Error was:", err)
  361. }
  362. // we update the avatar's money, happiness etc. by the rate of the object
  363. energyAgent, err := strconv.ParseFloat(*agent.Energy.Ptr(), 64) // get this as a float or else all hell will break loose!
  364. checkErr(err)
  365. energyCube, err := strconv.ParseFloat(*cube.RateEnergy.Ptr(), 64)
  366. checkErr(err)
  367. energyAgent += energyCube
  368. // in theory, this should now go to the engine, to see if it's enough or not; we'll see what happens (20170808)
  369. // We now call the agent and tell him about the new values:
  370. rsBody, err := callURL(*agent.PermURL.Ptr(), fmt.Sprintf("command=setEnergy&float=%f", energyAgent))
  371. if (err != nil) {
  372. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be updated in-world with new energy settings; in-world reply was: '%s'", *agent.Name.Ptr(), rsBody), "")
  373. } else {
  374. Log.Debug("Result from updating energy of ", *agent.Name.Ptr(), ":", rsBody)
  375. }
  376. // now do it for money!
  377. moneyAgent, err := strconv.ParseFloat(*agent.Money.Ptr(), 64)
  378. checkErr(err)
  379. moneyCube, err := strconv.ParseFloat(*cube.RateMoney.Ptr(), 64)
  380. checkErr(err)
  381. moneyAgent += moneyCube
  382. rsBody, err = callURL(*agent.PermURL.Ptr(), fmt.Sprintf("command=setMoney&float=%f", moneyAgent))
  383. if (err != nil) {
  384. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be updated in-world with new money settings; in-world reply was: '%s'", *agent.Name.Ptr(), rsBody), "")
  385. } else {
  386. Log.Debug("Result from updating money of ", *agent.Name.Ptr(), ":", rsBody)
  387. }
  388. // last but not least, make the agent more happy!
  389. happinessAgent, err := strconv.ParseFloat(*agent.Happiness.Ptr(), 64)
  390. checkErr(err)
  391. happinessCube, err := strconv.ParseFloat(*cube.RateHappiness.Ptr(), 64)
  392. checkErr(err)
  393. happinessAgent += happinessCube
  394. rsBody, err = callURL(*agent.PermURL.Ptr(), fmt.Sprintf("command=setHappiness&float=%f", happinessAgent))
  395. if (err != nil) {
  396. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be updated in-world with new happiness settings; in-world reply was: '%s'", *agent.Name.Ptr(), rsBody), "")
  397. } else {
  398. Log.Debug("Result from updating happiness of ", *agent.Name.Ptr(), ":", rsBody)
  399. }
  400. // stand up the bot, he's done his work
  401. rsBody, err = callURL(*agent.PermURL.Ptr(), "command=osNpcStand")
  402. if (err != nil) {
  403. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be made to stand-up; in-world reply was: '%s'", *agent.Name.Ptr(), rsBody), "")
  404. } else {
  405. Log.Debug("Result from making", *agent.Name.Ptr(), "stand up:", rsBody)
  406. }
  407. // the next step is to update the database with the new values
  408. stmt, err := db.Prepare("UPDATE Agents SET `Energy`=?, `Money`=?, `Happiness`=? WHERE UUID=?")
  409. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Update prepare failed:", err)
  410. if (err != nil) {
  411. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be updated in database with new energy/money/happiness settings; database reply was: '%v'", *agent.Name.Ptr(), err), "")
  412. }
  413. defer stmt.Close()
  414. _, err = stmt.Exec(energyAgent, moneyAgent, happinessAgent, *agent.UUID.Ptr())
  415. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Replace exec failed:", err)
  416. if (err != nil) {
  417. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be updated in database with new energy/money/happiness settings; database reply was: '%v'", *agent.Name.Ptr(), err), "")
  418. }
  419. Log.Debug("Agent", *agent.Name.Ptr(), "updated database with new energy:", energyAgent, "money:", moneyAgent, "happiness:", happinessAgent)
  420. w.WriteHeader(http.StatusOK)
  421. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  422. fmt.Fprintf(w, "'%s' successfully updated.", *agent.Name.Ptr())
  423. sendMessageToBrowser("status", "success", fmt.Sprintf("'%s' successfully updated.", *agent.Name.Ptr()), "")
  424. // return
  425. }