Bladeren bron

d05 Добавление апгрейда шахты

SVI 2 jaren geleden
bovenliggende
commit
ff4ae4b247
29 gewijzigde bestanden met toevoegingen van 1547 en 35 verwijderingen
  1. 1 3
      pkg/types/ibattle_action.go
  2. 5 0
      pkg/types/iis_shot.go
  3. 131 10
      server/serv_bots/warbot/angar/base/mine/mine.go
  4. 42 2
      server/serv_bots/warbot/angar/base/polygon/polygon.go
  5. 3 3
      server/serv_bots/warbot/angar/battle/battle.go
  6. 4 4
      server/serv_bots/warbot/angar/battle/battle_register/battle_register.go
  7. 5 5
      server/serv_bots/warbot/angar/battle/battle_wait/battle_wait.go
  8. 5 5
      server/serv_bots/warbot/angar/battle/battle_worker/battle_worker.go
  9. 1 1
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/battleon.go
  10. 81 0
      server/serv_bots/warbot/angar/death_match/battle.go
  11. 114 0
      server/serv_bots/warbot/angar/death_match/battle_wait/battle_wait.go
  12. 28 0
      server/serv_bots/warbot/angar/death_match/death_net/death_net.go
  13. 72 0
      server/serv_bots/warbot/angar/death_match/death_register/death_register.go
  14. 49 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/battlesound/battlesound.go
  15. 41 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/battlesound/isplay/isplay.go
  16. 114 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/death_on.go
  17. 102 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/health.go
  18. 46 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/healthtime/healthtime.go
  19. 41 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/isrepair/isrepair.go
  20. 77 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/repairtime/repairtime.go
  21. 43 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/manevr/ismanevr/ismanevr.go
  22. 173 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/manevr/manevr.go
  23. 53 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/damage/damage.go
  24. 41 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/isshot/isshot.go
  25. 95 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/shot.go
  26. 76 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_on/shottime/shottime.go
  27. 69 0
      server/serv_bots/warbot/angar/death_match/death_worker/death_worker.go
  28. 33 0
      server/serv_bots/warbot/angar/death_match/isrun/isrun.go
  29. 2 2
      web/tmpl/state_bot.tmpl.html

+ 1 - 3
pkg/types/ibattle_action.go

