Forráskód Böngészése

Добавление кода

SVI 3 éve
szülő
commit
71039a4052
72 módosított fájl, 9863 hozzáadás és 0 törlés
  1. 21 0
      server/helper/helper.go
  2. 73 0
      server/serv_bots/serv_bots.go
  3. 362 0
      server/serv_bots/warbot/angar/angar.go
  4. 314 0
      server/serv_bots/warbot/angar/angar_attack/angar_attack.go
  5. 43 0
      server/serv_bots/warbot/angar/angarnet/angarnet.go
  6. 382 0
      server/serv_bots/warbot/angar/base/arsenal/arsenal.go
  7. 35 0
      server/serv_bots/warbot/angar/base/arsenal/arsenalnet/arsenalnet.go
  8. 220 0
      server/serv_bots/warbot/angar/base/bank/bank.go
  9. 38 0
      server/serv_bots/warbot/angar/base/bank/bankmode/bankmode.go
  10. 37 0
      server/serv_bots/warbot/angar/base/bank/banknet/banknet.go
  11. 1053 0
      server/serv_bots/warbot/angar/base/base.go
  12. 36 0
      server/serv_bots/warbot/angar/base/basenet/basenet.go
  13. 181 0
      server/serv_bots/warbot/angar/base/market/market.go
  14. 35 0
      server/serv_bots/warbot/angar/base/market/marketnet/marketnet.go
  15. 379 0
      server/serv_bots/warbot/angar/base/mine/mine.go
  16. 42 0
      server/serv_bots/warbot/angar/base/mine/minenet/minenet.go
  17. 368 0
      server/serv_bots/warbot/angar/base/polygon/polygon.go
  18. 35 0
      server/serv_bots/warbot/angar/base/polygon/polygonnet/polygonnet.go
  19. 167 0
      server/serv_bots/warbot/angar/batmas/batmas.go
  20. 34 0
      server/serv_bots/warbot/angar/batmas/batmasnet/batmasnet.go
  21. 106 0
      server/serv_bots/warbot/angar/battle/battle.go
  22. 88 0
      server/serv_bots/warbot/angar/battle/battle_register/battle_register.go
  23. 99 0
      server/serv_bots/warbot/angar/battle/battle_wait/battle_wait.go
  24. 83 0
      server/serv_bots/warbot/angar/battle/battle_worker/battle_worker.go
  25. 183 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/battleon.go
  26. 49 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/battlesound/battlesound.go
  27. 41 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/battlesound/isplay/isplay.go
  28. 305 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/health.go
  29. 46 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/healthtime/healthtime.go
  30. 41 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/isrepair/isrepair.go
  31. 77 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/repairtime/repairtime.go
  32. 43 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/manevr/ismanevr/ismanevr.go
  33. 175 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/manevr/manevr.go
  34. 53 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/damage/damage.go
  35. 41 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/isshot/isshot.go
  36. 205 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/shot.go
  37. 76 0
      server/serv_bots/warbot/angar/battle/battle_worker/battleon/shottime/shottime.go
  38. 42 0
      server/serv_bots/warbot/angar/battle/battlenet/battlenet.go
  39. 33 0
      server/serv_bots/warbot/angar/battle/isrun/isrun.go
  40. 466 0
      server/serv_bots/warbot/angar/convoy/convoy.go
  41. 42 0
      server/serv_bots/warbot/angar/convoy/convoynet/convoynet.go
  42. 14 0
      server/serv_bots/warbot/angar/division/division.go
  43. 228 0
      server/serv_bots/warbot/angar/division/divwar/divwar.go
  44. 34 0
      server/serv_bots/warbot/angar/division/divwar/divwarnet/divwarnet.go
  45. 190 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/divwaron.go
  46. 49 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/divwarsound/divwarsound.go
  47. 41 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/divwarsound/isplay/isplay.go
  48. 358 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/health/health.go
  49. 46 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/health/healthtime/healthtime.go
  50. 77 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/health/repairtime/repairtime.go
  51. 43 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/manevr/ismanevr/ismanevr.go
  52. 193 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/manevr/manevr.go
  53. 53 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/shot/damage/damage.go
  54. 238 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/shot/shot.go
  55. 76 0
      server/serv_bots/warbot/angar/division/divwar/divwaron/shottime/shottime.go
  56. 91 0
      server/serv_bots/warbot/angar/fuel/fuel.go
  57. 519 0
      server/serv_bots/warbot/angar/missions/missions.go
  58. 34 0
      server/serv_bots/warbot/angar/missions/missionsnet/missionsnet.go
  59. 304 0
      server/serv_bots/warbot/angar/netstat/netstat.go
  60. 47 0
      server/serv_bots/warbot/tank/basestat/basestat.go
  61. 36 0
      server/serv_bots/warbot/tank/tank.go
  62. 58 0
      server/serv_bots/warbot/tank/tankstat/static_param/static_param.go
  63. 64 0
      server/serv_bots/warbot/tank/tankstat/tankstat.go
  64. 104 0
      server/serv_bots/warbot/warbot.go
  65. 64 0
      server/serv_bots/warbot/warbot_net/bot_cookie/bot_cookie.go
  66. 38 0
      server/serv_bots/warbot/warbot_net/bot_net_conn/bot_net_conn.go
  67. 199 0
      server/serv_bots/warbot/warbot_net/bot_net_login/bot_net_login.go
  68. 84 0
      server/serv_bots/warbot/warbot_net/warbot_net.go
  69. 77 0
      server/serv_gui/serv_gui.go
  70. 88 0
      server/serv_web/serv_web.go
  71. 183 0
      server/serv_web/web_api/web_api.go
  72. 84 0
      server/server.go

+ 21 - 0
server/helper/helper.go

@@ -0,0 +1,21 @@
+// package helper --
+package helper
+
+import (
+	"fmt"
+)
+
+// Печатает башку приложухи
+func PrintHeader(port string) {
+	fmt.Println()
+	fmt.Println()
+	fmt.Println("/=================================[ wartank.ru ] ================================\\")
+	fmt.Println("|                                                                                |")
+	fmt.Println("|                                                                                |")
+	fmt.Printf("|        Для слежения за ботом открыть ссылку: http://localhost:%v/           |\n", port)
+	fmt.Println("|                                                                                |")
+	fmt.Println("|                                                                                |")
+	fmt.Println("\\===============================[ FunnyBot Soft ]================================/")
+	fmt.Println()
+	fmt.Println()
+}

+ 73 - 0
server/serv_bots/serv_bots.go

@@ -0,0 +1,73 @@
+// package serv_bots -- словарь ботов сервера
+package serv_bots
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"sync"
+
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot"
+)
+
+// ServBots -- словарь ботов на сервере
+type ServBots struct {
+	server types.IServer
+	store  types.IStore
+	dict   map[string]types.IServBot
+	block  sync.RWMutex
+}
+
+// NewServBots -- возвращает новый словарь серверных ботов
+func NewServBots(server types.IServer) (*ServBots, error) {
+	if server == nil {
+		return nil, fmt.Errorf("NewServBots(): IServer == nil")
+	}
+	sf := &ServBots{
+		server: server,
+		store:  server.Store(),
+		dict:   make(map[string]types.IServBot),
+	}
+	return sf, nil
+}
+
+// Get -- возвращает боевого бота по имени
+func (sf *ServBots) Get(name string) types.IServBot {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	bot, isOk := sf.dict[name]
+	if !isOk {
+		return nil
+	}
+	return bot
+}
+
+// Загружает ботов из хранилища
+func (sf *ServBots) Load() error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	log.Println("ServBots.load()")
+	mapRes, err := sf.store.Find("/bot/list")
+	if err != nil {
+		return fmt.Errorf("ServBots.load(): in get list bot, err=\n\t%w", err)
+	}
+	strList := mapRes["/bot/list"]
+	lstBot := make([]string, 0)
+	if strList != "" { // Такое может быть при первом запуске
+		err = json.Unmarshal([]byte(strList), &lstBot)
+		if err != nil {
+			return fmt.Errorf("ServBots.load(): in unmarshal list bot, err=\n\t%w", err)
+		}
+	}
+
+	for _, name := range lstBot {
+		bot, err := warbot.NewWarBot(sf.server, name)
+		if err != nil {
+			return fmt.Errorf("ServBots.load(): in create bot %q, err=\n\t%w", name, err)
+		}
+		go bot.Run()
+		sf.dict[name] = bot
+	}
+	return nil
+}

+ 362 - 0
server/serv_bots/warbot/angar/angar.go

@@ -0,0 +1,362 @@
+package angar
+
+import (
+	"fmt"
+	"sync"
+
+	// "log"
+	"strconv"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/angar_attack"
+	"wartank/server/serv_bots/warbot/angar/angarnet"
+	"wartank/server/serv_bots/warbot/angar/base"
+	"wartank/server/serv_bots/warbot/angar/batmas"
+	"wartank/server/serv_bots/warbot/angar/battle"
+	"wartank/server/serv_bots/warbot/angar/convoy"
+	"wartank/server/serv_bots/warbot/angar/fuel"
+	"wartank/server/serv_bots/warbot/angar/missions"
+	"wartank/server/serv_bots/warbot/angar/netstat"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Объект ангара приложения
+*/
+
+// Angar -- ангар для танка
+type Angar struct {
+	*section.Section
+	net     *angarnet.AngarNet
+	netKill *angar_attack.AngarAttack
+
+	server types.IServer
+	bot    types.IServBot
+	convoy *convoy.Convoy
+	battle *battle.Battle
+	batMas *batmas.BatMas
+	base   *base.Base
+	award  *missions.Missions
+
+	gold     types.IStatParam
+	level    types.IStatParam
+	progress types.IStatParam
+	fuel     *fuel.Fuel
+	online   types.IStatParam
+
+	silverAll    types.IStatParam
+	silverOnline types.IStatParam
+
+	stat  *netstat.NetStat
+	block sync.Mutex
+}
+
+// NewAngar -- возвращает новый *Angar
+func NewAngar(server types.IServer, bot types.IServBot) (*Angar, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewAngar(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewAngar(): IServBot == nil")
+		}
+	}
+
+	sf := &Angar{
+		server:   server,
+		bot:      bot,
+		gold:     static_param.NewStaticParam("gold"),
+		level:    static_param.NewStaticParam("level"),
+		progress: static_param.NewStaticParam("progress"),
+		online:   static_param.NewStaticParam("online"),
+
+		silverAll:    static_param.NewStaticParam("silver_all"),
+		silverOnline: static_param.NewStaticParam("silver_online"),
+	}
+	return sf, nil
+}
+
+// Одноразовый вызов для создания компонентов при запуске в работу
+func (sf *Angar) make() error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	var err error
+	{ // Section
+		sf.Section, err = section.NewSection(sf.server, `<title>Ангар</title>`)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // Сеть
+		sf.net, err = angarnet.NewAngarNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create <AngarNet>, err=\n\t%w", err)
+		}
+	}
+	{ // Сеть атаки
+		sf.netKill, err = angar_attack.NewAngarAttack(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create <AngarAttackNet>, err=\n\t%w", err)
+		}
+	}
+	{ // Статистика
+		sf.stat, err = netstat.NewNetStat(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create NetResource, err=\n\t%w", err)
+		}
+	}
+	{ // Convoy
+		sf.convoy, err = convoy.NewConvoy(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create IConvoy, err=\n\t%w", err)
+		}
+	}
+	{ // Сражение
+		sf.battle, err = battle.NewBattle(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create IBattle, err=\n\t%w", err)
+		}
+	}
+	{ // Битва мастеров
+		sf.batMas, err = batmas.NewBatMas(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in create *BatMas, err=\n\t%w", err)
+		}
+		// go sf.batMas.Run()
+	}
+	{ // База
+		sf.base, err = base.NewBase(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in make IBase, err=\n\t%w", err)
+		}
+	}
+	{ // Миссии
+		sf.award, err = missions.NewMissions(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in make *Missions, err=\n\t%w", err)
+		}
+	}
+	{ // Топливо
+		sf.fuel, err = fuel.NewFuel(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Angar.make(): in make *Missions, err=\n\t%w", err)
+		}
+	}
+	return nil
+}
+
+// запускает обработку ангара
+func (sf *Angar) Run() error {
+	{ // Запуск компонентов
+		err := sf.make()
+		if err != nil {
+			return fmt.Errorf("Angar.Run(): при создании компонентов ангара, err=\n\t%w", err)
+		}
+		if err := sf.net.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске сетевой секции ангара, err=\n\t%w", err)
+		}
+		if err := sf.netKill.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске сетевой секции опыт за топливо, err=\n\t%w", err)
+		}
+		if err := sf.convoy.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске конвоя, err=\n\t%w", err)
+		}
+		if err := sf.battle.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске сражения, err=\n\t%w", err)
+		}
+		if err := sf.batMas.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске схватки, err=\n\t%w", err)
+		}
+		if err := sf.base.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске базы, err=\n\t%w", err)
+		}
+		if err := sf.award.Run(); err != nil {
+			return fmt.Errorf("Angar.Run(): при пуске наград, err=\n\t%w", err)
+		}
+		go sf.fuel.Run()
+	}
+
+	go func() {
+		sf.CountDown().Set(1)
+		count := 0
+		for {
+			select {
+			case <-sf.server.Done(): // Отмена контекста
+				sf.CountDown().Stop()
+				return
+			case <-sf.CountDown().ChanSig(): // Метка времени
+				sf.updateResurs()
+				sf.stat.Update()
+				switch count {
+				case 0:
+					sf.checkConvoy()
+					count = 6
+				default:
+					count--
+				}
+				sf.CountDown().Set(60)
+			}
+		}
+	}()
+	return nil
+}
+
+// Обновляет ресурсы ангара
+func (sf *Angar) updateResurs() {
+	if err := sf.net.UpdateLst("ангар"); err != nil {
+		// log._rintf("ERRO Angar.updateResurs(): при обработке ангара в сети, err=\n\t%v\n", err)
+		return
+	}
+	sf.findGold()
+	sf.findSilver()
+}
+
+// IMissions -- возвращает ссылку на объект миссий
+func (sf *Angar) Missions() types.IMissions {
+	return sf.award
+}
+
+// Battle -- возвращает объект сражения
+func (sf *Angar) Battle() types.IBattle {
+	return sf.battle
+}
+
+// Convoy -- возвращает объект конвоя
+func (sf *Angar) Convoy() types.IConvoy {
+	return sf.convoy
+}
+
+// Gold -- возвращает объект золота
+func (sf *Angar) Gold() types.IStatParam {
+	return sf.gold
+}
+
+// Fuel -- возвращает объект топлива
+func (sf *Angar) Fuel() types.IStatParam {
+	return sf.fuel.Fuel()
+}
+
+// Level -- возвращает объект уровня игрока
+func (sf *Angar) Level() types.IStatParam {
+	return sf.level
+}
+
+// Progress -- возвращает прогрес уровня игрока
+func (sf *Angar) Progress() types.IStatParam {
+	return sf.progress
+}
+
+// Online -- возвращает количество игроков онлайн
+func (sf *Angar) Online() types.IStatParam {
+	return sf.online
+}
+
+// SilverAll -- возвращает объект всего серебра в ангаре
+func (sf *Angar) SilverAll() types.IStatParam {
+	return sf.silverAll
+}
+
+// SilverOnline -- возвращает объект серебра за сессию в ангаре
+func (sf *Angar) SilverOnline() types.IStatParam {
+	return sf.silverOnline
+}
+
+// SilverUpdate -- на основе фактического серебра -- обновляет вырабатанное серебро
+func (sf *Angar) SilverUpdate(silverFact int) {
+	if sf.silverAll.Get() == 0 { // Если запуск
+		sf.silverAll.Set(silverFact)
+		return
+	}
+	if sf.silverAll.Get() > silverFact { // Если потрачего серебро
+		sf.silverAll.Set(silverFact)
+		return
+	}
+	if sf.silverAll.Get() < silverFact { // Если заработано
+		so := sf.silverOnline.Get()
+		sd := silverFact - sf.silverAll.Get()
+		sf.silverOnline.Set(so + sd)
+	}
+	sf.silverAll.Set(silverFact)
+}
+
+// Base -- возвращает базу
+func (sf *Angar) Base() types.IBase {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	return sf.base
+}
+
+// Проверяет на исполнение конвоя
+func (sf *Angar) checkConvoy() {
+	var (
+		strOut   string
+		isFind   bool
+		lstAngar = sf.GetLst()
+	)
+	for _, strOut = range lstAngar {
+		if strings.Contains(strOut, `>Конвой</span>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `<a class="simple-but border gray mb1" href="convoy"><span><span>Конвой</span></span></a>`) {
+		return
+	}
+	sf.convoy.UpdateLst()
+}
+
+// Ищет в теле текста ангара серебро
+func (sf *Angar) findSilver() {
+	// _mt.Println("\tAngarNet.findSilver()")
+	lstAngar := sf.GetLst()
+	var strOut string
+	for _, strSilver := range lstAngar {
+		if strings.Contains(strSilver, `<img title="Серебро" `) {
+			strOut = strSilver
+			break
+		}
+	}
+	// Выделить топливо
+	lstSilver := strings.Split(strOut, `<img title="Серебро" alt="Серебро" src="/images/icons/silver.png?2"/> `)
+	strSilver := lstSilver[1]
+	iSilver, err := strconv.Atoi(strSilver)
+	if err != nil {
+		// log._rintf("ERRO AngarNet.findSilver(): silver(%v) not number, err=\n\t%v\n", strSilver, err)
+		return
+	}
+	sf.silverAll.Set(iSilver)
+}
+
+// Ищет в теле текста ангара золото
+func (sf *Angar) findGold() {
+	var (
+		lstAngar = sf.bot.Angar().GetLst()
+		strOut   string
+		isFind   bool
+	)
+
+	for _, strGold := range lstAngar {
+		if strings.Contains(strGold, `<img title="Золото" `) {
+			strOut = strGold
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// Выделить топливо
+	lstGold := strings.Split(strOut, `<img title="Золото" alt="Золото" src="/images/icons/gold.png?2"/> `)
+	strGold := lstGold[1]
+	iGold, err := strconv.Atoi(strGold)
+	if err != nil {
+		// log._rintf("ERRO AngarNet.findGold(): gold(%v) not number, err=\n\t%v\n", strGold, err)
+		return
+	}
+	sf.bot.Angar().Gold().Set(iGold)
+}

+ 314 - 0
server/serv_bots/warbot/angar/angar_attack/angar_attack.go

@@ -0,0 +1,314 @@
+// package angar_attack
+package angar_attack
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"strings"
+	"time"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Объект боя на топливе
+*/
+
+// AngarAttack -- объект боя на топливе
+type AngarAttack struct {
+	*sectionnet.SectionNet
+	server types.IServer
+	angar  types.IAngar
+	bot    types.IServBot
+}
+
+// NewAngarAttack -- возвращает новый *AngarAttackNet
+func NewAngarAttack(server types.IServer, bot types.IServBot) (*AngarAttack, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewAngarAttack(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewAngarAttack(): IServBot == nil")
+		}
+	}
+
+	sf := &AngarAttack{
+		server: server,
+		angar:  bot.Angar(),
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+func (sf *AngarAttack) Run() error {
+	sf.SectionNet = sectionnet.NewSectionNet(sf.server, sf.bot, sf.angar, "http://wartank.ru/battle")
+	go sf.run()
+	return nil
+}
+
+func (sf *AngarAttack) run() {
+	for {
+		time.Sleep(time.Second * 15) // Минимальный интервал ожттдания прибавки топлива
+		fuel := sf.angar.Fuel().Get()
+		if fuel < 314 { // Минимальная ёмкость бака -- 315
+			continue
+		}
+		log.Printf("Fuel.Run: val=%v\n", fuel)
+		lstBattle, err := sf.makeAtack()
+		if err != nil {
+			// log._rintf("ERRO AngarAttack.findFuel(): in get page battle, err=\n\t%v\n", err)
+			continue
+		}
+		lstShoot2, err := sf.makeSelectBattle(lstBattle)
+		if err != nil {
+			// log._rintf("ERRO AngarAttack.findFuel(): in get page shooting, err=\n\t%v\n", err)
+			continue
+		}
+		if err := sf.makeShooting(lstShoot2); err != nil {
+			log.Printf("ERRO AngarAttack.findFuel(): in make shooting, err=\n\t%v\n", err)
+		}
+	}
+
+}
+
+// Идёт в атаку, если топлива больше 90
+func (sf *AngarAttack) makeAtack() (lstBattle []string, err error) {
+	// Получить ссылку на атаку
+	// _mt.Println("\t AngarAttack.makeAtack()")
+	lstAngar := sf.angar.GetLst()
+	var strOut string
+	for _, strAtack := range lstAngar {
+		if strings.Contains(strAtack, `<span>В бой!</span>`) {
+			strOut = strAtack
+			break
+		}
+	}
+	// Вырезать ссылку на атаку
+	lstAngar = strings.Split(strOut, `<a class="simple-but border mb1" href="`)
+	linkBatle := lstAngar[1]
+	lstAngar = strings.Split(linkBatle, `"><span><span>В бой!</span></span></a>`)
+	linkBatle = "http://wartank.ru/" + lstAngar[0]
+	lstBattle, err = sf.Get(linkBatle)
+	if err != nil {
+		return nil, fmt.Errorf("AngarAttack.makeAtack(): in make GET-request to battle, err=\n\t%w", err)
+	}
+	fuel := sf.angar.Fuel().Get()
+	fuel -= 30
+	sf.angar.Fuel().Set(fuel)
+	return lstBattle, nil
+}
+
+// Выбирает первого более слабого противника и делает первый выстрел
+func (sf *AngarAttack) makeSelectBattle(lstBattle []string) (lstShoot2 []string, err error) {
+	// _mt.Println("\tAngarNet.makeSelectBattle()")
+	var strOut string
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			msg := time.Now().Local().Format("2006-01-02 15:04:05.000 ") + "fnShoot1\n"
+			msg += "\tNetClient.makeSelectBattle().fnShoot1()\n"
+			msg0 := fmt.Sprintf("%v\n", _panic)
+			msg1 := ""
+			for _, _msg := range strings.Split(msg0, "\n") {
+				if _msg == "" {
+					continue
+				}
+				msg1 += "\t" + _msg + "\n"
+			}
+			msg += msg1
+			// _mt.Println(msg)
+			err = fmt.Errorf("%v", msg)
+			msg1 = "" // Сброс накопленной ошибки
+			for _, _msg := range lstBattle {
+				if _msg == "" {
+					continue
+				}
+				msg1 += "\t" + _msg + "\n"
+			}
+			msg += msg1
+			// Выкинуть ошибку в файл
+			_ = os.MkdirAll("./errors", 0700)
+			err = os.WriteFile("./errors/attack_shoot1.html", []byte(msg), 0600)
+		}
+	}()
+	// Выдернуть строку с первой ссылкой на противника
+	for _, strBattle := range lstBattle {
+		if strings.Contains(strBattle, `opponents-opponents-0`) {
+			strOut = strBattle
+			break
+		}
+	}
+	var linkBattle string
+	switch strOut == "" {
+	case true: // Такая ситуация возможна, если уже были какие-то выстрелы
+		return lstBattle, nil
+	default: // Успешный выстрел
+		// Вырезать ссылку из строки
+		lstBattle = strings.Split(strOut, `<td class="cntr"><a href="`)
+		linkBattle = lstBattle[1]
+		lstBattle = strings.Split(linkBattle, `"><img class="tank-img" alt="tank" src="/tankimg?`)
+		linkBattle = "http://wartank.ru/" + lstBattle[0]
+	}
+	lstShoot2, err = sf.Get(linkBattle)
+	if err != nil {
+		return nil, fmt.Errorf("AngarAttack.makeSelectBattle(): in GET-response select battle tank, err=\n\t%w", err)
+	}
+	return lstShoot2, nil
+}
+
+// Ведёт бой в 2 выстрела (2 и 3 выстрел)
+func (sf *AngarAttack) makeShooting(lstShoot2 []string) error {
+	// _mt.Println("\tAngarNet.makeShooting()")
+	var lstShoot3 []string           // Тело страницы для третьего выстрела
+	fnShoot2 := func() (err error) { // Второй выстрел
+		// _mt.Println("\tAngarNet.makeShooting().fnShoot2()")
+		defer func() {
+			if _panic := recover(); _panic != nil {
+				msg := time.Now().Local().Format("2006-01-02 15:04:05.000 ") + "fnShoot2\n"
+				msg += "\tNetClient.makeShooting().fnShoot2()\n"
+				msg0 := fmt.Sprintf("%v\n", _panic)
+				msg1 := ""
+				for _, _msg := range strings.Split(msg0, "\n") {
+					if _msg == "" {
+						continue
+					}
+					msg1 += "\t" + _msg + "\n"
+				}
+				msg += msg1
+				// _mt.Println(msg)
+				err = fmt.Errorf("%v", msg)
+				msg1 = "" // Сброс накопленной ошибки
+				for _, _msg := range lstShoot2 {
+					if _msg == "" {
+						continue
+					}
+					msg1 += "\t" + _msg + "\n"
+				}
+				msg += msg1
+				// Выкинуть ошибку в файл
+				_ = os.MkdirAll("./errors", 0700)
+				err = os.WriteFile("./errors/attack_shoot2.html", []byte(msg), 0600)
+			}
+		}()
+		// Получить ссылку на второй выстрел
+		var strOut string
+		for _, strShoot := range lstShoot2 {
+			if strings.Contains(strShoot, `<span>Добить</span>`) {
+				strOut = strShoot
+				break
+			}
+		}
+		var linkShoot2 string
+		switch strOut == "" {
+		case true: // Первый выстрел был неудачным
+			for _, strShoot := range lstShoot2 {
+				if strings.Contains(strShoot, `<span>Взять реванш</span>`) {
+					strOut = strShoot
+					break
+				}
+			}
+			if strOut == "" { // Это ситуация для третьего выстрела
+				lstShoot3 = lstShoot2
+				return nil
+			}
+			// Вырезать ссылку из строки
+			lstShoot2 = strings.Split(strOut, `<a class="simple-but border" href="`)
+			linkShoot2 = lstShoot2[1]
+			lstShoot2 = strings.Split(linkShoot2, `"><span><span>Взять реванш</span></span></a>`)
+			linkShoot2 = "http://wartank.ru/" + lstShoot2[0]
+		default: // Первый выстрел был удачным
+			// Вырезать ссылку из строки
+			lstShoot2 = strings.Split(strOut, `<a class="simple-but border" href="`)
+			linkShoot2 = lstShoot2[1]
+			lstShoot2 = strings.Split(linkShoot2, `"><span><span>Добить</span></span></a>`)
+			linkShoot2 = "http://wartank.ru/" + lstShoot2[0]
+		}
+		lstShoot3, err = sf.Get(linkShoot2)
+		if err != nil {
+			return fmt.Errorf("AngarAttack.makeShooting(): in Get-response shoot2, err=\n\t%w", err)
+		}
+		fuel := sf.angar.Fuel().Get()
+		fuel -= 30
+		sf.angar.Fuel().Set(fuel)
+		return nil
+	}
+	if err := fnShoot2(); err != nil {
+		return err
+	}
+	fnShoot3 := func() (err error) { // Третий выстрел
+		// _mt.Println("\tAngarNet.makeShooting().fnShoot3()")
+		defer func() {
+			if _panic := recover(); _panic != nil {
+				msg := time.Now().Local().Format("2006-01-02 15:04:05.000 fnShoot3\n")
+				msg += "\tNetClient.makeShooting().fnShoot3()\n"
+				msg0 := fmt.Sprintf("%v\n", _panic)
+				msg1 := ""
+				for _, _msg := range strings.Split(msg0, "\n") {
+					if _msg == "" {
+						continue
+					}
+					msg1 += "\t" + _msg + "\n"
+				}
+				msg += msg1
+				// _mt.Println(msg)
+				err = fmt.Errorf("%v", msg)
+				msg1 = "" // Сброс накопленной ошибки
+				for _, _msg := range lstShoot3 {
+					if _msg == "" {
+						continue
+					}
+					msg1 += "\t" + _msg + "\n"
+				}
+				msg += msg1
+				// Выкинуть ошибку в файл
+				_ = os.MkdirAll("./errors", 0700)
+				err = os.WriteFile("./errors/attack_shoot3.html", []byte(msg), 0600)
+			}
+		}()
+		// Получить ссылку на третий выстрел
+		var strOut string
+		for _, strShoot3 := range lstShoot3 {
+			if strings.Contains(strShoot3, `<span>Уничтожить</span>`) {
+				strOut = strShoot3
+				break
+			}
+		}
+		linkShoot3 := ""
+		switch strOut == "" {
+		case true: // Если не найдена ссылка -- значит было поражение в выстреле
+			if strOut == "" {
+				for _, strShoot3 := range lstShoot3 {
+					if strings.Contains(strShoot3, `<span>Взять реванш</span>`) {
+						strOut = strShoot3
+						break
+					}
+				}
+			}
+			// Вырезать ссылку из строки
+			lstShoot3 = strings.Split(strOut, `<a class="simple-but border" href="`)
+			linkShoot3 = lstShoot3[1]
+			lstShoot3 = strings.Split(linkShoot3, `"><span><span>Взять реванш</span></span></a>`)
+			linkShoot3 = "http://wartank.ru/" + lstShoot3[0]
+		default: // Успешный выстрел
+			// Вырезать ссылку из строки
+			lstShoot3 = strings.Split(strOut, `<a class="simple-but border" href="`)
+			linkShoot3 = lstShoot3[1]
+			lstShoot3 = strings.Split(linkShoot3, `"><span><span>Уничтожить</span></span></a>`)
+			linkShoot3 = "http://wartank.ru/" + lstShoot3[0]
+		}
+
+		if _, err = sf.Get(linkShoot3); err != nil {
+			return fmt.Errorf("AngarAttack.makeShooting(): in Get-response shoot3, err=\n\t%w", err)
+		}
+		fuel := sf.angar.Fuel().Get()
+		fuel -= 30
+		sf.angar.Fuel().Set(fuel)
+		return nil
+	}
+	if err := fnShoot3(); err != nil {
+		return err
+	}
+	return nil
+}

+ 43 - 0
server/serv_bots/warbot/angar/angarnet/angarnet.go

@@ -0,0 +1,43 @@
+package angarnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Объект сетевого ангара
+*/
+
+// AngarNet -- объект сетевого ангара
+type AngarNet struct {
+	*sectionnet.SectionNet
+	server types.IServer
+	bot    types.IServBot
+}
+
+// NewAngarNet -- возвращает новый *AngarNet
+func NewAngarNet(server types.IServer, bot types.IServBot) (*AngarNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewAngarNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewAngarNet(): IServBot is nil")
+		}
+	}
+
+	sf := &AngarNet{
+		server: server,
+		bot:    bot,
+	}
+
+	return sf, nil
+}
+
+func (sf *AngarNet) Run() error {
+	sf.SectionNet = sectionnet.NewSectionNet(sf.server, sf.bot, sf.bot.Angar(), "http://wartank.ru/angar")
+	return nil
+}

+ 382 - 0
server/serv_bots/warbot/angar/base/arsenal/arsenal.go

