health.go 11 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/safe_bool"
  13. "wartank/pkg/types"
  14. )
  15. /*
  16. Контролирует состояние здоровья танка
  17. */
  18. // Health -- контроль здоровья танка
  19. type Health struct {
  20. types.ИДивизияВойнаДействие // FIXME:
  21. fnCancel func()
  22. temp *healthtime.HealthTime // Изменяемое здоровье танка
  23. full *healthtime.HealthTime // Полное здоровье танка
  24. isRepair *safe_bool.БезопБул // Необходимость восстановления
  25. repairTime *repairtime.RepairTime // Время до восстановления
  26. isEnd *safe_bool.БезопБул // Ссылка на признак конца сражения
  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.ИДивизияВойнаДействие, isEnd *safe_bool.БезопБул, 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. сам := &Health{
  47. ИДивизияВойнаДействие: divwar,
  48. fnCancel: divwar.CancelBattle,
  49. ctxBattle: divwar.Ctx(),
  50. temp: healthtime.NewHealthTime(),
  51. full: healthtime.NewHealthTime(),
  52. isRepair: safe_bool.НовБезопБул(),
  53. repairTime: repairtime.NewRepairTime(),
  54. isEnd: divwar.ЕслиКонец(),
  55. login: login,
  56. chTick: make(chan int, 2),
  57. }
  58. go сам.makeTik()
  59. go сам.run()
  60. return сам, nil
  61. }
  62. // Отправляе ттики с заданным равным интервалом
  63. func (сам *Health) makeTik() {
  64. defer func() {
  65. сам.CancelBattle()
  66. close(сам.chTick)
  67. // log._rintf("Health.makeTick(): сражение завершёно\n")
  68. }()
  69. count := 0
  70. repairTime := 0
  71. for {
  72. select {
  73. case <-сам.ctxBattle.Done():
  74. return
  75. default:
  76. if сам.IsDeath() {
  77. return
  78. }
  79. if сам.repairTime.Get() == repairTime {
  80. count++
  81. } else {
  82. repairTime = сам.repairTime.Get()
  83. count = 0
  84. }
  85. if count > 90 {
  86. return
  87. }
  88. }
  89. сам.chTick <- 1
  90. time.Sleep(time.Second * 1)
  91. сам.repairTime.Dec()
  92. }
  93. }
  94. // Главный цикл обработки здоровья в сражении
  95. func (сам *Health) run() {
  96. for {
  97. select {
  98. case <-сам.ctxBattle.Done():
  99. сам.isEnd.Уст()
  100. return
  101. case <-сам.chTick:
  102. if err := сам.findHealth(); err != nil { // Найти свой здоровье
  103. // log._rintf("ERRO Health.run(): при попытке найти здоровье, err=\n\t%v\n", err)
  104. }
  105. сам.findRepairTime()
  106. if сам.ВыстрелБлок().Получ() {
  107. if сам.isRepair.Получ() {
  108. go сам.repair()
  109. }
  110. continue
  111. }
  112. if сам.isRepair.Получ() {
  113. go сам.repair()
  114. }
  115. }
  116. }
  117. }
  118. // Full -- возвращает объект полного здоровья танка
  119. func (сам *Health) Full() int {
  120. return сам.full.Get()
  121. }
  122. // IsDeath -- возвращает признак мертвичины танка
  123. func (сам *Health) IsDeath() bool {
  124. if сам.isEnd.Получ() {
  125. сам.fnCancel()
  126. return true
  127. }
  128. lstBattle := сам.СписПолучить()
  129. for _, strOut := range lstBattle {
  130. if strings.Contains(strOut, `>Ваш танк подбит.`) {
  131. // log._rintf("INFO Health.repair(): танк подбит\n")
  132. сам.temp.Set(0)
  133. сам.isEnd.Уст()
  134. сам.CancelBattle()
  135. return true
  136. }
  137. }
  138. return сам.isEnd.Получ()
  139. }
  140. // Ищет время восстановления ремки
  141. func (сам *Health) findRepairTime() {
  142. defer func() {
  143. if сам.repairTime.IsReady() {
  144. return
  145. }
  146. if сам.repairTime.IsChange() {
  147. // log._rintf("INFO Health.findRepair(): до ремки=%v\n", сам.repairTime.Get())
  148. }
  149. }()
  150. if сам.repairTime.IsReady() {
  151. return
  152. }
  153. var (
  154. strOut string
  155. lstBattle = сам.СписПолучить()
  156. еслиНайдено 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. еслиНайдено = true
  167. break
  168. }
  169. }
  170. if !еслиНайдено {
  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. сам.isEnd.Уст()
  179. сам.CancelBattle()
  180. return
  181. }
  182. strTime := lstTime[1]
  183. lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
  184. strTime = lstTime[0]
  185. if err := сам.repairTime.Set(strTime); err != nil {
  186. // log._rintf("ERRO Health.findRepair(): при установке времени восстановления ремки, err=\n\t%v\n", err)
  187. }
  188. }
  189. // Восстановливает здоровье (~)
  190. func (сам *Health) repair() {
  191. var (
  192. strOut string
  193. lstBattleOn = сам.СписПолучить()
  194. еслиНайденоRepair 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. еслиНайденоRepair = true
  202. break
  203. }
  204. }
  205. if !еслиНайденоRepair {
  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 = "https://wartank.ru/" + lstLink[0]
  214. lstBattleOn, err := сам.Сеть().Get(strLink)
  215. if err != nil {
  216. // log._rintf("ERRO Health.repair(): при выполнении GET-команды ремонта, err=\n\t%v\n", err)
  217. сам.isEnd.Уст()
  218. сам.CancelBattle()
  219. return
  220. }
  221. if err = сам.СтрОбновить(lstBattleOn); err != nil {
  222. // log._rintf("ERRO Health.repair(): при обновлении lstBattle, err=\n\t%v\n", err)
  223. сам.isEnd.Уст()
  224. сам.CancelBattle()
  225. return
  226. }
  227. // sound.Repair()
  228. // log._rintf("INFO Health.repair(): здоровье восстановлено\n")
  229. }
  230. // Ищет своё здоровье (~)
  231. func (сам *Health) findHealth() error {
  232. var (
  233. ind int
  234. strOut string
  235. еслиНайдено bool
  236. lstBattle = сам.СписПолучить()
  237. )
  238. if len(lstBattle) == 0 { // Принудительно обновим сражение
  239. if err := сам.Сеть().Обновить(); err != nil {
  240. сам.isEnd.Уст()
  241. сам.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="`+сам.login+`"`) {
  247. еслиНайдено = true
  248. break
  249. }
  250. }
  251. if !еслиНайдено { // Свой танк не найден
  252. сам.isEnd.Уст()
  253. сам.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. сам.isEnd.Уст()
  266. сам.CancelBattle()
  267. return fmt.Errorf("Health.findHealth(): здоровье(%v) не число, err=%w", strHealth, err)
  268. }
  269. сам.setHealth(iHealth)
  270. return nil
  271. }
  272. // setHealth -- устанавливает текущее здоровье
  273. func (сам *Health) setHealth(val int) {
  274. if val < 0 {
  275. // log._rintf("WARN Health.setHealth(): кривое значение здоровья танка(%v)\n", val)
  276. val = 0
  277. }
  278. if val > сам.full.Get() {
  279. // log._rintf("WARN Health.setHealth(): кривое текущее здоровье, %v/%v\n", val, сам.full.Get())
  280. сам.full.Set(val)
  281. сам.temp.Set(val)
  282. сам.deltaOld = 0
  283. сам.ВыстрелБлок().Сброс()
  284. сам.isRepair.Сброс()
  285. return
  286. }
  287. delta := сам.temp.Get() - val
  288. if delta > 0 { // Дельта будет больше нуля, если только
  289. if delta != сам.deltaOld {
  290. // log._rintf("INFO Health.setHealth(): потеря здоровья=%v/%v\n", -delta, val)
  291. сам.deltaOld = delta
  292. сам.temp.Set(val)
  293. }
  294. }
  295. switch {
  296. case сам.isEnd.Получ():
  297. сам.temp.Set(0)
  298. сам.isEnd.Уст()
  299. сам.CancelBattle()
  300. return
  301. case val == 0:
  302. сам.temp.Set(0)
  303. сам.isEnd.Уст()
  304. сам.CancelBattle()
  305. return
  306. case val <= 500: // Запретить стрельбу
  307. сам.ВыстрелБлок().Уст() // Установить запрет стрельбы пока слабое здоровье
  308. сам.isRepair.Уст()
  309. // log._rintf("WARN Health.setHealth(): низкий уровень здоровья(%v)\n", val)
  310. сам.Манёвр()
  311. case val > 500: // Разрешить стрельбы
  312. сам.ВыстрелБлок().Сброс()
  313. сам.isRepair.Сброс()
  314. if delta > сам.full.Get()*4/10 { // Проверить на критичность падения здоровья на 40%
  315. // log._rintf("WARN Health.setHealth(): большая разовая потеря здоровья(%v)\n", delta)
  316. сам.Манёвр()
  317. сам.isRepair.Уст()
  318. return
  319. }
  320. }
  321. isMask := сам.ВыстрелБлок().Получ()
  322. switch isMask {
  323. case true:
  324. сам.countLow++
  325. if сам.countLow >= 200 {
  326. сам.isEnd.Уст()
  327. сам.CancelBattle()
  328. return
  329. }
  330. default:
  331. сам.countLow = 0
  332. }
  333. if val == сам.full.Get() {
  334. сам.temp.Set(val)
  335. сам.isRepair.Сброс()
  336. сам.ВыстрелБлок().Сброс()
  337. сам.countLow = 0
  338. }
  339. }