@@ -2,8 +2,6 @@ package types
 
 import (
 	"context"
-
-	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/isshot"
 )
 
 /*
@@ -18,7 +16,7 @@ type ИСражениеДействие interface {
 	// МанёврНадоУст -- устанавливает признак необходимости манёвра
 	МанёврНадоУст()
 	// ВыстрелБлок -- признак запрета на стрельбу
-	ВыстрелБлок() *isshot.IsShot
+	ВыстрелБлок() ИЕслиВыстрел
 	// Кнт -- возвращает контекст битвы
 	Кнт() context.Context
 	// Отменить -- вызывает контекст отмены битвы

+ 5 - 0
pkg/types/iis_shot.go

@@ -0,0 +1,5 @@
+package types
+
+// ИЕслиВыстрел -- интерфейс к выстрелу
+type ИЕслиВыстрел interface {
+}

+ 131 - 10
server/serv_bots/warbot/angar/base/mine/mine.go

@@ -100,11 +100,22 @@ func (сам *Шахта) пуск() {
 	time.Sleep(time.Second * 3)
 	фнРабота := func() {
 		defer time.Sleep(time.Minute * 5)
-		for !сам.шахтаЗабрать() {
+		счёт := 5
+		for счёт > 0 {
+			if сам.шахтаЗабрать() {
+				break
+			}
+			счёт--
 		}
 		сам.уровеньОбновить()
-		сам.Сделать()
 		сам.ускорениеПровер()
+		счёт = 5
+		for счёт > 0 {
+			if сам.проапгрейдить() {
+				break
+			}
+			счёт--
+		}
 		сам.количествоПолучить()
 		сам.бот.Ангар().РесурсыОбновить()
 		сам.Сделать()
@@ -271,15 +282,29 @@ func (сам *Шахта) уровеньОбновить() bool {
 		return false
 	}
 	сам.уровень.Уст(иУровень)
-	if иУровень == 0 { // шахту надо построить
-		сам.построить(списСтр)
-		return false
+	switch иУровень {
+	case 0: // шахту надо построить
+		счёт := 5
+		for счёт > 0 {
+			if сам.построить(списСтр) {
+				break
+			}
+			счёт--
+		}
+	default: // Пробуем проапгрейдить
+		счёт := 5
+		for счёт > 0 {
+			if сам.проапгрейдить() {
+				break
+			}
+			счёт--
+		}
 	}
 	return true
 }
 
 // Строит шахту при нулевом уровне
-func (сам *Шахта) построить(списСтр []string) {
+func (сам *Шахта) построить(списСтр []string) bool {
 	// <td style="width:50%;padding-left:1px;"><a class="simple-but border mb5" href="building-upgrade/Mine"><span><span>Построить</span></span></a></td>
 	var (
 		еслиНайти = false
@@ -292,7 +317,7 @@ func (сам *Шахта) построить(списСтр []string) {
 		}
 	}
 	if !еслиНайти {
-		return
+		return true
 	}
 	// Пробуем построить шахту
 	_стр := strings.TrimPrefix(стр, `<td style="width:50%;padding-left:1px;"><a class="simple-but border mb5" href="`)
@@ -301,7 +326,7 @@ func (сам *Шахта) построить(списСтр []string) {
 	списСтр, ош := сам.сеть.Клиент().Get(ссылка)
 	if ош != nil {
 		log.Printf("ERRO Шахта.построить(): при GET-команде 'построить шахту', err=\n\t%v\n", ош)
-		return
+		return false
 	}
 	еслиНайти = false
 	// "<a class=\"simple-but border mb5\" href=\"Mine?14-1.ILinkListener-upgradeLink-link\">"
@@ -312,7 +337,7 @@ func (сам *Шахта) построить(списСтр []string) {
 		}
 	}
 	if !еслиНайти {
-		return
+		return true
 	}
 	_стр = strings.TrimPrefix(стр, "<a class=\"simple-but border mb5\" href=\"")
 	_стр = strings.TrimSuffix(_стр, "\">")
@@ -321,8 +346,104 @@ func (сам *Шахта) построить(списСтр []string) {
 	_, ош = сам.сеть.Клиент().Get(ссылка)
 	if ош != nil {
 		log.Printf("ERRO Шахта.построить(): при GET-команде 'купить постройку шахты', err=\n\t%v\n", ош)
-		return
+		return false
+	}
+	return true
+}
+
+// Пытается проапгрейдить топливный склад
+func (сам *Шахта) проапгрейдить() bool {
+	time.Sleep(time.Millisecond * 1000)
+	var (
+		еслиНайти = false
+		списСтр   []string
+		стр       = ""
+		ош        error
+	)
+	фнКупить := func() bool {
+		defer time.Sleep(time.Millisecond * 1000)
+		списСтр, ош = сам.сеть.Клиент().Get("https://wartank.ru/building-upgrade/Mine")
+		if ош != nil {
+			log.Printf("Шахта.проапгрейдить().фнКупить(): при GET-команде 'купить постройку склада топлива', err=\n\t%v\n", ош)
+			return false
+		}
+		for _, стр = range списСтр {
+			// <a class="simple-but border mb5" href="FuelStorage?5-1.ILinkListener-upgradeLink-link">
+			if strings.Contains(стр, `ILinkListener-upgradeLink-link`) {
+				еслиНайти = true
+				break
+			}
+		}
+		if !еслиНайти {
+			return true
+		}
+		// Пробуем улучшить шахту
+		_стр := strings.TrimPrefix(стр, "<a class=\"simple-but border mb5\" href=\"")
+		_стр = strings.TrimSuffix(_стр, "\">")
+		// https://wartank.ru/building-upgrade/Mine?4-1.ILinkListener-upgradeLink-link
+		// <a class="simple-but border mb5" href="FuelStorage?50-1.ILinkListener-upgradeLink-link">
+		ссылка := "https://wartank.ru/building-upgrade/" + _стр
+		списСтр, ош = сам.сеть.Клиент().Get(ссылка)
+		if ош != nil {
+			log.Printf("Шахта.проапгрейдить().фнКупить(): при GET-команде 'купить постройку склада топлива', err=\n\t%v\n", ош)
+			return false
+		}
+		// Проверить, что постройка состоялась
+		for _, стр := range списСтр {
+			if strings.Contains(стр, "ILinkListener-upgradeLink-link") {
+				log.Printf("Шахта.проапгрейдить().фнКупить(): покупка склада топлива не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
+				return false // Покупка не оплачена
+			}
+		}
+		log.Printf("+++++Шахта.проапгрейдить().фнКупить(): покупка склада топлива прошла\n")
+		return true
+	}
+
+	фнПодтверждение := func() bool {
+		for _, стр = range списСтр {
+			// <a class="simple-but border w50 mXa mb10" w:id="confirmLink" href="../wicket/page?7-1.ILinkListener-confirmLink"><span><span>да, подтверждаю</span></span></a>
+			if strings.Contains(стр, `ILinkListener-confirmLink`) {
+				еслиНайти = true
+				break
+			}
+		}
+		if !еслиНайти {
+			return true
+		}
+		// Пробуем построить шахту
+		_стр := strings.TrimPrefix(стр, `<a class="simple-but border w50 mXa mb10" w:id="confirmLink" href="..`)
+		_стр = strings.TrimSuffix(_стр, `"><span><span>да, подтверждаю</span></span></a>`)
+		// https://wartank.ru/wicket/page?6-1.ILinkListener-confirmLink
+		ссылка := "https://wartank.ru" + _стр
+		списСтр, ош = сам.сеть.Клиент().Get(ссылка)
+		if ош != nil {
+			log.Printf("СкладТоплива.проапгрейдить().фнПодтверждение(): при GET-команде 'подтвердить постройку склада топлива', err=\n\t%v\n", ош)
+			return false
+		}
+		// Проверить, что постройка состоялась
+		for _, стр := range списСтр {
+			if strings.Contains(стр, "<title>Вы сделали слишком большую паузу</title>") {
+				log.Printf("СкладТоплива.проапгрейдить().фнПодтверждение(): подтверждение покупка склада топлива не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
+				return false // Покупка не оплачена
+			}
+		}
+		log.Printf("+++++СкладТоплива.проапгрейдить().фнПодтверждение(): подтверждение покупка склада топлива прошла\n")
+		return true
 	}
+
+	фнКомплекс := func() {
+		count := 5
+		for count > 0 {
+			if фнКупить() {
+				if фнПодтверждение() {
+					break
+				}
+			}
+			count--
+		}
+	}
+	фнКомплекс()
+	return true
 }
 
 // Сделать -- вызывается с базы, если она обнаружила, что пора сделать продукцию

+ 42 - 2
server/serv_bots/warbot/angar/base/polygon/polygon.go

@@ -5,6 +5,7 @@ import (
 	"log"
 	"strconv"
 	"strings"
+	"time"
 
 	"wartank/pkg/alias"
 	"wartank/pkg/components/safe_int"
@@ -100,6 +101,7 @@ const (
 func (сам *Полигон) пуск() {
 	сам.ОбратВремяУст("02")
 	фнРабота := func() {
+		defer time.Sleep(time.Minute * 20)
 		сам.усилениеДобавить()
 		сам.усилениеПровер()
 		сам.времяОбнов()
@@ -108,6 +110,13 @@ func (сам *Полигон) пуск() {
 		if сам.продуктСейчас.Получ() == стрАпгрейд {
 			сам.ВремяОстат().Уст("00:10:00")
 		}
+		счёт := 5
+		for счёт > 0 {
+			if сам.уровеньПолучить() {
+				break
+			}
+			счёт--
+		}
 	}
 	for {
 		select {
@@ -115,12 +124,43 @@ func (сам *Полигон) пуск() {
 			return
 		case <-сам.ВремяОстат().КаналСиг():
 			фнРабота()
-			// default:
-			// 	time.Sleep(time.Minute * 20)
+		default:
+			фнРабота()
 		}
 	}
 }
 
+// Проверяет уровень полигона
+func (сам *Полигон) уровеньПолучить() bool {
+	var (
+		стрВых     = ""
+		еслиНидено bool
+	)
+	lstBase, err := сам.сеть.Клиент().Get("https://wartank.ru/buildings")
+	if err != nil {
+		log.Printf("Полигон.уровеньПолучить(): при обновлении строк базы, err=\n\t%v\n", err)
+		return false
+	}
+	// <span class="green2">Полигон - 5</span><br/>
+	for _, стрВых = range lstBase {
+		if strings.Contains(стрВых, `<span class="green2">Полигон - `) {
+			еслиНидено = true
+			break
+		}
+	}
+	if !еслиНидено {
+		return false
+	}
+	стрУровень := strings.TrimPrefix(стрВых, `<span class="green2">Полигон - `)
+	стрУровень = strings.TrimSuffix(стрУровень, `</span><br/>`)
+	цУров, ош := strconv.Atoi(стрУровень)
+	if ош != nil {
+		return false
+	}
+	сам.уровень.Уст(цУров)
+	return true
+}
+
 // Проверяет на ускорение апгрейда полигона
 func (сам *Полигон) проверитьУскорение() {
 	var (

+ 3 - 3
server/serv_bots/warbot/angar/battle/battle.go

@@ -19,9 +19,9 @@ type Сражение struct {
 	бот    types.ИБот
 	клиент *http.Client
 
-	регистрация *battle_register.СражениеРегистрация // Регистратор на сражение
-	ожидание    *battle_wait.СражениеОжидание        // Ождатель начала сражения
-	действие    *battle_worker.СражениеИсполнитель   // Исполнитель сражения
+	регистрация *battle_register.СхваткаРегистрация // Регистратор на сражение
+	ожидание    *battle_wait.СхваткаОжидание        // Ождатель начала сражения
+	действие    *battle_worker.СхваткаИсполнитель   // Исполнитель сражения
 
 }
 

+ 4 - 4
server/serv_bots/warbot/angar/battle/battle_register/battle_register.go

@@ -13,7 +13,7 @@ import (
 )
 
 // СражениеРегистрация -- регистрирует танк к началу атаки
-type СражениеРегистрация struct {
+type СхваткаРегистрация struct {
 	*section.Секция
 	бот          types.ИБот
 	сеть         *scene_net.СценаСеть
@@ -21,12 +21,12 @@ type СражениеРегистрация struct {
 }
 
 // НовСражениеРегистрация -- возвращает новый ожидатель битвы
-func НовСражениеРегистрация(бот types.ИБот) (*СражениеРегистрация, error) {
+func НовСражениеРегистрация(бот types.ИБот) (*СхваткаРегистрация, error) {
 	секция, ош := section.НовСекция(бот, "Сражение", `<title>Сражения</title>`)
 	if ош != nil {
 		return nil, fmt.Errorf("НовСражениеРегистрация(): in create ISection, err=\n\t%w", ош)
 	}
-	сам := &СражениеРегистрация{
+	сам := &СхваткаРегистрация{
 		Секция:       секция,
 		бот:          бот,
 		счётРегистер: 10_000,
@@ -39,7 +39,7 @@ func НовСражениеРегистрация(бот types.ИБот) (*Ср
 }
 
 // Зарегистрироваться -- регистрирует танк на сражение
-func (сам *СражениеРегистрация) Зарегистрироваться() {
+func (сам *СхваткаРегистрация) Зарегистрироваться() {
 	// Найдено приглашение на участие
 	// https://wartank.ru/pve?{count}-1.ILinkListener-currentOverview-apply
 	фнРегис := func() []string {

+ 5 - 5
server/serv_bots/warbot/angar/battle/battle_wait/battle_wait.go

@@ -14,20 +14,20 @@ import (
 )
 
 // СражениеОжидание -- ожидатель начала битвы
-type СражениеОжидание struct {
+type СхваткаОжидание struct {
 	*section.Секция
 	bot types.ИБот
 	net *scene_net.СценаСеть
 }
 
 // НовСражениеОжидание -- возвращает новый ожидатель битвы
-func НовСражениеОжидание(bot types.ИБот) (*СражениеОжидание, error) {
+func НовСражениеОжидание(bot types.ИБот) (*СхваткаОжидание, error) {
 	section, err := section.НовСекция(bot, "Ожидание сражения", `<title>Сражения</title>`)
 	if err != nil {
 		return nil, fmt.Errorf("NewBattleWait(): in create ISection, err=\n\t%w", err)
 	}
 
-	сам := &СражениеОжидание{
+	сам := &СхваткаОжидание{
 		Секция: section,
 		bot:    bot,
 	}
@@ -39,7 +39,7 @@ func НовСражениеОжидание(bot types.ИБот) (*Сражени
 }
 
 // Ожидать -- ожидает начало сражения
-func (сам *СражениеОжидание) Ожидать() {
+func (сам *СхваткаОжидание) Ожидать() {
 
 	// Зайти в цикл ожидания сражения
 	for {
@@ -77,7 +77,7 @@ func (сам *СражениеОжидание) Ожидать() {
 }
 
 // Ждёт пока время не обнулится
-func (сам *СражениеОжидание) ждать() string {
+func (сам *СхваткаОжидание) ждать() string {
 	if ош := сам.net.Обновить(); ош != nil { // Здесь может уже обратный отсчёт перед сражением
 		return ""
 	}

+ 5 - 5
server/serv_bots/warbot/angar/battle/battle_worker/battle_worker.go

@@ -14,7 +14,7 @@ import (
 )
 
 // СражениеДействие -- исполнение битвы
-type СражениеИсполнитель struct {
+type СхваткаИсполнитель struct {
 	*section.Секция
 	бот  types.ИБот
 	сеть *scene_net.СценаСеть
@@ -28,7 +28,7 @@ type СражениеИсполнитель struct {
 }
 
 // НовСражениеДействие -- возвращает новый исполнитель битвы
-func НовСражениеИсполнитель(bot types.ИБот) (*СражениеИсполнитель, error) {
+func НовСражениеИсполнитель(bot types.ИБот) (*СхваткаИсполнитель, error) {
 	section, err := section.НовСекция(bot, "Ход сражения", `<title>Сражения</title>`)
 	if err != nil {
 		return nil, fmt.Errorf("NewBattleWorker(): in create *Section, err=\n\t%w", err)
@@ -37,7 +37,7 @@ func НовСражениеИсполнитель(bot types.ИБот) (*Сраж
 	if ош != nil {
 		return nil, fmt.Errorf("NewBattleWorker(): при создании стат еслиНачало, err=\n\t%w", ош)
 	}
-	сам := &СражениеИсполнитель{
+	сам := &СхваткаИсполнитель{
 		Секция:     section,
 		бот:        bot,
 		еслиНачало: еслиНачало,
@@ -51,7 +51,7 @@ func НовСражениеИсполнитель(bot types.ИБот) (*Сраж
 }
 
 // Танковать -- выполняет битву
-func (сам *СражениеИсполнитель) Танковать() {
+func (сам *СхваткаИсполнитель) Танковать() {
 	var ош error
 	сам.действие, ош = battleon.НовСражениеДействие(сам.бот) // IBattleOn (онлайн)
 	if ош != nil {
@@ -64,6 +64,6 @@ func (сам *СражениеИсполнитель) Танковать() {
 }
 
 // Тревога -- возвращает признак начала сражения (для браузера)
-func (сам *СражениеИсполнитель) Тревога() types.ИСтатПарам {
+func (сам *СхваткаИсполнитель) Тревога() types.ИСтатПарам {
 	return сам.еслиНачало
 }

+ 1 - 1
server/serv_bots/warbot/angar/battle/battle_worker/battleon/battleon.go

@@ -100,7 +100,7 @@ func (сам *СражениеДействие) МанёврНадоУст() {
 }
 
 // ВыстрелБлок -- признак запрета стрельбы при слабом здоровье
-func (сам *СражениеДействие) ВыстрелБлок() *isshot.IsShot {
+func (сам *СражениеДействие) ВыстрелБлок() types.ИЕслиВыстрел {
 	return сам.еслиВыстрел
 }
 

+ 81 - 0
server/serv_bots/warbot/angar/death_match/battle.go

@@ -0,0 +1,81 @@
+// package death_match -- объект схватки
+package death_match
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"wartank/pkg/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_register"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_wait"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker"
+)
+
+// Сражение -- объект схватки
+type Схватка struct {
+	*section.Секция
+	бот    types.ИБот
+	клиент *http.Client
+
+	регистрация *battle_register.СхваткаРегистрация // Регистратор на сражение
+	ожидание    *battle_wait.СхваткаОжидание        // Ождатель начала схватки
+	действие    *battle_worker.СхваткаИсполнитель   // Исполнитель схватки
+
+}
+
+// НовСражение -- возвращает новый *Battle
+func НовСхватка(бот types.ИБот) (*Схватка, error) {
+	секция, ош := section.НовСекция(бот, "Группа схватки", `<span>до начала `)
+	if ош != nil {
+		return nil, fmt.Errorf("НовСражение(): in create *Section, err=\n\t%w", ош)
+	}
+
+	сам := &Схватка{
+		Секция: секция,
+		бот:    бот,
+		клиент: бот.Сеть().Коннект(),
+	}
+	{
+		сам.регистрация, ош = battle_register.НовСражениеРегистрация(бот)
+		if ош != nil {
+			return nil, fmt.Errorf("НовСражение(): при создании регистратора на схватки, err=\n\t%w", ош)
+		}
+		сам.ожидание, ош = battle_wait.НовСражениеОжидание(бот)
+		if ош != nil {
+			return nil, fmt.Errorf("НовСражение(): при создании ожидателя схватки, err=\n\t%w", ош)
+		}
+		сам.действие, ош = battle_worker.НовСражениеИсполнитель(бот)
+		if ош != nil {
+			return nil, fmt.Errorf("НовСражение(): при создании исполнителя схватки, err=\n\t%w", ош)
+		}
+	}
+	// сам.shotTimeFull.Set(8000) // 8000 msec
+	return сам, nil
+}
+
+func (сам *Схватка) Пуск() error {
+	go сам.пуск()
+	return nil
+}
+
+// запускает в работу сражение
+func (сам *Схватка) пуск() {
+	for {
+		select {
+		case <-сам.бот.Кнт().Done():
+			return
+		default:
+			сам.регистрация.Зарегистрироваться()
+			сам.ожидание.Ожидать()
+			сам.действие.Танковать()
+			time.Sleep(time.Second * 2) // Пауза между циклами, чтобы сервер не долбить запросами
+		}
+	}
+}
+
+// ЕслиНачало -- возвращает признак начала схватки (для браузера)
+func (сам *Схватка) ЕслиНачало() types.ИСтатПарам {
+	return сам.действие.Тревога()
+}

+ 114 - 0
server/serv_bots/warbot/angar/death_match/battle_wait/battle_wait.go

@@ -0,0 +1,114 @@
+// package battle_wait -- заставляет ожидать начало битвы
+package battle_wait
+
+import (
+	"fmt"
+	// "log"
+	"strings"
+	"time"
+
+	"wartank/pkg/alias"
+	"wartank/pkg/components/scene_net"
+	"wartank/pkg/section"
+	"wartank/pkg/types"
+)
+
+// СражениеОжидание -- ожидатель начала битвы
+type СражениеОжидание struct {
+	*section.Секция
+	bot types.ИБот
+	net *scene_net.СценаСеть
+}
+
+// НовСражениеОжидание -- возвращает новый ожидатель битвы
+func НовСражениеОжидание(bot types.ИБот) (*СражениеОжидание, error) {
+	section, err := section.НовСекция(bot, "Ожидание сражения", `<title>Сражения</title>`)
+	if err != nil {
+		return nil, fmt.Errorf("NewBattleWait(): in create ISection, err=\n\t%w", err)
+	}
+
+	сам := &СражениеОжидание{
+		Секция: section,
+		bot:    bot,
+	}
+	сам.net, err = scene_net.НовСекцияСеть(сам, "https://wartank.ru/pve")
+	if err != nil {
+		return nil, fmt.Errorf("NewBattleWait(): in create *SectionNet, err=\n\t%w", err)
+	}
+	return сам, nil
+}
+
+// Ожидать -- ожидает начало сражения
+func (сам *СражениеОжидание) Ожидать() {
+
+	// Зайти в цикл ожидания сражения
+	for {
+		// countTime := сам.ВремяОпрос().Получ()
+		// if countTime > 0 {
+		// 	time.Sleep(time.Millisecond * 500)
+		// 	// log.Printf("BattleWait.Wait(): countTime=%v\n", сам.CountDown().String())
+		// 	continue
+		// }
+		стрВрем := сам.ждать()
+		if стрВрем == "" {
+			return
+		}
+		лстВрем := strings.Split(стрВрем, ":")
+		стрЧас := лстВрем[0]
+		if стрЧас > "00" {
+			time.Sleep(time.Hour * 1)
+			continue
+		}
+		стрМин := лстВрем[1]
+		if стрМин > "10" {
+			time.Sleep(time.Minute * 10)
+			continue
+		}
+		if стрМин > "01" {
+			time.Sleep(time.Minute * 1)
+			continue
+		}
+		if "00:00:05" < стрВрем && стрВрем < "00:00:59" {
+			time.Sleep(time.Second * 5)
+			continue
+		}
+		time.Sleep(time.Second * 1)
+	}
+}
+
+// Ждёт пока время не обнулится
+func (сам *СражениеОжидание) ждать() string {
+	if ош := сам.net.Обновить(); ош != nil { // Здесь может уже обратный отсчёт перед сражением
+		return ""
+	}
+	var (
+		strOut      string
+		lstBattle   = сам.СписПолучить()
+		еслиНайдено bool
+	)
+	for _, strOut = range lstBattle {
+		if strings.Contains(strOut, `<span>до начала `) {
+			еслиНайдено = true
+			break
+		}
+		// if strings.Contains(strOut, `>ОБЫЧНЫЕ<`) { // Это уже битва
+		// 	if len(сам.chBattle) == 0 {
+		// 		сам.chBattle <- 1
+		// 	}
+		// 	return
+		// }
+	}
+	if !еслиНайдено { // Сражение уже идёт
+		return ""
+	}
+	// Найдена строка ожидания начала сражения
+	lstTime := strings.Split(strOut, `<span>до начала `)
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, ` (`)
+	strTime = lstTime[0]
+	if err := сам.Секция.Уст(alias.Время(strTime)); err != nil { // Возможно уже всё
+		// log._rintf("WARN BattleWait.Wait(): при установке времени ожидания сражения(%v)\n\terr=%v\n", strTime, err)
+		return ""
+	}
+	return strTime
+}

+ 28 - 0
server/serv_bots/warbot/angar/death_match/death_net/death_net.go

@@ -0,0 +1,28 @@
+package death_net
+
+import (
+	"fmt"
+	"wartank/pkg/components/scene_net"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически воюет в схватке
+*/
+
+// СхваткаСеть -- танкует в схватке
+type СхваткаСеть struct {
+	*scene_net.СценаСеть
+}
+
+// НовСхваткаСеть -- возвращает новый *BattleNet
+func НовСхваткаСеть(battle types.ИСражениеСцена) (*СхваткаСеть, error) {
+	sectionNet, err := scene_net.НовСекцияСеть(battle, "https://wartank.ru/dm")
+	if err != nil {
+		return nil, fmt.Errorf("НовСхваткаСеть(): in create *SectionNet, err=\n\t%w", err)
+	}
+	сам := &СхваткаСеть{
+		СценаСеть: sectionNet,
+	}
+	return сам, nil
+}