@@ -0,0 +1,382 @@
+package arsenal
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/base/arsenal/arsenalnet"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Объект арсенала на базе
+*/
+
+// Arsenal -- объект арсенала на базе
+type Arsenal struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	base   types.IBase
+	net    *arsenalnet.ArsenalNet
+	fugas  types.IStatParam
+	armor  types.IStatParam
+	kumul  types.IStatParam
+	remka  types.IStatParam
+}
+
+// NewArsenal -- возвращает новый *Arsenal
+func NewArsenal(server types.IServer, bot types.IServBot) (*Arsenal, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewArsenal(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewArsenal(): IServBot == nil")
+		}
+	}
+
+	sf := &Arsenal{
+		server: server,
+		bot:    bot,
+		base:   bot.Angar().Base(),
+		fugas:  static_param.NewStaticParam("fugas"),
+		armor:  static_param.NewStaticParam("armor"),
+		kumul:  static_param.NewStaticParam("kumul"),
+		remka:  static_param.NewStaticParam("remka"),
+	}
+
+	return sf, nil
+}
+
+func (sf *Arsenal) Run() error {
+	var err error
+	{ // ISection
+		sf.Section, err = section.NewSection(sf.server, `<span class="green2">Ремкомплект</span><br/>`)
+		if err != nil {
+			return fmt.Errorf("Arsenal.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // ArsenalNet
+		sf.net, err = arsenalnet.NewArsenalNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Arsenal.Run(): in create NetArsenal, err=\n\t%w", err)
+		}
+	}
+	go sf.run()
+	return nil
+}
+
+// запускает обработку арсенала
+func (sf *Arsenal) run() {
+	// sf.getTime()
+	for {
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig():
+			if sf.ModeCurrent().Get() == "upgrade" {
+				continue
+			}
+			// if err := sf.updateArsenal(); err != nil {
+			// 	return fmt.Errorf("ArsenalNet.Run(): in update arsenal, err=\n\t%w", err)
+			// }
+			sf.makeArsenal()
+		}
+		// time.Sleep(time.Second * 30)
+	}
+}
+
+// Fugas -- возвращает объект числа фугасов
+func (sf *Arsenal) Fugas() types.IStatParam {
+	return sf.fugas
+}
+
+// Armor -- возвращает объект бронебойных снарядов
+func (sf *Arsenal) Armor() types.IStatParam {
+	return sf.armor
+}
+
+// Kumul -- возвращает объект бронебойных снарядов
+func (sf *Arsenal) Kumul() types.IStatParam {
+	return sf.kumul
+}
+
+// Remka -- возвращает объект ремкомплектов
+func (sf *Arsenal) Remka() types.IStatParam {
+	return sf.remka
+}
+
+// Обновляет состояние арсенала по требованию
+func (sf *Arsenal) UpdateArsenal() (err error) {
+	// _mt.Println("\tArsenalNet.updateArsenal()")
+	if err := sf.net.UpdateLst("арсенал"); err != nil {
+		return fmt.Errorf("Arsenal.UpdateArsenal(): при обновлении lstArsenal, err=%w", err)
+	}
+	var (
+		strOut     string
+		lstArsenal = sf.GetLst()
+	)
+	{ // Найти маркер фугасного снаряда
+		for _, strFugas := range lstArsenal {
+			if strings.Contains(strFugas, `<span class="nwr"><img class="rico vm" src="/images/shells/HighExplosive.png" alt="Фугасный снаряд" title="Фугасный снаряд"/> `) {
+				strOut = strFugas
+				break
+			}
+		}
+		lstFugas := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/shells/HighExplosive.png" alt="Фугасный снаряд" title="Фугасный снаряд"/> `)
+		strFugas := lstFugas[1]
+		lstFugas = strings.Split(strFugas, ` &nbsp;&nbsp;</span>`)
+		strFugas = lstFugas[0]
+		iFugas, err := strconv.Atoi(strFugas)
+		if err != nil {
+			return fmt.Errorf("ArsenalNet.updateArsenal(): fugas(%v) not number, err=\n\t%w", strFugas, err)
+		}
+		sf.Fugas().Set(iFugas)
+	}
+	{ // Найти маркер бронебойного снаряда
+		for _, strArmor := range lstArsenal {
+			if strings.Contains(strArmor, `alt="Бронебойный снаряд"`) {
+				strOut = strArmor
+				break
+			}
+		}
+		lstArmor := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/shells/ArmorPiercing.png" alt="Бронебойный снаряд" title="Бронебойный снаряд"/> `)
+		strArmor := lstArmor[1]
+		lstArmor = strings.Split(strArmor, ` &nbsp;&nbsp;</span>`)
+		strArmor = lstArmor[0]
+		iArmor, err := strconv.Atoi(strArmor)
+		if err != nil {
+			return fmt.Errorf("ArsenalNet.updateArsenal(): armor(%v) not number, err=\n\t%w", strArmor, err)
+		}
+		sf.Armor().Set(iArmor)
+	}
+	{ // Найти маркер кумулятивного снаряда
+		for _, strKumul := range lstArsenal {
+			if strings.Contains(strKumul, `<span class="nwr"><img class="rico vm" src="/images/shells/HollowCharge.png" alt="Кумулятивный снаряд" title="Кумулятивный снаряд"/> `) {
+				strOut = strKumul
+				break
+			}
+		}
+		lstKumul := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/shells/HollowCharge.png" alt="Кумулятивный снаряд" title="Кумулятивный снаряд"/> `)
+		strKumul := lstKumul[1]
+		lstKumul = strings.Split(strKumul, ` &nbsp;&nbsp;</span>`)
+		strKumul = lstKumul[0]
+		iKumul, err := strconv.Atoi(strKumul)
+		if err != nil {
+			return fmt.Errorf("ArsenalNet.updateArsenal(): kumul(%v) not number, err=\n\t%w", strKumul, err)
+		}
+		sf.Kumul().Set(iKumul)
+	}
+	{ // Найти маркер ремкомплекта
+		for ind, strRemka := range lstArsenal {
+			if strings.Contains(strRemka, `<span class="nwr"><img class="rico vm" src="/images/shells/repairkit.gif"/> `) {
+				strOut = lstArsenal[ind]
+				break
+			}
+		}
+		lstRemka := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/shells/repairkit.gif"/> `)
+		strRemka := lstRemka[1]
+		lstRemka = strings.Split(strRemka, `</span>`)
+		strRemka = lstRemka[0]
+		iRemka, err := strconv.Atoi(strRemka)
+		if err != nil {
+			return fmt.Errorf("ArsenalNet.updateArsenal(): remka(%v) not number, err=\n\t%w", strRemka, err)
+		}
+		sf.Remka().Set(iRemka)
+	}
+	return nil
+}
+
+// Выбирает что надо делать, запускает процесс изготовления
+func (sf *Arsenal) makeArsenal() {
+	err := sf.net.UpdateLst("арсенал")
+	if err != nil {
+		// log._rintf("ERRO Arsenal.Run(): при обновлении lstArsenal, err=\n\t%v\n", err)
+		return
+	}
+	fnMakeRemka := func() {
+		if err = sf.makeRemka(); err != nil {
+			// log._rintf("ERRO ArsenalNet.makeArsenal(): in make remka, err=\n\t%v\n", err)
+			return
+		}
+	}
+	// _mt.Println("\tArsenalNet.makeArsenal()")
+	{ // Контроль ремки по времени суток и минимальному количеству
+		iRemka := sf.Remka().Get()
+		if iRemka < 70 {
+			fnMakeRemka()
+			return
+		}
+	}
+	{ // Контроль по числу снарядов. В равных долях без приоритетов
+		iFugas := sf.Fugas().Get()
+		iKumul := sf.Kumul().Get()
+		iArmor := sf.Armor().Get()
+
+		typeArmor := "fugas"
+		typeVal := iFugas
+
+		if iKumul < typeVal {
+			typeArmor = "kumul"
+			typeVal = iKumul
+		}
+
+		if iArmor < typeVal {
+			typeArmor = "armor"
+		}
+		switch typeArmor {
+		case "fugas": // Мало фугасов
+			sf.makeFugas()
+		case "kumul": // Мало кумулятивов
+			sf.makeKumul()
+		case "armor": // Мало бронебойных
+			sf.makeArmor()
+		default:
+			// log._rintf("ERRO ArsenalNet.makeArsenal(): неизвестный тип арсенала(%v)", typeArmor)
+		}
+	}
+}
+
+// Создать бронебойные
+func (sf *Arsenal) makeArmor() {
+	var (
+		strOut     string
+		lstArsenal = sf.GetLst()
+		isFind     bool
+	)
+	for ind, strArmor := range lstArsenal {
+		if strings.Contains(strArmor, `<span class="green2">Бронебойный снаряд</span><br/>`) {
+			strOut = lstArsenal[ind+10]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if strOut == `<div class="clrb"></div>` { // Уже запущено производство
+		return
+	}
+	if strOut == `</div></div></div></div></div></div></div></div>` { // Уже запущено производство
+		return
+	}
+	// Получить ссылку на бронебойные
+	lstArmor := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstArmor[1]
+	lstArmor = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/" + lstArmor[0]
+	if _, err := sf.net.Get(strLink); err != nil {
+		// log._rintf("ERRO ArsenalNet.makeArmor(): in update lstArsenal,  err=\n\t%v\n", err)
+		return
+	}
+	sf.ModeCurrent().WorkSet("бронебойные")
+}
+
+// Создать кумулятивные
+func (sf *Arsenal) makeKumul() {
+	var (
+		strOut     string
+		lstArsenal = sf.GetLst()
+		isFind     bool
+		ind        int
+	)
+	for ind, strOut = range lstArsenal {
+		if strings.Contains(strOut, `<span class="green2">Кумулятивный снаряд</span><br/>`) {
+			ind += 10
+			strOut = lstArsenal[ind]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `>Начать производство<`) {
+		return
+	}
+	// Получить ссылку на кумулятив
+	lstKumul := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstKumul[1]
+	lstKumul = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/" + lstKumul[0]
+	if _, err := sf.net.Get(strLink); err != nil {
+		// log._rintf("ERRO ArsenalNet.makeKumul(): in make product arsenal kumul , err=\n\t%v\n", err)
+		return
+	}
+	sf.ModeCurrent().WorkSet("кумулятивы")
+}
+
+// Создать фугасы
+func (sf *Arsenal) makeFugas() {
+	var (
+		lstArsenal = sf.GetLst()
+		strOut     = ""
+		isFind     bool
+		ind        int
+	)
+
+	for ind, strOut = range lstArsenal {
+		if strings.Contains(strOut, `<span class="green2">Фугасный снаряд</span><br/>`) {
+			strOut = lstArsenal[ind+10]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `"><span><span>Начать производство</span></span></a>`) {
+		return
+	}
+	// Получить ссылку на ремку
+	lstKumul := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstKumul[1]
+	lstKumul = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/" + lstKumul[0]
+	if _, err := sf.net.Get(strLink); err != nil {
+		// log._rintf("ERRO ArsenalNet.makeFugas(): in make request arsenal product, err=\n\t%v\n", err)
+		return
+	}
+	sf.ModeCurrent().WorkSet("фугасы")
+	// log._rintf("INFO Arsenal.makeFugas()\n")
+}
+
+// Создать ремку. Выполняется если подходят условия
+func (sf *Arsenal) makeRemka() (err error) {
+	// _mt.Println("\tArsenalNet.makeRemka()")
+	var (
+		strOut     = ""
+		isFind     bool
+		lstArsenal = sf.GetLst()
+	)
+
+	for ind, strRemka := range lstArsenal {
+		if strings.Contains(strRemka, `<span class="green2">Ремкомплект</span><br/>`) {
+			ind += 10
+			strOut = lstArsenal[ind]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return nil
+	}
+	// Получить ссылку на ремку
+	lstRemka := strings.Split(strOut, `<a class="simple-but border" href="`)
+	if len(lstRemka) < 2 { // Значит уже запущено в производство
+		return
+	}
+	strLink := lstRemka[1]
+	lstRemka = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/Armory" + lstRemka[0]
+	if _, err = sf.net.Get(strLink); err != nil {
+		return fmt.Errorf("ArsenalNet.makeRemka(): in getpagebank product, err=\n\t%w", err)
+	}
+	sf.ModeCurrent().WorkSet("ремка")
+	return nil
+}

+ 35 - 0
server/serv_bots/warbot/angar/base/arsenal/arsenalnet/arsenalnet.go

@@ -0,0 +1,35 @@
+package arsenalnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Планировщик боеприпасов и ремок для сети
+*/
+
+// ArsenalNet -- планировщик беоприпасов и ремок в сети
+type ArsenalNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewArsenalNet -- возвращает новый *ArsenalNet
+func NewArsenalNet(server types.IServer, bot types.IServBot) (*ArsenalNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewArsenalNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewArsenalNet(): IServBot == nil")
+		}
+	}
+
+	sf := &ArsenalNet{
+		SectionNet: sectionnet.NewSectionNet(server, bot, bot.Angar().Base().Arsenal(), "http://wartank.ru/production/Armory"),
+	}
+
+	return sf, nil
+}

+ 220 - 0
server/serv_bots/warbot/angar/base/bank/bank.go

@@ -0,0 +1,220 @@
+package bank
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/base/bank/bankmode"
+	"wartank/server/serv_bots/warbot/angar/base/bank/banknet"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Предоставляет объект банка на базе
+*/
+
+// Bank -- объект банка на базе
+type Bank struct {
+	*section.Section
+	server    types.IServer
+	bot       types.IServBot
+	net       *banknet.BankNet
+	silverBot types.IStatParam
+	mode1     *bankmode.BankMode
+	mode2     *bankmode.BankMode
+}
+
+// NewBank -- возвращает новый *Bank
+func NewBank(server types.IServer, bot types.IServBot) (*Bank, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBank(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBank(): IServBot == nil")
+		}
+	}
+
+	sf := &Bank{
+		server:    server,
+		bot:       bot,
+		silverBot: static_param.NewStaticParam("silver_bot"),
+		mode1:     bankmode.NewBankMode(),
+		mode2:     bankmode.NewBankMode(),
+	}
+
+	return sf, nil
+}
+
+func (sf *Bank) Run() error {
+	log.Printf("Bank.Run()\n")
+	var err error
+	{ // Section
+		// sf.Section, err = section.NewSection(`<div class="thumb fl"><img src="/images/silver.jpg" alt="Серебро" title="Серебро"/>`, sf.chCall)
+		// <span class="green2">Серебро</span><br/>
+		sf.Section, err = section.NewSection(sf.server, `<span class="green2">Серебро</span><br/>`)
+		if err != nil {
+			return fmt.Errorf("Bank.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // BankNet
+		sf.net, err = banknet.NewBankNet(sf.server, sf, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Bank.Run(): in create NetBank, err=\n\t%w", err)
+		}
+	}
+	// sf.CountDown().S_t(1)
+	go sf.run()
+	return nil
+}
+
+// UpdateLst -- принудительно обновляет состояние банка
+func (sf *Bank) UpdateLst() {
+	if err := sf.net.UpdateLst("Банк"); err != nil {
+		log.Printf("Bank.UpdateLst(): err=\n\t%v\n", err)
+	}
+}
+
+// запускает банк в опрос
+func (sf *Bank) run() {
+	log.Printf("Bank.run()\n")
+	sf.CountDown().Set(5)
+	for range sf.CountDown().ChanSig() {
+		// time.Sleep(time.Second * 30)
+		count := sf.CountDown().Get()
+		log.Printf("Bank countDown=%d\n", count)
+		if err := sf.net.UpdateLst("банк"); err != nil {
+			// log._rintf("ERRO Bank.Run():  при обновлении lstBank, err=\n\t%v\n", err)
+			continue
+		}
+		if err := sf.getAllMode(); err != nil {
+			// log._rintf("ERRO Bank.Run(): при получении списка режимов банка, err=\n\t%v\n", err)
+			continue
+		}
+		if err := sf.makeProduct(); err != nil {
+			log.Printf("ERRO Bank.Run(): при пуске производства банка, err=\n\t%v\n", err)
+		}
+	}
+}
+
+// Mode2 -- возвращает объект режима2
+func (sf *Bank) Mode2() types.IBankMode {
+	return sf.mode2
+}
+
+// Mode1 -- возвращает объект режима1
+func (sf *Bank) Mode1() types.IBankMode {
+	return sf.mode1
+}
+
+// SilverBot -- возвращает серебро от бота
+func (sf *Bank) SilverBot() types.IStatParam {
+	return sf.silverBot
+}
+
+// Запускает в производство серебро
+func (sf *Bank) makeProduct() error {
+	var (
+		lstBank = sf.GetLst()
+		ind     int
+		strOut  string
+		strLink string
+		isFind  bool
+	)
+	time1 := sf.Mode1().Time()
+	time2 := sf.Mode2().Time()
+	if time1 > time2 {
+		time1 = time2
+	}
+	for ind, strOut = range lstBank {
+		if strings.Contains(strOut, time1) {
+			ind += 7
+			strLink = lstBank[ind]
+			isFind = true
+			break
+		}
+	}
+	if isFind && strings.Contains(strLink, `>Начать производство</span>`) {
+		lstLink := strings.Split(strLink, `<a class="simple-but border" href="`)
+		strLink = lstLink[1]
+		lstLink = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+		strLink = "http://wartank.ru/production/" + lstLink[0]
+		lstBank, err := sf.net.Get(strLink)
+		if err != nil {
+			return fmt.Errorf("BankNet.makeProduct(): при выполнении GET-запроса начать производство, err=%w", err)
+		}
+		for _, strOut := range lstBank {
+			if strings.Contains(strOut, `<title>База</title>`) { // Это база, а не банк
+				return nil
+			}
+		}
+		if err = sf.Update(lstBank); err != nil {
+			return fmt.Errorf("BankNet.makeProduct(): при обновлении lstBank, err=%w", err)
+		}
+		if err := sf.CountDown().Parse(time1); err != nil {
+			log.Printf("WARN Bank.makeProduct(): при установке времени производства банка(%v)\n\terr=%v\n", time1, err)
+		}
+	}
+	return nil
+}
+
+// Получает все режимы банка
+func (sf *Bank) getAllMode() error {
+	var (
+		lstBank  = sf.GetLst()
+		ind      int
+		strMode  string
+		strMode1 string
+		strMode2 string
+	)
+	{ // Получить первый режим
+		for ind, strMode = range lstBank {
+			if strings.Contains(strMode, `Кол-во: <span class="green2">`) {
+				strMode1 = strMode
+				break
+			}
+		}
+		lstMode := strings.Split(strMode1, `Кол-во: <span class="green2">`)
+		strMode1 = lstMode[1]
+		lstMode = strings.Split(strMode1, `</span><br/>`)
+		strMode1 = lstMode[0]
+		iNum1, err := strconv.Atoi(strMode1)
+		if err != nil {
+			return fmt.Errorf("BankNet.getAllMode(): numSilver1(%v) not number, err=\n\t%w", strMode1, err)
+		}
+		sf.Mode1().Silver().Set(iNum1)
+		// Установить время производства
+		strTime1 := lstBank[ind+2]
+		sf.Mode1().TimeSet(strTime1)
+		if iNum1 <= 2 { // Если банк слишком слабый
+			return nil
+		}
+	}
+	{ // Получить второй режим
+		for _ind := ind + 2; _ind < len(lstBank); _ind++ {
+			strMode := lstBank[_ind]
+			if strings.Contains(strMode, `Кол-во: <span class="green2">`) {
+				strMode2 = strMode
+				ind = _ind
+				break
+			}
+		}
+		lstMode := strings.Split(strMode2, `Кол-во: <span class="green2">`)
+		strMode2 = lstMode[1]
+		lstMode = strings.Split(strMode2, `</span><br/>`)
+		strMode2 = lstMode[0]
+		iNum2, err := strconv.Atoi(strMode2)
+		if err != nil {
+			return fmt.Errorf("BankNet.getAllMode(): numSilver2(%v) not number, err=\n\t%w", strMode2, err)
+		}
+		sf.Mode2().Silver().Set(iNum2)
+		// Установить время производства
+		strTime2 := lstBank[ind+2]
+		sf.Mode2().TimeSet(strTime2)
+	}
+	return nil
+}

+ 38 - 0
server/serv_bots/warbot/angar/base/bank/bankmode/bankmode.go

@@ -0,0 +1,38 @@
+package bankmode
+
+import (
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Объект допустимого режима банка.
+*/
+
+// BankMode -- объект допустимого режима банка
+type BankMode struct {
+	silver    types.IStatParam
+	timeCount string
+}
+
+// NewBankMode -- возвращает новый *BankMode
+func NewBankMode() *BankMode {
+	return &BankMode{
+		silver: static_param.NewStaticParam("silver"),
+	}
+}
+
+// Silver -- возвращает объект серебра режима
+func (sf *BankMode) Silver() types.IStatParam {
+	return sf.silver
+}
+
+// Time -- возвращает временя производства режима
+func (sf *BankMode) Time() string {
+	return sf.timeCount
+}
+
+// TimeSet -- устанавливает времея производства режима
+func (sf *BankMode) TimeSet(val string) {
+	sf.timeCount = val
+}

+ 37 - 0
server/serv_bots/warbot/angar/base/bank/banknet/banknet.go

@@ -0,0 +1,37 @@
+package banknet
+
+import (
+	"fmt"
+	"log"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически опрашивает банк, собирает ресурсы, отдаёт задачу на опрос банка.
+*/
+
+// BankNet -- обзор базы
+type BankNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewBankNet -- возвращает новый *BankNet
+func NewBankNet(server types.IServer, bank types.IBank, bot types.IServBot) (*BankNet, error) {
+	log.Printf("NewBankNet()\n")
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBankNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBankNet(): IServBot == nil")
+		}
+	}
+
+	sf := &BankNet{
+		SectionNet: sectionnet.NewSectionNet(server, bot, bank, "http://wartank.ru/production/Bank"),
+	}
+
+	return sf, nil
+}

+ 1053 - 0
server/serv_bots/warbot/angar/base/base.go

@@ -0,0 +1,1053 @@
+package base
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/components/sound"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/base/arsenal"
+	"wartank/server/serv_bots/warbot/angar/base/bank"
+	"wartank/server/serv_bots/warbot/angar/base/basenet"
+	"wartank/server/serv_bots/warbot/angar/base/market"
+	"wartank/server/serv_bots/warbot/angar/base/mine"
+	"wartank/server/serv_bots/warbot/angar/base/polygon"
+	"wartank/server/serv_bots/warbot/angar/battle"
+)
+
+/*
+	Объект базы в игре.
+*/
+
+const (
+	forceTimePay  = 60   // Время ожидания платного ускорения
+	forceTimeFree = 1810 // Время ожидания бесплатного ускорения
+
+)
+
+// Base -- объект базы
+type Base struct {
+	*section.Section
+	net       *basenet.BaseNet
+	server    types.IServer
+	bot       types.IServBot
+	arsenal   *arsenal.Arsenal
+	bank      *bank.Bank
+	polygon   *polygon.Polygon
+	mine      *mine.Mine
+	battle    types.IBattle
+	market    *market.Market
+	timeSleep int // Сколько времени спать до опроса базы
+	block     sync.Mutex
+}
+
+// NewBase -- возвращает новый *Base
+func NewBase(server types.IServer, bot types.IServBot) (*Base, error) {
+	log.Printf("NewBase()\n")
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBase(): IApp is nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBase(): client is nil")
+		}
+	}
+	sf := &Base{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+func (sf *Base) make() error {
+	log.Printf("Base.make()\n")
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	var err error
+	{ // Arsenal
+		sf.arsenal, err = arsenal.NewArsenal(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create IArsenal, err=\n\t%w", err)
+		}
+	}
+	{ // Bank
+		sf.bank, err = bank.NewBank(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create IBank, err=\n\t%w", err)
+		}
+	}
+	{ // Mine
+		sf.mine, err = mine.NewMine(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create IMine, err=\n\t%w", err)
+		}
+	}
+	{ // Battle
+		sf.battle, err = battle.NewBattle(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create IBattle, err=\n\t%w", err)
+		}
+	}
+	{ // Market
+		sf.market, err = market.NewMarket(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): при создании IMarket, err=\n\t%w", err)
+		}
+	}
+	{ // Polygon
+		sf.polygon, err = polygon.NewPolygon(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create IPolygon, err=\n\t%w", err)
+		}
+	}
+	{ // Section
+		sf.Section, err = section.NewSection(sf.server, `<title>База</title>`)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // База в сети
+		sf.net, err = basenet.NewBaseNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Base.make(): in create NetBase, err=\n\t%w", err)
+		}
+	}
+	return nil
+}
+
+// Запускает компоненты
+func (sf *Base) run() error {
+	log.Printf("Base.run()\n")
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if err := sf.arsenal.Run(); err != nil {
+		return fmt.Errorf("Base.run(): in run IArsenal, err=\n\t%w", err)
+	}
+	if err := sf.bank.Run(); err != nil {
+		return fmt.Errorf("Base.run(): in run IBank, err=\n\t%w", err)
+	}
+	if err := sf.mine.Run(); err != nil {
+		return fmt.Errorf("Base.run(): in run IMine, err=\n\t%w", err)
+	}
+	if err := sf.market.Run(); err != nil {
+		return fmt.Errorf("Base.run(): in run IMarket, err=\n\t%w", err)
+	}
+	if err := sf.polygon.Run(); err != nil {
+		return fmt.Errorf("Base.run(): in run IPolygon, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Запускает базу в обработку
+func (sf *Base) Run() error {
+	log.Printf("Base.Run()\n")
+	if err := sf.make(); err != nil {
+		return fmt.Errorf("Base.Run(): make, err=\n\t%w", err)
+	}
+	if err := sf.run(); err != nil {
+		return fmt.Errorf("Base.Run(): run, err=\n\t%w", err)
+	}
+	go func() {
+		for {
+			select {
+			case <-sf.server.Done():
+				sf.CountDown().Stop()
+			case <-sf.CountDown().ChanSig():
+				if err := sf.net.UpdateLst("Base.run()"); err != nil { // Обновить состояние базы
+					log.Printf("ERRO Base.Run(): при обновлении базы, err=\n\t%v\n", err)
+				}
+				sf.checkPolygonForce()
+				if err := sf.checkBank(); err != nil { // Проверка режимов банка
+					log.Printf("ERRO Base.Run(): при проверке банка, err=\n\t%v\n", err)
+				}
+				sf.checkArsenal()
+				sf.checkMine() // Проверка шахты
+				sf.setCountDown()
+				time.Sleep(time.Second * time.Duration(sf.timeSleep))
+				// sf.CountDown().Set(45)
+			}
+		}
+	}()
+	return nil
+}
+
+func (sf *Base) setCountDown() {
+	timeBase := sf.bank.CountDown().Get()
+	timeCount := timeBase
+	timeArsenal := sf.arsenal.CountDown().Get()
+	timeMine := sf.mine.CountDown().Get()
+	if timeArsenal < timeCount {
+		timeCount = timeArsenal
+	}
+	if timeMine < timeCount {
+		timeCount = timeMine
+	}
+	if timeCount <= 1 {
+		sf.timeSleep = 5
+		return
+	}
+	if timeCount > (600) {
+		sf.timeSleep = 600
+	}
+}
+
+// Arsenal -- возвращает объект арсенала
+func (sf *Base) Arsenal() types.IArsenal {
+	return sf.arsenal
+}
+
+// Bank -- возвращает объект банка
+func (sf *Base) Bank() types.IBank {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	return sf.bank
+}
+
+// Polygon -- возвращает объект полигона
+func (sf *Base) Polygon() types.IPolygon {
+	return sf.polygon
+}
+
+// Mine -- возвращает объект шахты
+func (sf *Base) Mine() types.IMine {
+	return sf.mine
+}
+
+// Battle -- возвращает объект сражения
+func (sf *Base) Battle() types.IBattle {
+	return sf.battle
+}
+
+// Market -- возвращает объект рынка
+func (sf *Base) Market() types.IMarket {
+	return sf.market
+}
+
+// Проверяет на забрать шахту
+func (sf *Base) checkMine() {
+	countTime := sf.mine.CountDown().Get()
+	if countTime > 3 {
+		return
+	}
+	if sf.mine.Ruda().Get() == 0 {
+		sf.checkMineStat()
+	}
+	sf.checkMineForce() // Нужно ли ускорить апгрейд шахты
+	if sf.mine.ModeCurrent().Get() == "upgrade" {
+		return
+	}
+	sf.checkMineStat()
+	sf.checkMineGet()     // Нужно ли забрать из шахты
+	sf.checkMineProduct() // Нужно ли производство в шахте
+
+	sf.checkMineTime()
+}
+
+// Проверяет время ожидания шахты
+func (sf *Base) checkMineTime() {
+	var (
+		lstBase = sf.GetLst()
+		strOut  string
+		isFind  bool
+		ind     int
+	)
+	for ind, strOut = range lstBase {
+		if !strings.Contains(strOut, `<span class="green2">Шахта - `) {
+			continue
+		}
+		ind += 11
+		strOut = lstBase[ind]
+		isFind = true
+		break
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `<td><div class="value-block lh1"><span><span>`) {
+		return
+	}
+	lstTime := strings.Split(strOut, `<td><div class="value-block lh1"><span><span>`)
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, `</span></span></div></td>`)
+	strTime = lstTime[0]
+	if err := sf.mine.CountDown().Parse(strTime); err != nil {
+		log.Printf("ERRO Base.checkMineTime(): при установке обратного отсчёта(%v), err=\n\t%v\n", strTime, err)
+	}
+}
+
+// Проверяет на забор из шахты
+func (sf *Base) checkMineGet() {
+	var (
+		strOut  string
+		ind     int
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	for ind, strOut = range lstBase {
+		if strings.Contains(strOut, `<span class="green2">Шахта -`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	ind += 18
+	strOut = lstBase[ind]
+	if !strings.Contains(strOut, `"><span><span>Забрать</span></span></a>`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Забрать</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBase1, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Base.checkMineGet(): при выполнении Get-запроса 'забрать', err=\n\t%v\n", err)
+		return
+	}
+	if err = sf.mine.Update(lstBase1); err != nil {
+		// log._rintf("ERRO Base.checkMineGet(): при обновлении lstMine, err=\n\t%v\n", err)
+		return
+	}
+	sf.mine.CountDown().Set(1)
+}
+
+// Проверка статистики шахты
+func (sf *Base) checkMineStat() {
+	var (
+		lstMine = sf.GetLst()
+		strOut  string
+		isFind  bool
+		ind     int
+	)
+	{ // Ищем руду
+		for _, strOut = range lstMine {
+			if strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/ore.png?2" alt="Руда" title="Руда"/>`) {
+				isFind = true
+				break
+			}
+		}
+		if isFind && strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/ore.png?2" alt="Руда" title="Руда"/> `) {
+			lstRuda := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/ore.png?2" alt="Руда" title="Руда"/> `)
+			strRuda := lstRuda[1]
+			lstRuda = strings.Split(strRuda, ` &nbsp;&nbsp;</span>`)
+			strRuda = lstRuda[0]
+			iRuda, err := strconv.Atoi(strRuda)
+			if err != nil {
+				log.Printf("ERRO Base.checkMineStat(): руда(%v) не число, err=\n\t%v\n", strRuda, err)
+				return
+			}
+			sf.mine.Ruda().Set(iRuda)
+		}
+	}
+	{ // Ищем железо
+		for _, strOut = range lstMine {
+			if strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/iron.png?2" alt="Железо" title="Железо"/> `) {
+				isFind = true
+				break
+			}
+		}
+		if isFind && strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/iron.png?2" alt="Железо" title="Железо"/> `) {
+			lstFerrum := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/iron.png?2" alt="Железо" title="Железо"/> `)
+			strFerrum := lstFerrum[1]
+			lstFerrum = strings.Split(strFerrum, ` &nbsp;&nbsp;</span>`)
+			strFerrum = lstFerrum[0]
+			iFerrum, err := strconv.Atoi(strFerrum)
+			if err != nil {
+				log.Printf("ERRO Base.checkMineStat(): железо(%v) не число, err=\n\t%v\n", strFerrum, err)
+				return
+			}
+			sf.mine.Ferrum().Set(iFerrum)
+		}
+	}
+	{ // Ищем сталь
+		for _, strOut = range lstMine {
+			if strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/steel.png?2" alt="Сталь" title="Сталь"/> `) {
+				isFind = true
+				break
+			}
+		}
+		if isFind && strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/steel.png?2" alt="Сталь" title="Сталь"/> `) {
+			lstSteel := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/steel.png?2" alt="Сталь" title="Сталь"/> `)
+			strSteel := lstSteel[1]
+			lstSteel = strings.Split(strSteel, ` &nbsp;&nbsp;</span>`)
+			strSteel = lstSteel[0]
+			iSteel, err := strconv.Atoi(strSteel)
+			if err != nil {
+				log.Printf("ERRO Base.checkMineStat(): сталь(%v) не число, err=\n\t%v\n", strSteel, err)
+				return
+			}
+			sf.mine.Steel().Set(iSteel)
+		}
+	}
+	{ // Ищем свинец
+		for _, strOut = range lstMine {
+			if strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/plumbum.png?2" alt="Свинец" title="Свинец"/> `) {
+				isFind = true
+				break
+			}
+		}
+		if isFind && strings.Contains(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/plumbum.png?2" alt="Свинец" title="Свинец"/> `) {
+			lstPlumbum := strings.Split(strOut, `<span class="nwr"><img class="rico vm" src="/images/icons/plumbum.png?2" alt="Свинец" title="Свинец"/> `)
+			strPlumbum := lstPlumbum[1]
+			lstPlumbum = strings.Split(strPlumbum, ` &nbsp;&nbsp;</span>`)
+			strPlumbum = lstPlumbum[0]
+			iPlumbum, err := strconv.Atoi(strPlumbum)
+			if err != nil {
+				log.Printf("ERRO Base.checkMineStat(): свинец(%v) не число, err=\n\t%v\n", strPlumbum, err)
+				return
+			}
+			sf.mine.Plumbum().Set(iPlumbum)
+		}
+	}
+	{ // Ищем время обработки и режим
+		lstBase := sf.GetLst()
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `<span class="green2">Шахта - `) {
+				isFind = true
+				ind += 11
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isFind && strings.Contains(strOut, `<td><div class="value-block lh1"><span><span>`) {
+			lstTime := strings.Split(strOut, `<td><div class="value-block lh1"><span><span>`)
+			strTime := lstTime[1]
+			lstTime = strings.Split(strTime, `</span></span></div></td>`)
+			strTime = lstTime[0]
+			if err := sf.mine.CountDown().Parse(strTime); err != nil {
+				log.Printf("ERRO Base.checkMineStat(): при установке времени ожидания шахты(%v)\n\terr=%v\n", strTime, err)
+			}
+		}
+
+		ind -= 3
+		if ind < 0 {
+			log.Printf("Base.checkMineStat(): отрицательный индекс(%v)!\n", ind)
+			return
+		}
+		strOut = lstBase[ind]
+		switch {
+		case strings.Contains(strOut, `/images/icons/iron.png`): // Железо
+			lstNum := strings.Split(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/iron.png?2" alt="iron"/>&nbsp;`)
+			strNum := lstNum[1]
+			lstNum = strings.Split(strNum, `</div></td>`)
+			strNum = lstNum[0]
+			sf.mine.ModeCurrent().WorkSet("Железо-" + strNum)
+		case strings.Contains(strOut, `/images/icons/ore.png`): // Руда
+			lstNum := strings.Split(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/ore.png?2" alt="ore"/>&nbsp;`)
+			strNum := lstNum[1]
+			lstNum = strings.Split(strNum, `</div></td>`)
+			strNum = lstNum[0]
+			sf.mine.ModeCurrent().WorkSet("Руда-" + strNum)
+		case strings.Contains(strOut, `/images/icons/steel.png`): // Руда
+			lstNum := strings.Split(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/steel.png?2" alt="steel"/>&nbsp;`)
+			strNum := lstNum[1]
+			lstNum = strings.Split(strNum, `</div></td>`)
+			strNum = lstNum[0]
+			sf.mine.ModeCurrent().WorkSet("Сталь-" + strNum)
+		}
+	}
+}
+
+// Проверяет на необходимость производства
+func (sf *Base) checkMineProduct() {
+	var (
+		strOut  string
+		ind     int
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	for ind, strOut = range lstBase {
+		if strings.Contains(strOut, `<span class="green2">Шахта -`) {
+			isFind = true
+			ind += 12
+			strOut = lstBase[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `"><span><span>Производство</span></span></a>`) {
+		return
+	}
+	sf.mine.NumProduct().Set(0)
+	sf.mine.CountDown().Set(1)
+}
+
+// Проверяет режимы арсенала
+func (sf *Base) checkArsenal() {
+	countTime := sf.arsenal.CountDown().Get()
+	if countTime > 3 {
+		return
+	}
+	if err := sf.arsenal.UpdateArsenal(); err != nil {
+		// log._rintf("ERRO Base.checkArsenal(): при обновлении арсенала, err=\n\t%v\n", err)
+		return
+	}
+	if mode := sf.arsenal.ModeCurrent().Work(); mode == "" {
+		sf.checkArsenalMode()
+	}
+	sf.checkArsenalForce() // Нужно ли ускорить апгрейд арсенала
+	if sf.arsenal.ModeCurrent().Get() == "upgrade" {
+		return
+	}
+	sf.checkArsenalGet() // Нужно ли забрать оружие
+	sf.checkArsenalTime()
+	sf.checkArsenalMode()
+}
+
+// Проверяет время готовности арсенала
+func (sf *Base) checkArsenalTime() {
+	var (
+		lstBase = sf.GetLst()
+		ind     int
+		strOut  string
+		isFind  bool
+	)
+	for ind, strOut = range lstBase {
+		if !strings.Contains(strOut, `Производит снаряды, ремкомплекты<br/>`) {
+			continue
+		}
+		ind += 10
+		strOut = lstBase[ind]
+		isFind = true
+		break
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, ":") {
+		return
+	}
+	lstTime := strings.Split(strOut, `<td><div class="value-block lh1"><span><span>`)
+	if len(lstTime) != 2 { // Возможно, производство
+		return
+	}
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, `</span></span></div></td>`)
+	strTime = lstTime[0]
+	if err := sf.arsenal.CountDown().Parse(strTime); err != nil {
+		log.Printf("ERRO Base.checkArsenalTime(): при установке времени ожидания арсенала(%v)\n\terr=%v\n", strTime, err)
+	}
+}
+
+// Проверяет на забрать оружейную
+func (sf *Base) checkArsenalGet() {
+	var (
+		strOut  string
+		ind     int
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	for ind, strOut = range lstBase {
+		if strings.Contains(strOut, `Производит снаряды, ремкомплекты<br/>`) {
+			isFind = true
+			ind += 17
+			strOut = lstBase[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `"><span><span>Забрать</span></span></a>`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Забрать</span></span></a>`)
+	// http://wartank.ru/buildings?80-1.ILinkListener-buildings-0-building-rootBlock-actionPanel-takeProductionLink
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBase, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Base.checkArsenalGet(): при выполнении Get-запроса 'забрать', err=\n\t%v\n", err)
+		return
+	}
+	if len(lstBase) == 0 {
+		// log._rintf("ERRO Base.checkArsenalGet(): len lstBase(%v)==0", len(lstBase))
+		return
+	}
+	isFind = false
+	for _, strOut = range lstBase {
+		if strings.Contains(strOut, `<title>Производство</title>`) {
+			isFind = true
+			break
+		}
+	}
+	sf.arsenal.CountDown().Set(1)
+	if isFind {
+		if err = sf.arsenal.Update(lstBase); err != nil {
+			log.Printf("ERRO Base.checkArsenalGet(): при обновлении lstArsenal, err=\n\t%v\n", err)
+		}
+		return
+	}
+	if err = sf.Update(lstBase); err != nil {
+		log.Printf("ERRO Base.checkArsenalGet(): при обновлении lstBase, err=\n\t%v\n", err)
+	}
+}
+
+// Проверяетрежим производства арсенала
+func (sf *Base) checkArsenalMode() {
+	var (
+		strOut  string
+		lstBase = sf.GetLst()
+	)
+	for _, strOut = range lstBase {
+		if strings.Contains(strOut, `HollowCharge.png`) {
+			sf.arsenal.ModeCurrent().WorkSet("кумулятивы")
+			return
+		}
+		if strings.Contains(strOut, `ArmorPiercing.png`) {
+			sf.arsenal.ModeCurrent().WorkSet("бронебойки")
+			return
+		}
+		if strings.Contains(strOut, `HighExplosive.png`) {
+			sf.arsenal.ModeCurrent().WorkSet("фугасы")
+			return
+		}
+		if strings.Contains(strOut, `repairkit.gif`) {
+			sf.arsenal.ModeCurrent().WorkSet("ремка")
+			return
+		}
+	}
+}
+
+// Проверяет на ускорение апгрейда арсенала
+func (sf *Base) checkArsenalForce() {
+	var (
+		ind     int
+		strOut  = ""
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	{ // Проверка на платное ускорение апгрейда + время
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит снаряды, ремкомплекты<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isFind = true
+				ind += 29
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if !isFind {
+			return
+		}
+		{ // Платное ускорение
+			if strings.Contains(strOut, `Ускорить за`) {
+				if err := sf.arsenal.CountDown().Set(forceTimePay); err != nil {
+					log.Printf("WARN Base.checkArsenalForce(): при установке платного времени ускорения апгрейда арсенала(%v)\n\terr=%v\n", forceTimePay, err)
+				}
+				sf.arsenal.ModeCurrent().Set("upgrade")
+				sf.arsenal.ModeCurrent().WorkSet("апгрейд")
+				return
+			}
+		}
+	}
+	{ // Проверка на бесплатное ускорение апгрейда
+		isFind = false
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит снаряды, ремкомплекты<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isFind = true
+				ind += 26
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if !isFind {
+			return
+		}
+		if !strings.Contains(strOut, `>Ускорение<`) {
+			return
+		}
+		sf.arsenal.ModeCurrent().Set("upgrade")
+		lstLink := strings.Split(strOut, `<td style="width:50%;padding-left:1px;"><a class="simple-but border" href="`)
+		strLink := lstLink[1]
+		lstLink = strings.Split(strLink, `"><span><span>Ускорение</span></span></a>`)
+		strLink = "http://wartank.ru/" + lstLink[0]
+		lstBase, err := sf.net.Get(strLink)
+		if err != nil {
+			// log._rintf("ERRO NetBank.checkArsenalForce(): при GET-запросе на бесплатном ускорении апгрейда арсенала, err=\n\t%v\n", err)
+			return
+		}
+		// sound.ArsenalForce()
+		if err := sf.Update(lstBase); err != nil {
+			// log._rintf("ERRO NetBank.checkArsenalForce(): при обновлении lstBase, err=\n\t%v\n", err)
+			return
+		}
+		sf.arsenal.ModeCurrent().Set("upgrade")
+		sf.arsenal.ModeCurrent().WorkSet("апгрейд")
+		if err := sf.arsenal.CountDown().Set(forceTimeFree); err != nil {
+			log.Printf("WARN Base.checkArsenalForce(): при установке бесплатного времени ускорения апгрейда арсенала(%v)\n\terr=%v\n", forceTimeFree, err)
+		}
+	}
+	// Все проверки прошли -- это просто работа
+	sf.arsenal.ModeCurrent().Set("work")
+}
+
+// Проверяет режим банка
+func (sf *Base) checkBank() error {
+	if sf.bank.ModeCurrent().Work() == "" {
+		sf.checkBankMode()
+	}
+	sf.checkBankTime() // Проверка времени ожидания
+	countTime := sf.bank.CountDown().Get()
+	if countTime > 5 {
+		return nil
+	}
+	sf.checkBankForce() // Нужно ли ускорить апгрейд банка
+	if sf.bank.ModeCurrent().Get() == "upgrade" {
+		return nil
+	}
+	sf.checkBankTake()    // Нужно ли забрать банк
+	sf.checkBankProduct() // Запуск производства в  банке
+	return nil
+}
+
+// Проверяет режим работы банка
+func (sf *Base) checkBankMode() {
+	var (
+		strOut  string
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	for _, strOut = range lstBase {
+		if strings.Contains(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/silver.png?2" alt="silver"/>&nbsp;`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	lstSilver := strings.Split(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/silver.png?2" alt="silver"/>&nbsp;`)
+	strSilver := lstSilver[1]
+	lstSilver = strings.Split(strSilver, `</div></td>`)
+	strSilver = "Серебро-" + lstSilver[0]
+	sf.bank.ModeCurrent().WorkSet(strSilver)
+
+}
+
+// Проверяет на время ожидания банка
+func (sf *Base) checkBankTime() {
+	var (
+		strOut  string
+		ind     int
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	for ind, strOut = range lstBase {
+		if strings.Contains(strOut, `Производит серебро<br/>`) {
+			isFind = true
+			ind += 10
+			strOut = lstBase[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `<td><div class="value-block lh1"><span><span>`) {
+		return
+	}
+	lstTime := strings.Split(strOut, `<td><div class="value-block lh1"><span><span>`)
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, `</span></span></div></td>`)
+	strTime = lstTime[0]
+	if err := sf.bank.CountDown().Parse(strTime); err != nil {
+		log.Printf("ERRO Base.checkBankTime(): при установке времени ожидания банка(%v)\n\terr=%v\n", strTime, err)
+	}
+}
+
+// Проверяет на произвести в банке
+func (sf *Base) checkBankProduct() {
+	var (
+		strOut  string
+		ind     int
+		isFind  bool
+		lstBase = sf.GetLst()
+	)
+	for ind, strOut = range lstBase {
+		if strings.Contains(strOut, `Производит серебро<br/>`) {
+			isFind = true
+			ind += 11
+			strOut = lstBase[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strOut, `>Производство</span>`) {
+		return
+	}
+	sf.bank.UpdateLst()
+	sf.bank.CountDown().Set(1)
+}
+
+// Проверка получения серебра из банка
+func (sf *Base) checkBankTake() {
+	var (
+		ind     int
+		strOut  string
+		isFind  bool
+		lstBank = sf.GetLst()
+		strLink string
+	)
+	for ind, strOut = range lstBank {
+		if strings.Contains(strOut, `<span class="green2">Банк - `) {
+			ind += 18
+			strLink = lstBank[ind]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strLink, `"><span><span>Забрать</span></span></a>`) {
+		return
+	}
+	lstLink := strings.Split(strLink, `<a class="simple-but border" href="`)
+	strLink = lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Забрать</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBank, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Base.checkBankTake(): при выполнении GET-запроса 'забрать серебро', err=\n\t%v\n", err)
+		return
+	}
+	sound.BankTake()
+	isFind = false
+	for _, strOut = range lstBank { // Проверка на производство в банке
+		if strings.Contains(strOut, `<title>Производство</title>`) {
+			isFind = true
+			break
+		}
+	}
+	if isFind {
+		if err := sf.bank.Update(lstBank); err != nil {
+			log.Printf("ERRO Base.checkBankTake(): при установке lstBank, err=\n\t%v'n", err)
+		}
+		sf.bank.CountDown().Set(1)
+		return
+	}
+	if err := sf.Update(lstBank); err != nil {
+		log.Printf("ERRO Base.checkBankTake(): при установке lstBase, err=\n\t%v'n", err)
+	}
+	sf.bank.CountDown().Set(1)
+}
+
+// Проверяет на ускорение апгрейда банка
+func (sf *Base) checkBankForce() {
+	var (
+		ind     int
+		strOut  = ""
+		isOut   bool
+		lstBase = sf.GetLst()
+	)
+	{ // Проверка на платное ускорение апгрейда
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит серебро<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isOut = true
+				ind += 29
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isOut && strings.Contains(strOut, `Ускорить за`) {
+			if err := sf.bank.CountDown().Set(forceTimePay); err != nil {
+				log.Printf("WARN Base.checkBankForce(): при установке времени ожидания платного ускорения апгрейда банка(%v)\n\terr=%v\n", forceTimePay, err)
+			}
+			sf.bank.ModeCurrent().Set("upgrade")
+			sf.bank.ModeCurrent().WorkSet("апгрейд")
+			return
+		}
+	}
+	{ // Проверка на бесплатное ускорение апгрейда
+		isOut = false
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит серебро<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isOut = true
+				ind += 26
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isOut && strings.Contains(strOut, `>Ускорение<`) {
+
+			lstLink := strings.Split(strOut, `<td style="width:50%;padding-left:1px;"><a class="simple-but border" href="`)
+			strLink := lstLink[1]
+			lstLink = strings.Split(strLink, `"><span><span>Ускорение</span></span></a>`)
+			strLink = "http://wartank.ru/" + lstLink[0]
+			lstBase, err := sf.net.Get(strLink)
+			if err != nil {
+				// log._rintf("ERRO NetBank.checkBankForce(): при GET-запросе на бесплатном ускорении апгрейда банка, err=\n\t%v\n", err)
+				return
+			}
+			if err := sf.Update(lstBase); err != nil {
+				// log._rintf("ERRO NetBank.checkBankForce(): при обновлении lstBase, err=\n\t%v\n", err)
+				return
+			}
+			sf.bank.ModeCurrent().Set("upgrade")
+			sf.bank.ModeCurrent().WorkSet("апгрейд")
+			if err := sf.bank.CountDown().Set(forceTimeFree); err != nil {
+				log.Printf("WARN Base.checkBankForce(): при установке времени бесплатного ускорения агрейда банка(%v)\n\terr=%v\n", forceTimeFree, err)
+			}
+		}
+	}
+}
+
+// Проверяет на ускорение апгрейда шахты
+func (sf *Base) checkMineForce() {
+	var (
+		ind     int
+		strOut  = ""
+		isOut   bool
+		lstBase = sf.GetLst()
+	)
+	{ // Проверка на платное ускорение апгрейда
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит ресурсы<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isOut = true
+				ind += 29
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isOut && strings.Contains(strOut, `Ускорить за`) {
+			sf.mine.ModeCurrent().Set("upgrade")
+			sf.mine.ModeCurrent().WorkSet("апгрейд")
+			if err := sf.mine.CountDown().Set(forceTimePay); err != nil {
+				log.Printf("WARN Base.checkMineForce(): при установке времени платного апгрейда шахты(%v)\n\terr=%v\n", forceTimePay, err)
+			}
+			return
+		}
+	}
+	{ // Проверка на время бесплатного ускорение апгрейда
+		isOut = false
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит ресурсы<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isOut = true
+				ind += 26
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isOut && strings.Contains(strOut, `>Ускорение<`) {
+			lstLink := strings.Split(strOut, `<td style="width:50%;padding-left:1px;"><a class="simple-but border" href="`)
+			strLink := lstLink[1]
+			lstLink = strings.Split(strLink, `"><span><span>Ускорение</span></span></a>`)
+			strLink = "http://wartank.ru/" + lstLink[0]
+			lstBase, err := sf.net.Get(strLink)
+			if err != nil {
+				// log._rintf("ERRO NetBank.checkMineForce(): при GET-запросе на бесплатном ускорении апгрейда шахты, err=\n\t%v\n", err)
+				return
+			}
+			if err := sf.Update(lstBase); err != nil {
+				// log._rintf("ERRO NetBank.checkMineForce(): при обновлении lstBase, err=\n\t%v\n", err)
+				return
+			}
+			if err := sf.mine.CountDown().Set(forceTimeFree); err != nil {
+				log.Printf("WARN Base.checkMineForce(): при установке времени апгрейда шахты(%v)\n\terr=%v\n", forceTimeFree, err)
+			}
+			// sound.MineForce()
+			sf.mine.ModeCurrent().Set("upgrade")
+			sf.mine.ModeCurrent().WorkSet("апгрейд")
+			return
+		}
+	}
+	// Все проверки прошли -- это просто работа
+	sf.mine.ModeCurrent().Set("work")
+}
+
+// Проверяет на ускорение апгрейда полигона
+func (sf *Base) checkPolygonForce() {
+	var (
+		ind     int
+		strOut  = ""
+		isOut   bool
+		lstBase = sf.GetLst()
+	)
+	countTime := sf.polygon.CountDown().Get()
+	if countTime > 3 {
+		return
+	}
+	{ // Проверка на платное ускорение апгрейда
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит усиление танка<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isOut = true
+				ind += 29
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isOut && strings.Contains(strOut, `Ускорить за`) {
+			// strTime := lstBase[ind-21]
+			// lstTime := strings.Split(strTime, `<td><div class="value-block lh1"><span><span>`)
+			// strTime = lstTime[1]
+			// lstTime = strings.Split(strTime, `</span></span></div></td>`)
+			// strTime = lstTime[0]
+			// if err := sf.polygon.CountDown().Set(strTime); err != nil {
+			// 	// log._rintf("WARN Base.checkMineForce(): при установке времени апгрейда полигона(%v)\n\terr=%v\n", strTime, err)
+			// }
+			sf.polygon.ModeCurrent().Set("upgrade")
+			sf.polygon.ModeCurrent().WorkSet("апгрейд")
+			if err := sf.polygon.CountDown().Set(forceTimePay); err != nil {
+				log.Printf("WARN Base.checkPolygonForce(): при установке платного времени апгрейда полигона(%v)\n\terr=%v\n", forceTimePay, err)
+			}
+			return
+		}
+	}
+	{ // Проверка на бесплатное ускорение апгрейда
+		isOut = false
+		for ind, strOut = range lstBase {
+			if strings.Contains(strOut, `Производит усиление танка<br/>`) {
+				// Убедиться что есть строка платного ускорения
+				isOut = true
+				ind += 26
+				strOut = lstBase[ind]
+				break
+			}
+		}
+		if isOut && strings.Contains(strOut, `>Ускорение<`) {
+			lstLink := strings.Split(strOut, `<td style="width:50%;padding-left:1px;"><a class="simple-but border" href="`)
+			strLink := lstLink[1]
+			lstLink = strings.Split(strLink, `"><span><span>Ускорение</span></span></a>`)
+			strLink = "http://wartank.ru/" + lstLink[0]
+			lstBase, err := sf.net.Get(strLink)
+			if err != nil {
+				// log._rintf("ERRO NetBank.checkPolygonForce(): при GET-запросе на бесплатном ускорении апгрейда полигона, err=\n\t%v\n", err)
+				return
+			}
+			if err := sf.Update(lstBase); err != nil {
+				// log._rintf("ERRO NetBank.checkPolygonForce(): при обновлении lstBase, err=\n\t%v\n", err)
+				return
+			}
+			// sound.MineForce()
+			sf.polygon.ModeCurrent().Set("upgrade")
+			sf.polygon.ModeCurrent().WorkSet("апгрейд")
+			// Установить время ожидания для обновления
+			if err := sf.polygon.CountDown().Set(forceTimeFree); err != nil {
+				log.Printf("WARN Base.checkPolygonForce(): при установке времени бесплатного апгрейда полигона(%v)\n\terr=%v\n", forceTimeFree, err)
+			}
+			// log._rintf("INFO NetBank.checkPolygonForce(): ускорено строительство полигона\n")
+			return
+		}
+	}
+	// Все проверки прошли -- это просто работа
+	sf.polygon.ModeCurrent().Set("work")
+}

+ 36 - 0
server/serv_bots/warbot/angar/base/basenet/basenet.go

@@ -0,0 +1,36 @@
+package basenet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Исходник предоставляет тип для хождения по базе.
+*/
+
+// BaseNet -- обзор базы
+type BaseNet struct {
+	*sectionnet.SectionNet
+	strUrl string
+}
+
+// NewBaseNet -- возвращает новый *BaseNet
+func NewBaseNet(server types.IServer, bot types.IServBot) (*BaseNet, error) {
+	{ // Предусловие
+		if server == nil {
+			return nil, fmt.Errorf("NewBaseNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBaseNet(): IServBot == nil")
+		}
+	}
+	strUrl := "http://wartank.ru/buildings"
+	sf := &BaseNet{
+		SectionNet: sectionnet.NewSectionNet(server, bot, bot.Angar().Base(), strUrl),
+		strUrl:     strUrl,
+	}
+	return sf, nil
+}

+ 181 - 0
server/serv_bots/warbot/angar/base/market/market.go

@@ -0,0 +1,181 @@
+package market
+
+import (
+	"fmt"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/base/market/marketnet"
+)
+
+/*
+	Объект рынка
+*/
+
+// Market -- объект рынка
+type Market struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	net    *marketnet.MarketNet
+}
+
+// NewMarket -- возвращает новый рынок
+func NewMarket(server types.IServer, bot types.IServBot) (*Market, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewMarket(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewMarket(): IServBot == nil")
+		}
+	}
+	sf := &Market{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+func (sf *Market) Run() error {
+	var err error
+	{ // Секция
+		sf.Section, err = section.NewSection(sf.server, `<title>Рынок</title>`)
+		if err != nil {
+			return fmt.Errorf("Market.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // Маркет
+		sf.net, err = marketnet.NewMarketNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Market.Run(): in create NetMarket, err=\n\t%w", err)
+		}
+	}
+	go sf.run()
+	return nil
+}
+
+// выполняет опрос рынка базы, должен работать как горутина
+func (sf *Market) run() {
+	sf.CountDown().Set(25)
+	for {
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig():
+			_ = sf.buyGold()
+			// Если золото не куплено -- обновить время ожидания
+			sf.checkTime()
+		}
+	}
+}
+
+// Проверяет  время ожидания рынка
+func (sf *Market) checkTime() {
+	var (
+		strOut string
+		isFind bool
+	)
+	// countDown := sf.CountDown().Get()
+	fnIsSilver := func() bool { // Найти счётчик цены серебра
+		if err := sf.net.UpdateLst("Рынок"); err != nil { // Принудительное ПЕРВОЕ обновление рынка
+			// log._rintf("ERRO Market.checkTime(): при обновлении lstMarket, err=\n\t%v\n", err)
+			return false
+		}
+		isFind := false
+		lstMarket := sf.GetLst()
+		for _, strOut = range lstMarket {
+			if strings.Contains(strOut, `alt="Серебро" title="Серебро"> `) {
+				isFind = true
+				break
+			}
+		}
+		if isFind {
+			lstSilver := strings.Split(strOut, `<img class="ico vm" src="/images/icons/silver.png?2" alt="Серебро" title="Серебро"> `)
+			strSilver := lstSilver[1]
+			switch strSilver {
+			case "10", "50", "100", "500":
+				return true
+			default:
+				return false
+			}
+		}
+		return false
+	}
+	fnGetCountDown := func() { // Искать счётчик времени
+		lstMarket := sf.GetLst()
+		// Найти счётчик времени
+		for _, strOut = range lstMarket {
+			if strings.Contains(strOut, `Минимальная цена через `) {
+				isFind = true
+				break
+			}
+		}
+		if !isFind {
+			return // Минимальная цена
+		}
+		lstTime := strings.Split(strOut, `Минимальная цена через `)
+		strTime := lstTime[1]
+		if err := sf.CountDown().Parse(strTime); err != nil {
+			// log._rintf("ERRO Market.checkTime(): при установке времени ожидания рынка(%v)\n\terr=%v\n", strTime, err)
+			return // Возможно минимальная цена
+		}
+	}
+	if fnIsSilver() {
+		return
+	}
+	fnGetCountDown()
+}
+
+// Проверяет рынок на режим покупки
+func (sf *Market) buyGold() bool {
+	var (
+		ind       int
+		isFind    bool
+		strOut    string
+		lstMarket = sf.GetLst()
+		strSilver string
+	)
+	for ind, strOut = range lstMarket {
+		if strings.Contains(strOut, `alt="Серебро" title="Серебро"> `) {
+			isFind = true
+			break
+		}
+	}
+	if isFind { // Найдена продажа золота за серебро
+		lstSilver := strings.Split(strOut, `<img class="ico vm" src="/images/icons/silver.png?2" alt="Серебро" title="Серебро"> `)
+		strSilver = lstSilver[1]
+		switch strSilver {
+		case "10", "50", "100", "500": // Допустимые суммы трат
+			ind -= 15
+			strOut = lstMarket[ind]
+			lstLink := strings.Split(strOut, `<a class="simple-but border mb5" href="`)
+			if len(lstLink) < 2 {
+				return false
+			}
+			strLink := lstLink[1]
+			lstLink = strings.Split(strLink, `"><span><span>Получить `)
+			strLink = "http://wartank.ru/" + lstLink[0]
+			lstMarket, err := sf.net.Get(strLink)
+			if err != nil {
+				// log._rintf("ERRO Market.buyGold(): при выполнении GET-команды на покупку золота, err=\n\t%v\n", err)
+				return true
+			}
+			for _, strOut = range lstMarket {
+				if strings.Contains(strOut, `Ошибка на сервере. Сообщение админу уже отправлено.`) {
+					// log._rintf("ERRO Market.buyGold(): при получении lstMarket, strHTML=%v, err=\nt%v\n", strOut, err)
+					return false
+				}
+			}
+			if err = sf.Update(lstMarket); err != nil {
+				// log._rintf("Market.buyGold(): при обновлении lstMarket, err=\n\t%v\n", err)
+				return true
+			}
+		default: // Недопустимая сумма, либо больше чем надо
+			return false
+		}
+	}
+	return true
+}

+ 35 - 0
server/serv_bots/warbot/angar/base/market/marketnet/marketnet.go

@@ -0,0 +1,35 @@
+package marketnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически опрашивает рынок, покупает золотишко.
+*/
+
+// MarketNet -- обзор рынка
+type MarketNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewMarketNet -- возвращает новый *MarketNet
+func NewMarketNet(server types.IServer, bot types.IServBot) (*MarketNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewMarketNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewMarketNet(): IServBot == nil")
+		}
+	}
+
+	sf := &MarketNet{
+		SectionNet: sectionnet.NewSectionNet(server, bot, bot.Angar().Base().Market(), "http://wartank.ru/market"),
+	}
+
+	return sf, nil
+}

+ 379 - 0
server/serv_bots/warbot/angar/base/mine/mine.go

@@ -0,0 +1,379 @@
+package mine
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/base/mine/minenet"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Объект шахты на базе
+*/
+
+// Mine -- объект шахты на базе
+type Mine struct {
+	*section.Section
+	net        *minenet.MineNet
+	server     types.IServer
+	bot        types.IServBot
+	base       types.IBase
+	ruda       types.IStatParam
+	ferrum     types.IStatParam
+	steel      types.IStatParam
+	plumbum    types.IStatParam
+	numProduct types.IStatParam
+}
+
+// NewMine -- возвращает новый *Mine
+func NewMine(server types.IServer, bot types.IServBot) (*Mine, error) {
+	{ // Предусловие
+		if server == nil {
+			return nil, fmt.Errorf("NewMine(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewMine(): IServBot == nil")
+		}
+	}
+	sf := &Mine{
+		server:     server,
+		bot:        bot,
+		base:       bot.Angar().Base(),
+		ruda:       static_param.NewStaticParam("ruda"),
+		ferrum:     static_param.NewStaticParam("ferrum"),
+		steel:      static_param.NewStaticParam("steel"),
+		plumbum:    static_param.NewStaticParam("plumbum"),
+		numProduct: static_param.NewStaticParam("plumbum"),
+	}
+	return sf, nil
+}
+
+func (sf *Mine) Run() error {
+	var err error
+	{ // Секция
+		sf.Section, err = section.NewSection(sf.server, `<span class="green2">Руда</span><br/>`)
+		if err != nil {
+			return fmt.Errorf("Mine.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+
+	{ // Шахта
+		sf.net, err = minenet.NewMineNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Mine.Run(): in create NetMine, err=\n\t%w", err)
+		}
+		if err := sf.net.Run(); err != nil {
+			return fmt.Errorf("Mine.Run(): in run NetMine, err=\n\t%w", err)
+		}
+	}
+	go sf.run()
+	return nil
+}
+
+// run -- запускает обработку шахты
+func (sf *Mine) run() {
+	sf.CountDown().Set(1)
+	for {
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig():
+			log.Printf("Mine.run(): time sig")
+			work := sf.ModeCurrent().Work()
+			if work == "upgrade" {
+				continue
+			}
+			if err := sf.net.UpdateLst("Шахта"); err != nil {
+				log.Printf("ERRO Mine.Run(): при обновлении lstMine, err=\n\t%v\n", err)
+				continue
+			}
+			if err := sf.selectProduct(); err != nil {
+				log.Printf("ERRO MineNet.Run(): при выборе продукции, err=\n\t%v\n", err)
+				continue
+			}
+
+			switch work {
+			case "руда":
+				sf.makeRuda()
+			case "железо":
+				sf.makeFerrum()
+			case "сталь":
+				sf.makeSteel()
+			default:
+				// log._rintf("ERRO MineNet.Run(): неизвестный режим производства, режим=%q\n", work)
+			}
+		}
+		// time.Sleep(time.Second * 30)
+	}
+}
+
+// Plumbum -- возвращает объект свинца
+func (sf *Mine) Plumbum() types.IStatParam {
+	return sf.plumbum
+}
+
+// Steel -- возвращает объект стали
+func (sf *Mine) Steel() types.IStatParam {
+	return sf.steel
+}
+
+// Ferrum -- возвращает объект железа
+func (sf *Mine) Ferrum() types.IStatParam {
+	return sf.ferrum
+}
+
+// Ruda -- возвращает объект руды
+func (sf *Mine) Ruda() types.IStatParam {
+	return sf.ruda
+}
+
+// NumProduct -- возвращает количество прозводимого продукта
+func (sf *Mine) NumProduct() types.IStatParam {
+	return sf.numProduct
+}
+
+// Выбирает продукцию по возможности произвести и её количеству
+func (sf *Mine) selectProduct() error {
+	var (
+		mapProduct = make(map[string]bool) // Словарь известной продукции
+		lstMine    = sf.GetLst()
+	)
+
+	fnProduct := func() { // вычисляет список допустимой продукции
+		mapProduct["ruda"] = true // Руда есть всегда
+		mapProduct["ferrum"] = false
+		mapProduct["steel"] = false
+		mapProduct["plumbum"] = false
+		for _, strProd := range lstMine { // Проверить железо
+			if strings.Contains(strProd, `<span class="green2">Железо</span><br/>`) {
+				mapProduct["ferrum"] = true
+				break
+			}
+		}
+		for _, strProd := range lstMine { // Проверить сталь
+			if strings.Contains(strProd, `<span class="green2">Сталь</span><br/>`) {
+				mapProduct["steel"] = true
+				break
+			}
+		}
+		for _, strProd := range lstMine { // Проверить свинец
+			if strings.Contains(strProd, `<span class="green2">Свинец</span><br/>`) {
+				mapProduct["plumbum"] = true
+				break
+			}
+		}
+	}
+	fnProduct()
+	sf.ModeCurrent().WorkSet("руда")
+	ruda := sf.Ruda().Get()
+
+	ferrum := sf.Ferrum().Get()
+	if mapProduct["ferrum"] {
+		if ruda >= ferrum*2 {
+			sf.ModeCurrent().WorkSet("железо")
+		}
+	}
+
+	steel := sf.Steel().Get()
+	if mapProduct["steel"] {
+		if ferrum >= steel*2 {
+			sf.ModeCurrent().WorkSet("сталь")
+		}
+	}
+
+	plumbum := sf.Plumbum().Get()
+	if mapProduct["plumbum"] {
+		if steel > plumbum*2 {
+			sf.ModeCurrent().WorkSet("свинец")
+		}
+	}
+
+	return nil
+}
+
+// Создаёт руду
+func (sf *Mine) makeRuda() {
+	var (
+		lstMine = sf.GetLst()
+		ind     int
+		strOut  string
+		strTime string
+		strLink string
+		strNum  string
+		isFind  bool
+	)
+	for ind, strOut = range lstMine {
+		if strings.Contains(strOut, `<span class="green2">Руда</span><br/>`) {
+			strNum = lstMine[ind+1]
+			strTime = lstMine[ind+3]
+			strLink = lstMine[ind+10]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strLink, `>Начать производство<`) {
+		return
+	}
+	lstLink := strings.Split(strLink, `<a class="simple-but border" href="`)
+	strLink = lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/" + lstLink[0]
+	lstMine, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO MineNet.makeRuda(): при GET-команде 'начать производство руды', err=\n\t%v\n", err)
+		return
+	}
+	isFind = false
+	for _, strOut = range lstMine {
+		if strings.Contains(strOut, `<title>База</title>`) {
+			// log._rintf("WARN MineNet.makeRuda(): при обновлении lstMine обнаружено lstBase\n")
+			return
+		}
+	}
+	if err = sf.Update(lstMine); err != nil {
+		// log._rintf("ERRO MineNet.makeRuda(): при обновлении lstMine, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.CountDown().Parse(strTime); err != nil {
+		log.Printf("ERRO Mine.makeRuda(): при установке времени ожидания добычи руды(%v)\n\terr=%v\n", strTime, err)
+	}
+	lstNum := strings.Split(strNum, `Кол-во: <span class="green2">`)
+	strNum = lstNum[1]
+	lstNum = strings.Split(strNum, `</span><br/>`)
+	strNum = lstNum[0]
+	iNum, err := strconv.Atoi(strNum)
+	if err != nil {
+		// log._rintf("ERRO MineNet.makeRuda(): кол-во(%v) не число, err=\n\t%v\n", strNum, err)
+		return
+	}
+	sf.NumProduct().Set(iNum)
+}
+
+// Создаёт железо
+func (sf *Mine) makeFerrum() {
+	var (
+		lstMine = sf.GetLst()
+		ind     int
+		strOut  string
+		strTime string
+		strLink string
+		strNum  string
+		isFind  bool
+	)
+	for ind, strOut = range lstMine {
+		if strings.Contains(strOut, `<span class="green2">Железо</span><br/>`) {
+			strNum = lstMine[ind+1]
+			strTime = lstMine[ind+3]
+			strLink = lstMine[ind+10]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strLink, `>Начать производство<`) {
+		return
+	}
+	lstLink := strings.Split(strLink, `<a class="simple-but border" href="`)
+	strLink = lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/" + lstLink[0]
+	lstMine, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO MineNet.makeFerrum(): при GET-команде 'начать производство железа', err=\n\t%v\n", err)
+		return
+	}
+	for _, strOut := range lstMine { // Проверка на базу
+		if strings.Contains(strOut, `<title>База</title>`) {
+			// log._rintf("ERRO MineNet.makeFerrum(): при обновлении lstMine найден lstBase")
+			return
+		}
+	}
+	if err = sf.Update(lstMine); err != nil {
+		// log._rintf("ERRO MineNet.makeFerrum(): при обновлении lstMine, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.CountDown().Parse(strTime); err != nil {
+		log.Printf("ERRO Mine.makeFerrum(): при установке времени производства железа(%v)\n\terr=%v\n", strTime, err)
+	}
+	lstNum := strings.Split(strNum, `Кол-во: <span class="green2">`)
+	strNum = lstNum[1]
+	lstNum = strings.Split(strNum, `</span><br/>`)
+	strNum = lstNum[0]
+	iNum, err := strconv.Atoi(strNum)
+	if err != nil {
+		// log._rintf("ERRO MineNet.makeFerrum(): кол-во(%v) не число, err=\n\t%v\n", strNum, err)
+		return
+	}
+	sf.NumProduct().Set(iNum)
+}
+
+// Создаёт сталь
+func (sf *Mine) makeSteel() {
+	var (
+		lstMine = sf.GetLst()
+		ind     int
+		strOut  string
+		strTime string
+		strLink string
+		strNum  string
+		isFind  bool
+	)
+	for ind, strOut = range lstMine {
+		if strings.Contains(strOut, `<span class="green2">Сталь</span><br/>`) {
+			strNum = lstMine[ind+1]
+			strTime = lstMine[ind+3]
+			strLink = lstMine[ind+10]
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	if !strings.Contains(strLink, `>Начать производство<`) {
+		return
+	}
+	lstLink := strings.Split(strLink, `<a class="simple-but border" href="`)
+	strLink = lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Начать производство</span></span></a>`)
+	strLink = "http://wartank.ru/production/" + lstLink[0]
+	lstMine, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO MineNet.makeSteel(): при GET-команде 'начать производство стали', err=\n\t%v\n", err)
+		return
+	}
+	for _, strOut := range lstMine { // Проверка на базу
+		if strings.Contains(strOut, `<title>База</title>`) {
+			// log._rintf("ERRO MineNet.makeSteel(): при обновлении lstMine найден lstBase")
+			return
+		}
+	}
+	if err = sf.Update(lstMine); err != nil {
+		// log._rintf("ERRO MineNet.makeSteel(): при обновлении lstMine, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.CountDown().Parse(strTime); err != nil {
+		log.Printf("ERRO Mine.makeSteel(): при установке времени производства железа(%v)\n\terr=%v\n", strTime, err)
+	}
+	lstNum := strings.Split(strNum, `Кол-во: <span class="green2">`)
+	strNum = lstNum[1]
+	lstNum = strings.Split(strNum, `</span><br/>`)
+	strNum = lstNum[0]
+	iNum, err := strconv.Atoi(strNum)
+	if err != nil {
+		// log._rintf("ERRO MineNet.makeSteel(): кол-во(%v) не число, err=\n\t%v\n", strNum, err)
+		return
+	}
+	sf.NumProduct().Set(iNum)
+}

+ 42 - 0
server/serv_bots/warbot/angar/base/mine/minenet/minenet.go

@@ -0,0 +1,42 @@
+package minenet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Опрашивает шахту в сети
+*/
+
+// MineNet -- опрашивает шахту на базе
+type MineNet struct {
+	*sectionnet.SectionNet
+	server types.IServer
+	bot    types.IServBot
+}
+
+// NewMineNet -- возвращает новый *MineNet
+func NewMineNet(server types.IServer, bot types.IServBot) (*MineNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewMineNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewMineNet(): IServBot == nil")
+		}
+	}
+
+	sf := &MineNet{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+func (sf *MineNet) Run() error {
+	sf.SectionNet = sectionnet.NewSectionNet(sf.server, sf.bot, sf.bot.Angar().Base().Mine(), "http://wartank.ru/production/Mine")
+	return nil
+}

+ 368 - 0
server/serv_bots/warbot/angar/base/polygon/polygon.go

@@ -0,0 +1,368 @@
+package polygon
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/base/polygon/polygonnet"
+)
+
+/*
+	Объект полигона на базе
+*/
+
+// Polygon -- объект полигона на базе
+type Polygon struct {
+	*section.Section
+	server   types.IServer
+	bot      types.IServBot
+	tankStat types.ITankStat
+	net      *polygonnet.PolygonNet
+}
+
+// NewPolygon -- возвращает новый *Polygon
+func NewPolygon(server types.IServer, bot types.IServBot) (*Polygon, error) {
+	if server == nil {
+		return nil, fmt.Errorf("NewPolygon(): IServer == nil")
+	}
+	if bot == nil {
+		return nil, fmt.Errorf("NewPolygon(): IServBot == nil")
+	}
+	sf := &Polygon{
+		server:   server,
+		bot:      bot,
+		tankStat: bot.Tank().TankStat(),
+	}
+
+	return sf, nil
+}
+
+func (sf *Polygon) Run() error {
+	var err error
+	{ // Секция
+		sf.Section, err = section.NewSection(sf.server, `<title>Полигон</title>`)
+		if err != nil {
+			return fmt.Errorf("Polygon.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // Polygon
+		sf.net, err = polygonnet.NewPolygonNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Polygon.Run(): in create NetPolygon, err=\n\t%w", err)
+		}
+	}
+	go sf.run()
+	return nil
+}
+
+// выполняет опрос полигона базы.
+func (sf *Polygon) run() {
+	sf.CountDown().Set(2)
+	for {
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig():
+			mode := sf.ModeCurrent().Get()
+			if mode == "upgrade" {
+				continue
+			}
+			sf.addForce()
+			sf.checkForce()
+			sf.updateTime()
+		}
+	}
+}
+
+// Обновляет оставшееся время полигона
+//
+//	Этот объект сам описывает своё время
+func (sf *Polygon) updateTime() {
+	var (
+		strLastTime string
+		isFind      bool
+		isSet       bool
+		lstPolygon  = sf.GetLst()
+	)
+	defer func() {
+		if !isSet {
+			sf.CountDown().Set(5)
+		}
+	}()
+	for _, lastTime := range lstPolygon {
+		if strings.Contains(lastTime, `>Осталось: `) {
+			strLastTime = lastTime
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Время полигона вышло
+		return
+	}
+	lstTime := strings.Split(strLastTime, `>Осталось: `)
+	strLastTime = lstTime[1]
+	lstTime = strings.Split(strLastTime, `</span>`)
+	strLastTime = lstTime[0]
+	if err := sf.CountDown().Parse(strLastTime); err != nil {
+		// log._rintf("ERRO Polygon.updateTime(): при установке времени ожидания полигона(%v)\n\terr=%v\n", strLastTime, err)
+		return
+	}
+	isSet = true
+}
+
+// Проверяет что именно активировано
+func (sf *Polygon) checkForce() {
+	var (
+		isFind     bool
+		lstPolygon = sf.GetLst()
+		ind        = 0
+		strOut     string
+	)
+
+	for ind, strOut = range lstPolygon {
+		if strings.Contains(strOut, `<span>Активно</span>`) {
+			ind -= 9
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	strOut = lstPolygon[ind]
+	switch { // Вычисляем контрольную строку
+	case strings.Contains(strOut, `>улучшение точности<`):
+		sf.tankStat.Force().SetName("fyne")
+	case strings.Contains(strOut, `>увеличение прочности<`):
+		sf.tankStat.Force().SetName("hard")
+	case strings.Contains(strOut, `>усиление брони<`):
+		sf.tankStat.Force().SetName("armor")
+	case strings.Contains(strOut, `>усиление атаки<`):
+		sf.tankStat.Force().SetName("attack")
+	}
+	// Вычислим на сколько
+	strOut = lstPolygon[ind+1]
+	lstOut := strings.Split(strOut, `<span class="green2">+`)
+	strOut = lstOut[1]
+	lstOut = strings.Split(strOut, ` на `)
+	strOut = lstOut[0]
+	iForce, err := strconv.Atoi(strOut)
+	if err != nil {
+		// log._rintf("NetPolygon.checkTime(): force(%v) not number, err=\n\t%v\n", strOut, err)
+		return
+	}
+	sf.tankStat.Force().Set(iForce)
+}
+
+// Выбирает самый слабый параметр и усиливает его
+func (sf *Polygon) addForce() {
+	if err := sf.net.UpdateLst("Полигон"); err != nil {
+		// log._rintf("Polygon.checkPolygon(): при принудительном обновлении lstPlygon, mode=%s\terr=\n\t%v\n", sf.ModeCurrent().Get(), err)
+		sf.CountDown().Set(5)
+		return
+	}
+	lstPoligon := sf.GetLst()
+	if len(lstPoligon) == 0 {
+		return
+	}
+	stat := sf.tankStat
+	iAttack := stat.Attack().Get()
+	iArmor := stat.Armor().Get()
+	iFyne := stat.Fyne().Get()
+	iHard := stat.Hard().Get()
+
+	strParam := "attack"
+	iParam := iHard
+	{
+		/*
+			Вычислить самый слабый параметр.
+
+			Политика вычислений:
+				1) hard -- прочность, самый низкоприоритетный параметр
+				2) armor -- броня, чуть лучше power
+				3) fyne -- точность, чуть лучше armor
+				4) attack -- атака, самый важный
+		*/
+
+		if iArmor <= iParam {
+			iParam = iArmor
+			strParam = "armor"
+		}
+		if iFyne <= iParam {
+			iParam = iFyne
+			strParam = "fyne"
+		}
+		if iAttack < iParam {
+			strParam = "attack"
+		}
+	}
+
+	// Найти нужную строку активации
+	var (
+		ind    int
+		strOut string
+		isFind bool
+	)
+	switch strParam {
+	case "attack": // Усиливаем атаку
+		for ind, strOut = range lstPoligon {
+			if strings.Contains(strOut, `>усиление атаки<`) {
+				isFind = true
+				break
+			}
+		}
+		if !isFind {
+			return
+		}
+		ind += 8
+		strOut = lstPoligon[ind]
+		if strOut == "" {
+			return
+		}
+		lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+		strOut = lstLink[1]
+		lstLink = strings.Split(strOut, `"><span><span>Получить бесплатно</span></span></a>`)
+		strLink := "http://wartank.ru/" + lstLink[0]
+		if _, err := sf.net.Get(strLink); err != nil {
+			// log._rintf("ERRO NetPolygon.addForce(): in make request force attack, err=\n\t%v\n", err)
+			return
+		}
+		{ // Узнать на сколько форсирована атака
+			strForce := lstPoligon[ind-7]
+			lstForce := strings.Split(strForce, `<span class="green2">+`)
+			strForce = lstForce[1]
+			lstForce = strings.Split(strForce, ` на `)
+			strForce = lstForce[0]
+			iForce, err := strconv.Atoi(strForce)
+			if err != nil {
+				// log._rintf("ERRO NetPolygon.addForce(): strForceAttack(%v) not int, err=\n\t%v\n", strForce, err)
+				return
+			}
+			sf.tankStat.Force().Set(iForce)
+			sf.tankStat.Force().SetName("attack")
+			sf.ModeCurrent().Set("атака")
+		}
+	case "armor": // Усиливаем броню
+		isFind = false
+		for ind, strOut = range lstPoligon {
+			if strings.Contains(strOut, `>усиление брони<`) {
+				isFind = true
+				break
+			}
+		}
+		if !isFind {
+			return
+		}
+		ind += 8
+		strOut = lstPoligon[ind]
+		if strOut == "" {
+			return
+		}
+		lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+		strOut = lstLink[1]
+		lstLink = strings.Split(strOut, `"><span><span>Получить бесплатно</span></span></a>`)
+		strLink := "http://wartank.ru/" + lstLink[0]
+		if _, err := sf.net.Get(strLink); err != nil {
+			// log._rintf("NetPolygon.addForce(): in make request force armor, err=\n\t%v\n", err)
+			return
+		}
+		{ // Узнать на сколько форсирована броня
+			strForce := lstPoligon[ind-7]
+			lstForce := strings.Split(strForce, `<span class="green2">+`)
+			strForce = lstForce[1]
+			lstForce = strings.Split(strForce, ` на `)
+			strForce = lstForce[0]
+			iForce, err := strconv.Atoi(strForce)
+			if err != nil {
+				// log._rintf("ERRO NetPolygon.addForce(): strForceArmor(%v) not int, err=\n\t%v\n", strForce, err)
+				return
+			}
+			sf.tankStat.Force().Set(iForce)
+			sf.tankStat.Force().SetName("armor")
+			sf.ModeCurrent().Set("броня")
+		}
+	case "fyne": // Усиливаем точность
+		isFind = false
+		for ind, strOut = range lstPoligon {
+			if strings.Contains(strOut, `>улучшение точности<`) {
+				isFind = true
+				break
+			}
+		}
+		if !isFind {
+			return
+		}
+		ind += 8
+		strOut = lstPoligon[ind]
+		if strOut == "" {
+			return
+		}
+		lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+		strOut = lstLink[1]
+		lstLink = strings.Split(strOut, `"><span><span>Получить бесплатно</span></span></a>`)
+		strLink := "http://wartank.ru/" + lstLink[0]
+		if _, err := sf.net.Get(strLink); err != nil {
+			// log._rintf("ERRO NetPolygon.addForce(): in make request force fyne, err=\n\t%v\n", err)
+			return
+		}
+		{ // Узнать на сколько форсирована точность
+			strForce := lstPoligon[ind-7]
+			lstForce := strings.Split(strForce, `<span class="green2">+`)
+			strForce = lstForce[1]
+			lstForce = strings.Split(strForce, ` на `)
+			strForce = lstForce[0]
+			iForce, err := strconv.Atoi(strForce)
+			if err != nil {
+				// log._rintf("ERRO NetPolygon.addForce(): strForceFyne(%v) not int, err=\n\t%v\n", strForce, err)
+				return
+			}
+			sf.tankStat.Force().Set(iForce)
+			sf.tankStat.Force().SetName("fyne")
+			sf.ModeCurrent().Set("точность")
+		}
+	case "hard": // Усиливаем мощность
+		isFind = false
+		for ind, strOut = range lstPoligon {
+			if strings.Contains(strOut, `>увеличение прочности<`) {
+				isFind = true
+				break
+			}
+		}
+		if !isFind {
+			return
+		}
+		ind += 8
+		strOut = lstPoligon[ind]
+		lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+		strOut = lstLink[1]
+		lstLink = strings.Split(strOut, `"><span><span>Получить бесплатно</span></span></a>`)
+		strLink := "http://wartank.ru/" + lstLink[0]
+		if _, err := sf.net.Get(strLink); err != nil {
+			// log._rintf("NetPolygon.addForce(): in make request force hard, err=\n\t%v\n", err)
+			return
+		}
+		{ // Узнать на сколько форсирована прочность
+			strForce := lstPoligon[ind-7]
+			lstForce := strings.Split(strForce, `<span class="green2">+`)
+			strForce = lstForce[1]
+			lstForce = strings.Split(strForce, ` на `)
+			strForce = lstForce[0]
+			iForce, err := strconv.Atoi(strForce)
+			if err != nil {
+				// log._rintf("ERRO NetPolygon.addForce(): strForceHard(%v) not int, err=\n\t%v\n", strForce, err)
+				return
+			}
+			sf.tankStat.Force().Set(iForce)
+			sf.tankStat.Force().SetName("hard")
+			sf.ModeCurrent().Set("прочность")
+		}
+	default: // Неизвестно что
+		sf.ModeCurrent().Set("неизвестно")
+		// log._rintf("ERRO NetPolygon.addForce(): неизвестно что это, strParam=%q", strParam)
+		return
+	}
+}

+ 35 - 0
server/serv_bots/warbot/angar/base/polygon/polygonnet/polygonnet.go

@@ -0,0 +1,35 @@
+package polygonnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Опрашивает полигон в сети
+*/
+
+// PolygonNet -- опрашивает полигон на базе
+type PolygonNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewPolygonNet -- возвращает новый *PolygonNet
+func NewPolygonNet(server types.IServer, bot types.IServBot) (*PolygonNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewPolygonNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewPolygonNet(): IServBot == nil")
+		}
+	}
+
+	sf := &PolygonNet{
+		SectionNet: sectionnet.NewSectionNet(server, bot, bot.Angar().Base().Polygon(), "http://wartank.ru/polygon"),
+	}
+
+	return sf, nil
+}

+ 167 - 0
server/serv_bots/warbot/angar/batmas/batmas.go

@@ -0,0 +1,167 @@
+package batmas
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/batmas/batmasnet"
+)
+
+/*
+	Битва мастеров. Работает примерно раз в сутки.
+	Требуется три победы, потом нужно загрести золотишко.
+	Между битвами надо удерживать рейтинг, чтобы не кидало к монстрам.
+*/
+
+// BatMas -- объект битвы мастеров
+type BatMas struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	net    *batmasnet.BatMasNet
+}
+
+// NetBatMas -- возвращает новый *BatMas
+func NewBatMas(server types.IServer, bot types.IServBot) (*BatMas, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBatMas(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBatMas(): IServBot == nil")
+		}
+	}
+	sf := &BatMas{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+// Run -- запускает в работу битву мастеров
+func (sf *BatMas) Run() error {
+	var err error
+	{ // ISection (ожидание)
+		sf.Section, err = section.NewSection(sf.server, `/> Битва мастеров <`)
+		if err != nil {
+			return fmt.Errorf("BatMas.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // Net (ожидание)
+		sf.net, err = batmasnet.NewBatMasNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("BatMas.Run(): при создании BatMasNet, err=\n\t%w", err)
+		}
+	}
+	_ = sf.goBatMas()
+	go func() {
+		for {
+			select {
+			case <-sf.server.Done():
+				sf.CountDown().Stop()
+				return
+			case <-sf.CountDown().ChanSig():
+				if !sf.goBatMas() { // Проверка на начало сражения
+					continue
+				}
+			}
+		}
+	}()
+	return nil
+}
+
+// Вычисляет нужно ли идти в битву мастеров
+//
+//	если нужно, то время проверять уже не надо
+func (sf *BatMas) goBatMas() bool {
+	sf.findTimeCount()
+	if !sf.upBattle() {
+		return false
+	}
+	countTime := sf.CountDown().Get()
+	countTime -= 5
+	if countTime > 25 {
+		if err := sf.CountDown().Set(countTime); err != nil {
+			log.Printf("ERRO BatMas.goBatMas(): при установке времени ожидания битвы мастеров(%v)\n\terr=%v\n", countTime, err)
+		}
+		return false
+	}
+
+	// Время меньше 25 сек, надо уточнять (тут возможна ошибка с экраном ожидания)
+	if err := sf.net.UpdateLst("Битва мастеров"); err != nil {
+		// log._rintf("ERRO Battle.goBattle().fnCountDown(): при обновлении lstBattle, err=\n\t%v\n", err)
+		// Возможно времени уже не осталось
+		return true
+	}
+	// Время ожидания вышло, надо начать атаку
+	if err := sf.CountDown().Set(0); err != nil {
+		log.Printf("ERRO BatMas.goBatMas(): при установке времени ожидания битвы мастеров(0)\n\terr=%v\n", err)
+	}
+	return false
+}
+
+// Ищет время до начала битвы мастеров
+func (sf *BatMas) findTimeCount() {
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+	)
+	// Обновление через: 12:02:22
+	for _, strOut = range lstBattle {
+		if strings.Contains(strOut, `Обновление через: `) {
+			isFind = true
+			break
+		}
+	}
+	if isFind { // Ждём начала битвы мастеров
+		lstTime := strings.Split(strOut, `Обновление через: `)
+		strTime := lstTime[1]
+		lstTime = strings.Split(strTime, ` (`)
+		strTime = lstTime[0]
+
+		if err := sf.CountDown().Parse(strTime); err != nil {
+			log.Printf("WARN BatMas.findTimeCount(): при установке времени ожидания битвы мастеров(%v)\n\terr=%v\n", strTime, err)
+		}
+	}
+}
+
+// При необходимости даёт команду на участие в битве мастеров,
+//
+//	вызывается только если есть награда
+func (sf *BatMas) upBattle() bool {
+	if err := sf.net.UpdateLst("Битва мастеров"); err != nil {
+		// log._rintf("ERRO BatMas.upBattle(): при обновлении lstBattle, err=\n\t%v\n", err)
+		return false
+	}
+	// log.Error("BatMas.upBattle(): доделать")
+	// var (
+	// 	strOut    string
+	// 	lstBattle = sf.GetLst()
+	// 	isFind    bool
+	// )
+	// for _, strOut = range lstBattle {
+	// 	if strings.Contains(strOut, `>Взвод, подъем! В атаку!<`) {
+	// 		isFind = true
+	// 		break
+	// 	}
+	// }
+	// if isFind {
+	// 	lstUp := strings.Split(strOut, `<a class="simple-but border" href="`)
+	// 	linkUp := lstUp[1]
+	// 	lstUp = strings.Split(linkUp, `"><span><span>Взвод, подъем! В атаку!</span></span></a>`)
+	// 	linkUp = "http://wartank.ru/" + lstUp[0]
+	// 	lstBattle, err := sf.net.Get(linkUp)
+	// 	if err != nil {
+	// 		log.WithError(err).Error("Battle.upBattle(): при выполнении GET-команды на подъём в атаку")
+	// 		return false
+	// 	}
+	// 	if err = sf.Update(lstBattle); err != nil {
+	// 		log.WithError(err).Error("Battle.upBattle(): при обновлении lstBattle")
+	// 	}
+	// }
+	return false
+}

+ 34 - 0
server/serv_bots/warbot/angar/batmas/batmasnet/batmasnet.go

@@ -0,0 +1,34 @@
+package batmasnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически воюет в битве мастеров
+*/
+
+// BatMasNet -- танкует в битве мастеров
+type BatMasNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewBatMasNet -- возвращает новый *BatMasNet
+func NewBatMasNet(server types.IServer, bot types.IServBot) (*BatMasNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBatMasNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBatMasNet(): IServBot == nil")
+		}
+	}
+
+	sf := &BatMasNet{
+		// SectionNet: sectionnet.NewSectionNet(server, bot, ..., "http://wartank.ru/pvp"),
+	}
+	return sf, nil
+}

+ 106 - 0
server/serv_bots/warbot/angar/battle/battle.go

@@ -0,0 +1,106 @@
+package battle
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"wartank/pkg/components/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"
+)
+
+/*
+	Объект ожидания сражения на базе
+*/
+
+// Battle -- объект ожидания сражения на базе
+type Battle struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	conn   *http.Client
+
+	battleRegister *battle_register.BattleRegister // Регистратор на сражение
+	battleWaiter   *battle_wait.BattleWait         // Ождатель начала сражения
+	battleWorker   *battle_worker.BattleWorker     // Исполнитель сражения
+
+}
+
+// NewBattle -- возвращает новый *Battle
+func NewBattle(server types.IServer, bot types.IServBot) (*Battle, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBattle(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBattle(): IServBot == nil")
+		}
+	}
+
+	sf := &Battle{
+		server: server,
+		bot:    bot,
+		conn:   bot.BotNet().Conn(),
+	}
+	var err error
+	{
+		sf.battleRegister, err = battle_register.NewBattleRegister(server, bot)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattle(): при создании регистратора на сражения, err=\n\t%w", err)
+		}
+		sf.battleWaiter, err = battle_wait.NewBattleWait(server, bot)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattle(): при создании ожидателя сражения, err=\n\t%w", err)
+		}
+		sf.battleWorker, err = battle_worker.NewBattleWorker(server, bot)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattle(): при создании исполнителя сражения, err=\n\t%w", err)
+		}
+	}
+
+	// sf.shotTimeFull.Set(8000) // 8000 msec
+
+	return sf, nil
+}
+
+func (sf *Battle) Run() error {
+	var err error
+	{ // ISection
+		sf.Section, err = section.NewSection(sf.server, `<span>до начала `)
+		if err != nil {
+			return fmt.Errorf("Battle.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	sf.battleRegister.Run()
+	sf.battleWaiter.Run()
+	sf.battleWorker.Run()
+	go sf.run()
+	return nil
+}
+
+// запускает в работу сражение
+func (sf *Battle) run() {
+	// sf.CountDown().SetInt(2)
+	for {
+		select {
+		case <-sf.server.Done():
+			return
+		default:
+			sf.battleRegister.Register()
+			sf.battleWaiter.Wait()
+			sf.battleWorker.Work()
+			for len(sf.CountDown().ChanSig()) > 0 {
+				<-sf.CountDown().ChanSig()
+			}
+			time.Sleep(time.Second * 2) // Пауза между циклами, чтобы сервер не долбить запросами
+		}
+	}
+}
+
+// Alarm -- возвращает признак начала сражения (для браузера)
+func (sf *Battle) Alarm() types.IStatParam {
+	return sf.battleWorker.Alarm()
+}

+ 88 - 0
server/serv_bots/warbot/angar/battle/battle_register/battle_register.go

@@ -0,0 +1,88 @@
+// package battle_register -- регестрирует танк в битве
+package battle_register
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+// BattleRegister -- регистрирует танк к началу атаки
+type BattleRegister struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	net    *sectionnet.SectionNet
+}
+
+// NewBattleRegister -- возвращает новый ожидатель битвы
+func NewBattleRegister(server types.IServer, bot types.IServBot) (*BattleRegister, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBattleRegister(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBattleRegister():IServBot == nil")
+		}
+	}
+	sf := &BattleRegister{
+		server: server,
+		bot:    bot,
+	}
+	var err error
+	{ // ISection (ожидание)
+		sf.Section, err = section.NewSection(server, `<title>Сражения</title>`)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattleRegister(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	return sf, nil
+}
+
+// Run -- запускает регистратор в работу
+func (sf *BattleRegister) Run() {
+	sf.net = sectionnet.NewSectionNet(sf.server, sf.bot, sf, "http://wartank.ru/pve")
+}
+
+// Register -- регистрирует танк на сражение
+func (sf *BattleRegister) Register() {
+	if err := sf.net.UpdateLst("Ближайшее сражение"); err != nil { // Здесь может уже обратный отсчёт перед сражением
+		return
+	}
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+	)
+	for _, strOut = range lstBattle {
+		if strings.Contains(strOut, `>Взвод, подъем! В атаку!<`) {
+			isFind = true
+			break
+		}
+		if strings.Contains(strOut, `<div class="white medium cntr bold mb5">Вы в рядах участников</div>`) {
+			// log._rintf("INFO BattleRegister.Register(): уже зарегистрирован\n")
+			return
+		}
+	}
+	if !isFind { // Возможно, ожидание или битва уже идёт
+		return
+	}
+	// Найдено приглашение на участие
+	lstUp := strings.Split(strOut, `<a class="simple-but border" href="`)
+	linkUp := lstUp[1]
+	lstUp = strings.Split(linkUp, `"><span><span>Взвод, подъем! В атаку!</span></span></a>`)
+	linkUp = "http://wartank.ru/" + lstUp[0]
+	lstBattle, err := sf.net.Get(linkUp)
+	if err != nil {
+		// log._rintf("ERRO BattleRegister.Register(): при выполнении GET-команды на подъём в атаку, err=\n\t%v\n", err)
+		return
+	}
+	if err = sf.Update(lstBattle); err != nil {
+		log.Printf("BattleRegister.Register(): при обновлении lstBattle, err=\n\t%v\n", err)
+	}
+	// log._rintf("INFO BattleRegister.Register(): регистрация прошла успешно\n")
+}

+ 99 - 0
server/serv_bots/warbot/angar/battle/battle_wait/battle_wait.go

@@ -0,0 +1,99 @@
+// package battle_wait -- заставляет ожидать начало битвы
+package battle_wait
+
+import (
+	"fmt"
+	// "log"
+	"strings"
+	"time"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+// BattleWait -- ожидатель начала битвы
+type BattleWait struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	net    *sectionnet.SectionNet
+}
+
+// NewBattleWait -- возвращает новый ожидатель битвы
+func NewBattleWait(server types.IServer, bot types.IServBot) (*BattleWait, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBattleWait(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBattleWait():IServBot == nil")
+		}
+	}
+	sf := &BattleWait{
+		server: server,
+		bot:    bot,
+	}
+	var err error
+	{ // ISection (ожидание)
+		sf.Section, err = section.NewSection(server, `<title>Сражения</title>`)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattleWait(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	return sf, nil
+}
+
+// Run -- запускает ожидание в работу
+func (sf *BattleWait) Run() {
+	sf.net = sectionnet.NewSectionNet(sf.server, sf.bot, sf, "http://wartank.ru/pve")
+}
+
+// Wait -- ожидает начало сражения
+func (sf *BattleWait) Wait() {
+	if err := sf.net.UpdateLst("Ближайшее сражение"); err != nil { // Здесь может уже обратный отсчёт перед сражением
+		return
+	}
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+	)
+	for _, strOut = range lstBattle {
+		if strings.Contains(strOut, `<span>до начала `) {
+			isFind = true
+			break
+		}
+		// if strings.Contains(strOut, `>ОБЫЧНЫЕ<`) { // Это уже битва
+		// 	if len(sf.chBattle) == 0 {
+		// 		sf.chBattle <- 1
+		// 	}
+		// 	return
+		// }
+	}
+	if !isFind { // Сражение уже идёт
+		return
+	}
+	// Найдена строка ожидания начала сражения
+	lstTime := strings.Split(strOut, `<span>до начала `)
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, ` (`)
+	strTime = lstTime[0]
+	if err := sf.CountDown().Parse(strTime); err != nil {
+		// log._rintf("WARN BattleWait.Wait(): при установке времени ожидания сражения(%v)\n\terr=%v\n", strTime, err)
+		return
+	}
+	// Зайти в цикложидания сражения
+	for {
+		countTime := sf.CountDown().Get()
+		if countTime > 0 {
+			time.Sleep(time.Millisecond * 500)
+			// log.Printf("BattleWait.Wait(): countTime=%v\n", sf.CountDown().String())
+			continue
+		}
+		for len(sf.CountDown().ChanSig()) > 0 {
+			<-sf.CountDown().ChanSig()
+		}
+		return
+	}
+}

+ 83 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battle_worker.go

@@ -0,0 +1,83 @@
+// package battle_worker -- исполнение битвы
+package battle_worker
+
+import (
+	"fmt"
+	"time"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/battlesound"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+// BattleWorker -- исполнение битвы
+type BattleWorker struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	net    *sectionnet.SectionNet
+
+	alarm types.IStatParam
+
+	// Непосредственное сражение
+	baton *battleon.BattleOn
+
+	sound *battlesound.BattleSound // Однопоточное проигрывание звука
+}
+
+// NewBattleWorker -- возвращает новый исполнитель битвы
+func NewBattleWorker(server types.IServer, bot types.IServBot) (*BattleWorker, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBattleWorker(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBattleWorker():IServBot == nil")
+		}
+	}
+	sf := &BattleWorker{
+		server: server,
+		bot:    bot,
+		alarm:  static_param.NewStaticParam("alarm"),
+		sound:  battlesound.NewBattleSound(),
+	}
+	var err error
+	{ // ISection (ожидание)
+		sf.Section, err = section.NewSection(server, `<title>Сражения</title>`)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattleWorker(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	return sf, nil
+}
+
+// Run -- запускает ожидание в работу
+func (sf *BattleWorker) Run() {
+	sf.net = sectionnet.NewSectionNet(sf.server, sf.bot, sf, "http://wartank.ru/pve")
+}
+
+// Work -- выполняет битву
+func (sf *BattleWorker) Work() {
+	var err error
+	sf.baton, err = battleon.NewBattleOn(sf.server, sf.bot) // IBattleOn (онлайн)
+	if err != nil {
+		return
+	}
+	sf.alarm.Set(1)
+	sf.sound.Play()
+	time.Sleep(time.Second * 10) // Задержка для звука на странице
+	sf.alarm.Set(0)
+	// Ожидание завершения
+	<-sf.baton.Ctx().Done()
+	sf.baton = nil
+	sf.CountDown().Set(2)
+	// log._rintf("Battle.runBaton(): сражение завершено\n")
+}
+
+// Alarm -- возвращает признак начала сражения (для браузера)
+func (sf *BattleWorker) Alarm() types.IStatParam {
+	return sf.alarm
+}

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

@@ -0,0 +1,183 @@
+package battleon
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/health"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/manevr"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/isshot"
+)
+
+/*
+	Предоставляет сетевой компонент при непосредственном сражении
+*/
+
+// BattleOn -- непосредственно танкует в сражении
+type BattleOn struct {
+	*section.Section
+	net            *sectionnet.SectionNet
+	server         types.IServer
+	bot            types.IServBot
+	ctxBattle      context.Context // Контекст сражения
+	fnCancelBattle func()          // Функция отмены сражения
+
+	shot   *shot.Shot     // Объект выстрела
+	health *health.Health // Текущее здоровье танка
+	manevr *manevr.Manevr // Возможность маневрирования
+	login  string
+	isShot *isshot.IsShot // Признак необходимости маскирования (запрет стрельбы, когда слабое здоровье)
+	chTick chan int       // Ежесекудная проверка на окончание сражения
+}
+
+// NewBattleOn -- возвращает новый *BattleOn
+func NewBattleOn(server types.IServer, bot types.IServBot) (*BattleOn, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBattleOn(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBattleOn():IServBot == nil")
+		}
+	}
+	ctxBattle, fnCancelBattle := context.WithTimeout(server.CtxApp(), time.Second*305)
+	sf := &BattleOn{
+		server:         server,
+		bot:            bot,
+		ctxBattle:      ctxBattle,
+		fnCancelBattle: fnCancelBattle,
+		login:          bot.Name(),
+		isShot:         isshot.NewIsShot(),
+		chTick:         make(chan int, 2),
+	}
+	var err error
+	{ // ISection (ожидание)
+		sf.Section, err = section.NewSection(server, `<title>Сражения</title>`)
+		if err != nil {
+			return nil, fmt.Errorf("NewBattleOn(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	go sf.makeTick()
+	go sf.run()
+	return sf, nil
+}
+
+// Ежесекудный тик
+func (sf *BattleOn) makeTick() {
+	defer func() {
+		close(sf.chTick)
+	}()
+	for {
+		select {
+		case <-sf.server.Done(): // Отмена контекста приложения
+			sf.fnCancelBattle()
+			return
+		case <-sf.ctxBattle.Done(): // Битва закончилась
+			return
+		default:
+			sf.chTick <- 1
+			time.Sleep(time.Second * 1)
+		}
+	}
+}
+
+// запускает сражение
+func (sf *BattleOn) run() {
+	{ // Создание сети
+		sf.net = sectionnet.NewSectionNet(sf.server, sf.bot, sf.bot.Angar().Battle(), "http://wartank.ru/pve")
+		sf.net.Run()
+	}
+	defer func() {
+		sf.fnCancelBattle()
+		// log._rintf("BattleOn.run(): сражение завершено\n")
+	}()
+	{ // Подготовка к сражению
+		var err error
+		sf.shot, err = shot.NewShot(sf, sf.login) // Объект выстрела
+		if err != nil {
+			// log._rintf("ERRO BattleOn.Run(): при создании выстрела танка, err=\n\t%v\n", err)
+			time.Sleep(time.Second * 1)
+			return
+		}
+		sf.health, err = health.NewHealth(sf, sf.login)
+		if err != nil {
+			// log._rintf("ERRO BattleOn.Run(): при создании здоровья танка, err=\n\t%v\n", err)
+			time.Sleep(time.Second * 1)
+			return
+		}
+		sf.manevr, err = manevr.NewManevr(sf)
+		if err != nil {
+			// log._rintf("ERRO BattleOn.Run(): при создании маневра танка, err=\n\t%v\n", err)
+			time.Sleep(time.Second * 1)
+			return
+		}
+	}
+	for { // Рабочий цикл сражения
+		select {
+		case <-sf.server.Done(): // Прерывание работы от приложения
+			return
+		case <-sf.CountDown().ChanSig(): // Вышло время (сигнал от секции)
+			return
+		case <-sf.ctxBattle.Done():
+			return
+		case <-sf.chTick: // Ежесекундный сигнал в сражении
+			sf.checkEnd()
+		}
+	}
+}
+
+// Net -- возвращает сетевой компонент секции
+func (sf *BattleOn) Net() types.ISectionNet {
+	return sf.net
+}
+
+// Проверяет окончание сражения
+func (sf *BattleOn) checkEnd() {
+	// log._rintf("BattleOn.checkEnd()\n")
+	fnPrintEnd := func() {
+		sf.fnCancelBattle()
+		// log._rintf("BattleOn.checkEnd(): сражение завершено\n")
+	}
+
+	if err := sf.net.UpdateLst("Сражение+"); err != nil {
+		// log._rintf("WARN BattleOn.checkEnd(): при обновлении lstBattleOn, err=\n\t%v\n", err)
+		fnPrintEnd()
+		return
+	}
+	lstBattleOn := sf.GetLst()
+	for _, strOut := range lstBattleOn { // Сражение ещё идёт
+		if strings.Contains(strOut, `" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>`) {
+			return
+		}
+	}
+	// Сражение закончилось
+	fnPrintEnd()
+}
+
+func (sf *BattleOn) SetNeedManevr() {
+	if sf.manevr == nil {
+		return
+	}
+	sf.manevr.SetNeed()
+}
+
+// Masking -- признак запрета стрельбы при слабом здоровье
+func (sf *BattleOn) Masking() *isshot.IsShot {
+	return sf.isShot
+}
+
+// Ctx -- возвращает контекст отмены сражения
+func (sf *BattleOn) Ctx() context.Context {
+	return sf.ctxBattle
+}
+
+// CancelBattle - -вызов функции отмены контекста сражения
+func (sf *BattleOn) CancelBattle() {
+	sf.fnCancelBattle()
+}

+ 49 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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 (sf *BattleSound) Play() {
+	if sf.isPlay.Get() {
+		return
+	}
+	go sf.play()
+}
+
+// Проигрывает экслюзивно в отдельном потоке звук
+func (sf *BattleSound) play() {
+	sf.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)
+	}
+	sf.isPlay.Reset()
+}

+ 41 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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 (sf *IsPlay) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (sf *IsPlay) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (sf *IsPlay) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 305 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/health.go

@@ -0,0 +1,305 @@
+package health
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+	"time"
+
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/healthtime"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/isrepair"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/health/repairtime"
+
+	// "wartank/pkg/components/sound"
+	"wartank/pkg/types"
+)
+
+/*
+	Контролирует состояние здоровья танка
+*/
+
+// Health -- контроль здоровья танка
+type Health struct {
+	types.IBattleOn
+	temp       *healthtime.HealthTime // Изменяемое здоровье танка
+	full       *healthtime.HealthTime // Полное здоровье танка
+	isRepair   *isrepair.IsRepair     // Необходимость восстановления
+	repairTime *repairtime.RepairTime // Время до восстановления
+	login      string                 // Для поиска контрольных строк
+	chTick     chan int               // Канал для ровной отправки тиков
+	deltaOld   int                    // Старая дельта потери здоровья
+}
+
+// NewHealth -- возвращает новый *Health
+func NewHealth(battle types.IBattleOn, login string) (*Health, error) {
+	{ // Предусловия
+		if battle == nil {
+			return nil, fmt.Errorf("NewHealth(): battle is nil")
+		}
+		if login == "" {
+			return nil, fmt.Errorf("NewHealth(): login is empty")
+		}
+	}
+	sf := &Health{
+		IBattleOn:  battle,
+		temp:       healthtime.NewHealthTime(),
+		full:       healthtime.NewHealthTime(),
+		isRepair:   isrepair.NewIsRepair(),
+		repairTime: repairtime.NewRepairTime(),
+		login:      login,
+		chTick:     make(chan int, 2),
+	}
+	go sf.makeTik()
+	go sf.run()
+	return sf, nil
+}
+
+// Отправляе ттики с заданным равным интервалом
+func (sf *Health) makeTik() {
+	defer func() {
+		close(sf.chTick)
+		sf.CancelBattle()
+		// log._rintf("Health.makeTick(): сражение завершёно\n")
+	}()
+	countLow := 0
+	for {
+		select {
+		case <-sf.Ctx().Done():
+			return
+		default:
+			if sf.IsDeath() {
+				return
+			}
+			switch sf.repairTime.Get() <= 0 {
+			case true:
+				countLow++
+			default:
+				countLow = 0
+			}
+			if countLow > 90 {
+				return
+			}
+		}
+		sf.chTick <- 1
+		time.Sleep(time.Second * 1)
+		sf.repairTime.Dec()
+	}
+}
+
+// Главный цикл обработки здоровья в сражении
+func (sf *Health) run() {
+	for range sf.chTick {
+		if err := sf.findHealth(); err != nil { // Найти свой здоровье
+			log.Printf("Health.run(): при попытке найти здоровье, err=\n\t%v\n", err)
+		}
+		sf.findRepairTime()
+		if sf.Masking().Get() {
+			if !sf.isRepair.Get() {
+				continue
+			}
+			sf.repair()
+		}
+	}
+}
+
+// Full -- возвращает объект полного здоровья танка
+func (sf *Health) Full() int {
+	return sf.full.Get()
+}
+
+// IsDeath -- возвращает признак мертвичины танка
+func (sf *Health) IsDeath() bool {
+	lstBattle := sf.GetLst()
+	for _, strOut := range lstBattle {
+		if strings.Contains(strOut, `>Ваш танк подбит.`) {
+			// log._rintf("INFO Health.repair(): танк подбит\n")
+			sf.temp.Set(0)
+			return true
+		}
+	}
+	return false
+}
+
+// Ищет время восстановления ремки
+func (sf *Health) findRepairTime() {
+	defer func() {
+		if sf.repairTime.IsReady() {
+			return
+		}
+		if sf.repairTime.IsChange() {
+			log.Printf("Health.findRepair(): до ремки=%v\n", sf.repairTime.Get())
+		}
+	}()
+	if sf.repairTime.IsReady() {
+		time.Sleep(time.Second * 1)
+		return
+	}
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+		ind       int
+	)
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
+	//
+	for ind, strOut = range lstBattle {
+		if !strings.Contains(strOut, `ILinkListener-currentControl-repairLink`) {
+			continue
+		}
+		if strings.Contains(strOut, ` секунд</span></span></a>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		time.Sleep(time.Second * 1)
+		return
+	}
+	strOut = lstBattle[ind]
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
+	lstTime := strings.Split(strOut, `ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>`)
+	if len(lstTime) < 2 {
+		// log._rintf("ERRO Health.findRepair(): при попытке получить ссылку на ремонт, strOut=\n%v\n", strOut)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
+	strTime = lstTime[0]
+	if err := sf.repairTime.Set(strTime); err != nil {
+		// log._rintf("ERRO Health.findRepair(): при установке времени восстановления ремки, err=\n\t%v\n", err)
+		time.Sleep(time.Second * 1)
+	}
+}
+
+// Восстановливает здоровье (~)
+func (sf *Health) repair() {
+	var (
+		strOut       string
+		lstBattleOn  = sf.GetLst()
+		isFindRepair bool
+		ind          int
+	)
+	// <span>Ремкомплект</span>
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
+	for ind, strOut = range lstBattleOn {
+		if strings.Contains(strOut, `<span>Ремкомплект</span>`) {
+			isFindRepair = true
+			break
+		}
+	}
+	if !isFindRepair {
+		time.Sleep(time.Second * 1)
+		return
+	}
+	strOut = lstBattleOn[ind]
+	// <a href="pve?6-26.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
+	lstLink := strings.Split(strOut, `<a href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Ремкомплект</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBattleOn, err := sf.Net().Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Health.repair(): при выполнении GET-команды ремонта, err=\n\t%v\n", err)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	if err = sf.Update(lstBattleOn); err != nil {
+		// log._rintf("ERRO Health.repair(): при обновлении lstBattle, err=\n\t%v\n", err)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	// sound.Repair()
+	sf.setHealth(sf.full.Get())
+	// log._rintf("INFO Health.repair(): здоровье восстановлено\n")
+}
+
+// Ищет своё здоровье (~)
+func (sf *Health) findHealth() error {
+	var (
+		ind       int
+		strOut    string
+		isFind    bool
+		lstBattle = sf.GetLst()
+	)
+	if len(lstBattle) == 0 { // Принудительно обновим сражение
+		if err := sf.Net().UpdateLst("Сражение+ Здоровье"); err != nil {
+			time.Sleep(time.Second * 1)
+			return fmt.Errorf("Health.findHealth(): пустой lsBattleOn, err=\n\t%w", err)
+		}
+	}
+	for ind, strOut = range lstBattle {
+		if strings.Contains(strOut, `alt="`+sf.login+`"`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Свой танк не найден
+		time.Sleep(time.Second * 1)
+		return fmt.Errorf("Health.findHealth(): своё здоровье не найдено")
+	}
+	// Свой танк найден, ищем здоровье
+	ind += 11
+	strOut = lstBattle[ind]
+	lstHealth := strings.Split(strOut, `<div class="value-block lh1"><span><span>`)
+	strHealth := lstHealth[1]
+	lstHealth = strings.Split(strHealth, `</span></span></div>`)
+	strHealth = lstHealth[0]
+	iHealth, err := strconv.Atoi(strHealth)
+	if err != nil {
+		time.Sleep(time.Second * 1)
+		return fmt.Errorf("Health.findHealth(): здоровье(%v) не число, err=%w", strHealth, err)
+	}
+	sf.setHealth(iHealth)
+	return nil
+}
+
+// setHealth -- устанавливает текущее здоровье
+func (sf *Health) setHealth(val int) {
+	if val < 0 {
+		// log._rintf("WARN Health.setHealth(): кривое значение здоровья танка(%v)\n", val)
+		val = 0
+		return
+	}
+
+	if val > sf.full.Get() {
+		// log._rintf("WARN Health.setHealth(): кривое текущее здоровье, %v/%v\n", val, sf.full.Get())
+		sf.full.Set(val)
+		sf.temp.Set(val)
+		sf.deltaOld = 0
+		sf.Masking().Reset()
+		sf.isRepair.Reset()
+		return
+	}
+
+	delta := sf.temp.Get() - val
+	if delta > 0 { // Дельта будет больше нуля, если только
+		if delta != sf.deltaOld {
+			// log._rintf("INFO Health.setHealth(): потеря здоровья=%v/%v\n", -delta, val)
+			sf.deltaOld = delta
+			sf.temp.Set(val)
+		}
+	}
+
+	switch {
+	case val == 0:
+		sf.temp.Set(0)
+		sf.CancelBattle()
+		return
+	case val <= 500: // Запретить стрельбу
+		sf.Masking().Set() // Установить запрет стрельбы пока слабое здоровье
+		sf.isRepair.Set()
+		// log._rintf("WARN Health.setHealth(): низкий уровень здоровья(%v)\n", val)
+		sf.SetNeedManevr()
+	case val > 500: // Разрешить стрельбы
+		sf.Masking().Reset()
+		sf.isRepair.Reset()
+		if delta > sf.full.Get()*4/10 { // Проверить на критичность падения здоровья на 40%
+			// log._rintf("WARN Health.setHealth(): большая разовая потеря здоровья(%v)\n", delta)
+			sf.isRepair.Set()
+			sf.SetNeedManevr()
+			return
+		}
+	}
+}

+ 46 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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 (sf *HealthTime) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// IsZero -- возвращает истину, если значение обнулено
+func (sf *HealthTime) IsZero() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val == 0
+}
+
+// Set -- устанавливает значение по требованию
+func (sf *HealthTime) Set(val int) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if val < 0 {
+		// log._rintf("WARN HealthTime.Set(): отрицательное значение(%v)\n", val)
+		sf.val = 0
+		return
+	}
+	sf.val = val
+}

