SQL.gd 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. extends ServiceBase
  2. #
  3. var db : Object = null
  4. var backups : SQLBackups = null
  5. var queryMutex : Mutex = Mutex.new()
  6. # Accounts
  7. func AddAccount(username : String, password : String, email : String) -> bool:
  8. var salt : String = Hasher.GenerateSalt()
  9. var hashedPassword : String = Hasher.HashPassword(password, salt)
  10. var accountData : Dictionary = {
  11. "username" : username,
  12. "password_salt" : salt,
  13. "password" : hashedPassword,
  14. "email" : email,
  15. "created_timestamp" : SQLCommons.Timestamp()
  16. }
  17. return db.insert_row("account", accountData)
  18. func RemoveAccount(accountID : int) -> bool:
  19. return db.delete_rows("account", "account_id = %d" % accountID)
  20. func HasAccount(username : String) -> bool:
  21. return not QueryBindings("SELECT account_id FROM account WHERE username = ?;", [username]).is_empty()
  22. func Login(username : String, triedPassword : String) -> int:
  23. var results : Array[Dictionary] = QueryBindings("SELECT account_id, password, password_salt FROM account WHERE username = ?;", [username])
  24. assert(results.size() <= 1, "Duplicated account row")
  25. if not results.is_empty():
  26. var salt = results[0].get("password_salt", null)
  27. var correctPassword = results[0].get("password", null)
  28. var accountID = results[0].get("account_id", null)
  29. if salt and correctPassword and accountID and salt is String and correctPassword is String and accountID is int:
  30. var hashedTriedPassword : String = Hasher.HashPassword(triedPassword, salt)
  31. if hashedTriedPassword == correctPassword:
  32. return accountID
  33. return NetworkCommons.RidUnknown
  34. func UpdateAccount(accountID : int) -> bool:
  35. var newTimestamp : int = SQLCommons.Timestamp()
  36. var data : Dictionary = {
  37. "last_timestamp": newTimestamp
  38. }
  39. return db.update_rows("account", "account_id = %d;" % accountID, data)
  40. # Characters
  41. func AddCharacter(accountID : int, nickname : String, stats : Dictionary, traits : Dictionary, attributes : Dictionary) -> bool:
  42. var charData : Dictionary = {
  43. "account_id": accountID,
  44. "nickname": nickname,
  45. "created_timestamp": SQLCommons.Timestamp()
  46. }
  47. var ret : bool = db.insert_row("character", charData)
  48. if ret:
  49. var charID : int = GetCharacterID(accountID, nickname)
  50. ret = ret and db.update_rows("stat", "char_id = %d" % charID, stats)
  51. ret = ret and db.update_rows("trait", "char_id = %d" % charID, traits)
  52. ret = ret and db.update_rows("attribute", "char_id = %d" % charID, attributes)
  53. return ret
  54. func RemoveCharacter(charID : int) -> bool:
  55. if charID != NetworkCommons.RidUnknown:
  56. return db.delete_rows("character", "char_id = %d" % charID)
  57. return false
  58. func GetCharacters(accountID : int) -> Array[int]:
  59. var charIDs : Array[int] = []
  60. for result in db.select_rows("character", "account_id = %d" % accountID, ["char_id"]):
  61. charIDs.append(result["char_id"])
  62. return charIDs
  63. func GetCharacterInfo(charID : int) -> Dictionary:
  64. var results : Array[Dictionary] = Query("SELECT * \
  65. FROM character \
  66. INNER JOIN stat ON character.char_id = stat.char_id \
  67. INNER JOIN trait ON character.char_id = trait.char_id \
  68. INNER JOIN attribute ON character.char_id = attribute.char_id \
  69. WHERE character.char_id = %d;" % charID)
  70. assert(results.size() == 1, "Character information tables are missing")
  71. return results[0] if results.size() > 0 else {}
  72. func RefreshCharacter(player : PlayerAgent) -> bool:
  73. var charID : int = Peers.GetCharacter(player.rpcRID)
  74. if charID == NetworkCommons.RidUnknown:
  75. return false
  76. var success : bool = charID != NetworkCommons.RidUnknown
  77. success = success and UpdateAttribute(charID, player.stat)
  78. success = success and UpdateTrait(charID, player.stat)
  79. success = success and UpdateStat(charID, player.stat)
  80. success = success and UpdateCharacter(player)
  81. success = success and UpdateProgress(charID, player.progress)
  82. return success
  83. func HasCharacter(nickname : String) -> bool:
  84. return not QueryBindings("SELECT char_id FROM character WHERE nickname = ?;", [nickname]).is_empty()
  85. func CharacterLogin(charID : int) -> bool:
  86. var newTimestamp : int = SQLCommons.Timestamp()
  87. var data : Dictionary = {
  88. "last_timestamp": newTimestamp
  89. }
  90. return db.update_rows("character", "char_id = %d;" % charID, data)
  91. # Character
  92. func GetCharacterID(accountID : int, nickname : String) -> int:
  93. var results : Array[Dictionary] = QueryBindings("SELECT char_id FROM character WHERE account_id = ? AND nickname = ?;", [accountID, nickname])
  94. assert(results.size() <= 1, "Duplicated character row for account %d and nickname '%s'" % [accountID, nickname])
  95. return results[0]["char_id"] if results.size() > 0 else NetworkCommons.RidUnknown
  96. func GetCharacter(charID : int) -> Dictionary:
  97. var results : Array[Dictionary] = db.select_rows("character", "char_id = %d" % charID, ["*"])
  98. assert(results.size() <= 1, "Duplicated character row %d" % charID)
  99. return results[0] if results.size() > 0 else {}
  100. func UpdateCharacter(player : PlayerAgent) -> bool:
  101. if player == null:
  102. return false
  103. var charID : int = Peers.GetCharacter(player.rpcRID)
  104. if charID == NetworkCommons.RidUnknown:
  105. return false
  106. var map : WorldMap = WorldAgent.GetMapFromAgent(player)
  107. var newTimestamp : int = SQLCommons.Timestamp()
  108. var data : Dictionary = GetCharacter(charID)
  109. data["total_time"] = SQLCommons.GetOrAddValue(data, "total_time", 0) + newTimestamp - SQLCommons.GetOrAddValue(data, "last_timestamp", newTimestamp)
  110. data["last_timestamp"] = newTimestamp
  111. if map != null and not map.HasFlags(WorldMap.Flags.NO_REJOIN) and ActorCommons.IsAlive(player):
  112. data["pos_x"] = player.position.x
  113. data["pos_y"] = player.position.y
  114. data["pos_map"] = map.id
  115. else:
  116. data["pos_x"] = player.respawnDestination.pos.x
  117. data["pos_y"] = player.respawnDestination.pos.y
  118. data["pos_map"] = player.respawnDestination.mapID
  119. data["respawn_x"] = player.respawnDestination.pos.x
  120. data["respawn_y"] = player.respawnDestination.pos.y
  121. data["respawn_map"] = player.respawnDestination.mapID
  122. if player.exploreOrigin != null:
  123. data["explore_x"] = player.exploreOrigin.pos.x
  124. data["explore_y"] = player.exploreOrigin.pos.y
  125. return db.update_rows("character", "char_id = %d;" % charID, data)
  126. # Stats
  127. func GetAttribute(charID : int) -> Dictionary:
  128. var results : Array[Dictionary] = db.select_rows("attribute", "char_id = %d" % charID, ["*"])
  129. assert(results.size() == 1, "Character attribute row is missing")
  130. return results[0] if results.size() > 0 else {}
  131. func UpdateAttribute(charID : int, stats : ActorStats) -> bool:
  132. if stats == null:
  133. return false
  134. var data : Dictionary = {
  135. "strength" = stats.strength,
  136. "vitality" = stats.vitality,
  137. "agility" = stats.agility,
  138. "endurance" = stats.endurance,
  139. "concentration" = stats.concentration
  140. }
  141. return db.update_rows("attribute", "char_id = %d" % charID, data)
  142. func GetTrait(charID : int) -> Dictionary:
  143. var results : Array[Dictionary] = db.select_rows("trait", "char_id = %d" % charID, ["*"])
  144. assert(results.size() == 1, "Character trait row is missing")
  145. return results[0] if results.size() > 0 else {}
  146. func UpdateTrait(charID : int, stats : ActorStats) -> bool:
  147. if stats == null:
  148. return false
  149. var data : Dictionary = {
  150. "hairstyle" = stats.hairstyle,
  151. "haircolor" = stats.haircolor,
  152. "race" = stats.race,
  153. "skintone" = stats.skintone,
  154. "gender" = stats.gender,
  155. "shape" = stats.shape,
  156. "spirit" = stats.spirit
  157. }
  158. return db.update_rows("trait", "char_id = %d" % charID, data)
  159. func GetStat(charID : int) -> Dictionary:
  160. var results : Array[Dictionary] = db.select_rows("stat", "char_id = %d" % charID, ["*"])
  161. assert(results.size() == 1, "Character stat row is missing")
  162. return results[0] if results.size() > 0 else {}
  163. func UpdateStat(charID : int, stats : ActorStats) -> bool:
  164. if stats == null:
  165. return false
  166. var data : Dictionary = {
  167. "level" = stats.level,
  168. "experience" = stats.experience,
  169. "gp" = stats.gp,
  170. "health" = stats.health,
  171. "mana" = stats.mana,
  172. "stamina" = stats.stamina,
  173. "karma" = stats.karma
  174. }
  175. return db.update_rows("stat", "char_id = %d" % charID, data)
  176. # Inventory
  177. func GetItem(charID : int, itemID : int, customfield : String, storageType : int = 0) -> Dictionary:
  178. var results : Array[Dictionary] = db.select_rows("item", "item_id = %d AND char_id = %d AND storage = %d AND customfield = '%s'" % [itemID, charID, storageType, customfield], ["*"])
  179. assert(results.size() <= 1, "Duplicated item %d on character %d with storage %d" % [itemID, charID, storageType])
  180. return {} if results.is_empty() else results[0]
  181. func AddItem(charID : int, itemID : int, customfield : String, itemCount : int = 1, storageType : int = 0) -> bool:
  182. var data : Dictionary = GetItem(charID, itemID, customfield, storageType)
  183. # Increment item count
  184. if not data.is_empty():
  185. data["count"] += 1
  186. return db.update_rows("item", "item_id = %d AND char_id = %d AND storage = %d AND customfield = '%s'" % [itemID, charID, storageType, customfield], data)
  187. # Insert new item
  188. data = {
  189. "item_id": itemID,
  190. "char_id": charID,
  191. "count": itemCount,
  192. "storage": storageType,
  193. "customfield": customfield
  194. }
  195. return db.insert_row("item", data)
  196. func RemoveItem(charID : int, itemID : int, customfield : String, itemCount : int = 1, storageType : int = 0) -> bool:
  197. var data : Dictionary = GetItem(charID, itemID, customfield, storageType)
  198. var condition : String = "item_id = %d AND char_id = %d AND storage = %d AND customfield = '%s'" % [itemID, charID, storageType, customfield]
  199. if not data.is_empty():
  200. # Decrement item count
  201. if data["count"] > itemCount:
  202. data["count"] -= itemCount
  203. return db.update_rows("item", condition, data)
  204. # Remove item
  205. elif data["count"] == itemCount:
  206. return db.delete_rows("item", condition)
  207. return false
  208. func GetStorage(charID : int, storageType : int = 0) -> Array[Dictionary]:
  209. return db.select_rows("item", "char_id = %d AND storage = %d" % [charID, storageType], ["*"])
  210. # Equipment
  211. func GetEquipment(charID : int) -> Dictionary:
  212. var results : Array[Dictionary] = db.select_rows("equipment", "char_id = %d" % charID, ["*"])
  213. assert(results.size() <= 1, "Duplicated equipment on character %d" % charID)
  214. return results[0] if results.size() > 0 else {}
  215. func UpdateEquipment(charID : int, data : Dictionary) -> bool:
  216. return db.update_rows("equipment", "char_id = %d" % charID, data)
  217. # Progress
  218. func UpdateProgress(charID : int, progress : ActorProgress):
  219. progress.questMutex.lock()
  220. for entryID in progress.quests:
  221. Launcher.SQL.SetQuest(charID, entryID, progress.quests[entryID])
  222. progress.questMutex.unlock()
  223. progress.bestiaryMutex.lock()
  224. for entryID in progress.bestiary:
  225. Launcher.SQL.SetBestiary(charID, entryID, progress.bestiary[entryID])
  226. progress.bestiaryMutex.unlock()
  227. for entryID in progress.skills:
  228. Launcher.SQL.SetSkill(charID, entryID, progress.skills[entryID])
  229. return true
  230. # Skill
  231. func SetSkill(charID : int, skillID : int, value : int) -> bool:
  232. var results : Array[Dictionary] = db.select_rows("skill", "char_id = %d AND skill_id = %d" % [charID, skillID], ["*"])
  233. assert(results.size() <= 1, "Duplicated skill for %d on character %d" % [skillID, charID])
  234. if results.size() > 0:
  235. results[0]["level"] = value
  236. return db.update_rows("skill", "char_id = %d AND skill_id = %d" % [charID, skillID], results[0])
  237. var data : Dictionary = {
  238. "char_id": charID,
  239. "skill_id": skillID,
  240. "level": value,
  241. }
  242. return db.insert_row("skill", data)
  243. func GetSkills(charID : int) -> Array[Dictionary]:
  244. return db.select_rows("skill", "char_id = %d" % [charID], ["*"])
  245. # Bestiary
  246. func SetBestiary(charID : int, mobID : int, value : int) -> bool:
  247. var results : Array[Dictionary] = db.select_rows("bestiary", "char_id = %d AND mob_id = %d" % [charID, mobID], ["*"])
  248. assert(results.size() <= 1, "Duplicated bestiary row for %d on character %d" % [mobID, charID])
  249. if results.size() > 0:
  250. results[0]["killed_count"] = value
  251. return db.update_rows("bestiary", "char_id = %d AND mob_id = %d" % [charID, mobID], results[0])
  252. var data : Dictionary = {
  253. "char_id": charID,
  254. "mob_id": mobID,
  255. "killed_count": value,
  256. }
  257. return db.insert_row("bestiary", data)
  258. func GetBestiaries(charID : int) -> Array[Dictionary]:
  259. return db.select_rows("bestiary", "char_id = %d" % [charID], ["*"])
  260. # Quest
  261. func SetQuest(charID : int, questID : int, value : int) -> bool:
  262. var results : Array[Dictionary] = db.select_rows("quest", "char_id = %d AND quest_id = %d" % [charID, questID], ["*"])
  263. assert(results.size() <= 1, "Duplicated quest row for %d on character %d" % [questID, charID])
  264. if results.size() > 0:
  265. results[0]["state"] = value
  266. return db.update_rows("quest", "char_id = %d AND quest_id = %d" % [charID, questID], results[0])
  267. var data : Dictionary = {
  268. "char_id": charID,
  269. "quest_id": questID,
  270. "state": value,
  271. }
  272. return db.insert_row("quest", data)
  273. func GetQuests(charID : int) -> Array[Dictionary]:
  274. return db.select_rows("quest", "char_id = %d" % [charID], ["*"])
  275. # Commons
  276. func Query(query : String) -> Array[Dictionary]:
  277. var data : Array[Dictionary] = []
  278. queryMutex.lock()
  279. if db.query(query):
  280. data = db.query_result
  281. queryMutex.unlock()
  282. return data
  283. func QueryBindings(query : String, params : Array) -> Array[Dictionary]:
  284. var data : Array[Dictionary] = []
  285. queryMutex.lock()
  286. if db.query_with_bindings(query, params):
  287. data = db.query_result
  288. queryMutex.unlock()
  289. return data
  290. #
  291. func _post_launch():
  292. var dbPath : String = SQLCommons.GetDBPath()
  293. if not FileSystem.FileExists(dbPath) and not SQLCommons.CopyDatabase(dbPath):
  294. return
  295. if LauncherCommons.isWeb:
  296. db = DummySQL.new()
  297. else:
  298. db = SQLite.new()
  299. db.path = dbPath
  300. db.verbosity_level = SQLite.VERBOSE if Launcher.Debug else SQLite.NORMAL
  301. if not db.open_db():
  302. assert(false, "Failed to open database: "+ db.error_message)
  303. else:
  304. if not Launcher.Debug:
  305. backups = SQLBackups.new()
  306. isInitialized = true
  307. func Destroy():
  308. if backups:
  309. backups.Stop()
  310. if db:
  311. db.close_db()
  312. func Wipe():
  313. db.delete_rows("account", "")
  314. db.delete_rows("attribute", "")
  315. db.delete_rows("bestiary", "")
  316. db.delete_rows("character", "")
  317. db.delete_rows("equipment", "")
  318. db.delete_rows("item", "")
  319. db.delete_rows("quest", "")
  320. db.delete_rows("skill", "")
  321. db.delete_rows("sqlite_sequence", "")
  322. db.delete_rows("stat", "")
  323. db.delete_rows("trait", "")