+ 72 - 0
server/serv_bots/warbot/angar/death_match/death_register/death_register.go

@@ -0,0 +1,72 @@
+// package death_register -- регестрирует танк в схватке
+package death_register
+
+import (
+	"fmt"
+	"log"
+	"strings"
+	"time"
+
+	"wartank/pkg/components/scene_net"
+	"wartank/pkg/section"
+	"wartank/pkg/types"
+)
+
+// СхваткаРегистрация -- регистрирует танк к началу схватки
+type СхваткаРегистрация struct {
+	*section.Секция
+	бот          types.ИБот
+	сеть         *scene_net.СценаСеть
+	счётРегистер int // Счётчик регистраций на сражение
+}
+
+// НовСхваткаРегистрация -- возвращает новый ожидатель битвы
+func НовСхваткаРегистрация(бот types.ИБот) (*СхваткаРегистрация, error) {
+	секция, ош := section.НовСекция(бот, "Сражение", `<title>Сражения</title>`)
+	if ош != nil {
+		return nil, fmt.Errorf("НовСхваткаРегистрация(): in create ISection, err=\n\t%w", ош)
+	}
+	сам := &СхваткаРегистрация{
+		Секция:       секция,
+		бот:          бот,
+		счётРегистер: 10_000,
+	}
+	сам.сеть, ош = scene_net.НовСекцияСеть(сам, "https://wartank.ru/dm")
+	if ош != nil {
+		return nil, fmt.Errorf("НовСхваткаРегистрация(): in create *SectionNet, err=\n\t%w", ош)
+	}
+	return сам, nil
+}
+
+// Зарегистрироваться -- регистрирует танк на сражение
+func (сам *СхваткаРегистрация) Зарегистрироваться() {
+	// Найдено приглашение на участие
+	// https://wartank.ru/dm?{count}-1.ILinkListener-currentOverview-apply
+	фнРегис := func() []string {
+		стрСсылка := "https://wartank.ru/dm?0-1.ILinkListener-currentOverview-apply"
+		стрКонтроль := "https://wartank.ru/dm?0-1.ILinkListener-currentOverview-apply"
+		for {
+			time.Sleep(time.Second * 1)
+			лстСражение, err := сам.сеть.Get(стрСсылка)
+			if err != nil {
+				log.Printf("ERRO СхваткаРегистрация.Зарегистрироваться(): при выполнении GET-команды на подъём в атаку, err=\n\t%v\n", err)
+			}
+			if len(лстСражение) < 113 {
+				continue
+			}
+			стрКонтроль = лстСражение[113]
+			if !strings.Contains(стрКонтроль, "ILinkListener-currentOverview-apply") {
+				return лстСражение
+			}
+			log.Printf("СхваткаРегистрация.Зарегистрироваться(): регистрация не прошла\n")
+			стрСсылка = strings.TrimPrefix(стрКонтроль, `<a class="simple-but border" href="`)
+			стрСсылка = strings.TrimSuffix(стрСсылка, `.ILinkListener-currentOverview-apply"><span><span>Взвод, подъем! В атаку!</span></span></a>`)
+			стрСсылка = "https://wartank.ru/" + стрСсылка + ".ILinkListener-currentOverview-apply"
+		}
+	}
+
+	if ош := сам.СтрОбновить(фнРегис()); ош != nil {
+		log.Printf("СхваткаРегистрация.Зарегистрироваться(): при обновлении lstBattle, err=\n\t%v\n", ош)
+	}
+	// log._rintf("INFO СхваткаРегистрация.Зарегистрироваться(): регистрация прошла успешно\n")
+}