+ 41 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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 (sf *IsRepair) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (sf *IsRepair) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (sf *IsRepair) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 77 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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{}
+}
+
+// Get -- возвращает хранимое значение времени
+func (sf *RepairTime) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// GetOld -- возвращает хранимое старое значение времени
+func (sf *RepairTime) GetOld() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.valOld
+}
+
+// Set -- устанавливает хранимое время восстановления ремки
+func (sf *RepairTime) Set(val string) error {
+	sf.block.Lock()
+	defer sf.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)
+	}
+	sf.valOld = sf.val
+	sf.val = iVal
+	return nil
+}
+
+// Dec -- уменьшает на секунду время восстановления
+func (sf *RepairTime) Dec() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if sf.val > 0 {
+		sf.valOld = sf.val
+		sf.val--
+	}
+}
+
+// IsReady -- возвращает признак готовности восстановления
+func (sf *RepairTime) IsReady() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val == 0
+}
+
+// IsChange -- возвращает признак изменения здоровья после присвоения
+func (sf *RepairTime) IsChange() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val == sf.valOld
+}

+ 43 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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 (sf *IsManevr) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (sf *IsManevr) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (sf *IsManevr) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 175 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/manevr/manevr.go

@@ -0,0 +1,175 @@
+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"
+)
+
+/*
+	Пытается маневрировать после выстрела
+*/
+
+// Manevr -- маневрирует после выстрела
+type Manevr struct {
+	types.IBattleOn                        // FIXME:
+	isNeedManevr    *ismanevr.IsManevr     // Требование выполнить манёвр
+	manevrTime      *repairtime.RepairTime // Время до востановления манёвра
+	chTick          chan int               // Тики для поиска маневра
+}
+
+// NewManevr -- возвращает новый *Manevr
+func NewManevr(battle types.IBattleOn) (*Manevr, error) {
+	{ // Предусловия
+		if battle == nil {
+			return nil, fmt.Errorf("NewManevr(): battle is nil")
+		}
+	}
+	sf := &Manevr{
+		IBattleOn:    battle,
+		isNeedManevr: ismanevr.NewIsManevr(),
+		manevrTime:   repairtime.NewRepairTime(),
+		chTick:       make(chan int, 1),
+	}
+	_ = sf.manevrTime.Set("0") // При запуске боя есть возможность маневрировать
+	go sf.makeTick()
+	go sf.run()
+	return sf, nil
+}
+
+// Генерирует тик для уменьшения времени ожидания восстановления возможности манёвра
+func (sf *Manevr) makeTick() {
+	defer func() {
+		close(sf.chTick)
+		// log._rintf("Manevr.makeTick(): сражение завершено\n")
+	}()
+	for {
+		select {
+		case <-sf.Ctx().Done():
+			return
+		default:
+			if sf.manevrTime.Get() <= 0 {
+				sf.chTick <- 1
+			}
+			sf.manevrTime.Dec()
+			time.Sleep(time.Second * 1)
+		}
+	}
+}
+
+// Рабочий цикл поиска маневра (~)
+func (sf *Manevr) run() {
+	for range sf.chTick {
+		if !sf.isNeedManevr.Get() { // Если нет требования манёвра -- пропускаем
+			continue
+		}
+		sf.manevr()
+		sf.findManevrTime() // Найти время после манёвра
+	}
+}
+
+// Ищет время для манёвра
+func (sf *Manevr) findManevrTime() {
+	var (
+		isFind      bool
+		ind         int
+		lstBattleOn = sf.GetLst()
+		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`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Или манёвр успел восстановиться, или конец сражения
+		if strings.Contains(strOut, `<span>Маневр</span>`) {
+			_ = sf.manevrTime.Set("0")
+			time.Sleep(time.Second * 1)
+			return
+		}
+		logrus.WithField("strOut", strOut).Warn("Manevr.findManevrTime(): ошибка в поиске времени манёвра")
+		time.Sleep(time.Second * 1)
+		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(): нет двух полей во времени ожидания")
+			time.Sleep(time.Second * 1)
+			return
+		}
+		strTime := lstTime[1]
+		lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
+		strTime = lstTime[0]
+		if err := sf.manevrTime.Set(strTime); err != nil {
+			logrus.WithError(err).Error("Manevr.findManevrTime(): при обновлении времени ожидания манёвра")
+			time.Sleep(time.Second * 1)
+			return
+		}
+	}
+	logrus.WithField("время", sf.manevrTime.Get()).Info("Manevr.findManevrTime(): до манёвра")
+}
+
+// Манёвр по возможности
+func (sf *Manevr) manevr() {
+	var (
+		isFind      = false
+		lstBattleOn = sf.GetLst()
+		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>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Либо ждём восстановления манёвра, либо сражение закончилось
+		time.Sleep(time.Second * 1)
+		return
+	}
+	{ // Попытка манёвра
+		lstLink := strings.Split(strOut, `<a href="`)
+		strLink := lstLink[1]
+		lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Маневр</span></span></a>`)
+		strLink = "http://wartank.ru/" + lstLink[0]
+		lstBattleOn, err := sf.Net().Get(strLink)
+		if err != nil {
+			logrus.WithError(err).Error("Manevr.Manevr(): при выполнении GET-команды маневра")
+			time.Sleep(time.Second * 1)
+			return
+		}
+		if err = sf.Update(lstBattleOn); err != nil {
+			logrus.WithError(err).Error("Manevr.Manevr(): при обновлении lstBattle")
+			time.Sleep(time.Second * 1)
+			return
+		}
+		// sound.Manevr()
+	}
+	sf.isNeedManevr.Reset()
+}
+
+// IsReady -- возвращает готовность манёвра
+func (sf *Manevr) IsReady() bool {
+	return sf.manevrTime.IsReady()
+}
+
+// SetNeed -- устанавливает признак необходимости манёвра
+func (sf *Manevr) SetNeed() {
+	sf.isNeedManevr.Set()
+}

+ 53 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/damage/damage.go

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

+ 41 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/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 (sf *IsShot) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (sf *IsShot) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (sf *IsShot) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 205 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/shot.go

@@ -0,0 +1,205 @@
+package shot
+
+import (
+	"fmt"
+	// "log"
+	"strconv"
+	"strings"
+	"time"
+
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/damage"
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/shottime"
+
+	// "wartank/internal/components/sound"
+	"wartank/pkg/alias"
+	"wartank/pkg/types"
+)
+
+/*
+	Исходник предоставляет выстрел со свойствами:
+		- время до выстрела
+		- длительность перезарядки
+
+	Первый параметр постоянно изменяется (после выстрела восстанавливается)
+	Второй параметр меняется медленно (в зависимости от количества очков после выстрела)
+*/
+
+// Shot -- объект выстрела
+type Shot struct {
+	types.IBattleOn                    // FIXME!!!
+	recharge        *shottime.ShotTime // Сколько времени нужно для полной перезарядки
+	damage          *damage.Damage     // Урон от выстрела с памятью
+	damageSum       alias.ADamage      // Суммарный урон
+	login           string             // Логин для поиска контрольных строк
+	chTick          chan int           // Тик для выстрела
+}
+
+// NewShot -- возвращает новый *Shot
+func NewShot(battle types.IBattleOn, login string) (*Shot, error) {
+	{ // Предусловия
+		if battle == nil {
+			return nil, fmt.Errorf("NewShot(): battle is nil")
+		}
+		if login == "" {
+			return nil, fmt.Errorf("NewShot(): login is empty")
+		}
+	}
+	sf := &Shot{
+		IBattleOn: battle,
+		recharge:  shottime.NewShotTime(),
+		damage:    damage.NewDamage(),
+		login:     login,
+		chTick:    make(chan int, 2),
+	}
+	// Установить время перезарядки
+	sf.recharge.Set(6800)
+	go sf.makeTick()
+	go sf.run()
+	return sf, nil
+}
+
+// Генерирует тики, когда можно стрелять
+func (sf *Shot) makeTick() {
+	defer func() {
+		close(sf.chTick)
+		// log._rintf("Shot.makeTick(): сражение завершёно\n")
+	}()
+	for {
+		select {
+		case <-sf.Ctx().Done():
+			return
+		default:
+			sf.chTick <- 1 // Первый выстрел, потом спать по таймингу
+			recharge := sf.recharge.Get()
+			// log._rintf("INFO Shot.run() перезарядка=%v msec\n", recharge)
+			// Если идёт перезарядка -- постепенно обнуляем время ожидания
+			time.Sleep(time.Millisecond * time.Duration(recharge))
+		}
+	}
+}
+
+// Цикл выстрела (в отдельном потоке)
+func (sf *Shot) run() {
+	for range sf.chTick {
+		if sf.Masking().Get() { // Стрелять нельзя
+			continue
+		}
+		// Стрелять можно, стандартное ожидание
+		sf.shot()
+		sf.findDamage()
+	}
+}
+
+// Обновляет возможность выстрела (~)
+//
+//	Вызывается из отдельного потока
+func (sf *Shot) shot() {
+	if err := sf.Net().UpdateLst("Сражение+ выстрел"); err != nil { // Проверка на непосредственно битву
+		// <span>закончилась 00:00:07 назад</span>
+		// log._rintf("ERRO Shot.shot(): при обновлении lstBattleOn, err=\n\t%v\n", err)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+		err       error
+	)
+	// <a href="pve?6-26.ILinkListener-currentControl-attackRegularShellLink" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>
+	for _, strOut = range lstBattle {
+		if strings.Contains(strOut, `-currentControl-attackRegularShellLink`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		// log._rintf("WARN Shot.shot(): не найдены ссылка на выстрел\n")
+		time.Sleep(time.Second * 1)
+		return
+	}
+	lstLink := strings.Split(strOut, `<a href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBattle, err = sf.Net().Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Shot.shot(): при исполнении GET-команды выстрела обычным снарядом, err=\n\t%v\n", err)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	if err = sf.Update(lstBattle); err != nil {
+		// log._rintf("ERRO Shot.shot(): при обновлении lstBattle, err=\n\t%v\n", err)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	sf.SetNeedManevr()
+	// sound.Shot()
+}
+
+// Ищет урон от выстрела
+func (sf *Shot) findDamage() {
+	var (
+		ind     = 0
+		isFind  = false
+		lstShot = sf.GetLst()
+		strOut  string
+	)
+
+	for ind, strOut = range lstShot {
+		// <span class="yellow1 td_u">prospero tank</span>
+		if strings.Contains(strOut, `<span class="yellow1 td_u">`+sf.login+`</span>`) {
+			ind += 2
+			strOut = lstShot[ind]
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Возможно был рикошет или манёвр
+		// log._rintf("WARN Shot.findDamage(): не найден урон от выстрела, strOut=%q, ind=%v\n", strOut, ind)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	lstDamage := strings.Split(strOut, ` на  <span class="red1">`)
+	if len(lstDamage) == 1 {
+		time.Sleep(time.Second * 1)
+		return
+	}
+	strDamage := lstDamage[1]
+	iDamage, err := strconv.Atoi(strDamage)
+	if err != nil {
+		// log._rintf("ERRO Shot.findDamage(): damage(%v) не число, err=\n\t%v\n", strDamage, err)
+		time.Sleep(time.Second * 1)
+		return
+	}
+	if iDamage <= 0 { // Кривой урон от выстрела
+		sf.recharge.Dec5()
+		// log._rintf("WARN Shot.findDamage(): ошибка в значении урона(%v)\n", iDamage)
+		iDamage = 0
+	}
+	sf.damageSum += alias.ADamage(iDamage)
+	// log._rintf("INFO Shot.Damage(): damageSum=%v\n", sf.damageSum)
+	if iDamage < 70 {
+		sf.damage.Set(alias.ADamage(iDamage))
+		sf.recharge.Inc210()
+	}
+	// log._rintf("INFO Shot.findDamage(): выстрел=+%v, урон=%v", iDamage, sf.damageSum)
+	if iDamage == 0 {
+		time.Sleep(time.Second * 1)
+		return
+	}
+	sf.setDamage(alias.ADamage(iDamage))
+}
+
+// setDamage -- обновляет время перезарядки в зависимости от произведённого урона
+func (sf *Shot) setDamage(val alias.ADamage) {
+	sf.damage.Set(val)
+	switch sf.damage.Result() {
+	case "none":
+		sf.recharge.Dec5()
+	case "up":
+		sf.recharge.Dec30()
+	case "down":
+		sf.recharge.Inc210()
+	}
+}

+ 76 - 0
server/serv_bots/warbot/angar/battle/battle_worker/battleon/shottime/shottime.go

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

+ 42 - 0
server/serv_bots/warbot/angar/battle/battlenet/battlenet.go

@@ -0,0 +1,42 @@
+package battlenet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически воюет в сражении
+*/
+
+// BattleNet -- танкует в сражении
+type BattleNet struct {
+	*sectionnet.SectionNet
+	server types.IServer
+	bot    types.IServBot
+}
+
+// NewBattleNet -- возвращает новый *BattleNet
+func NewBattleNet(server types.IServer, bot types.IServBot) (*BattleNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewBattleNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewBattleNet(): IServBot == nil")
+		}
+	}
+
+	sf := &BattleNet{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+// Запусает сеть в работу
+func (sf *BattleNet) Run() {
+	sf.SectionNet = sectionnet.NewSectionNet(sf.server, sf.bot, sf.bot.Angar().Battle(), "http://wartank.ru/pve")
+}

+ 33 - 0
server/serv_bots/warbot/angar/battle/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 (sf *IsRun) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+func (sf *IsRun) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+func (sf *IsRun) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 466 - 0
server/serv_bots/warbot/angar/convoy/convoy.go

@@ -0,0 +1,466 @@
+package convoy
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/sirupsen/logrus"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/convoy/convoynet"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Объект конвоя в ангаре
+*/
+
+// Convoy -- объект конвоя в ангаре
+type Convoy struct {
+	*section.Section
+	net      *convoynet.ConvoyNet
+	server   types.IServer
+	bot      types.IServBot
+	glory    types.IStatParam // Количество славы
+	chAttack chan int         // Канал проведения атаки
+}
+
+// NewConvoy -- возвращает новый *Convoy
+func NewConvoy(server types.IServer, bot types.IServBot) (*Convoy, error) {
+	sf := &Convoy{
+		server:   server,
+		bot:      bot,
+		glory:    static_param.NewStaticParam("glory"),
+		chAttack: make(chan int, 5),
+	}
+	return sf, nil
+}
+
+func (sf *Convoy) Run() error {
+	var err error
+	{ // Section
+		sf.Section, err = section.NewSection(sf.server, `<title>Конвой</title>`)
+		if err != nil {
+			return fmt.Errorf("Convoy.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // SectionNet
+		sf.net, err = convoynet.NewConvoyNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Convoy.Run(): in create *SectionNet, err=\n\t%w", err)
+		}
+		if err := sf.net.Run(); err != nil {
+			return fmt.Errorf("Convoy.Run(): in run *SectionNet, err=\n\t%w", err)
+		}
+	}
+	go sf.reservTick()
+	go sf.run()
+	return nil
+}
+
+// UpdateLst -- принудительно обновляет состояние конвоя
+func (sf *Convoy) UpdateLst() {
+	if err := sf.net.UpdateLst("Конвой"); err != nil {
+		log.Printf("Convoy.UpdateLst(): err=\n\t%v\n", err)
+	}
+}
+
+// Glory --возвращает счётчик славы конвоя
+func (sf *Convoy) Glory() types.IStatParam {
+	return sf.glory
+}
+
+// Если время конвоя замерло -- посылает контрольный сигнал на атаку
+func (sf *Convoy) reservTick() {
+	defer func() {
+		close(sf.chAttack)
+	}()
+	for {
+		select {
+		case <-sf.server.Done():
+			return
+		default:
+			ct0 := sf.CountDown().Get()
+			time.Sleep(time.Second * 60)
+			ct1 := sf.CountDown().Get()
+			if ct1 != ct0 {
+				continue
+			}
+			sf.chAttack <- 1
+		}
+	}
+}
+
+// обрабатывает конвой
+func (sf *Convoy) run() {
+	sf.chAttack <- 1
+	for {
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig(): // Время истекло
+			sf.updateTime()
+			sf.updateGlory()
+		case <-sf.chAttack: // Сигнал к атаке
+			sf.attackConvoy() // в этом месте только атаковать
+			sf.checkWarForce()
+			sf.checkMaster()
+			sf.check6frage()
+		}
+	}
+}
+
+// Обновляет славу потребованию
+func (sf *Convoy) updateGlory() {
+	// Найти строку с упоминанием оставшегося времени конвоя
+	lstConvoy := sf.GetLst()
+	var (
+		strGlory string
+		isFind   bool
+	)
+	for _, lastTime := range lstConvoy {
+		if strings.Contains(lastTime, `alt="Слава" title="Слава"> `) {
+			strGlory = lastTime
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Не найдена строка со славой -- это атака
+		return
+	}
+	// Ищем количество славы
+	lstGlory := strings.Split(strGlory, `alt="Слава" title="Слава"> `)
+	strGlory = lstGlory[1]
+	iGlory, err := strconv.Atoi(strGlory)
+	if err != nil {
+		// log._rintf("ERRO ConvoyNet.updateGlory(): слава(%v) не число, err=\n\t%v\n", strGlory, err)
+		return
+	}
+	sf.glory.Set(iGlory)
+}
+
+// Обновляет оставшееся время конвоя
+func (sf *Convoy) updateTime() {
+	// Время подходит надо обновляться
+	if err := sf.net.UpdateLst("Конвой"); err != nil {
+		logrus.WithError(err).Error("ConvoyNet.updateTime(): при выполнении GET-команды обновления")
+		sf.CountDown().Set(20)
+		return
+	}
+	// Найти строку с упоминанием оставшегося времени конвоя
+	lstConvoy := sf.GetLst()
+	var (
+		strLastTime string
+		isFind      bool
+		isMask      bool
+	)
+	for _, lastTime := range lstConvoy {
+		if strings.Contains(lastTime, `До следующего конвоя: `) {
+			strLastTime = lastTime
+			isFind = true
+			break
+		}
+		if strings.Contains(lastTime, `Полная маскировка через `) {
+			strLastTime = lastTime
+			isMask = true
+			break
+		}
+		// <div class="bot"><a class="simple-but border red" w:id="startFight" href="convoy?7-1.ILinkListener-root-startFight"><span><span>В БОЙ!</span></span></a></div>
+		if strings.Contains(lastTime, `ILinkListener-root-startFight`) {
+			sf.chAttack <- 1
+			return
+		}
+		if strings.Contains(lastTime, `ILinkListener-root-findEnemy`) {
+			sf.chAttack <- 1
+			return
+		}
+		// <div class="bot"><a class="simple-but border" w:id="startMasking" href="convoy?12-1.ILinkListener-root-startMasking"><span><span>В БОЙ!</span></span></a></div>
+		if strings.Contains(lastTime, `ILinkListener-root-startMasking`) {
+			sf.chAttack <- 1
+			return
+		}
+	}
+	switch {
+	case isFind: // Большая пауза между конвоями
+		// Ждём окончания ожидания конвоя
+		lstTime := strings.Split(strLastTime, `До следующего конвоя: `)
+		strLastTime = lstTime[1]
+		if err := sf.CountDown().Parse(strLastTime); err != nil {
+			// log._rintf("WARN Convoy.updateTime(): при установке времени ожидания конвоя(%v)\n\terr=%v\n", strLastTime, err)
+			sf.CountDown().Set(10)
+		}
+	case isMask: // Если маскировка между конвоями
+		// Ждём окончания ожидания конвоя
+		lstTime := strings.Split(strLastTime, `Полная маскировка через `)
+		strLastTime = lstTime[1]
+		if err := sf.CountDown().Parse(strLastTime); err != nil {
+			// log._rintf("ERRO Bank.getAllMode(): при установке времени банка для 1го режима(%v)\n\terr=%v\n", strLastTime, err)
+			sf.CountDown().Set(10)
+		}
+	}
+}
+
+// Проводит атаку на конвой
+func (sf *Convoy) attackConvoy() {
+	// Найти контрольную строку
+	var (
+		strOut    = ""
+		lstConvoy = sf.GetLst()
+		isFind    bool
+	)
+
+	for _, strLink := range lstConvoy {
+		if strings.Contains(strLink, `>Начать разведку<`) {
+			strOut = strLink
+			lstLink := strings.Split(strOut, `<div class="bot"><a class="simple-but border" w:id="findEnemy" href="`)
+			strOut = lstLink[1]
+			lstLink = strings.Split(strOut, `"><span><span>Начать разведку</span></span></a></div>`)
+			strOut = "http://wartank.ru/" + lstLink[0]
+			isFind = true
+			break
+		}
+		if strings.Contains(strLink, `<span>В БОЙ!</span>`) {
+			strOut = strLink
+			lstLink := strings.Split(strOut, `<div class="bot"><a class="simple-but border" w:id="startMasking" href="`)
+			if len(lstLink) == 1 {
+				lstLink = strings.Split(strOut, `<div class="bot"><a class="simple-but border red" w:id="startFight" href="`)
+			}
+			strOut = lstLink[1]
+			lstLink = strings.Split(strOut, `"><span><span>В БОЙ!</span></span></a></div>`)
+			strOut = "http://wartank.ru/" + lstLink[0]
+			isFind = true
+			break
+		}
+		if strings.Contains(strLink, `>ОБЫЧНЫЕ<`) {
+			strOut = strLink
+			lstLink := strings.Split(strOut, `<a href="`)
+			strOut = lstLink[1]
+			lstLink = strings.Split(strOut, `" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>`)
+			strOut = "http://wartank.ru/" + lstLink[0]
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Время ожидания
+		if err := sf.CountDown().Set(1); err != nil {
+			panic(fmt.Errorf("ConvoyNet.attackConvoy(): пр установке CountDown, err=\n\t%w", err))
+		}
+		return
+	}
+	strLink := strOut
+	// Можно начать разведку
+	lstConvoy, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO ConvoyNet.attackConvoy(): при выполнении GET-команды 'В атаку!', err=\n\t%v\n", err)
+		return
+	}
+	if err = sf.Update(lstConvoy); err != nil {
+		for _, strOut = range lstConvoy {
+			if strings.Contains(strOut, `<title>Ошибка на сервере. Сообщение админу уже отправлено.</title>`) {
+				// log._rintf("ERRO ConvoyNet.attackConvoy(): при обновлении lstConvoy, strOut=\n\t%v\n", strOut)
+				return
+			}
+		}
+		for _, strOut = range lstConvoy {
+			if strings.Contains(strOut, `<title>База</title>`) {
+				// log._rintf("ERRO ConvoyNet.attackConvoy(): при обновлении lstConvoy (найдено lstBase), strOut=\n\t%v\n", strOut)
+				return
+			}
+		}
+		// log._rintf("ERRO ConvoyNet.attackConvoy(): при обновлении lstConvoy, err=\n\t%v\n", err)
+		return
+	}
+	for sf.attack() {
+	}
+	if err := sf.CountDown().Set(1); err != nil {
+		panic(fmt.Errorf("ConvoyNet.attackConvoy(): при установке CountDown, err=\n\t%w", err))
+	}
+}
+
+// Выполняет атаку на конвой
+func (sf *Convoy) attack() (isNext bool) {
+	// Вырезать ссылку на атаку
+	strOut := ""
+	lstConvoy := sf.GetLst()
+	// <a href="convoy?13-2.ILinkListener-root-fightView-attackRegular" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>
+	for _, strAttack := range lstConvoy {
+		if strings.Contains(strAttack, `>ОБЫЧНЫЕ<`) {
+			strOut = strAttack
+			break
+		}
+		// Полная маскировка через 39:53
+		if strings.Contains(strAttack, `Полная маскировка через `) {
+			return false
+		}
+	}
+	if strOut == "" { // Нечего атаковать
+		return false
+	}
+	// Атакуем конвой
+	lstLink := strings.Split(strOut, `<a href="`)
+	if len(lstLink) != 2 {
+		return false
+	}
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	{ // Выполнить атаку
+		var err error
+		lstConvoy, err = sf.net.Get(strLink)
+		if err != nil {
+			logrus.WithError(err).Error("ConvoyNet.attack(): in get page find attack")
+			return false
+		}
+		if err = sf.Update(lstConvoy); err != nil {
+			logrus.WithError(err).Error("Convoy.attack(): при обновлении lstConvoy")
+			return false
+		}
+	}
+	return true
+}
+
+// Забирает награду в конвое "Активируй боевую силу"
+func (sf *Convoy) checkWarForce() {
+	var (
+		strOut    string
+		isFind    bool
+		lstConvoy = sf.GetLst()
+		ind       int
+	)
+	if len(lstConvoy) == 0 {
+		if err := sf.net.UpdateLst("Конвой"); err != nil {
+			// log._rintf("Convoy.check6frage(): при обновлении пустого lstConvoy, err=\n\t%v\n", err)
+			return
+		}
+		lstConvoy = sf.GetLst()
+	}
+	for ind, strOut = range lstConvoy {
+		if strings.Contains(strOut, `Активируй усиление &quot;Боевая сила&quot;<br/>`) {
+			isFind = true
+			ind += 23
+			strOut = lstConvoy[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="convoy?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	if !strings.Contains(strOut, `ILinkListener-missions-cc-0-c-awardLink`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	// http://wartank.ru/convoy?80-1.ILinkListener-missions-cc-0-c-awardLink
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstConvoy, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Convoy.checkWarForce(): при выполнени команды GET, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstConvoy); err != nil {
+		// log._rintf("ERRO Convoy.checkWarForce(): пр обновлении lstConvoy, err=\n\t%v\n", err)
+		return
+	}
+	// log._rintf("INFO Convoy.checkWarForce(): награда получена\n")
+}
+
+// Забирает награду в конвое "Мастер дозора"
+func (sf *Convoy) checkMaster() {
+	var (
+		strOut    string
+		isFind    bool
+		lstConvoy = sf.GetLst()
+		ind       int
+	)
+	if len(lstConvoy) == 0 {
+		if err := sf.net.UpdateLst("Конвой"); err != nil {
+			// log._rintf("Convoy.check6frage(): при обновлении пустого lstConvoy, err=\n\t%v\n", err)
+			return
+		}
+		lstConvoy = sf.GetLst()
+	}
+	for ind, strOut = range lstConvoy {
+		if strings.Contains(strOut, `Проведи разведку в конвое<br/>`) {
+			isFind = true
+			ind += 23
+			strOut = lstConvoy[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="convoy?61-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	if !strings.Contains(strOut, `ILinkListener-missions-cc-0-c-awardLink`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	// http://wartank.ru/convoy?61-1.ILinkListener-missions-cc-0-c-awardLink
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstConvoy, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Convoy.checkMaster(): при выполнени команды GET, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstConvoy); err != nil {
+		// log._rintf("ERRO Convoy.checkMaster(): пр обновлении lstConvoy, err=\n\t%v\n", err)
+		return
+	}
+	// log._rintf("INFO Convoy.checkMaster(): награда получена\n")
+}
+
+// Забирает награду в конвое "Уничтожь 6 врагов в конвое"
+func (sf *Convoy) check6frage() {
+	var (
+		strOut    string
+		isFind    bool
+		lstConvoy = sf.GetLst()
+		ind       int
+	)
+	if len(lstConvoy) == 0 {
+		if err := sf.net.UpdateLst("Конвой"); err != nil {
+			// log._rintf("Convoy.check6frage(): при обновлении пустого lstConvoy, err=\n\t%v\n", err)
+			return
+		}
+		lstConvoy = sf.GetLst()
+	}
+	for ind, strOut = range lstConvoy {
+		if strings.Contains(strOut, `Уничтожь 6 врагов в конвое<br/>`) {
+			isFind = true
+			ind += 23
+			strOut = lstConvoy[ind]
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="convoy?61-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	if !strings.Contains(strOut, `ILinkListener-missions-cc-0-c-awardLink`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	// http://wartank.ru/convoy?61-1.ILinkListener-missions-cc-0-c-awardLink
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstConvoy, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Convoy.check6frage(): при выполнени команды GET, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstConvoy); err != nil {
+		// log._rintf("ERRO Convoy.check6frage(): пр обновлении lstConvoy, err=\n\t%v\n", err)
+		return
+	}
+	// log._rintf("INFO Convoy.check6frage(): награда получена\n")
+}

+ 42 - 0
server/serv_bots/warbot/angar/convoy/convoynet/convoynet.go

@@ -0,0 +1,42 @@
+package convoynet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически воюет с конвоем в сети
+*/
+
+// ConvoyNet -- танкует с конвоем в сети
+type ConvoyNet struct {
+	*sectionnet.SectionNet
+	server types.IServer
+	bot    types.IServBot
+}
+
+// NewConvoyNet -- возвращает новый *ConvoyNet
+func NewConvoyNet(server types.IServer, bot types.IServBot) (*ConvoyNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewConvoyNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewConvoyNet(): IServBot == nil")
+		}
+	}
+
+	sf := &ConvoyNet{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+func (sf *ConvoyNet) Run() error {
+	sf.SectionNet = sectionnet.NewSectionNet(sf.server, sf.bot, sf.bot.Angar().Convoy(), "http://wartank.ru/convoy")
+	return nil
+}

+ 14 - 0
server/serv_bots/warbot/angar/division/division.go

@@ -0,0 +1,14 @@
+package division
+
+import (
+	"wartank/server/serv_bots/warbot/angar/division/divwar"
+)
+
+/*
+	Дивизия и все объекты в ней
+*/
+
+// Division -- дивизия и все объекты в ней
+type Division struct {
+	divwar *divwar.DivWar // Битва дивизий
+}

+ 228 - 0
server/serv_bots/warbot/angar/division/divwar/divwar.go

@@ -0,0 +1,228 @@
+package divwar
+
+import (
+	"fmt"
+	// "log"
+	"net/http"
+	"strings"
+	"sync"
+	"time"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwarnet"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/divwarsound"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Объект ожидания битвы дивизий
+*/
+
+// DivWar -- объект ожидания битвы дивизий
+type DivWar struct {
+	*section.Section
+	server types.IServer
+	bot    types.IServBot
+	alarm  types.IStatParam
+	net    *divwarnet.DivWarNet
+	conn   *http.Client
+
+	// Непосредственная битва
+	divon *divwaron.DivWarOn
+	login string // Для несоредственной битвы дивизий
+	block sync.Mutex
+
+	chDivWar chan int // Сигнал начала битвы дивизий
+
+	sound *divwarsound.DivWarSound // Однопоточное проигрывание звука
+}
+
+// NewDivWar -- возвращает новый *DivWar
+func NewDivWar(server types.IServer, bot types.IServBot) (*DivWar, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewDivWar(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewDivWar(): IServBot nil")
+		}
+	}
+
+	sf := &DivWar{
+		server:   server,
+		bot:      bot,
+		alarm:    static_param.NewStaticParam("alarm"),
+		chDivWar: make(chan int, 1),
+		sound:    divwarsound.NewDivWarSound(),
+		conn:     bot.BotNet().Conn(),
+		login:    "prospero tank",
+	}
+	// sf.shotTimeFull.Set(8000) // 8000 msec
+	var err error
+	{ // ISection
+		sf.Section, err = section.NewSection(server, `<span>до начала `)
+		if err != nil {
+			return nil, fmt.Errorf("NewDivWar(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // Net
+		sf.net, err = divwarnet.NewDivWarNet(server, bot)
+		if err != nil {
+			return nil, fmt.Errorf("NewDivWar(): при создании DivWarNet, err=\n\t%w", err)
+		}
+	}
+	go sf.run()
+	go sf.reservTick()
+	return sf, nil
+}
+
+func (sf *DivWar) reservTick() {
+	defer sf.CountDown().Stop()
+	for {
+		select {
+		case <-sf.server.Done():
+			return
+		default:
+			ct0 := sf.CountDown().Get()
+			time.Sleep(time.Second * 7)
+			ct1 := sf.CountDown().Get()
+			if ct1 != ct0 {
+				continue
+			}
+			if sf.divon != nil {
+				continue
+			}
+			sf.chDivWar <- 1
+		}
+	}
+}
+
+// запускает в работу битву дивизий
+func (sf *DivWar) run() {
+	sf.chDivWar <- 1
+	for {
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig(): // Время обновить данные по сражению
+			sf.findTimeCount()
+			sf.upDivWar()
+		case <-sf.chDivWar: // Сигнал к началу сражения
+			sf.block.Lock()
+			if sf.divon != nil {
+				continue
+			}
+			sf.alarm.Set(1)
+			sf.sound.Play()
+			go sf.DivWar()               // Запустить цикл непосредственного сражения
+			time.Sleep(time.Second * 10) // Задержка для звука на странице
+			sf.alarm.Set(0)
+		}
+	}
+}
+
+// Ищет время до начала битвы дивизий
+func (sf *DivWar) findTimeCount() {
+	if err := sf.net.UpdateLst("Битва дивизий"); err != nil { // Здесь может уже обратный отсчёт перед сражением
+		sf.chDivWar <- 1
+		return
+	}
+	var (
+		strOut    string
+		lstDivWar = sf.GetLst()
+		isFind    bool
+		ind       int
+	)
+	for ind, strOut = range lstDivWar {
+		if strings.Contains(strOut, `до начала: `) {
+			ind++
+			strOut = lstDivWar[ind]
+			isFind = true
+			break
+		}
+		if strings.Contains(strOut, `>ОБЫЧНЫЕ<`) { // Это уже битва
+			sf.chDivWar <- 1
+			return
+		}
+	}
+	if !isFind { // Битва дивизий уже идёт
+		sf.chDivWar <- 1
+		return
+	}
+	lstTime := strings.Split(strOut, `<span>`)
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, `</span>`)
+	strTime = lstTime[0]
+	if err := sf.CountDown().Parse(strTime); err != nil {
+		// log._rintf("WARN DivWar.findTimeCount(): при установке времени ожидания битвы дивизий(%v)\n\terr=%v\n", strTime, err)
+		return
+	}
+}
+
+// При необходимости поднимает взвод в атаку, вызывается только если обнаружено приглашение (+)
+func (sf *DivWar) upDivWar() {
+	var (
+		strOut    string
+		lstDivWar = sf.GetLst()
+		isFind    bool
+	)
+	for _, strOut = range lstDivWar {
+		if strings.Contains(strOut, `>Взвод, подъем! В атаку!<`) {
+			isFind = true
+			break
+		}
+		if strings.Contains(strOut, `<div class="white medium cntr bold mb5">Вы в рядах участников</div>`) {
+			// log._rintf("INFO DivWar.upDivWar(): уже зарегистрирован\n")
+			return
+		}
+	}
+	if !isFind {
+		return
+	}
+	// Найдено приглашение на участие
+	lstUp := strings.Split(strOut, `<a class="simple-but border" href="`)
+	linkUp := lstUp[1]
+	lstUp = strings.Split(linkUp, `"><span><span>Взвод, подъем! В атаку!</span></span></a>`)
+	linkUp = "http://wartank.ru/" + lstUp[0]
+	lstDivWar, err := sf.net.Get(linkUp)
+	if err != nil {
+		// log._rintf("ERRO DivWar.upDivWar(): при выполнении GET-команды на подъём в атаку, err=\n\t%v\n", err)
+		return
+	}
+	if err = sf.Update(lstDivWar); err != nil {
+		// log._rintf("ERRO DivWar.upDivWar(): при обновлении lstDivWar, err=\n\t%v\n", err)
+	}
+	// log._rintf("INFO DivWar.upDivWar(): регистрация прошла успешно\n")
+}
+
+// Ведёт сражение
+func (sf *DivWar) DivWar() {
+	defer func() {
+		sf.divon = nil
+		sf.block.Unlock()
+		if err := sf.CountDown().Set(1); err != nil {
+			panic(fmt.Errorf("DivWar.DivWar(): при установке CountDown, err=\n\t%w", err))
+		}
+		// log.Printf("INFO DivWar.DivWar(): сражение завершено\n")
+	}()
+	var err error
+	sf.divon, err = divwaron.NewDivWarOn(sf.server, sf.bot) // IDivWarOn (онлайн)
+	if err != nil {
+		// log._rintf("ERRO DivWar.DivWarOn(): при создании IDivWarOn, err=\n\t%v\n", err)
+		time.Sleep(time.Millisecond * 250)
+		return
+	}
+	// Цикл ожидания окончания сражения
+	for !sf.divon.IsEnd().Get() {
+		time.Sleep(time.Second * 1)
+	}
+
+}
+
+// Alarm -- возвращает признак начала сражения (для браузера)
+func (sf *DivWar) Alarm() types.IStatParam {
+	return sf.alarm
+}

+ 34 - 0
server/serv_bots/warbot/angar/division/divwar/divwarnet/divwarnet.go

@@ -0,0 +1,34 @@
+package divwarnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически воюет в сражении
+*/
+
+// DivWarNet -- танкует в сражении
+type DivWarNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewDivWarNet -- возвращает новый *DivWarNet
+func NewDivWarNet(server types.IServer, bot types.IServBot) (*DivWarNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewDivWarNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewDivWarNet(): IServBpt == nil")
+		}
+	}
+
+	sf := &DivWarNet{
+		// SectionNet: sectionnet.NewSectionNet(server, bot, ..., "http://wartank.ru/bitva"),
+	}
+	return sf, nil
+}

+ 190 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/divwaron.go

@@ -0,0 +1,190 @@
+package divwaron
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/components/section"
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/manevr"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/shot"
+)
+
+/*
+	Предоставляет сетевой компонент при непосредственном сражении
+*/
+
+// DivWarOn -- непосредственно танкует в сражении
+type DivWarOn struct {
+	*section.Section
+	net            *sectionnet.SectionNet
+	server         types.IServer
+	ctxDivWar      context.Context // Контекст сражения
+	fnCancelDivWar func()          // Функция отмены сражения
+
+	shot      *shot.Shot     // Объект выстрела
+	health    *health.Health // Текущее здоровье танка
+	manevr    *manevr.Manevr // Возможность маневрирования
+	login     string
+	isMasking *safebool.SafeBool // Признак необходимости маскирования (запрет стрельбы, когда слабое здоровье)
+	chTick    chan int           // Ежесекудная проверка на окончание сражения
+	isEnd     *safebool.SafeBool
+}
+
+// NewDivWarOn -- возвращает новый *DivWarOn
+func NewDivWarOn(server types.IServer, bot types.IServBot) (*DivWarOn, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewDivWarOn(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewDivWarOn(): IServBot == nil")
+		}
+	}
+	ctxDivWar, fnCancelDivWar := context.WithTimeout(server.CtxApp(), time.Second*305)
+	sf := &DivWarOn{
+		server:         server,
+		ctxDivWar:      ctxDivWar,
+		fnCancelDivWar: fnCancelDivWar,
+		login:          bot.Name(),
+		isMasking:      safebool.NewSafeBool(),
+		isEnd:          safebool.NewSafeBool(),
+	}
+	var err error
+	{ // ISection (ожидание)
+		sf.Section, err = section.NewSection(server, `<title>Сражения</title>`)
+		if err != nil {
+			return nil, fmt.Errorf("NewDivWarOn(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	// sf.net = sectionnet.NewSectionNet(server, bot, "http://wartank.ru/pve")
+	if sf.checkEnd() {
+		return nil, fmt.Errorf("NewDivWarOn(): нет страницы для сражения")
+	}
+	go sf.makeTick()
+	go sf.run()
+	return sf, nil
+}
+
+// Ежесекудный тик
+func (sf *DivWarOn) makeTick() {
+	defer func() {
+		close(sf.chTick)
+		sf.isEnd.Set()
+	}()
+	for !sf.isEnd.Get() {
+		select {
+		case <-sf.server.Done(): // Отмена контекста приложения
+			return
+		case <-sf.ctxDivWar.Done(): // Битва закончилась
+			return
+		default:
+			if sf.isEnd.Get() {
+				return
+			}
+			sf.chTick <- 1
+			time.Sleep(time.Second * 1)
+		}
+	}
+}
+
+// запускает сражение
+func (sf *DivWarOn) run() {
+	// defer log._rintf("DivWarOn.run(): сражение завершено\n")
+	{ // Подготовка к сражению
+		var err error
+		sf.shot, err = shot.NewShot(sf, sf.login) // Объект выстрела
+		if err != nil {
+			// log._rintf("ERRO DivWarOn.Run(): при создании выстрела танка, err=\n\t%v\n", err)
+			return
+		}
+		sf.health, err = health.NewHealth(sf, sf.shot.IsEnd(), sf.login)
+		if err != nil {
+			// log._rintf("ERRO DivWarOn.Run(): при создании здоровья танка, err=\n\t%v\n", err)
+			sf.shot.IsEnd().Reset()
+			return
+		}
+		sf.manevr, err = manevr.NewManevr(sf, sf.shot.IsEnd())
+		if err != nil {
+			// log._rintf("ERRO DivWarOn.Run(): при создании маневра танка, err=\n\t%v\n", err)
+			sf.shot.IsEnd().Reset()
+			return
+		}
+	}
+	for { // Рабочий цикл сражения
+		select {
+		case <-sf.ctxDivWar.Done():
+			return
+		case <-sf.CountDown().ChanSig():
+			if sf.checkEnd() {
+				return
+			}
+		}
+	}
+}
+
+// Net -- возвращает сетевой компонент секции
+func (sf *DivWarOn) Net() types.ISectionNet {
+	return sf.net
+}
+
+// Проверяет окончание сражения
+func (sf *DivWarOn) checkEnd() bool {
+	defer func() {
+		if sf.isEnd.Get() {
+			sf.fnCancelDivWar()
+			// log._rintf("DivWarOn.checkEnd(): сражение завершено\n")
+		}
+	}()
+
+	if err := sf.net.UpdateLst("Битва дивизий+"); err != nil {
+		// log._rintf("WARN DivWarOn.checkEnd(): при обновлении lstDivWarOn, err=\n\t%v\n", err)
+		sf.isEnd.Set()
+		sf.fnCancelDivWar()
+		return true
+	}
+	lstDivWarOn := sf.GetLst()
+	for _, strOut := range lstDivWarOn {
+		if strings.Contains(strOut, `" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>`) {
+			sf.isEnd.Reset()
+			return false
+		}
+	}
+	sf.isEnd.Set()
+	sf.fnCancelDivWar()
+	return true
+}
+
+// IsEnd -- возвращает признак окончания сражения (интерфейс)
+func (sf *DivWarOn) IsEnd() *safebool.SafeBool {
+	return sf.isEnd
+}
+
+func (sf *DivWarOn) Manevr() {
+	if sf.manevr == nil {
+		return
+	}
+	if sf.manevr.IsReady() {
+		sf.manevr.Manevr()
+	}
+}
+
+// Masking -- признак запрета стрельбы при слабом здоровье
+func (sf *DivWarOn) Masking() *safebool.SafeBool {
+	return sf.isMasking
+}
+
+// Ctx -- возвращает контекст отмены сражения
+func (sf *DivWarOn) Ctx() context.Context {
+	return sf.ctxDivWar
+}
+
+// CancelBattle - -вызов функции отмены контекста сражения
+func (sf *DivWarOn) CancelBattle() {
+	sf.fnCancelDivWar()
+}

+ 49 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/divwarsound/divwarsound.go

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

+ 41 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/divwarsound/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 (sf *IsPlay) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (sf *IsPlay) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (sf *IsPlay) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 358 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/health/health.go

@@ -0,0 +1,358 @@
+package health
+
+import (
+	"context"
+	"fmt"
+
+	// "log"
+	"strconv"
+	"strings"
+	"time"
+
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/healthtime"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/repairtime"
+
+	// "wartank/internal/components/sound"
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/types"
+)
+
+/*
+	Контролирует состояние здоровья танка
+*/
+
+// Health -- контроль здоровья танка
+type Health struct {
+	types.IDivWarOn // FIXME:
+	fnCancel        func()
+	temp            *healthtime.HealthTime // Изменяемое здоровье танка
+	full            *healthtime.HealthTime // Полное здоровье танка
+	isRepair        *safebool.SafeBool     // Необходимость восстановления
+	repairTime      *repairtime.RepairTime // Время до восстановления
+	isEnd           *safebool.SafeBool     // Ссылка на признак конца сражения
+	login           string                 // Для поиска контрольных строк
+	chTick          chan int               // Канал для ровной отправки тиков
+	deltaOld        int                    // Старая дельта потери здоровья
+	countLow        int
+	ctxBattle       context.Context // Конекст сражения
+}
+
+// NewHealth -- возвращает новый *Health
+func NewHealth(divwar types.IDivWarOn, isEnd *safebool.SafeBool, login string) (*Health, error) {
+	{ // Предусловия
+		if divwar == nil {
+			return nil, fmt.Errorf("NewHealth(): battle is nil")
+		}
+		if isEnd == nil {
+			return nil, fmt.Errorf("NewHealth(): isEnd is nil")
+		}
+		if login == "" {
+			return nil, fmt.Errorf("NewHealth(): login is empty")
+		}
+	}
+	sf := &Health{
+		IDivWarOn:  divwar,
+		fnCancel:   divwar.CancelBattle,
+		ctxBattle:  divwar.Ctx(),
+		temp:       healthtime.NewHealthTime(),
+		full:       healthtime.NewHealthTime(),
+		isRepair:   safebool.NewSafeBool(),
+		repairTime: repairtime.NewRepairTime(),
+		isEnd:      divwar.IsEnd(),
+		login:      login,
+		chTick:     make(chan int, 2),
+	}
+	go sf.makeTik()
+	go sf.run()
+	return sf, nil
+}
+
+// Отправляе ттики с заданным равным интервалом
+func (sf *Health) makeTik() {
+	defer func() {
+		sf.CancelBattle()
+		close(sf.chTick)
+		// log._rintf("Health.makeTick(): сражение завершёно\n")
+	}()
+	count := 0
+	repairTime := 0
+	for {
+		select {
+		case <-sf.ctxBattle.Done():
+			return
+		default:
+			if sf.IsDeath() {
+				return
+			}
+			if sf.repairTime.Get() == repairTime {
+				count++
+			} else {
+				repairTime = sf.repairTime.Get()
+				count = 0
+			}
+			if count > 90 {
+				return
+			}
+		}
+		sf.chTick <- 1
+		time.Sleep(time.Second * 1)
+		sf.repairTime.Dec()
+	}
+}
+
+// Главный цикл обработки здоровья в сражении
+func (sf *Health) run() {
+	for {
+		select {
+		case <-sf.ctxBattle.Done():
+			sf.isEnd.Set()
+			return
+		case <-sf.chTick:
+			if err := sf.findHealth(); err != nil { // Найти свой здоровье
+				// log._rintf("ERRO Health.run(): при попытке найти здоровье, err=\n\t%v\n", err)
+			}
+			sf.findRepairTime()
+			if sf.Masking().Get() {
+				if sf.isRepair.Get() {
+					go sf.repair()
+				}
+				continue
+			}
+			if sf.isRepair.Get() {
+				go sf.repair()
+			}
+		}
+	}
+}
+
+// Full -- возвращает объект полного здоровья танка
+func (sf *Health) Full() int {
+	return sf.full.Get()
+}
+
+// IsDeath -- возвращает признак мертвичины танка
+func (sf *Health) IsDeath() bool {
+	if sf.isEnd.Get() {
+		sf.fnCancel()
+		return true
+	}
+	lstBattle := sf.GetLst()
+	for _, strOut := range lstBattle {
+		if strings.Contains(strOut, `>Ваш танк подбит.`) {
+			// log._rintf("INFO Health.repair(): танк подбит\n")
+			sf.temp.Set(0)
+			sf.isEnd.Set()
+			sf.CancelBattle()
+			return true
+		}
+	}
+	return sf.isEnd.Get()
+}
+
+// Ищет время восстановления ремки
+func (sf *Health) findRepairTime() {
+	defer func() {
+		if sf.repairTime.IsReady() {
+			return
+		}
+		if sf.repairTime.IsChange() {
+			// log._rintf("INFO Health.findRepair(): до ремки=%v\n", sf.repairTime.Get())
+		}
+	}()
+	if sf.repairTime.IsReady() {
+		return
+	}
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+		ind       int
+	)
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
+	//
+	for ind, strOut = range lstBattle {
+		if !strings.Contains(strOut, `ILinkListener-currentControl-repairLink`) {
+			continue
+		}
+		if strings.Contains(strOut, ` секунд</span></span></a>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	strOut = lstBattle[ind]
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
+	lstTime := strings.Split(strOut, `ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>`)
+	if len(lstTime) < 2 {
+		// log._rintf("ERRO Health.findRepair(): при попытке получить ссылку на ремонт, strOut=\n%v\n", strOut)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	strTime := lstTime[1]
+	lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
+	strTime = lstTime[0]
+	if err := sf.repairTime.Set(strTime); err != nil {
+		// log._rintf("ERRO Health.findRepair(): при установке времени восстановления ремки, err=\n\t%v\n", err)
+	}
+}
+
+// Восстановливает здоровье (~)
+func (sf *Health) repair() {
+	var (
+		strOut       string
+		lstBattleOn  = sf.GetLst()
+		isFindRepair bool
+		ind          int
+	)
+	// <span>Ремкомплект</span>
+	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
+	for ind, strOut = range lstBattleOn {
+		if strings.Contains(strOut, `<span>Ремкомплект</span>`) {
+			isFindRepair = true
+			break
+		}
+	}
+	if !isFindRepair {
+		return
+	}
+	strOut = lstBattleOn[ind]
+	// <a href="pve?6-26.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
+	lstLink := strings.Split(strOut, `<a href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Ремкомплект</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBattleOn, err := sf.Net().Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Health.repair(): при выполнении GET-команды ремонта, err=\n\t%v\n", err)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	if err = sf.Update(lstBattleOn); err != nil {
+		// log._rintf("ERRO Health.repair(): при обновлении lstBattle, err=\n\t%v\n", err)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	// sound.Repair()
+	// log._rintf("INFO Health.repair(): здоровье восстановлено\n")
+}
+
+// Ищет своё здоровье (~)
+func (sf *Health) findHealth() error {
+	var (
+		ind       int
+		strOut    string
+		isFind    bool
+		lstBattle = sf.GetLst()
+	)
+	if len(lstBattle) == 0 { // Принудительно обновим сражение
+		if err := sf.Net().UpdateLst("Битва дивизий+ здоровье"); err != nil {
+			sf.isEnd.Set()
+			sf.fnCancel()
+			return fmt.Errorf("Health.findHealth(): после принудительного обновления lsBattleOn, err=\n\t%w", err)
+		}
+	}
+	for ind, strOut = range lstBattle {
+		if strings.Contains(strOut, `alt="`+sf.login+`"`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Свой танк не найден
+		sf.isEnd.Set()
+		sf.fnCancel()
+		return fmt.Errorf("Health.findHealth(): своё здоровье не найдено")
+	}
+	// Свой танк найден, ищем здоровье
+	ind += 11
+	strOut = lstBattle[ind]
+	lstHealth := strings.Split(strOut, `<div class="value-block lh1"><span><span>`)
+	strHealth := lstHealth[1]
+	lstHealth = strings.Split(strHealth, `</span></span></div>`)
+	strHealth = lstHealth[0]
+	iHealth, err := strconv.Atoi(strHealth)
+	if err != nil {
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return fmt.Errorf("Health.findHealth(): здоровье(%v) не число, err=%w", strHealth, err)
+	}
+	sf.setHealth(iHealth)
+	return nil
+}
+
+// setHealth -- устанавливает текущее здоровье
+func (sf *Health) setHealth(val int) {
+	if val < 0 {
+		// log._rintf("WARN Health.setHealth(): кривое значение здоровья танка(%v)\n", val)
+		val = 0
+	}
+
+	if val > sf.full.Get() {
+		// log._rintf("WARN Health.setHealth(): кривое текущее здоровье, %v/%v\n", val, sf.full.Get())
+		sf.full.Set(val)
+		sf.temp.Set(val)
+		sf.deltaOld = 0
+		sf.Masking().Reset()
+		sf.isRepair.Reset()
+		return
+	}
+
+	delta := sf.temp.Get() - val
+	if delta > 0 { // Дельта будет больше нуля, если только
+		if delta != sf.deltaOld {
+			// log._rintf("INFO Health.setHealth(): потеря здоровья=%v/%v\n", -delta, val)
+			sf.deltaOld = delta
+			sf.temp.Set(val)
+		}
+	}
+
+	switch {
+	case sf.isEnd.Get():
+		sf.temp.Set(0)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	case val == 0:
+		sf.temp.Set(0)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	case val <= 500: // Запретить стрельбу
+		sf.Masking().Set() // Установить запрет стрельбы пока слабое здоровье
+		sf.isRepair.Set()
+		// log._rintf("WARN Health.setHealth(): низкий уровень здоровья(%v)\n", val)
+		sf.Manevr()
+	case val > 500: // Разрешить стрельбы
+		sf.Masking().Reset()
+		sf.isRepair.Reset()
+		if delta > sf.full.Get()*4/10 { // Проверить на критичность падения здоровья на 40%
+			// log._rintf("WARN Health.setHealth(): большая разовая потеря здоровья(%v)\n", delta)
+			sf.Manevr()
+			sf.isRepair.Set()
+			return
+		}
+	}
+
+	isMask := sf.Masking().Get()
+	switch isMask {
+	case true:
+		sf.countLow++
+		if sf.countLow >= 200 {
+			sf.isEnd.Set()
+			sf.CancelBattle()
+			return
+		}
+	default:
+		sf.countLow = 0
+	}
+	if val == sf.full.Get() {
+		sf.temp.Set(val)
+		sf.isRepair.Reset()
+		sf.Masking().Reset()
+		sf.countLow = 0
+	}
+}

+ 46 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/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 (sf *HealthTime) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// IsZero -- возвращает истину, если значение обнулено
+func (sf *HealthTime) IsZero() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val == 0
+}
+
+// Set -- устанавливает значение по требованию
+func (sf *HealthTime) Set(val int) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if val < 0 {
+		// log._rintf("WARN HealthTime.Set(): отрицательное значение(%v)\n", val)
+		sf.val = 0
+		return
+	}
+	sf.val = val
+}

+ 77 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/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{}
+}
+
+// Get -- возвращает хранимое значение времени
+func (sf *RepairTime) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// GetOld -- возвращает хранимое старое значение времени
+func (sf *RepairTime) GetOld() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.valOld
+}
+
+// Set -- устанавливает хранимое время восстановления ремки
+func (sf *RepairTime) Set(val string) error {
+	sf.block.Lock()
+	defer sf.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)
+	}
+	sf.valOld = sf.val
+	sf.val = iVal
+	return nil
+}
+
+// Dec -- уменьшает на секунду время восстановления
+func (sf *RepairTime) Dec() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if sf.val > 0 {
+		sf.valOld = sf.val
+		sf.val--
+	}
+}
+
+// IsReady -- возвращает признак готовности восстановления
+func (sf *RepairTime) IsReady() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val == 0
+}
+
+// IsChange -- возвращает признак изменения здоровья после присвоения
+func (sf *RepairTime) IsChange() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val == sf.valOld
+}

+ 43 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/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 (sf *IsManevr) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое состояние
+func (sf *IsManevr) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбрасывает хранимое состояние
+func (sf *IsManevr) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 193 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/manevr/manevr.go

@@ -0,0 +1,193 @@
+package manevr
+
+import (
+	"context"
+	"fmt"
+
+	// "log"
+	"strings"
+	"time"
+
+	"github.com/sirupsen/logrus"
+
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/repairtime"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/manevr/ismanevr"
+
+	// "wartank/internal/components/sound"
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/types"
+)
+
+/*
+	Пытается маневрировать после выстрела
+*/
+
+// Manevr -- маневрирует после выстрела
+type Manevr struct {
+	types.IDivWarOn // FIXME:
+	isEnd           *safebool.SafeBool
+	ctxEnd          context.Context
+	isManevr        *ismanevr.IsManevr     // Возможность выполнить манёвр
+	manevrTime      *repairtime.RepairTime // Время до востановления манёвра
+	chTick          chan int               // Тики для поиска маневра
+}
+
+// NewManevr -- возвращает новый *Manevr
+func NewManevr(divwar types.IDivWarOn, isDivWar *safebool.SafeBool) (*Manevr, error) {
+	{ // Предусловия
+		if divwar == nil {
+			return nil, fmt.Errorf("NewManevr(): battle is nil")
+		}
+	}
+	if isDivWar == nil {
+		return nil, fmt.Errorf("NewManevr(): isBattle is nil")
+	}
+	sf := &Manevr{
+		IDivWarOn:  divwar,
+		ctxEnd:     divwar.Ctx(),
+		isEnd:      isDivWar,
+		isManevr:   ismanevr.NewIsManevr(),
+		manevrTime: repairtime.NewRepairTime(),
+		chTick:     make(chan int, 1),
+	}
+	_ = sf.manevrTime.Set("0") // При запуске боя есть возможность маневрировать
+	go sf.makeTick()
+	go sf.run()
+	return sf, nil
+}
+
+// Генерирует тик для уменьшения времени ожидания восстановления возможности манёвра
+func (sf *Manevr) makeTick() {
+	defer func() {
+		close(sf.chTick)
+		// log._rintf("Manevr.makeTick(): сражение завершено\n")
+	}()
+	for {
+		select {
+		case <-sf.ctxEnd.Done():
+			return
+		default:
+			if sf.manevrTime.Get() == 0 {
+				sf.chTick <- 1
+			}
+
+			sf.manevrTime.Dec()
+			time.Sleep(time.Second * 1)
+		}
+	}
+}
+
+// Рабочий цикл поиска маневра (~)
+func (sf *Manevr) run() {
+	for range sf.chTick {
+		if !sf.isManevr.Get() {
+			continue
+		}
+		sf.findManevrTime()
+	}
+}
+
+// Ищет время для манёвра
+func (sf *Manevr) findManevrTime() {
+	var (
+		isFind      bool
+		ind         int
+		lstBattleOn = sf.GetLst()
+		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`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Или манёвр успел восстановиться, или конец сражения
+		if strings.Contains(strOut, `<span>Маневр</span>`) {
+			_ = sf.manevrTime.Set("0")
+			time.Sleep(time.Second * 1)
+			return
+		}
+		if sf.isEnd.Get() {
+			time.Sleep(time.Second * 1)
+			return
+		}
+		logrus.WithField("strOut", strOut).Warn("Manevr.findManevrTime(): ошибка в поиске времени манёвра")
+		time.Sleep(time.Second * 1)
+		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(): нет двух полей во времени ожидания")
+			sf.isManevr.Reset()
+			time.Sleep(time.Second * 1)
+			return
+		}
+		strTime := lstTime[1]
+		lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
+		strTime = lstTime[0]
+		if err := sf.manevrTime.Set(strTime); err != nil {
+			logrus.WithError(err).Error("Manevr.findManevrTime(): при обновлении времени ожидания манёвра")
+			sf.isManevr.Reset()
+			time.Sleep(time.Second * 1)
+			return
+		}
+	}
+	sf.isManevr.Set()
+	logrus.WithField("время", sf.manevrTime.Get()).Info("Manevr.findManevrTime(): до манёвра")
+}
+
+// Manevr -- принудительный манёвр по требованию
+func (sf *Manevr) Manevr() {
+	var (
+		isFind      = false
+		lstBattleOn = sf.GetLst()
+		strOut      = ""
+	)
+	if !sf.isManevr.Get() {
+		time.Sleep(time.Second * 1)
+		return
+	}
+	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>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Либо ждём восстановления манёвра, либо сражение закончилось
+		sf.isManevr.Reset()
+		time.Sleep(time.Second * 1)
+		return
+	}
+	{ // Попытка манёвра
+		lstLink := strings.Split(strOut, `<a href="`)
+		strLink := lstLink[1]
+		lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Маневр</span></span></a>`)
+		strLink = "http://wartank.ru/" + lstLink[0]
+		lstBattleOn, err := sf.Net().Get(strLink)
+		if err != nil {
+			logrus.WithError(err).Error("Manevr.Manevr(): при выполнении GET-команды маневра")
+			sf.isManevr.Reset()
+			time.Sleep(time.Second * 1)
+			return
+		}
+		if err = sf.Update(lstBattleOn); err != nil {
+			logrus.WithError(err).Error("Manevr.Manevr(): при обновлении lstBattle")
+			sf.isManevr.Reset()
+			time.Sleep(time.Second * 1)
+			return
+		}
+		// sound.Manevr()
+	}
+}
+
+// IsReady -- возвращает готовность манёвра
+func (sf *Manevr) IsReady() bool {
+	return sf.manevrTime.IsReady()
+}

+ 53 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/shot/damage/damage.go

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

+ 238 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/shot/shot.go

@@ -0,0 +1,238 @@
+package shot
+
+import (
+	"context"
+	"fmt"
+
+	// "log"
+	"strconv"
+	"strings"
+	"time"
+
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/shot/damage"
+	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/shottime"
+
+	// "wartank/internal/components/sound"
+	"wartank/pkg/alias"
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/types"
+)
+
+/*
+	Исходник предоставляет выстрел со свойствами:
+		- время до выстрела
+		- длительность перезарядки
+
+	Первый параметр постоянно изменяется (после выстрела восстанавливается)
+	Второй параметр меняется медленно (в зависимости от количества очков после выстрела)
+*/
+
+// Shot -- объект выстрела
+type Shot struct {
+	types.IDivWarOn                    // FIXME:
+	recharge        *shottime.ShotTime // Сколько времени нужно для полной перезарядки
+	damage          *damage.Damage     // Урон от выстрела с памятью
+	damageSum       alias.ADamage      // Суммарный урон
+	isEnd           *safebool.SafeBool // Признак конца сражения
+	login           string             // Логин для поиска контрольных строк
+	chTick          chan int           // Тик для выстрела
+	ctxEnd          context.Context    // Признак окончания сражения
+}
+
+// NewShot -- возвращает новый *Shot
+func NewShot(divWar types.IDivWarOn, login string) (*Shot, error) {
+	{ // Предусловия
+		if divWar == nil {
+			return nil, fmt.Errorf("NewShot(): battle is nil")
+		}
+		if login == "" {
+			return nil, fmt.Errorf("NewShot(): login is empty")
+		}
+	}
+	sf := &Shot{
+		IDivWarOn: divWar,
+		ctxEnd:    divWar.Ctx(),
+		recharge:  shottime.NewShotTime(),
+		damage:    damage.NewDamage(),
+		isEnd:     divWar.IsEnd(),
+		login:     login,
+		chTick:    make(chan int, 2),
+	}
+	// Установить время перезарядки
+	sf.recharge.Set(6800)
+	go sf.makeTick()
+	go sf.run()
+	return sf, nil
+}
+
+// Генерирует тики, когда можно стрелять
+func (sf *Shot) makeTick() {
+	defer func() {
+		sf.isEnd.Set()
+		close(sf.chTick)
+		sf.CancelBattle()
+		// log._rintf("Shot.makeTick(): сражение завершёно\n")
+	}()
+	countMasking := 0
+	for {
+		select {
+		case <-sf.ctxEnd.Done():
+			return
+		default:
+			if sf.isEnd.Get() { // Битва закончилась
+				return
+			}
+			switch sf.Masking().Get() { // Проверить запрет на стрельбу при слабом здоровье
+			case true:
+				// log._rintf("WARN Shot.run(): запрет на выстрел\n")
+				countMasking++
+				if countMasking >= 200 {
+					return
+				}
+				sf.Manevr()
+				time.Sleep(time.Millisecond * 500)
+				continue
+			default:
+				countMasking = 0
+			}
+			sf.chTick <- 1 // Здесь же первый выстрел
+			recharge := sf.recharge.Get()
+			// log._rintf("INFO Shot.run() перезарядка=%v msec\n", recharge)
+			// Если идёт перезарядка -- постепенно обнуляем время ожидания
+			time.Sleep(time.Millisecond * time.Duration(recharge))
+		}
+	}
+}
+
+// Цикл выстрела (в отдельном потоке)
+func (sf *Shot) run() {
+	for {
+		select {
+		case <-sf.ctxEnd.Done():
+			return
+		case <-sf.chTick:
+			// Стрелять можно, стандартное ожидание
+			sf.shot()
+			sf.findDamage()
+		}
+	}
+}
+
+// Обновляет возможность выстрела (~)
+//
+//	Вызывается из отдельного потока
+func (sf *Shot) shot() {
+	if err := sf.Net().UpdateLst("Битва дивизий+ выстрел"); err != nil { // Проверка на непосредственно битву
+		// <span>закончилась 00:00:07 назад</span>
+		// log._rintf("ERRO Shot.shot(): при обновлении lstBattleOn, err=\n\t%v\n", err)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	var (
+		strOut    string
+		lstBattle = sf.GetLst()
+		isFind    bool
+		err       error
+	)
+	// <a href="pve?6-26.ILinkListener-currentControl-attackRegularShellLink" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>
+	for _, strOut = range lstBattle {
+		if strings.Contains(strOut, `-currentControl-attackRegularShellLink`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		// log._rintf("WARN Shot.shot(): не найдены ссылка на выстрел\n")
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	lstLink := strings.Split(strOut, `<a href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `" class="simple-but gray"><span><span>ОБЫЧНЫЕ</span></span></a>`)
+	strLink = "http://wartank.ru/" + lstLink[0]
+	lstBattle, err = sf.Net().Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Shot.shot(): при исполнении GET-команды выстрела обычным снарядом, err=\n\t%v\n", err)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	if err = sf.Update(lstBattle); err != nil {
+		// log._rintf("ERRO Shot.shot(): при обновлении lstBattle, err=\n\t%v\n", err)
+		sf.isEnd.Set()
+		sf.CancelBattle()
+		return
+	}
+	// sound.Shot()
+	sf.Manevr()
+}
+
+// Ищет урон от выстрела
+func (sf *Shot) findDamage() {
+	var (
+		ind     = 0
+		isFind  = false
+		lstShot = sf.GetLst()
+		strOut  string
+	)
+
+	for ind, strOut = range lstShot {
+		// <span class="yellow1 td_u">prospero tank</span>
+		if strings.Contains(strOut, `<span class="yellow1 td_u">`+sf.login+`</span>`) {
+			ind += 2
+			strOut = lstShot[ind]
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Возможно был рикошет или манёвр
+		// log._rintf("WARN Shot.findDamage(): не найден урон от выстрела, strOut=%q, ind=%v\n", strOut, ind)
+		return
+	}
+	lstDamage := strings.Split(strOut, ` на  <span class="red1">`)
+	if len(lstDamage) == 1 {
+		return
+	}
+	strDamage := lstDamage[1]
+	iDamage, err := strconv.Atoi(strDamage)
+	if err != nil {
+		// log._rintf("ERRO Shot.findDamage(): damage(%v) не число, err=\n\t%v\n", strDamage, err)
+		return
+	}
+	if iDamage <= 0 { // Кривой урон от выстрела
+		sf.recharge.Dec5()
+		// log._rintf("WARN Shot.findDamage(): ошибка в значении урона(%v)\n", iDamage)
+		iDamage = 0
+	}
+	sf.damageSum += alias.ADamage(iDamage)
+	// log._rintf("INFO Shot.Damage(): damageSum=%v\n", sf.damageSum)
+	if iDamage < 70 {
+		sf.damage.Set(alias.ADamage(iDamage))
+		sf.recharge.Inc210()
+	}
+	// log._rintf("INFO Shot.findDamage(): выстрел=+%v, урон=%v", iDamage, sf.damageSum)
+	if iDamage == 0 {
+		return
+	}
+	sf.setDamage(alias.ADamage(iDamage))
+}
+
+// setDamage -- обновляет время перезарядки в зависимости от произведённого урона
+func (sf *Shot) setDamage(val alias.ADamage) {
+	sf.damage.Set(val)
+	switch sf.damage.Result() {
+	case "none":
+		sf.recharge.Dec5()
+	case "up":
+		sf.recharge.Dec30()
+	case "down":
+		sf.recharge.Inc210()
+	}
+}
+
+// IsEnd -- возвращает объект разрешения стрельбы
+func (sf *Shot) IsEnd() *safebool.SafeBool {
+	return sf.isEnd
+}

+ 76 - 0
server/serv_bots/warbot/angar/division/divwar/divwaron/shottime/shottime.go

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

+ 91 - 0
server/serv_bots/warbot/angar/fuel/fuel.go

@@ -0,0 +1,91 @@
+// package fuel -- топливо в баке
+package fuel
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+// Fuel -- топливо в баке
+type Fuel struct {
+	server types.IServer
+	angar  types.IAngar
+	bot    types.IServBot
+	fuel   types.IStatParam
+}
+
+// NewFuel -- возвращает новый топливный бак
+func NewFuel(server types.IServer, bot types.IServBot) (*Fuel, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewFuel(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewFuel(): IServBot == nil")
+		}
+	}
+	sf := &Fuel{
+		server: server,
+		angar:  bot.Angar(),
+		bot:    bot,
+		fuel:   static_param.NewStaticParam("fuel"),
+	}
+	return sf, nil
+}
+
+// Run -- должен работать в отдельном потоке, контролит топливо
+func (sf *Fuel) Run() {
+	count := 0 // Каждые 1500 сек (100 топлива проверять принудительно)
+	for {
+		time.Sleep(time.Second * 15)
+		if sf.fuel.Get() == 0 || count >= 100 {
+			sf.findFuel()
+			count = 0
+			continue
+		}
+		count++
+		fuel := sf.angar.Fuel().Get()
+		fuel++
+		sf.angar.Fuel().Set(fuel)
+		// log.Printf("Fuel.Run: val=%v\n", fuel)
+	}
+}
+
+// Ищет в теле текста ангара топливо
+func (sf *Fuel) findFuel() {
+	// _mt.Println("\tAngarNet.findFuel()")
+	if sf.fuel.Get() > 314 {
+		return
+	}
+	lstAngar := sf.bot.Angar().GetLst()
+	var strOut string
+	for _, strFuel := range lstAngar {
+		if strings.Contains(strFuel, `<img title="Топливо" `) {
+			strOut = strFuel
+			break
+		}
+	}
+	// Выделить топливо
+	lstFuel := strings.Split(strOut, `<img title="Топливо" alt="Топливо" src="/images/icons/fuel.png?2"/> `)
+	strFuel := lstFuel[1]
+	iFuel, err := strconv.Atoi(strFuel)
+	if err != nil {
+		// log._rintf("ERRO Fuel.findFuel(): fuel(%v) not number, err=\n\t%v\n", strFuel, err)
+		return
+	}
+	sf.bot.Angar().Fuel().Set(iFuel)
+	if iFuel <= 314 { // Минимальное значение "топливного склада-1" -- 315
+		return
+	}
+	// log._rintf("Fuel.findFuel():iFuel=%v\n", iFuel)
+}
+
+// Fuel -- возвращает объект сстатическог опараметра топлива
+func (sf *Fuel) Fuel() types.IStatParam {
+	return sf.fuel
+}

