health.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. package health
  2. import (
  3. "context"
  4. "fmt"
  5. // "log"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/healthtime"
  10. "wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/repairtime"
  11. // "wartank/internal/components/sound"
  12. "wartank/pkg/components/safebool"
  13. "wartank/pkg/types"
  14. )
  15. /*
  16. Контролирует состояние здоровья танка
  17. */
  18. // Health -- контроль здоровья танка
  19. type Health struct {
  20. types.IDivWarOn // FIXME:
  21. fnCancel func()
  22. temp *healthtime.HealthTime // Изменяемое здоровье танка
  23. full *healthtime.HealthTime // Полное здоровье танка
  24. isRepair *safebool.SafeBool // Необходимость восстановления
  25. repairTime *repairtime.RepairTime // Время до восстановления
  26. isEnd *safebool.SafeBool // Ссылка на признак конца сражения
  27. login string // Для поиска контрольных строк
  28. chTick chan int // Канал для ровной отправки тиков
  29. deltaOld int // Старая дельта потери здоровья
  30. countLow int
  31. ctxBattle context.Context // Конекст сражения
  32. }
  33. // NewHealth -- возвращает новый *Health
  34. func NewHealth(divwar types.IDivWarOn, isEnd *safebool.SafeBool, login string) (*Health, error) {
  35. { // Предусловия
  36. if divwar == nil {
  37. return nil, fmt.Errorf("NewHealth(): battle is nil")
  38. }
  39. if isEnd == nil {
  40. return nil, fmt.Errorf("NewHealth(): isEnd is nil")
  41. }
  42. if login == "" {
  43. return nil, fmt.Errorf("NewHealth(): login is empty")
  44. }
  45. }
  46. sf := &Health{
  47. IDivWarOn: divwar,
  48. fnCancel: divwar.CancelBattle,
  49. ctxBattle: divwar.Ctx(),
  50. temp: healthtime.NewHealthTime(),
  51. full: healthtime.NewHealthTime(),
  52. isRepair: safebool.NewSafeBool(),
  53. repairTime: repairtime.NewRepairTime(),
  54. isEnd: divwar.IsEnd(),
  55. login: login,
  56. chTick: make(chan int, 2),
  57. }
  58. go sf.makeTik()
  59. go sf.run()
  60. return sf, nil
  61. }
  62. // Отправляе ттики с заданным равным интервалом
  63. func (sf *Health) makeTik() {
  64. defer func() {
  65. sf.CancelBattle()
  66. close(sf.chTick)
  67. // log._rintf("Health.makeTick(): сражение завершёно\n")
  68. }()
  69. count := 0
  70. repairTime := 0
  71. for {
  72. select {
  73. case <-sf.ctxBattle.Done():
  74. return
  75. default:
  76. if sf.IsDeath() {
  77. return
  78. }
  79. if sf.repairTime.Get() == repairTime {
  80. count++
  81. } else {
  82. repairTime = sf.repairTime.Get()
  83. count = 0
  84. }
  85. if count > 90 {
  86. return
  87. }
  88. }
  89. sf.chTick <- 1
  90. time.Sleep(time.Second * 1)
  91. sf.repairTime.Dec()
  92. }
  93. }
  94. // Главный цикл обработки здоровья в сражении
  95. func (sf *Health) run() {
  96. for {
  97. select {
  98. case <-sf.ctxBattle.Done():
  99. sf.isEnd.Set()
  100. return
  101. case <-sf.chTick:
  102. if err := sf.findHealth(); err != nil { // Найти свой здоровье
  103. // log._rintf("ERRO Health.run(): при попытке найти здоровье, err=\n\t%v\n", err)
  104. }
  105. sf.findRepairTime()
  106. if sf.Masking().Get() {
  107. if sf.isRepair.Get() {
  108. go sf.repair()
  109. }
  110. continue
  111. }
  112. if sf.isRepair.Get() {
  113. go sf.repair()
  114. }
  115. }
  116. }
  117. }
  118. // Full -- возвращает объект полного здоровья танка
  119. func (sf *Health) Full() int {
  120. return sf.full.Get()
  121. }
  122. // IsDeath -- возвращает признак мертвичины танка
  123. func (sf *Health) IsDeath() bool {
  124. if sf.isEnd.Get() {
  125. sf.fnCancel()
  126. return true
  127. }
  128. lstBattle := sf.GetLst()
  129. for _, strOut := range lstBattle {
  130. if strings.Contains(strOut, `>Ваш танк подбит.`) {
  131. // log._rintf("INFO Health.repair(): танк подбит\n")
  132. sf.temp.Set(0)
  133. sf.isEnd.Set()
  134. sf.CancelBattle()
  135. return true
  136. }
  137. }
  138. return sf.isEnd.Get()
  139. }
  140. // Ищет время восстановления ремки
  141. func (sf *Health) findRepairTime() {
  142. defer func() {
  143. if sf.repairTime.IsReady() {
  144. return
  145. }
  146. if sf.repairTime.IsChange() {
  147. // log._rintf("INFO Health.findRepair(): до ремки=%v\n", sf.repairTime.Get())
  148. }
  149. }()
  150. if sf.repairTime.IsReady() {
  151. return
  152. }
  153. var (
  154. strOut string
  155. lstBattle = sf.GetLst()
  156. isFind bool
  157. ind int
  158. )
  159. // <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
  160. //
  161. for ind, strOut = range lstBattle {
  162. if !strings.Contains(strOut, `ILinkListener-currentControl-repairLink`) {
  163. continue
  164. }
  165. if strings.Contains(strOut, ` секунд</span></span></a>`) {
  166. isFind = true
  167. break
  168. }
  169. }
  170. if !isFind {
  171. return
  172. }
  173. strOut = lstBattle[ind]
  174. // <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
  175. lstTime := strings.Split(strOut, `ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>`)
  176. if len(lstTime) < 2 {
  177. // log._rintf("ERRO Health.findRepair(): при попытке получить ссылку на ремонт, strOut=\n%v\n", strOut)
  178. sf.isEnd.Set()
  179. sf.CancelBattle()
  180. return
  181. }
  182. strTime := lstTime[1]
  183. lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
  184. strTime = lstTime[0]
  185. if err := sf.repairTime.Set(strTime); err != nil {
  186. // log._rintf("ERRO Health.findRepair(): при установке времени восстановления ремки, err=\n\t%v\n", err)
  187. }
  188. }
  189. // Восстановливает здоровье (~)
  190. func (sf *Health) repair() {
  191. var (
  192. strOut string
  193. lstBattleOn = sf.GetLst()
  194. isFindRepair bool
  195. ind int
  196. )
  197. // <span>Ремкомплект</span>
  198. // <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
  199. for ind, strOut = range lstBattleOn {
  200. if strings.Contains(strOut, `<span>Ремкомплект</span>`) {
  201. isFindRepair = true
  202. break
  203. }
  204. }
  205. if !isFindRepair {
  206. return
  207. }
  208. strOut = lstBattleOn[ind]
  209. // <a href="pve?6-26.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
  210. lstLink := strings.Split(strOut, `<a href="`)
  211. strLink := lstLink[1]
  212. lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Ремкомплект</span></span></a>`)
  213. strLink = "http://wartank.ru/" + lstLink[0]
  214. lstBattleOn, err := sf.Net().Get(strLink)
  215. if err != nil {
  216. // log._rintf("ERRO Health.repair(): при выполнении GET-команды ремонта, err=\n\t%v\n", err)
  217. sf.isEnd.Set()
  218. sf.CancelBattle()
  219. return
  220. }
  221. if err = sf.Update(lstBattleOn); err != nil {
  222. // log._rintf("ERRO Health.repair(): при обновлении lstBattle, err=\n\t%v\n", err)
  223. sf.isEnd.Set()
  224. sf.CancelBattle()
  225. return
  226. }
  227. // sound.Repair()
  228. // log._rintf("INFO Health.repair(): здоровье восстановлено\n")
  229. }
  230. // Ищет своё здоровье (~)
  231. func (sf *Health) findHealth() error {
  232. var (
  233. ind int
  234. strOut string
  235. isFind bool
  236. lstBattle = sf.GetLst()
  237. )
  238. if len(lstBattle) == 0 { // Принудительно обновим сражение
  239. if err := sf.Net().UpdateLst("Битва дивизий+ здоровье"); err != nil {
  240. sf.isEnd.Set()
  241. sf.fnCancel()
  242. return fmt.Errorf("Health.findHealth(): после принудительного обновления lsBattleOn, err=\n\t%w", err)
  243. }
  244. }
  245. for ind, strOut = range lstBattle {
  246. if strings.Contains(strOut, `alt="`+sf.login+`"`) {
  247. isFind = true
  248. break
  249. }
  250. }
  251. if !isFind { // Свой танк не найден
  252. sf.isEnd.Set()
  253. sf.fnCancel()
  254. return fmt.Errorf("Health.findHealth(): своё здоровье не найдено")
  255. }
  256. // Свой танк найден, ищем здоровье
  257. ind += 11
  258. strOut = lstBattle[ind]
  259. lstHealth := strings.Split(strOut, `<div class="value-block lh1"><span><span>`)
  260. strHealth := lstHealth[1]
  261. lstHealth = strings.Split(strHealth, `</span></span></div>`)
  262. strHealth = lstHealth[0]
  263. iHealth, err := strconv.Atoi(strHealth)
  264. if err != nil {
  265. sf.isEnd.Set()
  266. sf.CancelBattle()
  267. return fmt.Errorf("Health.findHealth(): здоровье(%v) не число, err=%w", strHealth, err)
  268. }
  269. sf.setHealth(iHealth)
  270. return nil
  271. }
  272. // setHealth -- устанавливает текущее здоровье
  273. func (sf *Health) setHealth(val int) {
  274. if val < 0 {
  275. // log._rintf("WARN Health.setHealth(): кривое значение здоровья танка(%v)\n", val)
  276. val = 0
  277. }
  278. if val > sf.full.Get() {
  279. // log._rintf("WARN Health.setHealth(): кривое текущее здоровье, %v/%v\n", val, sf.full.Get())
  280. sf.full.Set(val)
  281. sf.temp.Set(val)
  282. sf.deltaOld = 0
  283. sf.Masking().Reset()
  284. sf.isRepair.Reset()
  285. return
  286. }
  287. delta := sf.temp.Get() - val
  288. if delta > 0 { // Дельта будет больше нуля, если только
  289. if delta != sf.deltaOld {
  290. // log._rintf("INFO Health.setHealth(): потеря здоровья=%v/%v\n", -delta, val)
  291. sf.deltaOld = delta
  292. sf.temp.Set(val)
  293. }
  294. }
  295. switch {
  296. case sf.isEnd.Get():
  297. sf.temp.Set(0)
  298. sf.isEnd.Set()
  299. sf.CancelBattle()
  300. return
  301. case val == 0:
  302. sf.temp.Set(0)
  303. sf.isEnd.Set()
  304. sf.CancelBattle()
  305. return
  306. case val <= 500: // Запретить стрельбу
  307. sf.Masking().Set() // Установить запрет стрельбы пока слабое здоровье
  308. sf.isRepair.Set()
  309. // log._rintf("WARN Health.setHealth(): низкий уровень здоровья(%v)\n", val)
  310. sf.Manevr()
  311. case val > 500: // Разрешить стрельбы
  312. sf.Masking().Reset()
  313. sf.isRepair.Reset()
  314. if delta > sf.full.Get()*4/10 { // Проверить на критичность падения здоровья на 40%
  315. // log._rintf("WARN Health.setHealth(): большая разовая потеря здоровья(%v)\n", delta)
  316. sf.Manevr()
  317. sf.isRepair.Set()
  318. return
  319. }
  320. }
  321. isMask := sf.Masking().Get()
  322. switch isMask {
  323. case true:
  324. sf.countLow++
  325. if sf.countLow >= 200 {
  326. sf.isEnd.Set()
  327. sf.CancelBattle()
  328. return
  329. }
  330. default:
  331. sf.countLow = 0
  332. }
  333. if val == sf.full.Get() {
  334. sf.temp.Set(val)
  335. sf.isRepair.Reset()
  336. sf.Masking().Reset()
  337. sf.countLow = 0
  338. }
  339. }