+ 49 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/battlesound/battlesound.go

@@ -0,0 +1,49 @@
+package battlesound
+
+import (
+	"time"
+
+	"wartank/pkg/components/sound"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/battlesound/isplay"
+)
+
+/*
+	Выполняет контроль за запуском одной озвучки битвы
+*/
+
+// BattleSound -- контроль одного раза запуска звука битвы
+type BattleSound struct {
+	isPlay *isplay.IsPlay
+}
+
+// NewBattleSound -- возвращает новый  *BattleSound
+func NewBattleSound() *BattleSound {
+	return &BattleSound{
+		isPlay: isplay.NewIsPlay(),
+	}
+}
+
+// Play -- играет музончик, если можно
+func (сам *BattleSound) Play() {
+	if сам.isPlay.Get() {
+		return
+	}
+	go сам.play()
+}
+
+// Проигрывает экслюзивно в отдельном потоке звук
+func (сам *BattleSound) play() {
+	сам.isPlay.Set()
+	val := 7
+	for val > 0 {
+		sound.Battle()
+		val--
+		time.Sleep(time.Second * 1)
+	}
+	val = 600 // Пауза для блокировки повторного вкелючения начатой битвы
+	for val >= 0 {
+		val--
+		time.Sleep(time.Second * 1)
+	}
+	сам.isPlay.Reset()
+}