+ 519 - 0
server/serv_bots/warbot/angar/missions/missions.go

@@ -0,0 +1,519 @@
+package missions
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"wartank/pkg/components/section"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar/missions/missionsnet"
+)
+
+/*
+	Сканирует миссии на предмет забрать золотишко.
+*/
+
+// Missions -- забирает золотишко
+type Missions struct {
+	*section.Section
+	bot    types.IServBot
+	net    *missionsnet.MissionsNet
+	server types.IServer
+}
+
+// NewMissions -- возвращает новый *Missions
+func NewMissions(server types.IServer, bot types.IServBot) (*Missions, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewMissions(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewMissions(): IServBot == nil")
+		}
+	}
+	sf := &Missions{
+		server: server,
+		bot:    bot,
+	}
+
+	return sf, nil
+}
+
+func (sf *Missions) Run() error {
+	var err error
+	{ // Section
+		sf.Section, err = section.NewSection(sf.server, `<title>Миссии</title>`)
+		if err != nil {
+			return fmt.Errorf("Missions.Run(): in create *Section, err=\n\t%w", err)
+		}
+	}
+	{ // SectionNet
+		sf.net, err = missionsnet.NewMissionsNet(sf.server, sf.bot)
+		if err != nil {
+			return fmt.Errorf("Missions.Run(): in create *SectionNet, err=\n\t%w", err)
+		}
+	}
+	go sf.run()
+	return nil
+}
+
+// обрабатывает конвой
+func (sf *Missions) run() {
+	defer func() {
+		sf.CountDown().Stop()
+	}()
+	time.Sleep(time.Second * 15)
+	for { // Время истекло
+		select {
+		case <-sf.server.Done():
+			sf.CountDown().Stop()
+			return
+		case <-sf.CountDown().ChanSig():
+			if err := sf.net.UpdateLst("Missions.run()"); err != nil {
+				// log._rintf("ERRO Missions.run(): при обновлении lstMissions, err=\n\t%v\n", err)
+				return
+			}
+			sf.checkAvard()
+		}
+		time.Sleep(time.Second * 30)
+		if err := sf.CountDown().Set(1); err != nil {
+			panic(fmt.Errorf("Missions.run(): при установке CountDown, err=\n\t%w", err))
+		}
+	}
+}
+
+// Забирает всё самое вкусное
+func (sf *Missions) checkAvard() {
+	sf.battleDefence()
+	sf.battleAttack()
+	sf.battleWar()
+	sf.battle5Fiting()
+	sf.battle6win()
+	sf.battle10Fiting()
+	sf.battleDogFyting()
+	sf.makeResource()
+	sf.kill3tanks()
+	sf.makeFuel()
+	sf.upMan()
+}
+
+// Проверяет награду за уничтожить 3 танка в бою
+func (sf *Missions) kill3tanks() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Уничтожь в бою 3 танка<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.kill3tanks(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.kill3tanks(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду оборонительного сражения
+func (sf *Missions) battleDefence() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Проведи одно оборонительное сражение<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battleDefence(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battleDefence(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду за одну войну
+func (sf *Missions) battleWar() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Проведи одну войну<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battleWar(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battleWar(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду наступательного сражения
+func (sf *Missions) battleAttack() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Проведи одно наступательное сражение<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battleAttack(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battleAttack(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду за схватку
+func (sf *Missions) battleDogFyting() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Проведи одну схватку<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battleDogFyting(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battleDogFyting(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду за ресурсы
+func (sf *Missions) makeResource() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Произведи на базе 10 ресурсов<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.makeResource(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.makeResource(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду за ресурсы
+func (sf *Missions) upMan() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Прокачай экипаж на 100 опыта экипажа<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?70-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.upMan(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.upMan(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+	// log._rintf("INFO Missions.upMan(): награда получена\n")
+}
+
+// Проверяет награду за топливо
+func (sf *Missions) makeFuel() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Получи топливо в дивизии<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?157-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 19
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду</`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.makeFuel(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.makeFuel(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+	// log._rintf("INFO Missions.makeFuel(): награда получена")
+}
+
+// Проверяет награду 5 боёв
+func (sf *Missions) battle5Fiting() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Проведи 5 боев<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?113-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battle5Fiting(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battle5Fiting(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду 10 боёв
+func (sf *Missions) battle10Fiting() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Уничтожь в бою 10 танков<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?113-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 23
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battle10Fiting(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battle10Fiting(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}
+
+// Проверяет награду за 6 побед подряд
+func (sf *Missions) battle6win() {
+	var (
+		strOut      string
+		lstMissions = sf.GetLst()
+		isFind      bool
+		ind         int
+	)
+	for ind, strOut = range lstMissions {
+		if strings.Contains(strOut, `Победи 6 раз подряд<br/>`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// <a class="simple-but border" href="?113-1.ILinkListener-missions-cc-0-c-awardLink"><span><span>Получить награду</span></span></a>
+	ind += 25
+	strOut = lstMissions[ind]
+	if !strings.Contains(strOut, `>Получить награду<`) {
+		return
+	}
+	lstLink := strings.Split(strOut, `<a class="simple-but border" href="`)
+	strLink := lstLink[1]
+	lstLink = strings.Split(strLink, `"><span><span>Получить награду</span></span></a>`)
+	strLink = "http://wartank.ru/missions/" + lstLink[0]
+	lstMissions, err := sf.net.Get(strLink)
+	if err != nil {
+		// log._rintf("ERRO Missions.battle6win(): при выполнении GET-запроса, err=\n\t%v\n", err)
+		return
+	}
+	if err := sf.Update(lstMissions); err != nil {
+		// log._rintf("ERRO Missions.battle6win(): при обновлении lstMissions, err=\n\t%v\n", err)
+		return
+	}
+}

+ 34 - 0
server/serv_bots/warbot/angar/missions/missionsnet/missionsnet.go

@@ -0,0 +1,34 @@
+package missionsnet
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/sectionnet"
+	"wartank/pkg/types"
+)
+
+/*
+	Автоматически собирает золото
+*/
+
+// MissionsNet -- автоматически собирает золото
+type MissionsNet struct {
+	*sectionnet.SectionNet
+}
+
+// NewMissionsNet -- возвращает новый *MissionsNet
+func NewMissionsNet(server types.IServer, bot types.IServBot) (*MissionsNet, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewMissionsNet(): ISerer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewMissionsNet(): IServBot == nil")
+		}
+	}
+
+	sf := &MissionsNet{
+		SectionNet: sectionnet.NewSectionNet(server, bot, bot.Angar().Missions(), "http://wartank.ru/missions/"),
+	}
+	return sf, nil
+}

+ 304 - 0
server/serv_bots/warbot/angar/netstat/netstat.go

@@ -0,0 +1,304 @@
+package netstat
+
+import (
+	"fmt"
+	// "log"
+	"strconv"
+	"strings"
+
+	"wartank/pkg/types"
+)
+
+/*
+	Исходник предоставляет тип для парсинга статистики уровня танка и сервера.
+*/
+
+// NetStat -- статистика уровня танка и сервера
+type NetStat struct {
+	server types.IServer
+	bot    types.IServBot
+}
+
+// NewNetStat -- возвращает новый *NetStat
+func NewNetStat(server types.IServer, bot types.IServBot) (*NetStat, error) {
+	if server == nil {
+		return nil, fmt.Errorf("NewNetStat(): IServer == nil")
+	}
+	if bot == nil {
+		return nil, fmt.Errorf("NewNetStat(): IServBot == nil")
+	}
+	sf := &NetStat{
+		server: server,
+		bot:    bot,
+	}
+	return sf, nil
+}
+
+func (sf *NetStat) Update() (err error) {
+	// _mt.Println("\n===NetStat.Update()===")
+	sf.findLevelTank()
+	if err = sf.findLevelProgress(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find level progress, err=\n\t%w", err)
+	}
+	if err = sf.findAtack(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find atack, err=\n\t%w", err)
+	}
+	if err = sf.findArmor(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find woll, err=\n\t%w", err)
+	}
+	if err = sf.findFyne(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find woll, err=\n\t%w", err)
+	}
+	if err = sf.findHard(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find body, err=\n\t%w", err)
+	}
+	if err = sf.findPower(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find power, err=\n\t%w", err)
+	}
+	if err = sf.findOnline(); err != nil {
+		return fmt.Errorf("NetStat.Update(): in find online, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Ищет в теле текста ангара мощность танка
+func (sf *NetStat) findPower() (err error) {
+	// _mt.Println("\tNetStat.findPower()")
+	lstAngar := sf.bot.Angar().GetLst()
+	if len(lstAngar) == 0 {
+		// log._rintf("WARN NetStat.findPower(): lstAngar is empty\n")
+		return
+	}
+	var strOut string
+	for _, strPower := range lstAngar {
+		if strings.Contains(strPower, `/images/icons/power.png?2`) {
+			strOut = strPower
+			break
+		}
+	}
+	// Выделить мощность
+	lstPower := strings.Split(strOut, `<img src="/images/icons/power.png?2" height="14" width="14"> <span class="green2">Танковая мощь: `)
+	strPower := lstPower[1]
+	lstPower = strings.Split(strPower, `</span>`)
+	strPower = lstPower[0]
+	iPower, err := strconv.Atoi(strPower)
+	if err != nil {
+		return fmt.Errorf("NetStat.findPower(): power(%v) not number, err=\n\t%w", strPower, err)
+	}
+	sf.bot.Tank().TankStat().Power().Set(iPower)
+	return nil
+}
+
+// Ищет в теле текста ангара прочность танка
+func (sf *NetStat) findHard() (err error) {
+	var (
+		strOut   string
+		lstAngar = sf.bot.Angar().GetLst()
+		isFind   bool
+	)
+	if len(lstAngar) == 0 {
+		// log._rintf("WARN NetStat.findHard(): lstAngar пустой\n")
+		return
+	}
+	for _, strOut = range lstAngar {
+		if strings.Contains(strOut, `/images/icons/durability.png?1`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// Выделить прочность
+	lstHard := strings.Split(strOut, `<img width="14" height="14" src="/images/icons/durability.png?1" alt="Прочность" title="Прочность"/> Прочность <span class="green2">`)
+	strHard := lstHard[1]
+	lstHard = strings.Split(strHard, `</span><br/>`)
+	strHard = lstHard[0]
+	iHard, err := strconv.Atoi(strHard)
+	if err != nil {
+		return fmt.Errorf("NetStat.findHard(): hard(%v) not number, err=\n\t%w", strHard, err)
+	}
+	sf.bot.Tank().TankStat().Hard().Set(iHard)
+	return nil
+}
+
+// Ищет в теле текста ангара точность танка
+func (sf *NetStat) findFyne() (err error) {
+	// _mt.Println("\tNetStat.findFyne()")
+	var (
+		strOut   string
+		lstAngar = sf.bot.Angar().GetLst()
+		isFind   bool
+	)
+	if len(lstAngar) == 0 {
+		// log._rintf("WARN NetStat.findFyne(): lstAngar пустой\n")
+		return
+	}
+	for _, strOut = range lstAngar {
+		if strings.Contains(strOut, `/images/icons/accuracy.png?1`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return nil
+	}
+	// Выделить броню
+	lstFyne := strings.Split(strOut, `<img width="14" height="14" src="/images/icons/accuracy.png?1" alt="Точность" title="Точность"/> Точность <span class="green2">`)
+	strFyne := lstFyne[1]
+	lstFyne = strings.Split(strFyne, `</span><br/>`)
+	strFyne = lstFyne[0]
+	iFyne, err := strconv.Atoi(strFyne)
+	if err != nil {
+		return fmt.Errorf("NetStat.findFyne(): fyne(%v) not number, err=\n\t%w", strFyne, err)
+	}
+	sf.bot.Tank().TankStat().Fyne().Set(iFyne)
+	return nil
+}
+
+// Ищет в теле текста ангара броню танка
+func (sf *NetStat) findArmor() (err error) {
+	// _mt.Println("\tNetStat.findArmor()")
+
+	var (
+		strOut   string
+		lstAngar = sf.bot.Angar().GetLst()
+		isFind   bool
+	)
+	if len(lstAngar) == 0 {
+		return nil
+	}
+	for _, strOut = range lstAngar {
+		if strings.Contains(strOut, `/images/icons/armor.png?1`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return
+	}
+	// Выделить броню
+	lstArmor := strings.Split(strOut, `<img width="14" height="14" src="/images/icons/armor.png?1" alt="Броня" title="Броня"/> Броня <span class="green2">`)
+	strArmor := lstArmor[1]
+	lstArmor = strings.Split(strArmor, `</span><br/>`)
+	strArmor = lstArmor[0]
+	iArmor, err := strconv.Atoi(strArmor)
+	if err != nil {
+		return fmt.Errorf("NetStat.findArmor(): armor(%v) not number, err=\n\t%w", strArmor, err)
+	}
+	sf.bot.Tank().TankStat().Armor().Set(iArmor)
+	return nil
+}
+
+// Ищет в теле текста ангара уровень танка
+func (sf *NetStat) findLevelTank() {
+	// _mt.Println("\tNetStat.findLevelTank()")
+	lstAngar := sf.bot.Angar().GetLst()
+	if len(lstAngar) == 0 {
+		// log._rintf("ERRO NetStat.findLevelTank(): пустой lstAngar")
+		return
+	}
+	var strOut string
+	for _, strLevel := range lstAngar {
+		if strings.Contains(strLevel, "/images/icons/level.png?2") {
+			strOut = strLevel
+			break
+		}
+	}
+	// Выделить уровень
+	lstLevel := strings.Split(strOut, `<td><div class="value-block lh1"><span><span><img height="14" width="14" src="/images/icons/level.png?2"/> `)
+	strLevel := lstLevel[1]
+	lstLevel = strings.Split(strLevel, `</span></span></div></td>`)
+	strLevel = lstLevel[0]
+	iLevel, err := strconv.Atoi(strLevel)
+	if err != nil {
+		// log._rintf("ERRO NetStat.findLevelTank(): level(%v) не число, err=\n\t%v\n", strLevel, err)
+		return
+	}
+	sf.bot.Angar().Level().Set(iLevel)
+}
+
+// Ищет в теле текста ангара прогресс уровня танка танка
+func (sf *NetStat) findLevelProgress() error {
+	// _mt.Println("\tNetStat.findLevelProgress()")
+	lstAngar := sf.bot.Angar().GetLst()
+	if len(lstAngar) == 0 {
+		// log._rintf("WARN NetStat.findLevelProgress(): lstAngar пустой\n")
+		return nil
+	}
+	var strOut string
+	for _, strProg := range lstAngar {
+		if strings.Contains(strProg, `class="progr"`) {
+			strOut = strProg
+			break
+		}
+	}
+	// Выделить прогресс
+	lstProg := strings.Split(strOut, `<td class="progr"><div class="scale-block"><div class="scale" style="width:`)
+	strProg := lstProg[1]
+	lstProg = strings.Split(strProg, `%;">&nbsp;</div></div></td>`)
+	strProg = lstProg[0]
+	iProg, err := strconv.Atoi(strProg)
+	if err != nil {
+		return fmt.Errorf("NetStat.findLevelProgress(): progress(%v) not number, err=\n\t%w", strProg, err)
+	}
+	sf.bot.Angar().Progress().Set(iProg)
+	return nil
+}
+
+// Ищет в теле текста ангара силу атаки танка
+func (sf *NetStat) findAtack() (err error) {
+	var (
+		strOut   string
+		lstAngar = sf.bot.Angar().GetLst()
+		isFind   bool
+	)
+	for _, strOut = range lstAngar {
+		if strings.Contains(strOut, `/images/icons/attack.png?1`) {
+			isFind = true
+			break
+		}
+	}
+	if !isFind {
+		return nil
+	}
+	// Выделить атаку
+	lstAtack := strings.Split(strOut, `<img width="14" height="14" src="/images/icons/attack.png?1" alt="Атака" title="Атака"/> Атака <span class="green2">`)
+	strAtack := lstAtack[1]
+	lstAtack = strings.Split(strAtack, `</span><br/>`)
+	strAtack = lstAtack[0]
+	iAtack, err := strconv.Atoi(strAtack)
+	if err != nil {
+		return fmt.Errorf("NetStat.findAtack(): atack(%v) not number, err=\n\t%w", strAtack, err)
+	}
+	sf.bot.Tank().TankStat().Attack().Set(iAtack)
+	return nil
+}
+
+// Ищет в теле текста ангара силу атаки танка
+func (sf *NetStat) findOnline() (err error) {
+	// _mt.Println("\tNetStat.findOnline()")
+	lstAngar := sf.bot.Angar().GetLst()
+	var strOut string
+	for _, strAtack := range lstAngar {
+		if strings.Contains(strAtack, `>Онлайн</a>: `) {
+			strOut = strAtack
+			break
+		}
+	}
+	// Выделить число игроков онлайн
+	lstAngar = strings.Split(strOut, `<span class="yellow1">`)
+	if len(lstAngar) <= 1 {
+		sf.server.CancelApp()
+		return
+	}
+	strOnline := lstAngar[1]
+	lstAngar = strings.Split(strOnline, `</span>`)
+	strOnline = lstAngar[0]
+	iOnline, err := strconv.Atoi(strOnline)
+	if err != nil {
+		return fmt.Errorf("NetStat.findOnline(): online(%v) not number, err=\n\t%w", iOnline, err)
+	}
+	sf.bot.Angar().Online().Set(iOnline)
+	return nil
+}

+ 47 - 0
server/serv_bots/warbot/tank/basestat/basestat.go

@@ -0,0 +1,47 @@
+package basestat
+
+import (
+	// "log"
+
+	"wartank/pkg/components/counttime"
+	"wartank/pkg/types"
+)
+
+/*
+	Статистика по базе
+*/
+
+// BaseStat -- статистика по базе
+type BaseStat struct {
+	app         types.IServer
+	poligonTime types.ICountTime // Сколько осталось времени работы полигона
+}
+
+// NewBaseStat -- возвращает новый *BaseStat
+func NewBaseStat(app types.IServer) *BaseStat {
+	sf := &BaseStat{
+		app: app,
+	}
+	sf.poligonTime = counttime.NewCountTime(app)
+	go sf.run()
+	return sf
+}
+
+// Главный цикл ожидания
+func (sf *BaseStat) run() {
+	for range sf.poligonTime.ChanSig() {
+
+	}
+}
+
+// PoligonTime -- сколько  осталось времени работы полигона
+func (sf *BaseStat) PoligonTime() types.ICountTime {
+	return sf.poligonTime
+}
+
+// PoligonUpdate -- обновляет время работы полигона
+func (sf *BaseStat) PoligonUpdate(poligonFact string) {
+	if err := sf.poligonTime.Parse(poligonFact); err != nil {
+		// log._rintf("ERRO BaseStat.PoligonUpdate(): при обновлении времени работы полигона\n\tpoligonFact=%q\terr=%v\n", poligonFact, err)
+	}
+}

+ 36 - 0
server/serv_bots/warbot/tank/tank.go

@@ -0,0 +1,36 @@
+package tank
+
+import (
+	"fmt"
+
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/tank/tankstat"
+)
+
+/*
+	Исходник предоставляет тип со свойствами танка.
+	Глобальный объект.
+*/
+
+// Tank -- описатель танка
+type Tank struct {
+	app      types.IServer
+	statTank types.ITankStat // Глобальная статистика танка и сервера
+}
+
+// NewTank -- возвращает новый *Tank
+func NewTank(app types.IServer) (*Tank, error) {
+	if app == nil {
+		return nil, fmt.Errorf("NewTank(): IApp is nil")
+	}
+	sf := &Tank{
+		app:      app,
+		statTank: tankstat.NewTankStat(),
+	}
+	return sf, nil
+}
+
+// TankStat -- возвращает объект статистики танка
+func (sf *Tank) TankStat() types.ITankStat {
+	return sf.statTank
+}

+ 58 - 0
server/serv_bots/warbot/tank/tankstat/static_param/static_param.go

@@ -0,0 +1,58 @@
+package static_param
+
+import (
+	"fmt"
+	"sync"
+)
+
+/*
+	Исходник предоставляет единичный статичный параметр статистики
+	танка. Проверяет присваиваемые значения.
+	Потокобезопасно.
+*/
+
+// StaticParam -- потокобезопасный параметр танка
+type StaticParam struct {
+	name  string
+	val   int
+	block sync.RWMutex
+}
+
+// NewStaticParam -- возвращает новый *StaticParam
+func NewStaticParam(name string) *StaticParam {
+	if name == "" {
+		panic(fmt.Errorf("NewStaticParam(): name is empty"))
+	}
+	sf := &StaticParam{
+		name: name,
+	}
+	return sf
+}
+
+// Set -- устанавливает параметр
+func (sf *StaticParam) Set(val int) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = val
+}
+
+// Get -- возвращает значение хранимого параметра
+func (sf *StaticParam) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Name -- возвращает имя хранимого параметра
+func (sf *StaticParam) Name() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.name
+}
+
+// SetName -- устанавливает имя параметра
+func (sf *StaticParam) SetName(name string) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.name = name
+}

+ 64 - 0
server/serv_bots/warbot/tank/tankstat/tankstat.go

@@ -0,0 +1,64 @@
+package tankstat
+
+import (
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
+)
+
+/*
+	Исходник предоставляет статические параметры танка.
+	Броня, атака, прочность и т.п.
+*/
+
+// TankStat -- статические параметры танка
+type TankStat struct {
+	attack types.IStatParam // Сила атаки танка
+	fyne   types.IStatParam // Точность танка
+	armor  types.IStatParam // Броня танка
+	hard   types.IStatParam // Броня танка
+	power  types.IStatParam // Танковая мощь (может не совпадать по сумме, если есть усиление)
+	force  types.IStatParam
+}
+
+// NewTankStat -- возвращает новый *TankStat
+func NewTankStat() *TankStat {
+	sf := &TankStat{
+		attack: static_param.NewStaticParam("attack"),
+		fyne:   static_param.NewStaticParam("fyne"),
+		armor:  static_param.NewStaticParam("armor"),
+		hard:   static_param.NewStaticParam("hard"),
+		power:  static_param.NewStaticParam("power"),
+		force:  static_param.NewStaticParam("force"),
+	}
+	return sf
+}
+
+// Attack -- возвращает объект силы атаки
+func (sf *TankStat) Attack() types.IStatParam {
+	return sf.attack
+}
+
+// Hard -- возвращает объект прочности танка
+func (sf *TankStat) Hard() types.IStatParam {
+	return sf.hard
+}
+
+// Fyne -- возвращает объект точности танка
+func (sf *TankStat) Fyne() types.IStatParam {
+	return sf.fyne
+}
+
+// Armor -- возвращает объект брони танка
+func (sf *TankStat) Armor() types.IStatParam {
+	return sf.armor
+}
+
+// Power -- возвращает объект мощи танка
+func (sf *TankStat) Power() types.IStatParam {
+	return sf.power
+}
+
+// Force -- возвращает объект форсированного параметра
+func (sf *TankStat) Force() types.IStatParam {
+	return sf.force
+}

+ 104 - 0
server/serv_bots/warbot/warbot.go

@@ -0,0 +1,104 @@
+// package warbot -- бот для игры в wartank
+package warbot
+
+import (
+	"fmt"
+
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/angar"
+	"wartank/server/serv_bots/warbot/tank"
+	"wartank/server/serv_bots/warbot/warbot_net"
+)
+
+// WarBot -- бот для игры в вартанк
+type WarBot struct {
+	server   types.IServer
+	store    types.IStore
+	tank     *tank.Tank
+	errFinal error // Финальная ошибка работы, если была
+	angar    types.IAngar
+	botNet   types.IBotNet
+	name     string // Имя бота
+	pass     string // Пароль бота
+}
+
+// NewWarBot -- возвращает новый WarBot
+func NewWarBot(server types.IServer, name string) (*WarBot, error) {
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewWarBot(): IApp is nil")
+		}
+		if name == "" {
+			return nil, fmt.Errorf("NewWarBot(): name is empty")
+		}
+	}
+
+	tank, err := tank.NewTank(server)
+	if err != nil {
+		return nil, fmt.Errorf("NewWarBot(): in create Tank, err=\n\t%w", err)
+	}
+	sf := &WarBot{
+		server: server,
+		store:  server.Store(),
+		tank:   tank,
+		name:   name,
+	}
+	sf.pass, err = sf.store.Get("/bot/" + sf.name)
+	if err != nil {
+		return nil, fmt.Errorf("NewWarBot(): in get pass from store, err=\n\t%w", err)
+	}
+
+	{ // WarBotNet
+		sf.botNet, err = warbot_net.NewWarBotNet(server, sf)
+		if err != nil {
+			return nil, fmt.Errorf("NewWarBot(): in make IBotNet, err=\n\t%w", err)
+		}
+	}
+	sf.angar, err = angar.NewAngar(sf.server, sf)
+	if err != nil {
+		return nil, fmt.Errorf("NewWarBot(): bot(%q) in make IAngar, err=\n\t%w", sf.name, err)
+	}
+	return sf, nil
+}
+
+// Name -- возвращает имя бота
+func (sf *WarBot) Name() string {
+	return sf.name
+}
+
+// Pass -- возвращает пароль бота
+func (sf *WarBot) Pass() string {
+	return sf.pass
+}
+
+// Run -- запускает бот в работу
+func (sf *WarBot) Run() (err error) {
+	err = sf.botNet.Login()
+	if err != nil {
+		return fmt.Errorf("WarBot.Run(): bot(%q) in net login, err=\n\t%w", sf.name, err)
+	}
+	if err := sf.angar.Run(); err != nil {
+		return fmt.Errorf("WarBot.Run(): bot(%q) in run angar, err=\n\t%w", sf.name, err)
+	}
+	return nil
+}
+
+// Error -- возвращает финальную ошибку работы, если была
+func (sf *WarBot) Error() error {
+	return sf.errFinal
+}
+
+// Angar -- возвращает ангар игры
+func (sf *WarBot) Angar() types.IAngar {
+	return sf.angar
+}
+
+// Tank -- возвращает объект танка
+func (sf *WarBot) Tank() types.ITank {
+	return sf.tank
+}
+
+// BotNet -- возвращает ссылку на свой сетевой клиент
+func (sf *WarBot) BotNet() types.IBotNet {
+	return sf.botNet
+}

+ 64 - 0
server/serv_bots/warbot/warbot_net/bot_cookie/bot_cookie.go

@@ -0,0 +1,64 @@
+package bot_cookie
+
+import (
+	"fmt"
+	"net/http"
+	"sync"
+)
+
+/*
+	Предоставляет разделяемый объект кукисов для соединения с игровым сервером
+*/
+
+// BotCookie -- кукисы для игрового сервера, ничего не требует для своей работы
+type BotCookie struct {
+	cookie []*http.Cookie
+	block  sync.RWMutex
+}
+
+// NewBotCookie -- возвращает новый *NetCookie
+func NewBotCookie() *BotCookie {
+	return &BotCookie{}
+}
+
+// Set -- устанавливает кукисы
+func (sf *BotCookie) Set(cook []*http.Cookie) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if cook == nil {
+		return fmt.Errorf("NetCookie.Set(): cookie is nil")
+	}
+	sf.cookie = cook
+	cookie := &http.Cookie{
+		Name:  "JSESSIONID",
+		Value: sf.cookie[0].Value,
+		Raw:   "JSESSIONID=" + sf.cookie[0].Value + "; _ym_uid=1642083867571238834; _ym_d=1642083867; _ym_isad=2; _ym_visorc=w",
+	}
+	cookie1 := sf.cookie[:0]
+	sf.cookie = cookie1
+	sf.cookie = append(sf.cookie, cookie)
+
+	cookie = &http.Cookie{
+		Name:  "_ym_d",
+		Value: "1642083867",
+	}
+	sf.cookie = append(sf.cookie, cookie)
+
+	cookie = &http.Cookie{
+		Name:  "_ym_isad",
+		Value: "2",
+	}
+	sf.cookie = append(sf.cookie, cookie)
+
+	cookie = &http.Cookie{
+		Name:  "_ym_visorc",
+		Value: "w",
+	}
+	sf.cookie = append(sf.cookie, cookie)
+	return nil
+}
+
+// Get -- возвращает хранимые кукисы
+func (sf *BotCookie) Get() []*http.Cookie {
+	return sf.cookie
+}

+ 38 - 0
server/serv_bots/warbot/warbot_net/bot_net_conn/bot_net_conn.go

@@ -0,0 +1,38 @@
+package bot_net_conn
+
+import (
+	"net/http"
+	"net/http/cookiejar"
+	"time"
+
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/warbot_net/bot_cookie"
+)
+
+type BotNetConn struct {
+	client *http.Client
+	cookie *bot_cookie.BotCookie
+}
+
+func NewBotNetConn() *BotNetConn {
+	sf := &BotNetConn{
+		client: &http.Client{
+			Transport: nil,
+			Jar:       nil,
+			Timeout:   time.Second * 10,
+		},
+		cookie: bot_cookie.NewBotCookie(),
+	}
+	sf.client.Jar, _ = cookiejar.New(nil)
+	return sf
+}
+
+// Client -- возвращает клиента
+func (sf *BotNetConn) Client() *http.Client {
+	return sf.client
+}
+
+// Cookie -- возвращает куки клиента
+func (sf *BotNetConn) Cookie() types.IBotCookie {
+	return sf.cookie
+}

+ 199 - 0
server/serv_bots/warbot/warbot_net/bot_net_login/bot_net_login.go

@@ -0,0 +1,199 @@
+package bot_net_login
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/warbot_net/bot_cookie"
+)
+
+/*
+	Исходник предоставляет тип для сетевого входа на сервер.
+*/
+
+type IBotNet interface {
+	Client() *http.Client
+	Cookie() *bot_cookie.BotCookie
+}
+
+// BotNetLogin -- объект сетевого входа на сервер
+type BotNetLogin struct {
+	bot    types.IServBot
+	conn   *http.Client
+	cookie types.IBotCookie
+}
+
+// NewBotNetLogin -- возвращает новый *NetLogin
+func NewBotNetLogin(bot types.IServBot) (*BotNetLogin, error) {
+	{ // Предусловия
+		if bot == nil {
+			return nil, fmt.Errorf("NewBotNetLogin(): IServBot is nil")
+		}
+	}
+
+	sf := &BotNetLogin{
+		bot:    bot,
+		conn:   bot.BotNet().Conn(),
+		cookie: bot.BotNet().Cookie(),
+	}
+	return sf, nil
+}
+
+// Connect -- должен вызываться один раз и работать в отдельном потоке
+func (sf *BotNetLogin) Connect() (err error) {
+	// _mt.Println("\n===BotNetLogin.Connect()===")
+	linkBegin, err := sf.getMainPage()
+	if err != nil {
+		return fmt.Errorf("BotNetLogin.Connect(): при получении главной страницы, err=\n\t%w", err)
+	}
+	switch linkBegin != "" {
+	case true: // Это вход на базу
+		strBodyLogin, err := sf.getPageLogin(linkBegin)
+		if err != nil {
+			return fmt.Errorf("BotNetLogin.Connect(): при получении страницы логина, err=\n\t%w", err)
+		}
+		if err = sf.makePostLogin(strBodyLogin); err != nil {
+			return fmt.Errorf("BotNetLogin.Connect(): при выполнении логина, err=\n\t%w", err)
+		}
+		return nil
+	default: // Логин уже был
+		req, err1 := http.NewRequest("GET", "http://wartank.ru/angar", nil)
+		if err1 != nil {
+			return fmt.Errorf("BotNetLogin.Connect(): in get page angar, err=\n\t%w", err1)
+
+		}
+		resp, err2 := sf.conn.Do(req)
+		defer func() {
+			err3 := resp.Body.Close()
+			if err3 != nil {
+				err = fmt.Errorf("BotNetLogin.Connect(): in close body, err=\n\t%w", err3)
+			}
+		}()
+		if err2 != nil {
+			return fmt.Errorf("BotNetLogin.Connect(): in make request, err=\n\t%w", err2)
+		}
+		_, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return fmt.Errorf("BotNetLogin.Connect(): in read body response, err=\n\t%w", err)
+		}
+	}
+	return nil
+}
+
+// Прочитать главную страницу для получения кукисов
+func (sf *BotNetLogin) getMainPage() (linkBegin string, err error) {
+	// _mt.Println("\BotNetLogin.getMainPage()")
+	resp, err := sf.conn.Get("http://wartank.ru/")
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			err = fmt.Errorf("BotNetLogin.getMainPage(): перехвачена паника в конце вызова главной страницы, panic=%v", _panic)
+		}
+	}()
+	defer func() {
+		err = resp.Body.Close()
+		if err != nil {
+			err = fmt.Errorf("NetLogin.getMainPage(): при закрытии тела ответапосле запроса главной страницы,err=%w", err)
+		}
+	}()
+	if err != nil {
+		return "", fmt.Errorf("BotNetLogin.getMainPage(): err=\n\t%w", err)
+	}
+	// Получить куки из ответа
+	cookies := resp.Cookies()
+	if len(cookies) > 0 {
+		if err = sf.cookie.Set(cookies); err != nil {
+			return "", fmt.Errorf("BotNetLogin.getMainPage(): при установки куки, err=\n\t%w", err)
+		}
+	}
+	binData, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("BotNetLogin.getMainPage(): при чтении тела ответа, err=\n\t%w", err)
+	}
+	// Вырезать из тела страницы ссылку на вход
+	lstStr := strings.Split(string(binData), "\n")
+	var strOut string
+	for _, strLink := range lstStr {
+		if strings.Contains(strLink, `w:id="showSigninLink"`) {
+			strOut = strLink
+			break
+		}
+	}
+	if strOut == "" { // Уже был логин
+		return "", nil
+	}
+	{ // Получить ссылку на вход
+		lstLink := strings.Split(strOut, `href="`)
+		lstLink = strings.Split(lstLink[1], `"><span><span>`)
+		linkBegin = "http://wartank.ru/" + lstLink[0]
+	}
+	return linkBegin, nil
+}
+
+// Получает страницу логина
+func (sf *BotNetLogin) getPageLogin(linkBegin string) (strBodyLogin string, err error) {
+	// _mt.Println("\BotNetLogin.getPageLogin()")
+	resp, err := sf.conn.Get(linkBegin)
+	defer func() {
+		err = resp.Body.Close()
+		if err != nil {
+			err = fmt.Errorf("NetLogin.getPageLogin(): in close body,err=%w", err)
+		}
+	}()
+	if err != nil {
+		return "", fmt.Errorf("NetLogin.getPageLogin(): in mske GET-request for send login page, err=\n\t%w", err)
+	}
+	// sf.cookie = resp.Cookies()
+	binData, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("NetLogin.getPageLogin(): in read body response, err=\n\t%w", err)
+	}
+	strBodyLogin = string(binData)
+	// sf.app.AddLog(fmt.Sprintf("BotNetLogin.getPageLogin(): page_login=\n%v\n\n", strBodyLogin))
+	return strBodyLogin, nil
+}
+
+// Выполняет логин через POST-запрос
+func (sf *BotNetLogin) makePostLogin(strBody string) (err error) {
+	// _mt.Println("\BotNetLogin.makePostLogin()")
+	var strBodyMain string
+	{ // Получить форму логина
+		lstLogin := strings.Split(strBody, "\n")
+		for _, strForm := range lstLogin {
+			if strings.Contains(strForm, `<form w:id="loginForm" id=`) {
+				strBodyMain = strForm
+				break
+			}
+		}
+	}
+	var postLink string
+	{ // Получить ссылку на POST-запрос
+		lstLink := strings.Split(strBodyMain, ` action="`)
+		strLink := lstLink[1]
+		lstLink = strings.Split(strLink, `"><div style=`)
+		postLink = "http://wartank.ru/" + lstLink[0]
+	}
+	{ // Конструируем ПОСТ-форму логина
+		form := url.Values{}
+		form.Add("id1_hf_0", "")
+		form.Add("login", sf.bot.Name())
+		form.Add("password", sf.bot.Pass())
+
+		resp, err := sf.conn.PostForm(postLink, form)
+		defer func() {
+			_ = resp.Body.Close()
+		}()
+		if err != nil {
+			return fmt.Errorf("BotNetLogin.makePostLogin(): in get POST-login response, err=\n\t%w", err)
+		}
+		if _, err = io.ReadAll(resp.Body); err != nil {
+			return fmt.Errorf("BotNetLogin.makePostLogin(): in read body POST-login response, err=\n\t%w", err)
+		}
+		urlObj, _ := url.Parse("http://wartank.ru/")
+		sf.conn.Jar.SetCookies(urlObj, sf.cookie.Get())
+	}
+	return nil
+}

+ 84 - 0
server/serv_bots/warbot/warbot_net/warbot_net.go

@@ -0,0 +1,84 @@
+package warbot_net
+
+import (
+	"fmt"
+	"net/http"
+
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/components/sectionnet/netclient"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots/warbot/warbot_net/bot_net_conn"
+	"wartank/server/serv_bots/warbot/warbot_net/bot_net_login"
+)
+
+/*
+	Веб-клиент для выполнения запросов на сервер
+*/
+
+// WarBotNet -- веб-клиент для выполнения запросов на веб-сервер
+type WarBotNet struct {
+	server     types.IServer
+	bot        types.IServBot
+	botNetConn *bot_net_conn.BotNetConn
+	login      *bot_net_login.BotNetLogin
+	isOnline   *safebool.SafeBool
+	net        *netclient.NetClient
+}
+
+// NewWarBotNet -- возвращает новый *NetClient
+func NewWarBotNet(server types.IServer, bot types.IServBot) (gc *WarBotNet, err error) {
+	fmt.Printf("NewWarBotNet()\n")
+	{ // Предусловия
+		if server == nil {
+			return nil, fmt.Errorf("NewWarBotNet(): IServer == nil")
+		}
+		if bot == nil {
+			return nil, fmt.Errorf("NewWarBotNet(): IServBot == nil")
+		}
+	}
+
+	sf := &WarBotNet{
+		server:     server,
+		bot:        bot,
+		botNetConn: bot_net_conn.NewBotNetConn(),
+		isOnline:   safebool.NewSafeBool(),
+	}
+	return sf, nil
+}
+
+// Login -- пытается законнектиться к серверу
+func (sf *WarBotNet) Login() error {
+	var err error
+	{ // Подключаем сетевой логин
+		sf.login, err = bot_net_login.NewBotNetLogin(sf.bot)
+		if err != nil {
+			return fmt.Errorf("WarBotNet.Login(): in create NetLogin, err=\n\t%w", err)
+		}
+	}
+	if err = sf.login.Connect(); err != nil {
+		return fmt.Errorf("WarBotNet.Login(): in login connect, err=\n\t%w", err)
+	}
+	sf.isOnline.Set()
+	sf.net = netclient.NewNetClient(sf.server, sf.bot)
+	return nil
+}
+
+// IsOnline -- возвращает признак нахождения в онлайне
+func (sf *WarBotNet) IsOnline() *safebool.SafeBool {
+	return sf.isOnline
+}
+
+// Net -- возвращает исполнитель запросов
+func (sf *WarBotNet) Net() types.INetClient {
+	return sf.net
+}
+
+// Conn -- возвращает HTTP-коннект
+func (sf *WarBotNet) Conn() *http.Client {
+	return sf.botNetConn.Client()
+}
+
+// Cookie -- возвращает куки для HTTP-коннект
+func (sf *WarBotNet) Cookie() types.IBotCookie {
+	return sf.botNetConn.Cookie()
+}

+ 77 - 0
server/serv_gui/serv_gui.go

@@ -0,0 +1,77 @@
+// package serv_gui -- десктопная графика для сервера
+package serv_gui
+
+import (
+	"fmt"
+
+	"github.com/AllenDang/giu"
+
+	"wartank/pkg/types"
+)
+
+// ServGui -- десктопная графика для сервера
+type ServGui struct {
+	serv types.IServer
+	wnd  *giu.MasterWindow
+}
+
+// NewServGui -- возвращает новую графику для сервера
+func NewServGui(serv types.IServer) (*ServGui, error) {
+	if serv == nil {
+		return nil, fmt.Errorf("NewServGui(): IServer==nil")
+	}
+	sf := &ServGui{
+		serv: serv,
+		wnd:  giu.NewMasterWindow("Hello world", 400, 200, 0),
+	}
+	return sf, nil
+}
+
+// Run -- запускает работу графики сервера
+func (sf *ServGui) Run() {
+	sf.wnd.Run(sf.loop)
+}
+
+func (sf *ServGui) onClickMe() {
+	fmt.Println("War-tank end work!")
+	sf.serv.CancelApp()
+	sf.wnd.Close()
+}
+
+func onImSoCute() {
+	fmt.Println("click btn!!")
+}
+
+func (sf *ServGui) loop() {
+	giu.SingleWindow().Layout(
+		giu.Row(
+			giu.Align(giu.AlignCenter).To(
+				giu.Style().SetFontSize(20).To(
+					giu.Label("[ WarTank ]"),
+				),
+			),
+		),
+		giu.Row(
+			giu.Column(
+				giu.Button("Выход").OnClick(sf.onClickMe),
+				giu.Button("Click me").OnClick(onImSoCute),
+			),
+			giu.Column(
+				giu.Button("Выход").OnClick(sf.onClickMe),
+				giu.Spacing(),
+				giu.Button("Click me").OnClick(onImSoCute),
+			),
+		),
+		giu.Row(
+			giu.Column(
+				giu.Button("Выход").OnClick(sf.onClickMe),
+				giu.Button("Click me").OnClick(onImSoCute),
+			),
+			giu.Column(
+				giu.Button("Выход").OnClick(sf.onClickMe),
+				giu.Spacing(),
+				giu.Button("Click me").OnClick(onImSoCute),
+			),
+		),
+	)
+}

+ 88 - 0
server/serv_web/serv_web.go

@@ -0,0 +1,88 @@
+// package serv_web -- сервер бота на файбере
+package serv_web
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"sync"
+	"time"
+
+	"github.com/gofiber/fiber/v2"
+	// "github.com/gofiber/template/html"
+
+	"wartank/server/serv_web/web_api"
+
+	"wartank/pkg/types"
+)
+
+/*
+	Главный тип бота на файбере
+*/
+
+// ServWeb -- главный тип приложения
+type ServWeb struct {
+	app    types.IServer
+	api    *web_api.WebApi
+	router *fiber.App
+	block  *sync.RWMutex
+	port   string // Порт веб-сервера
+}
+
+// NewServWeb -- возвращае тновый *ServFiber
+func NewServWeb(app types.IServer) (serv *ServWeb, err error) {
+	fmt.Printf("NewServWeb()\n")
+	if app == nil {
+		return nil, fmt.Errorf("NewServWeb(): IApp is nil")
+	}
+	port := os.Getenv("SERVER_PORT")
+	if port == "" {
+		return nil, fmt.Errorf("NewServWeb(): env SERVER_PORT not set")
+	}
+	sf := &ServWeb{
+		app:   app,
+		block: &sync.RWMutex{},
+		port:  port,
+	}
+	// engine := html.New("./static/tmpl", "*.tmpl.html")
+	fiberConfig := fiber.Config{
+		ServerHeader:    "WarTank",
+		BodyLimit:       10 * 1024 * 1024,
+		Concurrency:     512,
+		ReadTimeout:     30 * time.Second,
+		WriteTimeout:    30 * time.Second,
+		IdleTimeout:     30 * time.Second,
+		ReadBufferSize:  20 * 1024,
+		WriteBufferSize: 20 * 1024,
+		AppName:         "WarTank",
+		Network:         fiber.NetworkTCP4,
+		// Views:           engine,
+	}
+	sf.router = fiber.New(fiberConfig)
+	// sf.router.Static("/static", "./static")
+	return sf, nil
+}
+
+// Run -- запускает бота в работу
+func (sf *ServWeb) Run() error {
+	log.Printf("ServWeb.Run()\n")
+	var err error
+	{
+		sf.api, err = web_api.NewWebApi(sf.app)
+		if err != nil {
+			return fmt.Errorf("ServWeb.Run(): in make WebApi, err=\n\t%w", err)
+		}
+	}
+
+	sf.api.Run()
+	log.Printf("ServWeb.Run(): listen\n")
+	if err := sf.router.Listen(":" + sf.port); err != nil {
+		panic(fmt.Errorf("ServWeb.Run(): in serving web, err=\n\t%w", err))
+	}
+	return nil
+}
+
+// Router -- возвращает роутер сервера
+func (sf *ServWeb) Router() *fiber.App {
+	return sf.router
+}

+ 183 - 0
server/serv_web/web_api/web_api.go

@@ -0,0 +1,183 @@
+// package web_api -- реализация API запросов в веб-сервер
+package web_api
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"sync"
+	"time"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/websocket/v2"
+
+	"wartank/pkg/types"
+)
+
+// WebApi -- рализация веб-запросов
+type WebApi struct {
+	server types.IServer
+	store  types.IStore
+}
+
+// NewWebApi -- возвращает новый WebApi
+func NewWebApi(server types.IServer) (*WebApi, error) {
+	if server == nil {
+		return nil, fmt.Errorf("NewWebApi(): IServer is nil")
+	}
+
+	sf := &WebApi{
+		server: server,
+		store:  server.Store(),
+	}
+	server.ServWeb().Router().Use("/api/ws", sf.fnWs)
+	server.ServWeb().Router().Get("/api/ws", websocket.New(sf.fnWsRun))
+
+	return sf, nil
+}
+
+// Run -- заглушка к вызову
+func (sf *WebApi) Run() {}
+
+// fnWs -- апгрейдит веб-сокет
+func (sf *WebApi) fnWs(c *fiber.Ctx) error {
+	if websocket.IsWebSocketUpgrade(c) {
+		c.Locals("allowed", true)
+		return c.Next()
+	}
+	return fiber.ErrUpgradeRequired
+}
+
+// fnWsRun -- запускает веб-сокет в работу
+func (sf *WebApi) fnWsRun(c *websocket.Conn) {
+	// c.Locals is added to the *websocket.Conn
+	log.Println(c.Locals("allowed"))  // true
+	log.Println(c.Params("id"))       // 123
+	log.Println(c.Query("v"))         // 1.0
+	log.Println(c.Cookies("session")) // ""
+
+	// websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index
+	var (
+		mt       int
+		dictReq  = make(map[string]string)
+		dictResp = make(map[string]string)
+		binReq   []byte
+		err      error
+		block    sync.Mutex
+	)
+	fnWrite := func() (isOk bool) {
+		defer block.Unlock()
+		binResp, err := json.Marshal(dictResp)
+		if err != nil {
+			log.Println("WebApi.fnWsRun(): in marshall response, err=", err)
+			return false
+		}
+		if err = c.WriteMessage(mt, binResp); err != nil {
+			log.Println("WebApi.fnWsRun(): in write, err=", err)
+			return false
+		}
+		return true
+	}
+	for {
+		block.Lock()
+		if mt, binReq, err = c.ReadMessage(); err != nil {
+			log.Println("WebApi.fnWsRun(): in read, err=", err)
+			return
+		}
+		dictResp = make(map[string]string)
+		dictResp["err"] = ""
+		err = json.Unmarshal(binReq, &dictReq)
+		if err != nil {
+			dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in unmarshall request, err=\n\t%v", err)
+			if !fnWrite() {
+				return
+			}
+			continue
+		}
+		topic, isOk := dictReq["topic"]
+		if !isOk {
+			dictResp["err"] = "WebApi.fnWsRun(): dictReq not have key `topic`"
+			if !fnWrite() {
+				return
+			}
+			continue
+		}
+		switch topic {
+		case "/server/time": // Запрос времени сервера
+			dictResp["/server/time"] = time.Now().UTC().Format("2006-01-02 15:04:05.000")
+		case "/root/password/is_exists":
+			_, err := sf.store.Get("/root/password/val")
+			if err != nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in get root password, err=\n\t%v", err)
+			} else {
+				dictResp["/root/password/is_exists"] = "true"
+			}
+		case "/root/password/set":
+			strPass := dictReq["pass"]
+			err := sf.store.Put("/root/password/val", strPass)
+			if err != nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in save root password, err=\n\t%v", err)
+			}
+		case "/root/password/check": // Проверка пароля рута
+			binPass, err_ := sf.store.Get("/root/password/val")
+			if err_ != nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in get root password, err=\n\t%v", err_)
+			} else {
+				strPassRoot := string(binPass)
+				strPassUser := dictReq["pass"]
+				if strPassRoot != strPassUser {
+					dictResp["err"] = "WebApi.fnWsRun(): bad root password"
+				}
+			}
+		case "/bot/list/load": // Запрос списка ботов
+			binBots, err := sf.store.Get("/bot/list")
+			if err != nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in get list bot from store, err=\n\t%v\n", err)
+			} else {
+				dictResp["/bot/list"] = string(binBots)
+			}
+		case "/bot/list/save": // Сохранение списка ботов
+			strBotList := dictReq["binData"]
+			err := sf.store.Put("/bot/list", strBotList)
+			if err != nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in save list bot to store, err=\n\t%v\n", err)
+			}
+		case "/store/put": // Поместить запись в хранилище
+			key := dictReq["key"]
+			val := dictReq["val"]
+			err := sf.store.Put(key, val)
+			if err != nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): in put key(%q) to store, err=\n\t%v\n", key, err)
+			}
+		case "/bot/status": // Получить статус бота
+			name := dictReq["name"]
+			bot := sf.server.ServBots().Get(name)
+			if bot == nil {
+				dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): unknown bit name (%q)", name)
+			} else {
+				dictResp["/bot/pass"] = bot.Pass()
+				dictResp["/bot/name"] = bot.Name()
+				dictResp["/bot/online"] = fmt.Sprint(bot.BotNet().IsOnline().Get())
+				dictResp["/bot/fuel"] = fmt.Sprint(bot.Angar().Fuel().Get())
+				dictResp["/bot/gold"] = fmt.Sprint(bot.Angar().Gold().Get())
+				angar := bot.Angar()
+				base := angar.Base()
+				bank := base.Bank()
+				silverBot := bank.SilverBot()
+				dictResp["/bot/silver"] = fmt.Sprint(silverBot.Get())
+				dictResp["/bank/silver-time"] = bot.Angar().Base().Bank().CountDown().String()
+				dictResp["/bank/silver-mode"] = bot.Angar().Base().Bank().ModeCurrent().Work()
+				dictResp["/angar/silver-all"] = fmt.Sprint(bot.Angar().SilverAll().Get())
+				dictResp["/bot/mine-time"] = bot.Angar().Base().Mine().CountDown().String()
+				dictResp["/bot/mine-mode"] = bot.Angar().Base().Mine().ModeCurrent().Work()
+				dictResp["/mine/ruda"] = fmt.Sprint(bot.Angar().Base().Mine().Ruda().Get())
+			}
+		default:
+			dictReq = make(map[string]string)
+			dictResp["err"] = fmt.Sprintf("WebApi.fnWsRun(): unknown topic (%q)", topic)
+		}
+		if !fnWrite() {
+			return
+		}
+	}
+}

