Character.gd 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. extends Control
  2. #
  3. @onready var characterName : PanelContainer = $PlayerName
  4. @onready var characterNameLineEdit : LineEdit = $PlayerName/MarginContainer/VBoxContainer/Entry
  5. @onready var characterNameDisplay : Label = $PlayerName/MarginContainer/VBoxContainer/Name
  6. @onready var display : HBoxContainer = $Display
  7. @onready var traitsPanel : PanelContainer = $Display/Traits
  8. @onready var attributesPanel : PanelContainer = $Display/Attributes
  9. @onready var statsPanel : PanelContainer = $Display/Stats
  10. var isCharacterCreatorEnabled : bool = false
  11. var charactersInfo : Array[Dictionary] = []
  12. var charactersNode : Array[Entity] = []
  13. var currentCharacterID : int = ActorCommons.InvalidCharacterSlot
  14. #
  15. func FillWarningLabel(err : NetworkCommons.CharacterError):
  16. if isCharacterCreatorEnabled:
  17. FSM.EnterState(FSM.States.CHAR_SCREEN)
  18. if err == NetworkCommons.CharacterError.ERR_OK:
  19. EnableCharacterCreator(false)
  20. else:
  21. EnableCharacterCreator(true)
  22. var warn : String = ""
  23. match err:
  24. NetworkCommons.CharacterError.ERR_OK:
  25. warn = ""
  26. EnableCharacterCreator(false)
  27. NetworkCommons.CharacterError.ERR_ALREADY_LOGGED_IN:
  28. warn = "Character is already logged in."
  29. NetworkCommons.CharacterError.ERR_TIMEOUT:
  30. warn = "Could not connect to the server (Error %d)." % err
  31. NetworkCommons.CharacterError.ERR_MISSING_PARAMS:
  32. warn = "Some character information are missing."
  33. NetworkCommons.CharacterError.ERR_NAME_AVAILABLE:
  34. warn = "Character name not available."
  35. NetworkCommons.CharacterError.ERR_NAME_VALID:
  36. warn = "Name should should only include alpha-numeric characters and symbols."
  37. NetworkCommons.CharacterError.ERR_NAME_SIZE:
  38. warn = "Name length should be inbetween %d and %d character long." % [NetworkCommons.PlayerNameMinSize, NetworkCommons.PlayerNameMaxSize]
  39. NetworkCommons.CharacterError.ERR_EMPTY_ACCOUNT:
  40. EnableCharacterCreator(true)
  41. _:
  42. warn = "Unknown character issue (Error %d).\nPlease contact us via our [url=%s][color=#%s]Discord server[/color][/url].\n" % [err, LauncherCommons.SocialLink, UICommons.DarkTextColor]
  43. if not warn.is_empty():
  44. warn = "[color=#%s]%s[/color]" % [UICommons.WarnTextColor.to_html(false), warn]
  45. Launcher.GUI.notificationLabel.AddNotification(warn)
  46. func FillMissingCharacterInfo(info : Dictionary):
  47. Util.DicCheckOrAdd(info, "nickname", "")
  48. Util.DicCheckOrAdd(info, "last_timestamp", ActorCommons.MaxStatValue)
  49. Util.DicCheckOrAdd(info, "created_timestamp", ActorCommons.MaxStatValue)
  50. Util.DicCheckOrAdd(info, "level", 1)
  51. Util.DicCheckOrAdd(info, "pos_map", LauncherCommons.DefaultStartMapID)
  52. Util.DicCheckOrAdd(info, "hairstyle", DB.UnknownHash)
  53. Util.DicCheckOrAdd(info, "haircolor", DB.UnknownHash)
  54. Util.DicCheckOrAdd(info, "gender", ActorCommons.Gender.MALE)
  55. Util.DicCheckOrAdd(info, "race", DB.UnknownHash)
  56. Util.DicCheckOrAdd(info, "skintone", DB.UnknownHash)
  57. func HasSlot(slotID : int) -> bool:
  58. return slotID >= 0 and slotID <= ActorCommons.MaxCharacterCount
  59. func GetDefaultSlot(slotID : int) -> int:
  60. return slotID if HasSlot(slotID) else 0
  61. func NextAvailableSlot() -> int:
  62. var availableSlot : int = ActorCommons.InvalidCharacterSlot
  63. for charID in ActorCommons.MaxCharacterCount:
  64. if charactersInfo[charID].is_empty():
  65. availableSlot = charID
  66. break
  67. return availableSlot
  68. func MoreRecentThan(firstChar : Dictionary, secondChar : Dictionary) -> bool:
  69. var firstVal : int = firstChar["created_timestamp"] if firstChar["last_timestamp"] == ActorCommons.MaxStatValue else firstChar["last_timestamp"]
  70. var secondVal : int = secondChar["created_timestamp"] if secondChar["last_timestamp"] == ActorCommons.MaxStatValue else secondChar["last_timestamp"]
  71. return firstVal > secondVal
  72. func AddCharacter(info : Dictionary, equipment : Dictionary, slotID : int = ActorCommons.InvalidCharacterSlot):
  73. FillMissingCharacterInfo(info)
  74. var availableSlot : int = NextAvailableSlot() if slotID == ActorCommons.InvalidCharacterSlot else slotID
  75. if availableSlot == ActorCommons.InvalidCharacterSlot:
  76. assert(false, "No free available placement")
  77. return
  78. var entityData : EntityData = DB.EntitiesDB.get(DB.PlayerHash, null)
  79. if not entityData:
  80. assert(false, "Could not retrieve the default entity database entry")
  81. return
  82. var entity : Entity = Instantiate.CreateEntity(ActorCommons.Type.PLAYER, entityData, info["nickname"], false)
  83. if not entity:
  84. assert(false, "Could not create character preview")
  85. return
  86. entity.inventory.ImportEquipment(equipment)
  87. entity.stat.hairstyle = info["hairstyle"]
  88. entity.stat.haircolor = info["haircolor"]
  89. entity.stat.gender = info["gender"]
  90. entity.stat.race = info["race"]
  91. entity.stat.skintone = info["skintone"]
  92. RemoveCharacter(availableSlot)
  93. charactersInfo[availableSlot] = info
  94. charactersNode[availableSlot] = entity
  95. Launcher.Map.AddChild(entity)
  96. var randDir : Vector2 = Vector2(randf_range(-0.8, 0.8), 1.0)
  97. var randState : ActorCommons.State = ActorCommons.State.IDLE if isCharacterCreatorEnabled or randi() % 2 == 1 else ActorCommons.State.SIT
  98. entity.Update(Vector2.ZERO, ActorCommons.CharacterScreenLocations[availableSlot], randDir, randState, -1, true)
  99. if not HasSlot(currentCharacterID) or MoreRecentThan(info, charactersInfo[currentCharacterID]) or info["nickname"].is_empty():
  100. UpdateSelectedCharacter(info, availableSlot)
  101. RefreshCharacterList()
  102. func RemoveCharacter(slotID : int):
  103. if HasSlot(slotID):
  104. if charactersNode[slotID] != null:
  105. Launcher.Map.RemoveChild(charactersNode[slotID])
  106. charactersNode[slotID] = null
  107. if not charactersInfo[slotID].is_empty():
  108. charactersInfo[slotID] = {}
  109. if currentCharacterID == slotID:
  110. var mostRecentTimestamp : int = -1
  111. var mostRecentCharacterID : int = ActorCommons.InvalidCharacterSlot
  112. for characterID in ActorCommons.MaxCharacterCount:
  113. if not charactersInfo[characterID].is_empty():
  114. if mostRecentCharacterID == ActorCommons.InvalidCharacterSlot:
  115. mostRecentCharacterID = characterID
  116. elif "last_timestamp" in charactersInfo[characterID] and charactersInfo[characterID]["last_timestamp"] != null and mostRecentTimestamp < charactersInfo[characterID]["last_timestamp"]:
  117. mostRecentTimestamp = charactersInfo[characterID]["last_timestamp"]
  118. mostRecentCharacterID = characterID
  119. UpdateSelectedCharacter(charactersInfo[mostRecentCharacterID] if HasSlot(mostRecentCharacterID) else {}, mostRecentCharacterID)
  120. RefreshCharacterList()
  121. func RandomizeCharacter():
  122. attributesPanel.Randomize()
  123. traitsPanel.Randomize()
  124. func RefreshCharacterList():
  125. var characterCount : int = 0
  126. for character in charactersNode:
  127. if character != null:
  128. characterCount += 1
  129. statsPanel.selection.set_visible(characterCount >= 2)
  130. func CreateCharacter():
  131. if isCharacterCreatorEnabled:
  132. var nickname : String = characterNameLineEdit.get_text()
  133. var err : NetworkCommons.CharacterError = NetworkCommons.CheckCharacterInformation(nickname)
  134. if err != NetworkCommons.CharacterError.ERR_OK:
  135. FillWarningLabel(err)
  136. else:
  137. if Network.CreateCharacter(nickname, traitsPanel.GetValues(), attributesPanel.GetValues()):
  138. FSM.EnterState(FSM.States.CHAR_PROGRESS)
  139. func SelectCharacter():
  140. if Network.ConnectCharacter(characterNameDisplay.get_text()):
  141. FSM.EnterState(FSM.States.CHAR_PROGRESS)
  142. func DeleteCharacter():
  143. UICommons.MessageBox("""Are you sure you want to delete this character?
  144. This action is permanent, and you will not be able to recover or use this character again.
  145. """,
  146. Callback.Empty, "Cancel",
  147. ConfirmDeleteCharacter, "Confirm")
  148. func ConfirmDeleteCharacter():
  149. if Network.DeleteCharacter(characterNameDisplay.get_text()):
  150. RemoveCharacter(currentCharacterID)
  151. func UpdateSelectedCharacter(info : Dictionary, slotID : int):
  152. var displayInformation : bool = not info.is_empty() or isCharacterCreatorEnabled
  153. characterName.set_visible(displayInformation)
  154. display.set_visible(displayInformation)
  155. if not isCharacterCreatorEnabled and not info.is_empty():
  156. statsPanel.SetInfo(info)
  157. characterNameDisplay.set_text(info["nickname"])
  158. if HasSlot(slotID) and charactersNode[slotID] != null:
  159. if HasSlot(currentCharacterID) and charactersNode[currentCharacterID]:
  160. EnableCharacterSelectionFx(false, currentCharacterID)
  161. EnableCharacterSelectionFx(true, slotID)
  162. if FSM.IsCharacterState():
  163. Launcher.Camera.EnableSceneCamera(ActorCommons.CharacterScreenLocations[GetDefaultSlot(slotID)])
  164. currentCharacterID = slotID
  165. func EnableCharacterSelectionFx(isEnabled : bool, slotID : int):
  166. if charactersNode[slotID].interactive:
  167. charactersNode[slotID].interactive.DisplayTarget(ActorCommons.Target.ALLY if isEnabled else ActorCommons.Target.NONE)
  168. else:
  169. Callback.PlugCallback(charactersNode[slotID].ready, EnableCharacterSelectionFx, [isEnabled, slotID])
  170. func ChangeSelectedCharacter(changeClockwise : bool = true):
  171. if not HasSlot(currentCharacterID):
  172. currentCharacterID = 0
  173. var operationValue : int = 1 if changeClockwise else -1
  174. for slotID in range(1, ActorCommons.MaxCharacterCount):
  175. var lookupID : int = (currentCharacterID + (slotID * operationValue) + ActorCommons.MaxCharacterCount) % ActorCommons.MaxCharacterCount
  176. var info : Dictionary = charactersInfo[lookupID]
  177. if not info.is_empty():
  178. UpdateSelectedCharacter(info, lookupID)
  179. break
  180. func EnableCharacterCreator(enable : bool):
  181. if isCharacterCreatorEnabled != enable:
  182. isCharacterCreatorEnabled = enable
  183. if enable:
  184. if Launcher.Camera.IsZooming(ActorCommons.CameraZoomDefault):
  185. Launcher.Camera.ZoomAt(ActorCommons.CameraZoomDouble)
  186. AddCharacter(traitsPanel.GetValues(), {}, ActorCommons.MaxCharacterCount)
  187. else:
  188. if Launcher.Camera.IsZooming(ActorCommons.CameraZoomDouble):
  189. Launcher.Camera.ZoomAt(ActorCommons.CameraZoomDefault)
  190. if not enable and HasSlot(currentCharacterID) and charactersNode[currentCharacterID] != null:
  191. RemoveCharacter(ActorCommons.MaxCharacterCount)
  192. statsPanel.set_visible(not enable and visible)
  193. characterNameDisplay.set_visible(not enable and visible)
  194. characterNameLineEdit.set_visible(enable)
  195. traitsPanel.set_visible(enable)
  196. attributesPanel.set_visible(enable)
  197. if enable:
  198. characterNameLineEdit.grab_focus()
  199. if Launcher.GUI.buttonBoxes:
  200. Launcher.GUI.buttonBoxes.ClearAll()
  201. if enable:
  202. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.PRIMARY, "Create", CreateCharacter)
  203. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.SECONDARY, "Randomize", RandomizeCharacter)
  204. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.CANCEL, "Cancel", EnableCharacterCreator.bind(false))
  205. else:
  206. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.PRIMARY, "Select", SelectCharacter)
  207. if NextAvailableSlot() != ActorCommons.InvalidCharacterSlot:
  208. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.SECONDARY, "New Player", EnableCharacterCreator.bind(true))
  209. if currentCharacterID != ActorCommons.InvalidCharacterSlot:
  210. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.TERTIARY, "Delete Player", DeleteCharacter.bind())
  211. Launcher.GUI.buttonBoxes.Bind(UICommons.ButtonBox.CANCEL, "Cancel", Leave)
  212. func UpdateCharacterCreatorBody():
  213. if isCharacterCreatorEnabled and charactersNode[ActorCommons.MaxCharacterCount]:
  214. var races : Array = DB.RacesDB.keys()
  215. var race : RaceData = DB.GetRace(races[traitsPanel.raceValue])
  216. var skins : Dictionary = race._skins if race else {}
  217. var skinsKeys : Array = skins.keys()
  218. charactersNode[ActorCommons.MaxCharacterCount].stat.race = races[traitsPanel.raceValue]
  219. charactersNode[ActorCommons.MaxCharacterCount].stat.skintone = skinsKeys[traitsPanel.skintoneValue]
  220. charactersNode[ActorCommons.MaxCharacterCount].stat.gender = traitsPanel.genderValue
  221. if charactersNode[ActorCommons.MaxCharacterCount].visual:
  222. charactersNode[ActorCommons.MaxCharacterCount].visual.SetBody()
  223. charactersNode[ActorCommons.MaxCharacterCount].visual.SetFace()
  224. func UpdateCharacterCreatorHair():
  225. if isCharacterCreatorEnabled and charactersNode[ActorCommons.MaxCharacterCount]:
  226. var hairstyles : Array = DB.HairstylesDB.keys()
  227. var haircolors : Array = DB.PalettesDB[DB.Palette.HAIR].keys()
  228. charactersNode[ActorCommons.MaxCharacterCount].stat.hairstyle = hairstyles[traitsPanel.hairstyleValue]
  229. charactersNode[ActorCommons.MaxCharacterCount].stat.haircolor = haircolors[traitsPanel.haircolorValue]
  230. if charactersNode[ActorCommons.MaxCharacterCount].visual:
  231. charactersNode[ActorCommons.MaxCharacterCount].visual.SetHair()
  232. func RefreshOnce():
  233. if isCharacterCreatorEnabled:
  234. EnableCharacterCreator(isCharacterCreatorEnabled)
  235. return
  236. Clear()
  237. Launcher.Map.EmplaceMapNode(ActorCommons.CharacterScreenMapID)
  238. currentCharacterID = ActorCommons.InvalidCharacterSlot
  239. for slotID in charactersInfo.size():
  240. RemoveCharacter(slotID)
  241. EnableCharacterCreator(isCharacterCreatorEnabled)
  242. UpdateSelectedCharacter({}, currentCharacterID)
  243. Network.CharacterListing()
  244. func Clear():
  245. for slotID in charactersInfo.size():
  246. RemoveCharacter(slotID)
  247. Launcher.GUI.buttonBoxes.ClearAll()
  248. currentCharacterID = ActorCommons.InvalidCharacterSlot
  249. func Leave():
  250. FSM.EnterState(FSM.States.LOGIN_SCREEN)
  251. Network.DisconnectAccount()
  252. Clear()
  253. func Close():
  254. if isCharacterCreatorEnabled:
  255. EnableCharacterCreator(false)
  256. else:
  257. Leave()
  258. #
  259. func _ready():
  260. assert(ActorCommons.MaxCharacterCount + 1 == ActorCommons.CharacterScreenLocations.size(), "Character screen locations count mismatch with the max character count")
  261. charactersInfo.resize(ActorCommons.MaxCharacterCount + 1)
  262. charactersNode.resize(ActorCommons.MaxCharacterCount + 1)
  263. statsPanel.previousButton.pressed.connect(ChangeSelectedCharacter.bind(false))
  264. statsPanel.nextButton.pressed.connect(ChangeSelectedCharacter.bind(true))
  265. traitsPanel.bodyUpdate.connect(UpdateCharacterCreatorBody)
  266. traitsPanel.hairUpdate.connect(UpdateCharacterCreatorHair)
  267. FSM.enter_game.connect(Clear)
  268. func _physics_process(_delta: float):
  269. if currentCharacterID != ActorCommons.InvalidCharacterSlot:
  270. var move : Vector2 = Launcher.Action.GetMove()
  271. if move != Vector2.ZERO:
  272. charactersNode[currentCharacterID].entityOrientation = move
  273. func _on_visibility_changed():
  274. if not visible:
  275. if statsPanel:
  276. statsPanel.set_visible(false)
  277. if traitsPanel:
  278. traitsPanel.set_visible(false)
  279. if attributesPanel:
  280. attributesPanel.set_visible(false)
  281. func _on_text_focus_entered():
  282. if Launcher.Action:
  283. Launcher.Action.Enable(false)
  284. func _on_text_focus_exited():
  285. if Launcher.Action:
  286. Launcher.Action.Enable(true)