+ 41 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/battlesound/isplay/isplay.go

@@ -0,0 +1,41 @@
+package isplay
+
+import (
+	"sync"
+)
+
+/*
+	Потокобезопасный признак проигрывания звука
+*/
+
+// IsPlay -- потокобезопасный признак проигрывания звука
+type IsPlay struct {
+	val   bool
+	block sync.RWMutex
+}
+
+// NewIsPlay -- возвращает новый *IsPlay
+func NewIsPlay() *IsPlay {
+	return &IsPlay{}
+}
+
+// Get -- возвращает хранимое состояние
+func (сам *IsPlay) Get() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (сам *IsPlay) Set() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (сам *IsPlay) Reset() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = false
+}

+ 114 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/death_on.go

@@ -0,0 +1,114 @@
+package death_on
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"wartank/pkg/components/scene_net"
+	"wartank/pkg/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/death_match/death_worker/death_on/health"
+	"wartank/server/serv_bots/warbot/angar/death_match/death_worker/death_on/manevr"
+	"wartank/server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot"
+	"wartank/server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/isshot"
+)
+
+/*
+	Предоставляет сетевой компонент при непосредственном сражении
+*/
+
+// СхваткаДействие -- непосредственно танкует в сражении
+type СхваткаДействие struct {
+	*section.Секция
+	сеть       types.ИСценаСеть
+	бот        types.ИБот
+	кнт        context.Context // Контекст сражения
+	фнОтменить func()          // Функция отмены сражения
+
+	выстрел     *shot.Выстрел    // Объект выстрела
+	здоровье    *health.Здоровье // Текущее здоровье танка
+	манёвр      *manevr.Манёвр   // Возможность маневрирования
+	логин       string
+	еслиВыстрел *isshot.IsShot // Признак необходимости маскирования (запрет стрельбы, когда слабое здоровье)
+}
+
+// НовСхваткаДействие -- возвращает новый *СхваткаДействие
+func НовСхваткаДействие(бот types.ИБот) (*СхваткаДействие, error) {
+	секция, ош := section.НовСекция(бот, "Исполнитель схватки", `<title>Схватка</title>`)
+	if ош != nil {
+		return nil, fmt.Errorf("НовСхваткаДействие(): in create ISection, err=\n\t%w", ош)
+	}
+
+	// Ограничить время сражения бота
+	кнтСражение, фнОтменить := context.WithTimeout(бот.Кнт(), time.Second*305)
+	сам := &СхваткаДействие{
+		Секция:      секция,
+		бот:         бот,
+		кнт:         кнтСражение,
+		фнОтменить:  фнОтменить,
+		логин:       бот.Имя(),
+		еслиВыстрел: isshot.NewIsShot(),
+	}
+	сам.сеть, ош = scene_net.НовСекцияСеть(сам, "https://wartank.ru/de")
+	if ош != nil {
+		return nil, fmt.Errorf("NewСхваткаДействие(): in create *SectionNet, err=\n\t%w", ош)
+	}
+	go сам.пуск()
+	return сам, nil
+}
+
+// запускает сражение
+func (сам *СхваткаДействие) пуск() {
+	defer func() {
+		сам.фнОтменить()
+		// log._rintf("СхваткаДействие.run(): сражение завершено\n")
+	}()
+	{ // Подготовка к сражению
+		var err error
+		сам.выстрел, err = shot.НовВыстрел(сам) // Объект выстрела
+		if err != nil {
+			// log._rintf("ERRO СхваткаДействие.Run(): при создании выстрела танка, err=\n\t%v\n", err)
+			return
+		}
+		сам.здоровье, err = health.НовЗдоровье(сам)
+		if err != nil {
+			// log._rintf("ERRO СхваткаДействие.Run(): при создании здоровья танка, err=\n\t%v\n", err)
+			return
+		}
+		сам.манёвр, err = manevr.НовМанёвр(сам)
+		if err != nil {
+			// log._rintf("ERRO СхваткаДействие.Run(): при создании маневра танка, err=\n\t%v\n", err)
+			return
+		}
+	}
+	// Рабочий цикл сражения
+	<-сам.кнт.Done()
+}
+
+// Сеть -- возвращает сетевой компонент секции
+func (сам *СхваткаДействие) Сеть() types.ИСценаСеть {
+	return сам.сеть
+}
+
+func (сам *СхваткаДействие) МанёврНадоУст() {
+	if сам.манёвр == nil {
+		return
+	}
+	сам.манёвр.УстНадо()
+}
+
+// ВыстрелБлок -- признак запрета стрельбы при слабом здоровье
+func (сам *СхваткаДействие) ВыстрелБлок() types.ИЕслиВыстрел {
+	return сам.еслиВыстрел
+}
+
+// Кнт -- возвращает контекст отмены сражения
+func (сам *СхваткаДействие) Кнт() context.Context {
+	return сам.кнт
+}
+
+// ОтменитьДействие -- вызов функции отмены контекста сражения
+func (сам *СхваткаДействие) Отменить() {
+	сам.фнОтменить()
+}

+ 102 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/health.go

@@ -0,0 +1,102 @@
+package health
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"wartank/pkg/types"
+)
+
+/*
+	Контролирует состояние здоровья танка
+*/
+
+// Здоровье -- контроль здоровья танка
+type Здоровье struct {
+	types.ИСражениеДействие
+	канТик     chan int // Канал для ровной отправки тиков
+	счётЛечить int      // Счётчик вызовов лечения
+}
+
+// НовЗдоровье -- возвращает новый *Здоровье
+func НовЗдоровье(действие types.ИСражениеДействие) (*Здоровье, error) {
+	{ // Предусловия
+		if действие == nil {
+			return nil, fmt.Errorf("НовЗдоровье(): действие==nil")
+		}
+	}
+	сам := &Здоровье{
+		ИСражениеДействие: действие,
+		канТик:            make(chan int, 2),
+	}
+	go сам.makeTik()
+	go сам.пуск()
+	return сам, nil
+}
+
+// Отправляе ттики с заданным равным интервалом
+func (сам *Здоровье) makeTik() {
+	defer func() {
+		close(сам.канТик)
+		сам.Отменить()
+		// log._rintf("Здоровье.makeTick(): сражение завершёно\n")
+	}()
+	for {
+		select {
+		case <-сам.Кнт().Done():
+			return
+		default:
+			сам.канТик <- 1
+			time.Sleep(time.Second * 10)
+		}
+	}
+}
+
+// Главный цикл обработки здоровья в сражении
+func (сам *Здоровье) пуск() {
+	defer func() {
+		сам.Отменить()
+	}()
+	for range сам.канТик {
+		сам.лечить()
+	}
+}
+
+// Полное -- возвращает объект полного здоровья танка
+func (сам *Здоровье) Полное() int {
+	return 0
+}
+
+// ЕслиМёртвый -- возвращает признак мертвичины танка
+func (сам *Здоровье) ЕслиМёртвый() bool {
+	lstBattle := сам.СписПолучить()
+	for _, strOut := range lstBattle {
+		if strings.Contains(strOut, `>Ваш танк подбит.`) {
+			// log._rintf("INFO Здоровье.repair(): танк подбит\n")
+			сам.Отменить()
+			return true
+		}
+	}
+	return false
+}
+
+// Восстановливает здоровье (~)
+func (сам *Здоровье) лечить() {
+
+	// <span>Ремкомплект</span>
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
+
+	strLink := "https://wartank.ru/pve?19-{count}.ILinkListener-currentControl-repairLink"
+	// <a href="pve?6-26.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
+	strLink = strings.ReplaceAll(strLink, "{count}", fmt.Sprint(сам.счётЛечить))
+	lstBattleOn, err := сам.Сеть().Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Здоровье.repair(): при выполнении GET-команды ремонта, err=\n\t%v\n", err)
+		return
+	}
+	if err = сам.СтрОбновить(lstBattleOn); err != nil {
+		// log._rintf("ERRO Здоровье.repair(): при обновлении lstBattle, err=\n\t%v\n", err)
+		return
+	}
+}