+ 84 - 0
server/server.go

@@ -0,0 +1,84 @@
+// package server -- главный тип приложения, содержит бота, интерфейсы и т.п.
+package server
+
+import (
+	"fmt"
+
+	"wartank/pkg/components/kernel"
+	"wartank/pkg/store"
+	"wartank/pkg/types"
+	"wartank/server/serv_bots"
+	"wartank/server/serv_gui"
+	"wartank/server/serv_web"
+)
+
+// Server -- главный тип приложения
+type Server struct {
+	*kernel.Kernel
+	store    types.IStore
+	servWeb  *serv_web.ServWeb
+	servBots *serv_bots.ServBots
+	servGui  *serv_gui.ServGui
+}
+
+// NewServer -- возвращает новый объект приложения
+func NewServer() (sf *Server, err error) {
+	kernel, err := kernel.NewKernel()
+	if err != nil {
+		return nil, fmt.Errorf("NewServer(): in make IKernel, err=\n\t%w", err)
+	}
+	sf = &Server{
+		Kernel: kernel,
+	}
+	sf.store, err = store.NewStore(sf)
+	if err != nil {
+		return nil, fmt.Errorf("NewServer(): in open store, err=%w", err)
+	}
+	sf.servBots, err = serv_bots.NewServBots(sf)
+	if err != nil {
+		return nil, fmt.Errorf("NewServer(): in create ServBots, err=%w", err)
+	}
+	sf.servWeb, err = serv_web.NewServWeb(sf)
+	if err != nil {
+		return nil, fmt.Errorf("NewServer(): in make IServWeb, err=\n\t%w", err)
+	}
+	sf.servGui, err = serv_gui.NewServGui(sf)
+	if err != nil {
+		return nil, fmt.Errorf("NewServer(): in make ServGui, err=\n\t%w", err)
+	}
+	return sf, nil
+}
+
+// Store -- возвращает хранилище
+func (sf *Server) Store() types.IStore {
+	return sf.store
+}
+
+// Run -- запускает сервер бота в работу
+func (sf *Server) Run() error {
+	// go sf.bot.Run()
+	if err := sf.servBots.Load(); err != nil {
+		return fmt.Errorf("Server.Run(): in load bots, err=\n\t%w", err)
+	}
+
+	go sf.servWeb.Run()
+	sf.servGui.Run()
+	<-sf.Done()
+	// if err := sf._erverWeb.Error(); err != nil {
+	// 	return fmt.Errorf("Server.Run(): in work IServHttp, err=\n\t%w", err)
+	// }
+	// if err := sf.bot.Error(); err != nil {
+	// 	return fmt.Errorf("Server.Run(): in work IBot, err=\n\t%w", err)
+	// }
+	return nil
+}
+
+// ServWeb -- возвращает веб-сервер
+func (sf *Server) ServWeb() types.IServWeb {
+	return sf.servWeb
+}
+
+// ServBots -- возвращает словарь ботов
+func (sf *Server) ServBots() types.IServBots {
+	return sf.servBots
+}