MOLinePKViewModel.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. //
  2. // MOLinePKViewModel.swift
  3. // MiMoLive
  4. //
  5. // Created by OneeChan on 2025/9/23.
  6. //
  7. import Foundation
  8. extension MOLiveManager {
  9. static var curPkViewModel: MOLinePKViewModel? {
  10. curLive?.lineViewModel?.curPKViewModel
  11. }
  12. }
  13. @objc
  14. protocol MOLinePKViewModelDelegate {
  15. @objc optional func onLinePkStateChanged(viewModel: MOLinePKViewModel, state: LinePKState)
  16. @objc optional func onLinePkCampInfoChanged(viewModel: MOLinePKViewModel)
  17. @objc optional func onLinePkResult(viewModel: MOLinePKViewModel, result: LinePkResult)
  18. @objc optional func onLinePkSurrender(viewModel: MOLinePKViewModel)
  19. }
  20. @objcMembers
  21. class MOLinePKViewModel: NSObject {
  22. let pkMatchId: String
  23. private var pkUpdateTime: TimeInterval = 0
  24. private var pkRemain: Int = 0
  25. var curPkRemain: Int {
  26. max(pkRemain - Int((Date().timeIntervalSince1970 - pkUpdateTime)), 0)
  27. }
  28. private var pkPunishDuration: Int = 0
  29. var curPunishRemain: Int {
  30. if curState == .punishing {
  31. max(pkPunishDuration - Int((Date().timeIntervalSince1970 - pkUpdateTime)), 0)
  32. } else {
  33. 0
  34. }
  35. }
  36. private var camps: [MOLinePkCampInfo] = []
  37. private var campMap: [String: MOLinePkCampInfo] = [:] // camps 的 map 化
  38. var curOwnerCampInfo: MOLinePkCampInfo? {
  39. if let lineOwnerInfo = MOLiveManager.curLive?.lineViewModel?.curOwnerInfo,
  40. !lineOwnerInfo.pkCampCode.isEmpty,
  41. let info = campMap[lineOwnerInfo.pkCampCode] {
  42. return info
  43. }
  44. return camps.first(where: { $0.lastIsMyCamp })
  45. }
  46. var curPeerCampInfo: MOLinePkCampInfo? {
  47. if let lineOwnerInfo = MOLiveManager.curLive?.lineViewModel?.curOwnerInfo,
  48. !lineOwnerInfo.pkCampCode.isEmpty,
  49. let info = camps.first(
  50. where: { $0.campCode != lineOwnerInfo.pkCampCode
  51. }) {
  52. return info
  53. }
  54. return camps.first { !$0.lastIsMyCamp }
  55. }
  56. private(set) var curState: LinePKState = .none {
  57. didSet {
  58. if oldValue != curState {
  59. notifyLinePkStateChanged()
  60. if oldValue == .pking {
  61. notifyLinePkResult()
  62. }
  63. if curState == .punishing {
  64. startPunishTimeCountDown()
  65. } else if curState == .draw {
  66. startDrawTimeCountDown()
  67. }
  68. }
  69. }
  70. }
  71. private var delayTaskUUID: String?
  72. init(pkMatchId: String) {
  73. self.pkMatchId = pkMatchId
  74. super.init()
  75. }
  76. deinit {
  77. clear()
  78. }
  79. func clear() {
  80. guard curState != .none else { return }
  81. curState = .none
  82. MODelayTask.cancel(key: delayTaskUUID)
  83. }
  84. // roomInfo
  85. func updateInfo(roomInfo: MOLivePkLinkRoomInfoVo) {
  86. pkUpdateTime = roomInfo.now
  87. pkPunishDuration = roomInfo.pkPunishDuration
  88. MODelayTask.cancel(key: delayTaskUUID)
  89. if !roomInfo.camps.isEmpty {
  90. let campCodes = roomInfo.camps.map { $0.campCode }
  91. self.camps.removeAll { !campCodes.contains($0.campCode) } // 移除已不在列表的阵营信息
  92. roomInfo.camps.forEach { info in
  93. if let oldInfo = self.camps.first(where: { $0.campCode == info.campCode }) {
  94. oldInfo.updateBy(info: info)
  95. } else {
  96. let newCamp = MOLinePkCampInfo()
  97. newCamp.updateBy(info: info)
  98. self.camps.append(newCamp)
  99. }
  100. }
  101. campMap = self.camps.reduce(into: [String: MOLinePkCampInfo](), { partialResult, info in
  102. partialResult[info.campCode] = info
  103. })
  104. } else if let lastPkVo = roomInfo.lastPkVo {
  105. let campCodes = lastPkVo.camps.map { $0.campCode }
  106. self.camps.removeAll { !campCodes.contains($0.campCode) } // 移除已不在列表的阵营信息
  107. lastPkVo.camps.forEach { info in
  108. if let oldInfo = self.camps.first(where: { $0.campCode == info.campCode }) {
  109. oldInfo.updateBy(lastInfo: info)
  110. } else {
  111. let newCamp = MOLinePkCampInfo()
  112. newCamp.updateBy(lastInfo: info)
  113. self.camps.append(newCamp)
  114. }
  115. }
  116. campMap = self.camps.reduce(into: [String: MOLinePkCampInfo](), { partialResult, info in
  117. partialResult[info.campCode] = info
  118. })
  119. }
  120. notifyLinePkCampInfoChanged()
  121. let oldState = self.curState
  122. pkRemain = (roomInfo.pkEndTime - roomInfo.serviceTime) / 1000
  123. if curPkRemain > 0 {
  124. curState = .pking
  125. startPkTimeCountDown()
  126. } else if roomInfo.pkPunishDuration > 0 {
  127. curState = .punishing
  128. startPunishTimeCountDown()
  129. } else if curOwnerCampInfo?.pkStatus == .draw {
  130. curState = .draw
  131. startDrawTimeCountDown()
  132. } else {
  133. // 不在 PK 状态
  134. curState = .none
  135. return
  136. }
  137. if oldState != curState,
  138. roomInfo.lastPkVo != nil {
  139. notifyLinePkResult()
  140. }
  141. }
  142. // 38 推送
  143. func updateInfo(statusInfo: MORtmPkV2Status) {
  144. pkUpdateTime = statusInfo.now
  145. pkPunishDuration = statusInfo.pkPunishDuration
  146. MODelayTask.cancel(key: delayTaskUUID)
  147. let campCodes = statusInfo.pkCampInfos.map { $0.campCode }
  148. camps.removeAll { !campCodes.contains($0.campCode) } // 移除已不在列表的阵营信息
  149. statusInfo.pkCampInfos.forEach { info in
  150. if let oldInfo = camps.first(where: { $0.campCode == info.campCode }) {
  151. oldInfo.updateBy(linkInfo: info)
  152. } else {
  153. let newCamp = MOLinePkCampInfo()
  154. newCamp.updateBy(linkInfo: info)
  155. camps.append(newCamp)
  156. }
  157. }
  158. campMap = camps.reduce(into: [String: MOLinePkCampInfo](), { partialResult, info in
  159. partialResult[info.campCode] = info
  160. })
  161. notifyLinePkCampInfoChanged()
  162. if statusInfo.pkStatus == 1 {
  163. curState = .pking
  164. startPkTimeCountDown()
  165. } else if statusInfo.pkPunishDuration > 0 {
  166. curState = .punishing
  167. startPunishTimeCountDown()
  168. } else if curOwnerCampInfo?.pkStatus == .draw {
  169. curState = .draw
  170. startDrawTimeCountDown()
  171. } else {
  172. curState = .none
  173. }
  174. }
  175. static func loadContributeList(next: String, _ campCode: String, _ handler: @escaping (MOLivePkContributeVO?, Bool) -> Void) {
  176. MOHttpManager.shared().getLinePkContributeList(campCode, next, 50) { list, error in
  177. if error != nil {
  178. showNetError(err: error)
  179. return
  180. }
  181. handler(list, list?.list.count ?? 0 >= 50)
  182. }
  183. }
  184. func surrender(_ handler: @escaping (Bool) -> Void) {
  185. guard let lineViewModel = MOLiveManager.curLineViewModel else {
  186. handler(false)
  187. return
  188. }
  189. MOHttpManager.shared().surrenderPk(lineViewModel.curLineRoomId) { [weak self] error in
  190. guard error == nil else {
  191. showNetError(err: error)
  192. handler(false)
  193. return
  194. }
  195. handler(true)
  196. guard let self else { return }
  197. self.notifyLinePkSurrender()
  198. }
  199. }
  200. func handleTopUsersUpdate(_ info: MORtmPkV2StatusExpand) {
  201. info.camps.forEach {
  202. let camp = campMap[$0.campCode]
  203. camp?.updateBy(expandInfo: $0)
  204. }
  205. notifyLinePkCampInfoChanged()
  206. }
  207. private func settlePk() {
  208. guard let lineRoomId = MOLiveManager.curLineViewModel?.curLineRoomId else {
  209. return
  210. }
  211. MOHttpManager.shared().settlePk(lineRoomId, pkMatchId) { [weak self] info, error in
  212. if error != nil {
  213. showNetError(err: error)
  214. return
  215. }
  216. guard let self else { return }
  217. guard let info else {
  218. // 结算结果异常,直接结束 PK
  219. curState = .none
  220. return
  221. }
  222. self.updateInfo(statusInfo: info)
  223. }
  224. }
  225. }
  226. extension MOLinePKViewModel {
  227. private func startPkTimeCountDown() {
  228. guard MOLiveManager.curLive?.isOwner == true else { return }
  229. MODelayTask.cancel(key: delayTaskUUID)
  230. delayTaskUUID = MODelayTask.perform(delay: TimeInterval(curPkRemain)) { [weak self] in
  231. guard let self else { return }
  232. MODelayTask.perform(delay: 1) { [weak self] in
  233. guard let self else { return }
  234. self.settlePk()
  235. }
  236. }
  237. }
  238. private func startPunishTimeCountDown() {
  239. MODelayTask.cancel(key: delayTaskUUID)
  240. delayTaskUUID = MODelayTask.perform(delay: TimeInterval(pkPunishDuration)) { [weak self] in
  241. guard let self else { return }
  242. self.curState = .none
  243. }
  244. }
  245. private func startDrawTimeCountDown() {
  246. MODelayTask.cancel(key: delayTaskUUID)
  247. delayTaskUUID = MODelayTask.perform(delay: 10) { [weak self] in
  248. guard let self else { return }
  249. self.curState = .none
  250. }
  251. }
  252. }
  253. extension MOLinePKViewModel {
  254. private func notifyLinePkStateChanged() {
  255. MOEventDeliver.notifyEvent { $0.onLinePkStateChanged?(viewModel: self, state: curState) }
  256. }
  257. private func notifyLinePkCampInfoChanged() {
  258. MOEventDeliver.notifyEvent { $0.onLinePkCampInfoChanged?(viewModel: self) }
  259. }
  260. private func notifyLinePkResult() {
  261. guard let result = curOwnerCampInfo?.pkStatus else { return }
  262. MOEventDeliver.notifyEvent { $0.onLinePkResult?(viewModel: self, result: result) }
  263. }
  264. private func notifyLinePkSurrender() {
  265. MOEventDeliver.notifyEvent { $0.onLinePkSurrender?(viewModel: self) }
  266. }
  267. }