+ 46 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/healthtime/healthtime.go

@@ -0,0 +1,46 @@
+package healthtime
+
+import (
+	"sync"
+)
+
+/*
+	Содержит временное здоровье
+*/
+
+// HealthTime -- временное здоровье
+type HealthTime struct {
+	val   int // Здоровье FIXME: переделать в алиас
+	block sync.RWMutex
+}
+
+// NewHealthTime -- возвращает новый *HealthTime
+func NewHealthTime() *HealthTime {
+	return &HealthTime{}
+}
+
+// Get -- возвращает хранимое временное здоровье
+func (сам *HealthTime) Get() int {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// IsZero -- возвращает истину, если значение обнулено
+func (сам *HealthTime) IsZero() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val == 0
+}
+
+// Set -- устанавливает значение по требованию
+func (сам *HealthTime) Set(val int) {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	if val < 0 {
+		// log._rintf("WARN HealthTime.Set(): отрицательное значение(%v)\n", val)
+		сам.val = 0
+		return
+	}
+	сам.val = val
+}

+ 41 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/isrepair/isrepair.go

@@ -0,0 +1,41 @@
+package isrepair
+
+import (
+	"sync"
+)
+
+/*
+	Потокобезопасный признак необходимости восстановления здоровья
+*/
+
+// IsRepair -- потокобезопасный признак восстановления здоровья
+type IsRepair struct {
+	val   bool
+	block sync.RWMutex
+}
+
+// NewIsRepair -- возвращает новый *IsRepair
+func NewIsRepair() *IsRepair {
+	return &IsRepair{}
+}
+
+// Get -- возвращает хранимое состояние
+func (сам *IsRepair) Get() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (сам *IsRepair) Set() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (сам *IsRepair) Reset() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = false
+}

+ 77 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/health/repairtime/repairtime.go

@@ -0,0 +1,77 @@
+package repairtime
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+)
+
+/*
+	Потокобезопасное время ожидания до восстановлении ремки
+*/
+
+// RepairTime -- потокобезопасное время до восстановления ремки
+type RepairTime struct {
+	val    int
+	valOld int // Старое время до ремки
+	block  sync.RWMutex
+}
+
+// NewRepairTime -- возвращает новый *RepairTime
+func NewRepairTime() *RepairTime {
+	return &RepairTime{}
+}
+
+// Получ -- возвращает хранимое значение времени
+func (сам *RepairTime) Получ() int {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// GetOld -- возвращает хранимое старое значение времени
+func (сам *RepairTime) GetOld() int {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.valOld
+}
+
+// Set -- устанавливает хранимое время восстановления ремки
+func (сам *RepairTime) Set(val string) error {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	iVal, err := strconv.Atoi(val)
+	if err != nil {
+		return fmt.Errorf("RepairTime.Set(): val(%v) не число, err=%w", val, err)
+	}
+	if iVal < 0 {
+		return fmt.Errorf("RepairTime.Set(): val(%v) < 0", iVal)
+	}
+	сам.valOld = сам.val
+	сам.val = iVal
+	return nil
+}
+
+// Dec -- уменьшает на секунду время восстановления
+func (сам *RepairTime) Dec() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	if сам.val > 0 {
+		сам.valOld = сам.val
+		сам.val--
+	}
+}
+
+// ЕслиГотово -- возвращает признак готовности восстановления
+func (сам *RepairTime) ЕслиМожно() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val == 0
+}
+
+// ЕслиИзменилось -- возвращает признак изменения здоровья после присвоения
+func (сам *RepairTime) ЕслиИзменилось() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val == сам.valOld
+}

+ 43 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/manevr/ismanevr/ismanevr.go

@@ -0,0 +1,43 @@
+package ismanevr
+
+import (
+	"sync"
+)
+
+/*
+	Потокобезопасный признак внешнего разрешения манёвра
+*/
+
+// IsManevr -- потокобезопасный признак внешнего разрешения манёвра
+type IsManevr struct {
+	val   bool
+	block sync.RWMutex
+}
+
+// NewIsManevr -- возвращает новый *IsManevr
+func NewIsManevr() *IsManevr {
+	return &IsManevr{
+		val: true,
+	}
+}
+
+// Get -- возвращает хранимое состояние
+func (сам *IsManevr) Get() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (сам *IsManevr) Set() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = true
+}
+
+// Сброс -- сбрасывает хранимое состояние
+func (сам *IsManevr) Сброс() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = false
+}

+ 173 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/manevr/manevr.go

@@ -0,0 +1,173 @@
+package manevr
+
+import (
+	"fmt"
+	// "log"
+	"strings"
+	"time"
+
+	"github.com/sirupsen/logrus"
+
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/repairtime"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/manevr/ismanevr"
+
+	// "wartank/internal/components/sound"
+	"wartank/pkg/types"
+)
+
+/*
+	Пытается маневрировать после выстрела
+*/
+
+// Манёвр -- маневрирует после выстрела
+type Манёвр struct {
+	types.ИСражениеДействие                        // FIXME:
+	еслиМанёврНадо          *ismanevr.IsManevr     // Требование выполнить манёвр
+	времяЖдать              *repairtime.RepairTime // Время до востановления манёвра
+	chTick                  chan int               // Тики для поиска маневра
+}
+
+// НовМанёвр -- возвращает новый *Manevr
+func НовМанёвр(действие types.ИСражениеДействие) (*Манёвр, error) {
+	{ // Предусловия
+		if действие == nil {
+			return nil, fmt.Errorf("НовМанёвр(): действие==nil")
+		}
+	}
+	сам := &Манёвр{
+		ИСражениеДействие: действие,
+		еслиМанёврНадо:    ismanevr.NewIsManevr(),
+		времяЖдать:        repairtime.NewRepairTime(),
+		chTick:            make(chan int, 1),
+	}
+	_ = сам.времяЖдать.Set("0") // При запуске боя есть возможность маневрировать
+	go сам.makeTick()
+	go сам.пуск()
+	return сам, nil
+}
+
+// Генерирует тик для уменьшения времени ожидания восстановления возможности манёвра
+func (сам *Манёвр) makeTick() {
+	defer func() {
+		close(сам.chTick)
+		// log._rintf("Manevr.makeTick(): сражение завершено\n")
+	}()
+	for {
+		select {
+		case <-сам.Кнт().Done():
+			return
+		default:
+			if сам.времяЖдать.Получ() <= 0 {
+				сам.chTick <- 1
+			}
+			сам.времяЖдать.Dec()
+			time.Sleep(time.Second * 1)
+		}
+	}
+}
+
+// Рабочий цикл поиска маневра (~)
+func (сам *Манёвр) пуск() {
+	for range сам.chTick {
+		if !сам.еслиМанёврНадо.Get() { // Если нет требования манёвра -- пропускаем
+			continue
+		}
+		сам.манёвр()
+		сам.времяМанёврНайти() // Найти время после манёвра
+	}
+}
+
+// Ищет время для манёвра
+func (сам *Манёвр) времяМанёврНайти() {
+	var (
+		еслиНайдено bool
+		ind         int
+		lstBattleOn = сам.СписПолучить()
+		strOut      string
+	)
+	for ind, strOut = range lstBattleOn {
+		// <a href="pve?4-88.ILinkListener-currentControl-maneuverLink" class="simple-but blue"><span><span>5 секунд</span></span></a>
+		if strings.Contains(strOut, `-currentControl-maneuverLink`) {
+			еслиНайдено = true
+			break
+		}
+	}
+	if !еслиНайдено { // Или манёвр успел восстановиться, или конец сражения
+		if strings.Contains(strOut, `<span>Маневр</span>`) {
+			_ = сам.времяЖдать.Set("0")
+			return
+		}
+		logrus.WithField("strOut", strOut).Warn("Манёвр.времяМанёврНайти(): ошибка в поиске времени манёвра")
+		сам.Отменить()
+		return
+	}
+	{ // Найти время манёвра
+		lstTime := strings.Split(strOut, `ILinkListener-currentControl-maneuverLink" class="simple-but blue"><span><span>`)
+		if len(lstTime) != 2 {
+			logrus.WithField("ind", ind).
+				WithField("lstBattleOn[-1]", lstBattleOn[ind-1]).
+				WithField("lstBattleOn[ind]", strOut).
+				WithField("lstBattleOn[+1]", lstBattleOn[ind+1]).
+				Errorf("Manevr.findManevrTime(): нет двух полей во времени ожидания")
+			сам.Отменить()
+			return
+		}
+		strTime := lstTime[1]
+		lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
+		strTime = lstTime[0]
+		if err := сам.времяЖдать.Set(strTime); err != nil {
+			logrus.WithError(err).Error("Manevr.findManevrTime(): при обновлении времени ожидания манёвра")
+			сам.Отменить()
+			return
+		}
+	}
+	logrus.WithField("время", сам.времяЖдать.Получ()).Info("Manevr.findManevrTime(): до манёвра")
+}
+
+// Манёвр по возможности
+func (сам *Манёвр) манёвр() {
+	var (
+		еслиНайдено = false
+		lstBattleOn = сам.СписПолучить()
+		strOut      = ""
+	)
+	for _, strOut = range lstBattleOn {
+		// <a href="pve?4-21.ILinkListener-currentControl-maneuverLink" class="simple-but blue"><span><span>Маневр</span></span></a>
+		if strings.Contains(strOut, `<span>Маневр</span>`) {
+			еслиНайдено = true
+			break
+		}
+	}
+	if !еслиНайдено { // Либо ждём восстановления манёвра, либо сражение закончилось
+		return
+	}
+	{ // Попытка манёвра
+		lstLink := strings.Split(strOut, `<a href="`)
+		strLink := lstLink[1]
+		lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Маневр</span></span></a>`)
+		strLink = "https://wartank.ru/" + lstLink[0]
+		lstBattleOn, err := сам.Сеть().Get(strLink)
+		if err != nil {
+			logrus.WithError(err).Error("Manevr.Manevr(): при выполнении GET-команды маневра")
+			сам.Отменить()
+			return
+		}
+		if err = сам.СтрОбновить(lstBattleOn); err != nil {
+			logrus.WithError(err).Error("Manevr.Manevr(): при обновлении lstBattle")
+			сам.Отменить()
+			return
+		}
+		// sound.Manevr()
+	}
+	сам.еслиМанёврНадо.Сброс()
+}
+
+// ЕслиГотов -- возвращает готовность манёвра
+func (сам *Манёвр) ЕслиГотов() bool {
+	return сам.времяЖдать.ЕслиМожно()
+}
+
+// УстНадо -- устанавливает признак необходимости манёвра
+func (сам *Манёвр) УстНадо() {
+	сам.еслиМанёврНадо.Set()
+}

+ 53 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/damage/damage.go

@@ -0,0 +1,53 @@
+package damage
+
+import (
+	"sync"
+
+	"wartank/pkg/alias"
+)
+
+/*
+	Следит за уроном танка.
+	Сравнивает с предыдущим значением,
+	результат сравнения при каждом присвоении -- сохраняет
+*/
+
+// Damage -- урон танка с памятью
+type Damage struct {
+	val   alias.Урон
+	res   string
+	block sync.RWMutex
+}
+
+// NewDamage -- возвращает новый *Damage
+func NewDamage() *Damage {
+	return &Damage{
+		res: "none",
+	}
+}
+
+// Set -- устанавливает урон
+func (сам *Damage) Set(val alias.Урон) {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	if сам.val == 0 { // Первоначальное присвоение
+		сам.val = val
+		return
+	}
+	switch {
+	case сам.val > val: // Урон уменьшился
+		сам.res = "down"
+	case сам.val < val: // Урон увеличился
+		сам.res = "up"
+	default: // Урон не изменился
+		сам.res = "none"
+	}
+	сам.val = val
+}
+
+// Result -- возвращает результат сравнения урона старого и текущего
+func (сам *Damage) Result() string {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.res
+}

+ 41 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/isshot/isshot.go

@@ -0,0 +1,41 @@
+package isshot
+
+import (
+	"sync"
+)
+
+/*
+	Потокобезопасный признак внешнего разрешения выстрела
+*/
+
+// IsShot -- потокобезопасный признак внешнего разрешения выстрела
+type IsShot struct {
+	val   bool
+	block sync.RWMutex
+}
+
+// NewIsShot -- возвращает новый *IsShot
+func NewIsShot() *IsShot {
+	return &IsShot{}
+}
+
+// Get -- возвращает хранимое состояние
+func (сам *IsShot) Get() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (сам *IsShot) Set() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (сам *IsShot) Reset() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = false
+}

+ 95 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/shot/shot.go

@@ -0,0 +1,95 @@
+package shot
+
+import (
+	"fmt"
+	// "log"
+	"strings"
+	"time"
+
+	"wartank/pkg/types"
+)
+
+/*
+	Исходник предоставляет выстрел со свойствами:
+		- время до выстрела
+		- длительность перезарядки
+
+	Первый параметр постоянно изменяется (после выстрела восстанавливается)
+	Второй параметр меняется медленно (в зависимости от количества очков после выстрела)
+*/
+
+// Выстрел -- объект выстрела
+type Выстрел struct {
+	types.ИСражениеДействие          // FIXME!!!
+	канТик                  chan int // Тик для выстрела
+	выстрелСчёт             int      // Счётчик выстрелов для сервера
+}
+
+// НовВыстрел -- возвращает новый *Shot
+func НовВыстрел(действие types.ИСражениеДействие) (*Выстрел, error) {
+	{ // Предусловия
+		if действие == nil {
+			return nil, fmt.Errorf("НовВыстрел(): действие==nil")
+		}
+	}
+	сам := &Выстрел{
+		ИСражениеДействие: действие,
+		выстрелСчёт:       1,
+		канТик:            make(chan int, 2),
+	}
+	go сам.делайТик()
+	go сам.пуск()
+	return сам, nil
+}
+
+// Генерирует тики, когда можно стрелять
+func (сам *Выстрел) делайТик() {
+	defer func() {
+		close(сам.канТик)
+		// log._rintf("Shot.makeTick(): сражение завершёно\n")
+	}()
+	for {
+		select {
+		case <-сам.Кнт().Done():
+			return
+		default:
+			сам.канТик <- 1 // Первый выстрел, потом спать по таймингу
+			// log._rintf("INFO Shot.run() перезарядка=%v msec\n", recharge)
+			// Если идёт перезарядка -- постепенно обнуляем время ожидания
+			time.Sleep(time.Millisecond * 6800)
+		}
+	}
+}
+
+// Цикл выстрела (в отдельном потоке)
+func (сам *Выстрел) пуск() {
+	for range сам.канТик {
+		// Стрелять можно, стандартное ожидание
+		сам.выстрел()
+	}
+}
+
+// Обновляет возможность выстрела (~)
+//
+//	Вызывается из отдельного потока
+func (сам *Выстрел) выстрел() {
+	if ош := сам.Сеть().Обновить(); ош != nil { // Проверка на непосредственно битву
+		// <span>закончилась 00:00:07 назад</span>
+		// log._rintf("ERRO Shot.shot(): при обновлении lstBattleOn, err=\n\t%v\n", err)
+		сам.Отменить()
+		return
+	}
+
+	// <a href="pve?6-26.ILinkListener-currentControl-attackRegularShellLink" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>
+
+	strLink := "https://wartank.ru/pve?6-{count}.ILinkListener-currentControl-attackRegularShellLink"
+	strLink = strings.ReplaceAll(strLink, "{count}", fmt.Sprint(сам.выстрелСчёт))
+	сам.выстрелСчёт++
+	_, err := сам.Сеть().Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Shot.shot(): при исполнении GET-команды выстрела обычным снарядом, err=\n\t%v\n", err)
+		return
+	}
+	сам.МанёврНадоУст()
+	// sound.Shot()
+}

+ 76 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_on/shottime/shottime.go

@@ -0,0 +1,76 @@
+package shottime
+
+import (
+	"sync"
+
+	"github.com/sirupsen/logrus"
+
+	"wartank/pkg/alias"
+)
+
+/*
+	Содержит время до выстрела
+*/
+
+// ShotTime -- время до выстрела
+type ShotTime struct {
+	val   alias.МилСек // Время в мсек
+	block sync.RWMutex
+}
+
+// NewShotTime -- возвращает новый ShotTime
+func NewShotTime() *ShotTime {
+	return &ShotTime{}
+}
+
+// Get -- возвращает хранимое время до выстрела
+func (сам *ShotTime) Get() alias.МилСек {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+
+// Dec5 -- уменьшает время до выстрела на 5 мсек
+func (сам *ShotTime) Dec5() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val -= 5
+	if сам.val < 0 {
+		сам.val = 0
+	}
+}
+
+// Dec30 -- уменьшает время до выстрела на 30 мсек
+func (сам *ShotTime) Dec30() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val -= 30
+	if сам.val < 0 {
+		сам.val = 0
+	}
+}
+
+// Inc210 -- увеличивает время до выстрела на 210 мсек
+func (сам *ShotTime) Inc210() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val += 210
+}
+
+// IsZero -- возвращает истину, если значение обнулено
+func (сам *ShotTime) IsZero() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val == 0
+}
+
+// Set -- устанавливает значение по требованию
+func (сам *ShotTime) Set(val alias.МилСек) {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	if val < 0 {
+		logrus.WithField("val", val).Error("ShotTime.Set(): отрицательное значение")
+		return
+	}
+	сам.val = val
+}

+ 69 - 0
server/serv_bots/warbot/angar/death_match/death_worker/death_worker.go

@@ -0,0 +1,69 @@
+// package death_worker -- исполнение схватки
+package death_worker
+
+import (
+	"fmt"
+	"time"
+
+	"wartank/pkg/components/scene_net"
+	"wartank/pkg/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/death_math/death_worker/death_on"
+	"wartank/server/serv_bots/warbot/angar/death_math/death_worker/death_on/death_sound"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+// СражениеДействие -- исполнение схватки
+type СхваткаИсполнитель struct {
+	*section.Секция
+	бот  types.ИБот
+	сеть *scene_net.СценаСеть
+
+	еслиНачало types.ИСтатПарам
+
+	// Непосредственное сражение
+	действие *death_on.СражениеДействие
+
+	sound *death_sound.BattleSound // Однопоточное проигрывание звука
+}
+
+// НовСражениеДействие -- возвращает новый исполнитель схватки
+func НовСхваткаИсполнитель(bot types.ИБот) (*СхваткаИсполнитель, error) {
+	section, err := section.НовСекция(bot, "Ход сражения", `<title>Сражения</title>`)
+	if err != nil {
+		return nil, fmt.Errorf("НовСхваткаИсполнитель(): in create *Section, err=\n\t%w", err)
+	}
+	еслиНачало, ош := static_param.НовСтатПарам("alarm")
+	if ош != nil {
+		return nil, fmt.Errorf("НовСхваткаИсполнитель(): при создании стат еслиНачало, err=\n\t%w", ош)
+	}
+	сам := &СхваткаИсполнитель{
+		Секция:     section,
+		бот:        bot,
+		еслиНачало: еслиНачало,
+		sound:      death_sound.NewBattleSound(),
+	}
+	сам.сеть, err = scene_net.НовСекцияСеть(сам, "https://wartank.ru/pve")
+	if err != nil {
+		return nil, fmt.Errorf("НовСхваткаИсполнитель(): in create *SectionNet, err=\n\t%w", err)
+	}
+	return сам, nil
+}
+
+// Танковать -- выполняет битву
+func (сам *СхваткаИсполнитель) Танковать() {
+	var ош error
+	сам.действие, ош = death_on.НовСражениеДействие(сам.бот) // IBattleOn (онлайн)
+	if ош != nil {
+		return
+	}
+	сам.sound.Play()
+	time.Sleep(time.Second * 10) // Задержка для звука на странице
+	<-сам.действие.Кнт().Done()
+	// log._rintf("Battle.runBaton(): сражение завершено\n")
+}
+
+// Тревога -- возвращает признак начала сражения (для браузера)
+func (сам *СхваткаИсполнитель) Тревога() types.ИСтатПарам {
+	return сам.еслиНачало
+}

+ 33 - 0
server/serv_bots/warbot/angar/death_match/isrun/isrun.go

@@ -0,0 +1,33 @@
+package isrun
+
+import "sync"
+
+/*
+	Потокобезопасный признак запуска сражения
+*/
+
+type IsRun struct {
+	val   bool
+	block sync.RWMutex
+}
+
+func NewIsRun() *IsRun {
+	return &IsRun{}
+}
+
+func (сам *IsRun) Get() bool {
+	сам.block.RLock()
+	defer сам.block.RUnlock()
+	return сам.val
+}
+func (сам *IsRun) Set() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = true
+}
+
+func (сам *IsRun) Reset() {
+	сам.block.Lock()
+	defer сам.block.Unlock()
+	сам.val = false
+}

+ 2 - 2
web/tmpl/state_bot.tmpl.html

@@ -35,7 +35,7 @@
             <h3>Шахта</h3>
         </div>
         <div class="card-body">
-            <p class="card-text">Уровнеь: {{.шахта_уровень}}</p>
+            <p class="card-text">Уровень: {{.шахта_уровень}}</p>
             <p class="card-text">Режим: {{.шахта_режим}}</p>
             <p class="card-text">Кол: {{.шахта_сделать_кол}}</p>
             <p class="card-text">Тип: {{.шахта_сделать_назв}}</p>
@@ -49,7 +49,7 @@
             <h3>Полигон</h3>
         </div>
         <div class="card-body">
-            <p class="card-text">Уровнеь: {{.полигон_уровень}}</p>
+            <p class="card-text">Уровень: {{.полигон_уровень}}</p>
             <p class="card-text">Режим: {{.полигон_режим}}</p>
             <p class="card-text">Кол: {{.полигон_сделать_кол}}</p>
             <p class="card-text">Тип: {{.полигон_сделать_назв}}</p>