Союз | Union

Объявление

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Союз | Union » Картостроение и скриптовка » Как перестать бояться и начать скриптить


Как перестать бояться и начать скриптить

Сообщений 1 страница 30 из 108

1

Проблема скриптования стоит ещё более остро, чем рисование новых карт, так что эта тема призвана подтянуть общее понимание того, как писать качественные и работоспособные скрипты.

   Можно подумать, что чтобы написать качественный сценарий - нужно иметь высшее образование в области программирования, но спешу порадовать вас - это не так :)

   Однако, здесь я не буду досконально разжёвывать как написать ф-цию и вообще запустить скрипт - на форуме такая информация уже имеется и причём очень хорошо изложенная, здесь я лишь дам ссылки и, может быть некоторые комментарии к тому, что было написано ранее.
Итак, начнём.
   А с чего вообще начинается написание скрипта? Конечно же, с самого файла. По сути, скрипт - текстовый файл расширения lua с прописанным внутри кодом. Для написания своего сценарий сгодится даже обычный блокнот, но лучше использовать специальную программу, которая будет в этом архиве. Кроме того, там вы сможете найти "библию" любого картодела по скриптам, где описаны все применяемые команды/операторы(их так же можно именовать функциями) и справочник по редактору карт.
   Товарищ Reks, ныне ушедший от нас, уже давно создал тему, в которой очень доступно и понятно объяснил самые основы, поэтому привожу ссылки на самые нужные нам сообщения: 1, 2, 3
   Для начала внимательно прочитайте и переварите написанное. В принципе, это всё довольно легко и просто.

   От себя лишь добавлю теоретическую часть для понимания, как вообще происходит работа скрипта, и зачем нужен оператор Suicide()

   Язык lua таков, что он выполняется циклически, то есть весь файл работает сразу от начала и до конца, и так происходит много-много раз. В большинстве языков программирования выполнение идёт построчно, то есть есть одна главная программа, которая построчно выполняется. В нашем случае такого нет, у нас всё работает по функциям. "В чём разница?", - спросите вы. А в том, что мы имеем инициализирющую функцию под названием Init, из которой начинают запускаться другие функции. Init запускается вместе с миссией и его не надо вызывать самостоятельно. Если мы запускаем свою какую-нибудь функцию(слишком много функций, но вы терпите %-) ), например, RunScript("Attack1", 3000), то эта самая Attack1 будет запускаться каждые 3000 миллисекунд, при условии, если внутри не будет прописан Suicide(), то есть:

function Init()
   RunScript("Attack1",3000);
   RunScript("Attack2",3000);
end;

function Attack1()
   DisplayTrace("Началась атака 1");
end;

function Attack2()
   DisplayTrace("Началась атака 2");
   Suicide();
end;

В данном примере, при запуске карты, на экране сообщение "Началась атака 1" будет появляться каждые 3 секунды, а сообщение "Началась атака 2" будет выведено лишь единожды.

   Suicide() - это такая замечательная штука, которая не даёт функциям зацикливаться, но с ней есть несколько тонкостей, которые важно понимать для построения правильного алгоритма.
Когда выполняется этот оператор, ф-ция моментально выключается, однако, её можно будет вновь использовать для вызова. Вызов функции двумя RunScript подряд будет приводить к зацикливанию даже при наличии в ней Суицидов. Например, если в Init записать RunScript("Attack2",3000); дважды.
   Если вам нужно вызвать функцию определённое кол-во раз, то стоит дописать параметр после время вызова, который будет означать кол-ва срабатываний нужной функции RunScript("Attack2",3000,3);
   А вот если нужен постоянный запуск, например для проверки какого-то условия через if, то в такой функции Suicide() не нужен, а вернее, нужен, но в другом месте.

function Init()
   RunScript("Proverka1",3000);
end;

function Proverka1()
   if (GetNUnitsInArea(0,"zona") > 0) then
      DisplayTrace("В зоне");
      Suicide();
   end;
end;

В данном случае Proverka1 будет запускаться каждые 3 секунды, пока не выполнится условие, то есть хотя бы один юнит стороны 0 не окажется в зоне "zona". Без Суицида проверка будет работать дальше и выдавать сообщение "В зоне" каждые 3 секунды, пока хотя бы один юнит находится в зоне. А вот если Суицид поместить между двумя end;, то проверка отработает лишь один раз после первого запуска и больше работать не будет, по сути своей это будет одноразовая проверка условия(не циклическая). На этом принципе строится выполнение простейших заданий, то есть пока врага сколько-то или он где-то, то происходит проверка на наличие юнитов в скриптовой группе или зоне(зависит от задания).
   Это самые базовые знания для создания простеньких сценариев плана захватить то, уничтожить это, удержать вот здесь, доставить туда. На простеньком примере опишу каждое из таких заданий (открывайте руководство по луа и разбирайте все ключевые слова по ходу):

function Init()
RunScript("Obj0", 3000);
end;

function Obj0()
ObjectiveChanged(0,0) -- Выдача первого(нумерация начинается с нуля) задания, у нас это будет, допустим, захват деревни
RunScript("Obj0end", 3000); -- Запуск проверки выполнения первого задания
Suicide();
end;

function Obj0end()
if GetNUnitsInArea(1,"Village") < 2 then -- Если в зоне Village, которую мы рисуем в редакторе карт, юнитов врага меньше 2, то выполняется задание и выдаётся следующее. Желательно не писать в одной функции два и более ObjectiveChanged, иначе не будет появлятся окошко с заданием(не влияет на геймплей)
    ObjectiveChanged(0,1); -- Отображение завершения первого
    RunScript("Obj1", 3000); -- Запуск второго задания
    Suicide();
end;
end;

function Obj1()
ObjectiveChanged(1,0); -- Выдача второго задания, допустим, уничтожить стоящую на месте немецкую колонну
RunScript("Obj1end", 3000); -- Запуск проверки второго задания
Suicide();
end;

function Obj1end()
if GetNUnitsInScriptGroup(100) < 1 then --Если юнитов(грузовиков, например) с номером 100 меньше 1(можно так же писать "== 0", но если случайно потерять одно из двух "=", то скрипт не запустится вообще), то задание выполняется
    ObjectiveChanged(1,1);
    LandReinforcement(1); --Подкрепление для врага, задаётся в редакторе карт(см. свиток Reinforcement Groups в редакторе карт)
    RunScript("Attack", 10000); --Команды для юнитов подкрепления нужно задавать не сразу, а через некоторое время, потому что юниты развёртываются не мгновенно, а постепенно, и если команда будет задана пока юнита нет на карте, то он её не получит вообще.
    RunScript("Obj2", 3000);
    Suicide();
end;
end;

function Obj2()
ObjectiveChanged(2,0); -- Выдача третьего задания, допустим, удерживать деревню в течении 5 минут
RunScript("Obj2end", 300000); -- Запуск проверки третьего задания(по факту там нет проверки, а сразу выполение через 5 минут)
RunScript("Proval", 10000); -- Запуск проверки провала задания
Suicide();
end;

function Attack()
Cmd(19,10000,1,GetScriptAreaParams("Village")); -- Вызов бомбардировщиков, второй параметр для команд с авиацией всегда равен 10000, GetScriptAreaParams можно применять вместо координат
Cmd(3,20,GetScriptAreaParams("AttackPos")); -- Предположим, что все юниты в подкреплении имели номер 20, все атакующие будут идти в одну точку. Как сделать атаку более реалистичной будет расказано в следующем посте.
Suicide();
end;

function Proval()
if GetNUnitsInArea(0,"Village") < 2 then -- Если юнитов игрока в деревне будет меньше 2-х, то поражение
    ObjectiveChanged(2,2); -- Невыполнение задачи
    Loose(); -- Вызов окошка поражения
    Suicide();
end;
end;

function Obj2end()
ObjectiveChanged(2,1); -- Выполнение третьего задания
KillScript("Proval"); -- Принудительная остановка проверки на невыполнение
RunScript("Obj3", 3000); -- Запуск четвёртого задания
Suicide();
end;

function Obj3()
ObjectiveChanged(3,0); -- Выдача четвёртого задания, допустим, доставить прибывшего офицера к штабу
LandReinforcement(2); -- Прибытие офицера
RunScript("Obj3end", 3000); -- Запуск проверки четвёртого задания
Suicide();
end;

function Obj3end()
if GetNScriptUnitsInArea(80,"Shtab") > 0 then -- Если офицер с сриптовым номером 80 прибудет в зону "Shtab", то победа
    ObjectiveChanged(3,1); -- Выполнение четвёртого задания
    Win(0); -- Вывод окошка победы
    Suicide();
end;
end;

Такой скрипт пишется за полчаса. Как видим, не используя чего-то сверхсложного можно получить вполне интересную миссию ^^
    В следующих сообщениях постараюсь осветить такую тему, как переменные и использование готовых кусков скрипта, которые я буду приводить далее.
    Здесь можете задавать любые вопросы касательно скриптов, идей для каких-либо алгоритмов, работы каких-то конкретных функций и т.д.

+6

2

Для создания простых последовательных сценариев достаточно знание базовых приёмов, описанных выше, но что если хочется чего-нибудь необычного или нелинейного? Вот как раз для таких целей нам и пригодятся переменные.
   Что же такое эти самые переменные? Это ничто иное, как ячейки данных, с произвольным значением. Или, проще говоря, это те самые переменные из алгебры, в которые можно подставлять различные числа. У нас на вооружении имеются два типа переменных: локальные и глобальные.
   Начнём с локальных. Область их видимости, т.е. где эти переменные будут работать, ограничивается функцией, в которой они прописаны. Общий синтаксис выглядит так:

function test()
local a,b;
a = 10;
b = 20;
...

В справочнике по луа есть более подробные данные о синтаксисе и правильности применения переменных, настоятельно рекомендую ознакомиться с данным разделом. Тут я перейду к практическому использованию этой темы.
   Чем же на практике нам помогут переменные? А тем, что они позволяют считать нам какие-то определённые юниты, давать им импровизированные состояния и передавать информацию в другие части скрипта или оптимизировать код, в общем расширяют наши возможности по созданию необычных алгоритмов, которые было бы либо трудно реализовать без них, либо вообще невозможно. Тут без примеров не обойтись, и начнём мы, пожалуй, с простеньких оптимизаций, заодно расскажу о такой крайне полезной штуке, как цикл for.
   Допустим, у нас есть 20 пар юнитов типа танк-сквад. Каждый сквад должен следовать за своим танком, но как удобнее это сделать? Написать 20 строчек Cmd(39,*номер сквада*,*номер танка*)? А если таких пар 40? или 100? Не очень удобно копировать и вставлять одно и то же, но для решения этой задачи у нас есть очень хороший инструмент. Цикл for - это по сути счётчик. При нём обязательно есть переменная, которая увеличивается(или уменьшается) с каждым циклом. Присвоим сквадам чётные номера начиная со 100, а танкам нечётные, таким образом получится, что у нас заняты все номера от 100 до 139. Этим мы и воспользуемся. Цикл for выглядит следующим образом:

function Attack()
local i;
   for i = 1,20,1 do
      Cmd(39, 98+i*2, 99+i*2);
      Cmd(3, 99+i*2, GetScriptAreaParams("zona"..i));
   end;
   Suicide()
end;

Что же в этой абрадакабре происходит? А происходит вот что: объявляется переменная i, после чего запускается цикл for. Первый параметр - это начальное значение счётчика, которое будет применено на первой итерации цикла, второе значение - конечное значение i, то есть последнее значение перед выходом из цикла, третий параметр - приращение, другими словами, насколько будет увеличиваться переменная i после каждой итерации. А теперь к тому, что вообще происходит. Вот выполнился вход в цикл, переменная i имеет значение 1. Теперь берётся первая команда Cmd. Внутри неё, как видите, находятся не просто скриптовые номера, а выражения. Посчитаем же их: 98+1*2 = 100, 99+1*2 = 101. Как раз номера первой пары юнитов. Теперь пехота следует за танком. Следующая строчка задаёт координаты атаки танка, разберём и её: во втором параметре такое же выражение, как и в первом, то есть равно 101, а вот дальше идёт что-то необычное. На карте мы имеем зоны zona1, zona2, zona3... для каждой пары юнитов. Здесь же мы при помощи цикла задаём каждому танку свою зону всего лишь в одной строчке. В скобочках строка "zona" "складывается" со значением i и в итоге получается "zona1". Две точки означают сложение строк(об этом тоже подробнее рассказано в руководстве по луа). Далее первая итерация цикла заканчивается, переменная i увеличивается на 1 и становится равной 2. Цикл начинается снова, но теперь i = 2. Номера юнитов становятся равны 98+2*2=102, 99+2*2=103, а зона атаки "zona2" и таким образом итерации продолжаются дальше, пока i не станет равна 20, тогда произойдёт последняя итерация и цикл завершится. В результате мы имеем вместо 40 строк кода всего лишь 5, все сквады следуют за своими танками, а все танки идут в атаку к своим зонам. И чем больше пар, тем лучше мы экономим своё время и место в скрипте, и выглядит это намного приятней, согласитесь :)

   Следующий интересный предмет, который мы разберём, будет рандом. Рандом в переводе с английского - случайность, а в контексте скриптования это означает механизм создания случайных событий. Разберёмся же, зачем нам это нужно. Иногда хочется внести какой-то непредсказуемости в сценарий, чтобы игрок не мог предугадать, откуда пойдёт атака, или же у игрока выпадала случайная авиация для пользования, вот тут-то рандом нам и пригодится. Синтаксически эта конструкция выглядит так: local x = RandomInt(n); где n - любое целое (не дробное) число. В результате переменная x примет любое случайно значение в диапазоне от 0 до n-1. Пример скрипта для случайной атаки тут. Так же рандом можно применять для более случайного момента вызова функции внутри RunScript, например, RunScript("Attack", 60000 + RandomInt(120000)); В результате скрипт атаки запустится в случайном промежутке времени между одной и тремя минутами.

   Локальные переменные годятся для того, чтобы подсчитать что-то в пределах одного запуска функции, но мы не сможем сосчитать с её помощью, сколько раз функция запускалась или что-то, что выходит за рамки одной функции, и тут нам на помощь приходит глобальная переменная.
   Глобальные переменные в этом плане очень хорошие помощники, потому что область их видимости - весь скрипт, то есть применить их можно буквально из любого места. Создание и задание им значений происходит через ключевое слово SetIGlobalVar("имя переменной", значение);, а считывание через GetIGlobalVar("имя переменной", 0); Второе ключевое слово никак не изменяет переменную, а лишь возвращает её значение или же 0, если переменной с таким именем не существует. Применять это можно очень много где, и у меня не хватит фантазии, чтобы описать все возможности этого инструмента, постепенно вы сами научитесь читать чужие скрипты и сможете увидеть много новых фишек или же сами начнёте придумывать свои алгоритмы, а здесь я ограничусь небольшим примером. Допустим, у нас есть 4 задания, которые можно выполнять в любом порядке, а победа должна быть присвоена игроку, когда тот выполнит все 4. Наш код будет выглядеть так:

function Init()
SetIGlobalVar("temp.Objectives",0); -- Переменная, которая будет считать кол-во выполненных заданий
RunScript("Obj0", 3000);
RunScript("Obj1", 6000);
RunScript("Obj2", 9000);
RunScript("Obj3", 12000);
RunScript("ToWin", 3000);
end;

function ToWin()
if GetIGlobalVar("temp.Objectives", 0) == 4 then -- Проверка того, что все задания выполнены
    Win(0);
    Suicide
end;
end;

function Obj0()
ObjectiveChanged(0,0)
RunScript("Obj0end", 3000);
end;

function Obj0end()
if GetNUnitsInArea(1,"Village1") < 2 then
    ObjectiveChanged(0,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1); -- При выполнении задания переменная увеличивается на единицу. Значение внутри SetIGlobalVar задаётся при помощи GetIGlobalVar("temp.Objectives",0) + 1, то есть берётся текущее значение этой переменной и к ней прибавляется единица.
    Suicide();
end;
end;

function Obj1()
ObjectiveChanged(1,0)
RunScript("Obj1end", 3000);
end;

function Obj1end()
if GetNUnitsInArea(1,"Village2") < 2 then
    ObjectiveChanged(1,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1);
    Suicide();
end;
end;

function Obj2()
ObjectiveChanged(2,0)
RunScript("Obj2end", 3000);
end;

function Obj2end()
if GetNUnitsInArea(1,"Village3") < 2 then
    ObjectiveChanged(2,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1);
    Suicide();
end;
end;

function Obj3()
ObjectiveChanged(3,0)
RunScript("Obj3end", 3000);
end;

function Obj3end()
if GetNUnitsInArea(1,"Village4") < 2 then
    ObjectiveChanged(3,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1);
    Suicide();
end;
end;

   Следующей темой для разговора станет штука, о которой знают 2.5 человека. Она позволит всем использовать готовые сложные алгоритмы без всяких затруднений и даже не понимая их.

Отредактировано ИС 3 (2017-02-16 21:50:37)

+3

3

Переходим к самому вкусному. Сразу говорю, что нижеописанные инструменты ещё не изучены в полной мере, так что содержимое может дополняться и меняться.
   Для начала экскурс в программирование. Что мы вообще называем функцией? В классическом понимании, функция - это подпрограмма, которая возвращает значение. Кроме того, существует термин процедура - подпрограмма, которая НЕ возвращает значение. В нашем конкретном случае такого понятия, как процедура, формально не существует, но её роль у нас выполняет функция, то есть функция в луа универсальна, она  может как возвращать значение, так и не возвращать. В функции или процедуры, обычно, входит ряд параметров(но может и не входить), это мы можем видеть на примере любых ключевых слов из справочника, скажем, ChangePlayer(номер, сторона), что по факту является процедурой, потому что не возвращает значения, или GetNUnitsInScriptGroup(номер) - функция, которая возвращает кол-во юнитов в сценарной группе. В данном случае, номер и сторона как раз и являются параметрами, и что самое главное, мы сами можем писать такие же процедуры и функции самостоятельно! Дальше речь пойдёт именно о процедурах. Увы, мы не можем влезать в код игры, а можем лишь оптимизировать и упрощать с их помощью построение скрипта. Самая главная прелесть этого метода в том, что мы прописываем в скрипте нашу процедуру лишь единожды, а потом используем её из любого места скрипта. Но у этого метода есть много тонкостей и работа с ним отличается от простого использования функций без параметров, как мы обычно делаем. Далее расскажу на конкретных примерах, начну с простенького:

function Init()
RunScript("prepAt", 3000);
end;

function prepAt()
local i;
i = 1; -- Номер первого юнита
At(i);
i = 2; -- номер второго юнита
At(i);
Suicide();
end;

function At(i) -- Наша процедура
Cmd(0,i,GetScriptAreaParams("123"..i)); -- Зоны 1231 и 1232 для первого и второго юнита соответственно
end;

У нас есть наша процедура At(i), которая даёт команду юниту на движение в центр зоны с номером "123"..i . Запуск таких процедур отличается от запуска обычных функций тем, что не нужно прописывать RunScript. Соответственно, процедура стартует сразу же и её запуск нельзя отложить, а что самое главное, она не уходит в цикл! Да, в ней можно не писать Суицид, однако опять же, существует ряд тонкостей, о них попозже, пока только главный принцип. Итак, у нас запускается сразу две функции At(i) и причём они не пересекаются и не зацикливаются. Это был мой первый эксперимент на проверку работоспособности концепции в целом. В справочнике по луа об этом упоминается лишь пара слов без подробностей, а сам такой метод можно найти только на картах товарищей Jukov и Ilyaka (может быть, есть ещё у кого-то, но я не видел). Но это лишь простенькое использование, дальше - больше.
   В процедурах всё таки можно использовать зацикливания, но для этого нужны дополнительные манипуляции. Дальше вы посмотрите на скрипт атаки для одной пары типа танк-сквад с использованием координат(то есть танк будет ждать, пока пехота его догонит) на одну зону. Такие скрипты я давно использую на своих картах, но копировать и вставлять такой большой кусок для каждой(!) пары на карте было весьма утомительно, а новый метод позволит значительно уменьшить визуальную загруженность кода и сделает его более понятным и удобным. Проблема состояла в том, что сложные блоки скриптов зачастую циклические и требуют постоянной проверки, а процедуры не умеют зацикливаться, что же тогда делать? А выход оказался довольно прост. Ниже представлен скрипт для двух пар:

function Init()
RunScript("Attack1", 5000); -- Вспомогательная функция для первой пары
RunScript("Attack2", 5000); -- Вспомогательная функция для второй пары
SetIGlobalVar("const",400); -- Константа, задающая максимальное расстояние между сквадом и танком, прописывается один раз в скрипте обязательно!!!
end;

function AttTInf(t,inf,zone) -- Сама наша процедура, как я и говорил, в ней даже можете не разбираться. Никто вам не запрещает, и очень даже желательно понять, как она работает, но в этом посте разжёвывать я её не буду. Входные параметры такие: t - номер танка, inf - номер сквада, zone - название зоны.
local tx, ty, infx, infy, S, n;
if (GetNUnitsInScriptGroup(t) > 0) then
    if (GetNUnitsInScriptGroup(inf) > 0) then   
        tx, ty = GetObjCoord(t);
        infx, infy = GetObjCoord(inf);
        n = (tx - infx)*(tx - infx) + (ty - infy)*(ty - infy);
        if (n > GetIGlobalVar("const",0)*GetIGlobalVar("const",0)) then
            Cmd(50,t);
        else
            Cmd(3,t,GetScriptAreaParams(zone));
        end;
    else
    Cmd(3,t,GetScriptAreaParams(zone));
    end;
else
    if (GetNUnitsInScriptGroup(inf) > 0) then
    Cmd(3,inf,GetScriptAreaParams(zone));
    else
    Suicide();
    end;
end;
end;

function Attack1()
local t,inf,zone;
t = 100;
inf = 101;
zone = 100;
Cmd(39, inf, t); -- Задаём команду пехоте следовать за танком
AttTInf(t,inf,zone); -- Запуск процедуру
end;

function Attack2()
local t,inf,zone;
t = 102;
inf = 103;
zone = 102;
Cmd(39, inf, t);  -- Задаём команду пехоте следовать за танком
AttTInf(t,inf,zone); -- Запуск процедуру
end;

Сходу напрашивается вопрос: зачем нам нужны вспомогательные функции для каждой пары, не проще ли запустить процедуры через for сразу в одном месте, и почему в них нет суицида? Это и есть те самые тонкости, о которых я упоминал. Как я уже и говорил, процедуры не зацикливаются, и поэтому нам нужно зациклить их вызов, для этого и нужны вспомогательные функции без Суицидов. Кроме того, есть такая неприятная особенность, что одна процедура, запущенная несколько раз из одной функции, при срабатывании Suicide() внутри процедуры, убивается сразу для всех запусков, то есть если мы вызовем все процедуры через for и у одной из них отработает Suicide(), то все вызванные процедуры завершат свою работу тут же. Для этого, мы и выносим вызов каждой из них в отдельную функцию, потому что тогда процедуры становятся "как бы" разными и работают независимо друг от друга. В общем так этот метод работает. Кстати, эта первая процедура, которую вы можете использовать в своих картах. Сами вспомогательные функции свободно можно вызывать через for, после чего нужно будет накопировать столько функций Attack, сколько будет пар юнитов, к каждой прописать нужные параметры. Да, всё равно это копирование одного куска, но сравните копирование 50 строк и 8 по нескольку раз. Разница очевидна.
   Теперь небольшая ремарочка по поводу функций(те которые возвращают значение). В справочник есть следующие строки:

Функция, возвращающая значение:
function “имя_функции” (…)
тело функции;
return возвращаемое_значение;
end;

Это значит, что теоретически можно будет делать некие алгоритмы, которые будут возвращать число, например, a = Func(i), где Func(i) некая функция, в которой что-то обсчитывается и в результате получается число, которое можно положить в переменную(Так работают все функции, типа GetNUnitsInArea(), GetNUnitsInScriptGroup() и т.д.) . Пока я не вижу реального применения этого метода, но желающие могут поэкспериментировать в этом направлении.
   На этом моё изложение теории заканчивается. Опять же повторюсь, если возникнут вопросы, то пишите здесь, постараюсь разжевать подробней и с примерами. Часть с применением кусков скриптов на практике всё же перенесу в следующий пост.

+4

4

Пока что здесь будет всего лишь одна процедура, но со временем я буду дополнять этот пост(дополнять буду по желанию интересующихся)((новые "кубики" в нижеследующих сообщениях)).
1) Cкрипт атаки для одной пары типа танк-сквад с использованием координат с последовательным следованием через зоны.

Клац

Ниже приведён скрипт для одной атаки на n пар атакующих. Для следующих атак применяйте сквозную нумерацию зон.

function Init()
RunScript("prepAt1", 3000); -- Скрипт запуска вспомогательных функций для одной атаки
SetIGlobalVar("const",400); -- Та же константа, что и в примере до этого, не забывать про неё!
end;

function prepAt1()
local i;
for i = 1,n,1 do -- Здесь n - это количество пар в атаке, не забудьте указать! Для следующего скрипта атаки prepAt2 начальное i будет равно n+1, то есть for i = 4,..., если в прошлой атаке было 3 пары
SetIGlobalVar("zone"..i.."01",i*100+1); -- В этих глобальных переменных мы будем держать информацию о текущих зонах для каждой пары. Зоны на карте в этом случае будут называться так: "101", "102" ... "109" для первой пары, "201","202"..."209" для второй и т.д. Кол-во зон определяется в функциях Attack.
RunScript("Attack"..i, 5000); -- Функции будут носить названия Attack1, Attack2, ... AttackN, где N - поседняя пара
end;
Suicide();
end;

function Attack1() -- Будет копироваться для вызова процедуры для каждой пары атакующих(для каждой меняйте индекс в названии и параметры внутри)
local t,inf,startZ,finZ;
t = 100; -- номера юнитов используйте по вкусу, для каждого юнита свой номер!
inf = 101;
startZ = 101; -- Первая зона для первой пары
finZ = 103; -- Третья зона. Их может быть до 9 штук. Напоминаю, что следующая пара будет иметь зоны "201","202"..."209"
Cmd(39, inf, t); -- Задаём команду пехоте следовать за танком
AttTInfZ(t,inf,startZ,finZ); -- Запуск процедуры
end;

function Attack2()
.
.
.
function AttackN() -- Последняя пара
.
.
.
function AttTInfZ(t,inf,startZ,finZ) -- Собственно сама процедура, её нужно вставить в скрипт лишь один раз.
local tx, ty, infx, infy, S, n;
if (GetNUnitsInScriptGroup(t) > 0) then
    if (GetNUnitsInScriptGroup(inf) > 0) then   
        tx, ty = GetObjCoord(t);
        infx, infy = GetObjCoord(inf);
        n = (tx - infx)*(tx - infx) + (ty - infy)*(ty - infy);
        if (n > GetIGlobalVar("const",0)*GetIGlobalVar("const",0)) then
            Cmd(50,t);
        else
            if GetNScriptUnitsInArea(t,GetIGlobalVar("zone"..startZ,0)) > 0 then
            if GetIGlobalVar("zone"..startZ,0) == finZ then
                Suicide();
            else
                SetIGlobalVar("zone"..startZ, GetIGlobalVar("zone"..startZ,0) + 1);
                Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
            end;
            else
            Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
            end;
        end;
    else
    if GetNScriptUnitsInArea(t,GetIGlobalVar("zone"..startZ,0)) > 0 then
        if GetIGlobalVar("zone"..startZ,0) == finZ then
        Suicide();
        else
        SetIGlobalVar("zone"..startZ, GetIGlobalVar("zone"..startZ,0) + 1);
        Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
        end;
    else
        Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
    end;
    end;
else
    if (GetNUnitsInScriptGroup(inf) > 0) then
    if GetNScriptUnitsInArea(inf,GetIGlobalVar("zone"..startZ,0)) > 0 then
        if GetIGlobalVar("zone"..startZ,0) == finZ then
        Suicide();
        else
        SetIGlobalVar("zone"..startZ, GetIGlobalVar("zone"..startZ,0) + 1);
        Cmd(3,inf,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
        end;
    else
        Cmd(3,inf,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
    end;
    else
    Suicide();
    end;
end;
end;

+4

5

судя по всему, все задавлены интеллектом Докладчика и отходят от шока, вопросы будут задавать позже  :love:

кто еще не понял, то смысл посыла нашего уважаемого ИС3, что в этой теме будут размещены кубики (т.е. отдельные функции), из которых любой новичок сможет собрать вполне серьезный скриптовый сценарий.

кубик в копилку: счетчик времени на исполнение задания

таймер

объявление глобалок в предыдущих функциях
SetIGlobalVar("temp.start",1); --переключатель к стартовому условию
SetIGlobalVar("temp.timer",11); --установка таймера, минуты

function Init()
RunScript("defendZone", 60000);
end;
function defendZone()
if GetIGlobalVar("temp.start",0)==1 then --стартовое условие для начала отслеживания
if GetNUnitsInArea(0,"zone")<1 then -- ключевое условие задания = старт таймера
    if GetIGlobalVar("temp.timer",0)==0 then -- время закончилось
    RunScript("toLoose",5000); -- наказание
    else
    DisplayTrace("Срочно верните охранение на занятые позиции!");
    DisplayTrace("Иначе Вы будете отстранены от командования!");
    a=GetIGlobalVar("temp.timer",0)-1; -- счетчик
    DisplayTrace("На выполнение осталось %g мин!",a);
    SetIGlobalVar("temp.timer",a);
    end;
    else
    SetIGlobalVar("temp.timer",11); -- восстановление таймера, минуты
end;
end;
end;
function toLoose() -- функция наказания
Loose(0);
suicide();
end;

Работает это просто,
1. таймер начинает контроль когда глобальная переменная temp.start  равна 1 (можно название заменить на Objective0 тогда контроль начнет работать после выполнении первого задания (Objective6 - седьмого и т.п.).
2. Затем начинается отслеживание ключевого условия (их может быть много через and, or) для старта счетчика минут, в нашем случае это отсутствие юнитов Игрока в zone (область рисуется в редакторе)
3. Если время кончается, а ключевое условие не выполнено, то Игрок получает наказание.
3а. Если Игрок выполняет условие (занял зону), то таймер обновляется и так до бесконечности.
4. Если по сценарию проверка уже не нужна, не забудьте прописать в какой нибудь другой функции далее по сюжету, строку
KillScript("defendZone");

+8

6

Возможно, очень глупый вопрос, наверняка где-то здесь уже был задан, но:
Как заставить юнит окопаться по прибытию в место назначения?
Я пишу в скрипте:

Код:
Cmd (0, 102, 860, 568);
QCmd (45, 102, 860, 568);

Он доезжает до точки, но не окапывается!
Пробовал и без координат - безрезультатно!

Отредактировано BalashovM (2017-05-25 01:49:44)

0

7

BalashovM
Скорее всего эта команда не реализована для использования в скриптах, так что дать её можно только через редактор карт.

0

8

ИС 3 написал(а):

BalashovM
Скорее всего эта команда не реализована для использования в скриптах, так что дать её можно только через редактор карт.

Вот я тоже так подумал, ибо как начальная команда она спокойно себе выполняется. Но вот только зачем разрабы ее тогда включали в список возможных команд?

0

9

BalashovM написал(а):

Но вот только зачем разрабы ее тогда включали в список возможных команд?

А где она упоминается? В справочнике Жукова о ней не написано.

0

10

ИС 3 написал(а):

А где она упоминается? В справочнике Жукова о ней не написано.

В оригинальном мануале от разрабов, к редактору карт. Список команд, команда №45 - окопаться.

0

11

в интересной беседе, пришел для себя к выводу, что прием наших уважаемых картоделов с отключением топлива в юнитах, он не сам по себе негативный (для меня), а негативный из-за своей неопределенности - приехал в скриптовую зону - "электричество" кончилось... хм..

написал коротенький скрипт, надеюсь станет универсальным для сценарных приемов с топливом:

расход и заправка топливом

function startFuel()
local a,b,x,y;
for a=101,109,1 do -- скриптовые номера машин (начальный, конечный, шаг)
SetIGlobalVar("temp.fuel"..a,1000); -- первоначальная отметка топливомера=1000
b=GetIGlobalVar("temp.fuel"..a,0);
DisplayTrace("машина %g : топливомер отметка %g",a,b);
x,y=GetObjCoord(a);
SetIGlobalVar("temp.coord.x"..a,x);
SetIGlobalVar("temp.coord.y"..a,y);
end;
Suicide();
end;
function Fuel()
local a,b,t,x,y,bb,xx,yy,xt,yt;
for a=101,109,1 do -- скриптовые номера машин (начальный, конечный, шаг)
    if (GetNUnitsInScriptGroup(a) > 0) then
    xx=GetIGlobalVar("temp.coord.x"..a,0);
    yy=GetIGlobalVar("temp.coord.y"..a,0);
    x,y=GetObjCoord(a);
    SetIGlobalVar("temp.coord.x"..a,x);
    SetIGlobalVar("temp.coord.y"..a,y);
    xt,yt=GetObjCoord(201); -- скриптовый номер заправщика
    t=((x-xt)*(x-xt)+(y-yt)*(y-yt)); -- здесь плачет Пифагор
    if (t>100000) then -- расстояние для заправки
    bb=GetIGlobalVar("temp.fuel"..a,0);
    b=bb+0.4-((x-xx)*(x-xx)+(y-yy)*(y-yy))*0.0001; -- коэффициент расхода топлива
        if (b>0) then
        SetIGlobalVar("temp.fuel"..a,b);
        DisplayTrace("машина %g : топливомер отметка %g",a,b); -- отладка, уровень топлива
        else
        SetIGlobalVar("temp.fuel"..a,0);
        GiveCommand (9,a);
        ChangePlayer(a,2);-- наказание N1=неактивный юнит
        DamageObject(a,10);-- наказание N2=порча машины на 10 hр/время функции
        DisplayTrace("машина %g : закончилось топливо",a);
        end;
    else
    SetIGlobalVar("temp.fuel"..a,1000); -- заправились на 1000
    ChangePlayer(a,0);--отмена N1=активный юнит
    DisplayTrace("машина %g : заправлена",a);
    end;
    else
    end;
end;
end;
function Init()
RunScript("startFuel",2000); -- обьявление части констант
RunScript("Fuel",10000); -- основной блок, регулируйте частоту учета
end;

1. в нескольких местах задаются константы- не очень красиво, но не хотел перегружать процессор записью и считыванием глобалок, а так вроде бы минимизировано количество проверок и вычислений;

2. планировалось, что счетчик каждому юниту будет вызываться индивидуально после выбора юнита рамочкой, но к удивлению обнаружил что  function GetSelectedUnitsFeedBack(n) теперь не работает (видимо из-за сталинградского ailogic.dll, с родным точно работала) - если кто-то знает синтаксис для сталинградского отпишитесь пожалуйста), пока что выдается инфо сразу по всем живым юнитам (из массива которому назначен контроль топлива) с частотой сработки функции - решение в след.посте

2а. можно как-то обыграть получение информации по остатку топлива - командир приехал на мотоцикле к танку или еще как, это не сложно - буду рад интересным предложениям.

3. можно еще реализовать, чтобы автоматом определялся тип юнита и соответственно устанавливался коэффициент расхода топлива, например 0,001 легкие; 0,002 средние танки и т.д.

4. можно учитывать емкость самого заправщика, сколько перелили в танки и остаток.

5. наше стандартное наказание - смена игрока, т.е. юнит становится нейтралом на время и не ведет огонь, что не совсем логично (лучше отдавать юнит союзнику и контролировать его скриптом); так же есть вариант, чтобы юнит оставался под контролем, но получал урон (постоянный 10 хп за цикл как в примере скрипта) при уровне топливомера 0 (допустим это не топливо, а масло - при работе "на сухую" двигатель выходит из строя).

думаю, наши скриптостроители найдут как обыграть на своих картах эту игровую ситуацию, всем хорошей игры!

+4

12

WS7 написал(а):

расход и заправка топливом

Огромное спасибо за скрипт! Будем пробовать. Правда для меня сложноват, но буду разбираться.

0

13

разобрался с функцией выбора, она кстати нигде в мануалах и на форуме не описана корректно:

функция получения скриптового номера от выделения

function SelectedUnits()
AskClient("GetSelectedUnits()");
end;
function GetSelectedUnitsFeedBack(n)
local b;
if (n >= 101 and n<=109) then --скриптовые номера машин (начальный, конечный)
b=GetIGlobalVar("temp.fuel"..n,0);
DisplayTrace("машина %g : топливомер отметка %g",n,b); -- уровень топлива
end;
end;
function Init()
RunScript("SelectedUnits",30000); -- контроль уровня топлива, частота сообщений
end;

1. Функция состоит из двух частей, первая с произвольным названием и вызовом через Init (содержит AskClient) и вторая, располагаемая строго за первой - ниоткуда не вызываемая и изменить название которой нельзя GetSelectedUnitsFeedBack(n) - возвращает n=скриптовый номер выделенного юнита, если выделено несколько - вызывается несколько раз (встроенный цикл)

2. Итого в нашем примере про расход топлива 4 функции, просто соединяете два блока, в Init - три функции соответственно.
Строку номер 29  - DisplayTrace("машина %g : топливомер отметка %g",a,b); -- отладка, уровень топлива - удалите из первого блока, она лишняя.

пользуйтесь, открывает море доп.возможностей. Хорошего ГЗМ!

+3

14

Мейк Авиацию Грейт Эгейн!

Разбираясь с мануалом Константина Jukov, обратил внимание что оба примера скриптов на управление авиацией, работают только с ванильной логикой использования авиацией, которая к сожалению, еще слишком часто представлена на наших картах: время отдыха авиации всегда больше, чем время пребывания ее на карте, упаси боже дать игроку выпускать звено, если пред идущее еще на карте ... давно мне не нравились эти ограничения, которые кстати еще и косвенно порождают ситуации с багами "штурмовики vs разведчик" (Игрок же хочет использовать гарантированное время когда нет на карте ястребов компа).

Еще с 7.50 тестирую многократное увеличение времени операций авиации (FighterPatrolTime) - эффект очень положительный, появляются новые  тактические возможности с пошаговой реализацией - подвешиваем истребители над объектом, выпускаем разведчик, ИИ выпускает истребители на перехват, завязывается полномасштабное сражение, в которое по ходу можно опять добавлять самолеты ... движок нормально тянет по 30+ самолетов одновременно ... вот только картоделы редко дают такой праздник жизни устроить .. да и главная проблема, если автором применяется сложный скрипт на управление авиацией, то он почти всегда работает неправильно из-за изменения константы, не критично, но не так как задумано автором - непорядок.

Люди в моддинге блица делятся на две категории, кто написал свой скрипт на авиацию, и прочих, ... пора уже и мне перейти в высшую лигу :)

Авиаконтроль

function setupAvia()
SetIGlobalVar("temp.avia_scout",77); -- самолетовылетов разведчиков, если есть в наличии
SetIGlobalVar("temp.avia_fighter",77); -- число самолетовылетов истребителей
SetIGlobalVar("temp.avia_lander",9); -- спешиали фо simeo :)
SetIGlobalVar("temp.avia_bomber",77); -- самолетовылетов бомбардировщиков
SetIGlobalVar("temp.avia_attack",77); -- самолетовылетов штурмовиков
SwitchWeatherAutomatic(0); -- над всей Испанией чистое небо (c)
RunScript("controlAvia",2000); -- меньше значение = выше точность контроля = выше загрузка процессора
RunScript("infoAvia",30000); -- периодичнось информации о самолетовылетах
RunScript("endAvia",60000); -- выравниваем баланс, разгружаем процессор
EnableAviation(0,-1); --ключ на старт!
Suicide();
end;
function controlAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x,y,z,zy;
for b=1,5 do
x=GetIGlobalVar("temp."..a[b],0);
if x>0 then
z=GetNUnitsOfType(a[b],0);
y=GetIGlobalVar("temp.N"..a[b],0);
zy=z-y;
SetIGlobalVar("temp.N"..a[b],z);
    if zy>0 then
    SetIGlobalVar("temp."..a[b],x-zy);
    else
    end;
else
DisableAviation (0,b-1);
end;
end;
end;
function infoAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x;
for b=1,5 do
x=GetIGlobalVar("temp."..a[b],0);
DisplayTrace("--------------------------------------"); --здесь могла бы быть Ваша реклама! :)
DisplayTrace("лимит вылетов "..a[b]..": %g",x);
end;
end;
function endAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x;
x=0;
for b=1,5 do
x=x+GetIGlobalVar("temp."..a[b],0);
end;
if x<=0 then
DisableAviation (0,-1);
DisplayTrace("все доступные вылеты использованы, поддержка операции ВВС - прекращена!");
SwitchWeatherAutomatic(1); -- лето пролетело
KillScript("controlAvia");
KillScript("infoAvia");
Suicide();
end;
end;
function Init()
RunScript("setupAvia",2000);
end;

1. Скриптом считаются самолетовылеты! (а не нажатия кнопки вызовов, хотя можно настроить и счетчик кнопок), т.е. если у вас 10 самолетовылетов, а картодел дал вам два самолета с вылетом по двое, то использовать (нажать кнопку) такой тип самолетов можно пять раз, или девять - если в первом вылете один из них собьют ...

2. Желательно выставлять начальный лимит вылетов кратный составу звеньев, иначе возможна ситуация когда разрешено семь вылетов звену из четырех самолетов, они сделают два вылета полной пачкой итого восемь (нет возможности изменить состав звена скриптом), но скрипт запомнит такое и уже не выпустит вылет в последнем оставшемся типе самолетов.

3. Вместо больших звеньев, лучше делать небольшой релакс тайм на карте, если для бомберов звено все-таки это возможность отбиться от файтеров, то делать пачки всех остальных нет необходимости, а скрипт будет точнее работать

4. Картоделы! ставьте корректные самолеты на карту, ю87 - это штурмовик, не бомбер! В карточке юнита указано как его правильно использовать!

в следующий раз покажу этот же скрипт, но модифицированный с использованием псевдослучайностей. Хорошего ГЗМ!
p.s. ставим лайк - голосуем за официальное признание FighterPatrolTime=много :)

+5

15

Мейк Авиацию Рандом Эгейн!

Для скрипта авиации, модифицированного с использованием псевдослучайностей нужно задать вероятности доступности каждого типа вылетов:

Вероятности

SetIGlobalVar("temp.Ravia_scout",100); -- 100% вероятность доступности разведчиков по ротации рандома
SetIGlobalVar("temp.Ravia_fighter",50); -- %
SetIGlobalVar("temp.Ravia_lander",50); -- %
SetIGlobalVar("temp.Ravia_bomber",20); -- %
SetIGlobalVar("temp.Ravia_attack",0); -- недоступны (с момента включения рандом ротации)

Открываются сценарные возможности для поощрения или наказания Игрока за определенный стиль игры, достижения или упущения по ходу развития сюжета.

Пример сценарного блока

function missionAvia()--сценарный блок как пример
local a,b;
a=GetIGlobalVar("temp.avia_fighter",0);
SetIGlobalVar("temp.avia_fighter",a+1); -- плюс 1 самолетовылет за каждый цикл функции
b=GetIGlobalVar("temp.Ravia_attack",0);
SetIGlobalVar("temp.Ravia_attack",b+10); --плюс 10% вероятности доступности вызова за каждый цикл
end;

Вызывая функцию с периодичностью например раз в 10 минут, Игрок (или ИИ - если авиаскрипт будет использоваться для стороны противника) будет получать дополнительно 1 самолетовылет истребителей (если на авиабазе есть боеготовые) и увеличивается вероятность доступности штурмовиков на 10%. Таким образом, можно срежиссировать изменение доступности авиации в затяжных боях, за события, выполнение заданий и т.п.

Основной рандом блок

function randomAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x,y,z,r;
for b=1,5 do
x=GetIGlobalVar("temp."..a[b],0);
if x>0 then
r=RandomInt(100); -- бросаем кости
z=GetIGlobalVar("temp.R"..a[b],0);
y=z-r;
    if y>0 then
    EnableAviation(0,b-1);
    DisplayTrace("--------------------------------------"); -- и здесь могла бы быть Ваша реклама! :(
    DisplayTrace("вылеты "..a[b].." становятся доступны лимит: %g",x);
    else
    DisableAviation(0,b-1); --не повезло, увы
    end;
else
end;
end;
end;

0% - тип будет заблокирован всегда, 100% - всегда активен

если по сценарию нужно специфическое управление, например как в шедевр-миссии dubno_ger; по условию - разведчики всегда активны, транспорты всегда неактивны, а остальные вызываются со своими вероятностями (неравными), но из трех типов будет доступен только один - то это решается вот такой коротенькой добавкой к существующей системе:

логика из dubno_ger

function dubno_ger() -- логика из рандом блока шедевр-миссии dubno_ger
SetIGlobalVar("temp.Ravia_fighter",0); -- %
SetIGlobalVar("temp.Ravia_bomber",0); -- %
SetIGlobalVar("temp.Ravia_attack",0); -- %
local x = RandomInt(10);
if x <= 1  then
SetIGlobalVar("temp.Ravia_attack",100);
else
    if x <= 5  then
    SetIGlobalVar("temp.Ravia_fighter",100);
    else
    SetIGlobalVar("temp.Ravia_bomber",100);
    end;
end;
end;

В итоге авиаскрипт с рандомом имеет такой законченный вид:

Авиаконтроль рандом версия

function setupAvia()
SetIGlobalVar("temp.avia_scout",77); -- самолетовылетов разведчиков, если есть в наличии
SetIGlobalVar("temp.avia_fighter",77); -- число самолетовылетов истребителей
SetIGlobalVar("temp.avia_lander",9); -- спешиали фо simeo :)
SetIGlobalVar("temp.avia_bomber",77); -- самолетовылетов бомбардировщиков
SetIGlobalVar("temp.avia_attack",77); -- самолетовылетов штурмовиков
SetIGlobalVar("temp.Ravia_scout",100); -- 100% вероятность доступности разведчиков по ротации рандома
SetIGlobalVar("temp.Ravia_fighter",50); -- %
SetIGlobalVar("temp.Ravia_lander",50); -- %
SetIGlobalVar("temp.Ravia_bomber",20); -- %
SetIGlobalVar("temp.Ravia_attack",0); -- недоступны (с момента включения рандом ротации)
SwitchWeatherAutomatic(0); -- над всей Испанией чистое небо (c)
RunScript("controlAvia",2000);-- меньше значение = выше точность контроля = выше загрузка процессора
RunScript("infoAvia",120000);-- периодичнось информации о лимите самолетовылетов
RunScript("endAvia",190000);-- вызываем реже
RunScript("randomAvia",240000+RandomInt(120000)); -- включаем периодичность рандома рандомом (сорри Пушкин!)
RunScript("missionAvia",600000);
EnableAviation(0,-1); --ключ на старт!
Suicide();
end;
function controlAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x,y,z,zy;
for b=1,5 do
x=GetIGlobalVar("temp."..a[b],0);
if x>0 then
z=GetNUnitsOfType(a[b],0);
y=GetIGlobalVar("temp.N"..a[b],0);
zy=z-y;
SetIGlobalVar("temp.N"..a[b],z);
    if zy>0 then
    SetIGlobalVar("temp."..a[b],x-zy);
    else
    end;
else
DisableAviation(0,b-1);
end;
end;
end;
function infoAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x;
for b=1,5 do
x=GetIGlobalVar("temp."..a[b],0);
DisplayTrace("--------------------------------------"); --здесь могла бы быть Ваша реклама! :)
DisplayTrace("лимит вылетов "..a[b]..": %g",x);
end;
end;
function endAvia()-- выравниваем баланс, разгружаем процессор
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x;
x=0;
for b=1,5 do
x=x+GetIGlobalVar("temp."..a[b],0);
end;
if x<=0 then
DisableAviation (0,-1);
DisplayTrace("все доступные вылеты использованы, поддержка операции ВВС - прекращена!");
SwitchWeatherAutomatic(1); -- лето пролетело
KillScript("controlAvia");
KillScript("infoAvia");
KillScript("randomAvia");
KillScript("missionAvia");
Suicide();
end;
end;
function randomAvia()
local a={"avia_scout","avia_fighter","avia_lander","avia_bomber","avia_attack"};
local b,x,y,z,r;
for b=1,5 do
x=GetIGlobalVar("temp."..a[b],0);
if x>0 then
r=RandomInt(100); -- бросаем кости
z=GetIGlobalVar("temp.R"..a[b],0);
y=z-r;
    if y>0 then
    EnableAviation(0,b-1);
    DisplayTrace("--------------------------------------"); -- и здесь могла бы быть Ваша реклама! :(
    DisplayTrace("вылеты "..a[b].." становятся доступны лимит: %g",x);
    else
    DisableAviation(0,b-1); --не повезло, увы
    end;
else
end;
end;
end;
function missionAvia()--сценарный блок как пример
local a,b;
a=GetIGlobalVar("temp.avia_fighter",0);
SetIGlobalVar("temp.avia_fighter",a+1); -- плюс 1 самолетовылет за каждый цикл функции
b=GetIGlobalVar("temp.Ravia_attack",0);
SetIGlobalVar("temp.Ravia_attack",b+10); --плюс 10% вероятности доступности вызова за каждый цикл
end;
function Init()
RunScript("setupAvia",2000);
end;

Успехов в картостроении! Хорошего ГЗМ!

+4

16

От гениальных (а так оно и есть) создателей Блица, моддерам досталось не так уж и много инструментов для создания скриптов, куцый набор функций и команд, часто задача создания скрипта напоминает анекдотическое сложите из кубиков слово "счастье"...

Управление пехотой - одна из таких задач, вот ниже сообщение из далекого 2012 года:

ilyaka написал(а):

когда в траншее первого рубежа остается меньше половины обороняющейся там пехоты, пехота выходит из траншеи второго рубежа и атакует траншею первого рубежа, с посадкой в нее(атакует врага если он к тому времени уже занял траншею, а если там еще свои, то просто присоединяется к ним и продолжает обороняться вместе с ними)

ilyaka. скрипт управления пехотой

function Kontra19()
  local A_Swarm = 3;
  local A_Enter = 6;
  local A_Leave = 7;
  local a = GetNUnitsInScriptGroup(691)+GetNUnitsInScriptGroup(692)+GetNUnitsInScriptGroup(693)+GetNUnitsInScriptGroup(694)+GetNUnitsInScriptGroup(695)+GetNUnitsInScriptGroup(696);
  if (a <= 3) then
   Cmd(7,742,10283,9825);
   QCmd(3,742,11215,8650);
   QCmd(6,742,1159);
   Cmd(7,743,10395,9820);
   QCmd(3,743,11365,8685);
   QCmd(6,743,1160);
   Cmd(7,744,10480,9825);
   QCmd(3,744,11955,8110);
   QCmd(6,744,1161);
   Cmd(7,745,10600,9820);
   QCmd(3,745,12080,8225);
   QCmd(6,745,1162);
   Cmd(7,745,10700,9885);
   QCmd(3,745,12095,8480);
   QCmd(6,745,1152);
   Cmd(7,746,11330,9995);
   QCmd(3,746,11860,9465);
   QCmd(3,746,12160,8590);
   QCmd(6,746,1163);
   ChangeFormation(742,3);
   ChangeFormation(743,3);
   ChangeFormation(744,3);
   ChangeFormation(745,3);
   ChangeFormation(746,3);
   RunScript("Kontra20",3000);
   Suicide();
   end;
end;

все бы хорошо и для простых ситуаций сгодится, но к огромному сожалению, функция GetNUnitsInScriptGroup возвращает 0 если отделение погибло, и 1 - если жив хоть один солдат (т.е. функции все равно 1 солдат в скваде или 5/10); а когда нужно тонко настроить поведение пехоты, не дожидаясь гибели соседей? Есть другой путь, рисовать скриптовые зоны, но вне этих скриптовых зон опять же пехота становится управляемой не гибко, без учета изменившейся ситуации в бою... а трудоемкость такой работы огромна... да и в скрипте выше, все эти цифирки команд и координат...
http://sd.uploads.ru/t/JPMY5.jpg

существует все же способ через функцию GetNUnitsInCircle получать информацию о пехоте, правда в реальном времени получать точную информацию проблематично. На базе такой возможности представляю вам новый кубик для управления поведением пехоты:

логика пехоты

function infantry()
local a,b,s,e,xA,yA,xM,yM,xU,yU;
xM,yM=GetScriptAreaParams("Medic"); -- там нас полечат
for b=101,110 do
xU,yU=GetObjCoord(b); -- где я?
a=GetNUnitsInCircle(0,xU,yU,220); -- главный параметр для тонкой настройки
xA,yA=GetObjCoord(1000+b); -- номера окопов/зданий +1000 от пехоты
s=GetUnitState(b);
if a<4 then
Cmd(0,b,xM,yM); -- в больничку срочно
else
if a<7 then
Cmd(3,b,xM,yM); -- в больничку через атаку
else
    if s~=8 and s~=9 then
    Cmd(3,b,xA,yA);-- в атаку
    QCmd(6,b,1000+b);-- в окоп/здание
    else
    end;
end;
end;
e=GetNUnitsInCircle(1,xU,yU,3000); -- кто здесь?
if e>0 then
ChangeFormation (b,3); -- агрессия
else
ChangeFormation (b,1); -- марш
end;
end;
end;

из достоинств - скрипт очень легко использовать - задаем первый и последний номера сквадов под управлением логики, на карте скриптуем пехоту и окопы-здания на линии обороны которую мы будем удерживать, и всего одну зону, где будет получена медпомощь (можно обойтись просто координатами этой точки, а можно сделать ее динамичной - т.е. пехоты для лечения будет отходить в разные точки) ... все параметры легко настраиваются, можно делать ваших электронных болванов более агрессивными, или более робкими и т.д.

из недостатков - бывают ситуации когда сквады пробегая сквозь друг друга, меняют приказ на другой, но будем считать это не багом, а фичей - командир идущий от точки сбора в бой, по пути собирает деморализованных бойцов и они опять пробуют в атаку :) ...

как говорится лучше один раз увидеть :) ... вот тестовая карта, поместить в Data, запуск через новая_игра/дополнения/дополнительные миссии - должна быть первой в списке.

тестовая карта

р.ы. по ходу тестирования, была разработана еще одна яркая функция (простите игроки, теперь вам будут отключать не только топливо, но и снабжение) :)

р.ы.ы. картоделы! сильно не смейтесь, ... там главное скрипт  :flag:

Хорошего ГЗМ!

+8

17

Сегодня мы поговорим о снабжении. Бесконечное пополнение со складов, устроенное нам Нивалом, в исторически ресурсоемкой войне, это конечно же,- неприятное упрощение игровых сценариев, что влечет за собою болезненные перекосы в виде отказа картоделов давать Игроку личные юниты, "нарушающие" баланс.

творец многих шедевров ГЗМ (к сожалению, впоследствии получивший контузию майданизмом головного мозга) - Reks передает нам привет из далекого 2010 года:

убрать склады из ГЗМ
Reks написал(а):

Моя главная идея следующая - убрать все склады, как главные так и мелкие, с карты.  Вообще ВСЕ!

Вкратце, что это даст:

1) Ограниченный боезапас юнитов и всей группировки игрока в целом. Пополнение припасов (как и личного состава или артрасчетов) будет проводиться только за счет имеющихся грузовиков. Грузовикам пополниться негде, по этому имеющийся в них стартовый боезапас, и будет запасом снабжения игрока на весь сценарий. Грузовиков игроку будет выдано определенное количество, вполне достаточное для прохождения сценария при относительно бережном отношении к боеприпасам и личному составу. Как  игрок будет использовать грузовики, целиком и полностью лежит на его совести. Так что не исключено, что Игрок вполне может бессмысленно растратить свое снабжение на какие то ненужные вещи, и в следствии этого даже проиграть из-за недостатка боеприпасов или личного состава.

2) Игроку можно будет выдавать очень мощные орудия и прочие вооружения без опаски нарушить баланс миссии, и не опасаясь того, что игрок пройдет миссию исключительно только одними этими уберами. Дело в том, что все тяжелые пушки (как и разные супер танки и тяжелые САУ) имеют как правило довольно скудный боезапас. А пополнение припасами например того же Карла, или 204мм БР-4 будет сулить только очень быстрое истощение припасов игрока. Да даже СУ-152 или катюшу пополнить нужен не один грузовик. Это вынудит игроков выбирать какие войска для него более приоритетные. К примеру, на пополнение одной катюши наверняка уйдет два грузовика припасов. В то время, как с тех же двух грузовиков спокойно пополнят боезапас под завязку 5-6 Т-34. Игрок трижды подумает что ему лучше: один залп из катюши и пустые и бесполезные Т-34, либо ну его нафиг тот залп катюши, пусть лучше Т-34 воюют. Таким образом, можно будет не опасаясь, выдавать игроку те же катюши (будучи уверенным, что игрок не сможет ими отутюжить всю карту, а использует их только один-два раза), мощные орудия, тяжелую бронетехнику и т.п.

К счастью, столь радикальные решения не прижились в ГЗМ, да и не очень удобно весь запас хранить в грузовиках, а если на миссию выдавать 50-100 грузовиков, весь автопарк где-то нужно держать, а если налетят злые штуки? сортировать полные отдельно от пустых .. не практично...

А практично использовать для этого наш следующий скрипт-кубик :)

Грузовики Медицина учет снабжения

function TruckCheckZone() -- без инженерок
local a,b,s,ss,k,l,m,z;
a=0;
for b=401,420 do -- автомобили
s=GetUnitState(b);
if s<0 then
else
ss=GetIGlobalVar("temp.s"..b,0);
SetIGlobalVar("temp.s"..b,s);
z=0;
for m=1,2 do--кол-во складов (store1,2...)
z=z+GetNScriptUnitsInArea(b,"store"..m);
end;
    if z>0 then
    if  ss==0 and s~=0 then
    a=a+1;
    end;
end;
end;
end;
if a>0 then
k=GetIGlobalVar("temp.supply",0)+a;
SetIGlobalVar("temp.supply",k);
end;
l=GetIGlobalVar("temp.supplyLimit",0); -- лимит заправок (где-то нужно обьявить в начале сценария)
if k>=l then
RunScript("noSupply",2000);
DisplayTrace("Снабжение войск приостановлено");
Suicide();
end;
end;

как обычно, применять это легко и просто: вокруг каждого склада рисуем зону store1, store2 и т.п., назначаем машинкам скриптовые номера, указываем первый и последний номер в скрипте, задаем параметр общего лимита (талоны на посещение складов) и все работает.

наказание приходит в таком виде http://s8.uploads.ru/t/ExNSj.png

а скриптуется вот так

function noSupply()
local b,s;
for b=401,420 do -- автомобили, первый и последний
s=GetUnitState(b);
if s==0 then
Cmd(9,b);
DisplayTrace("Снарядов нет, но вы держитесь! (с)");
end;
end;
end;

к сожалению, в этой удачной схеме не очень вписываются инженерки, у которых недобросовестными кодерами нивала был заложен чит, потребовавший увеличения количества проверок и стал более громоздким (зоны вокруг складов должны быть больше радиуса откуда добегают саперы-это важно!), выглядит так:

универсальный учет снабжения

function EngTruckCheckZone() --универсальная
local a,b,s,ss,es,k,l,m,z;
a=0;
for b=401,420 do -- автомобили
s=GetUnitState(b);
if s<0 then
else
ss=GetIGlobalVar("temp.s"..b,0);
SetIGlobalVar("temp.s"..b,s);
z=0;
for m=1,2 do --кол-во складов (store1,2...)
z=z+GetNScriptUnitsInArea(b,"store"..m);
end;
    if z>0 then
    if  ss==0 and s~=0 then
    a=a+1;
    elseif s>38 or s==35 then
    Cmd(9,b);
    DisplayTrace("станьте в очередь на склад");
    elseif s==19 or s==18 then
    Cmd(9,b);
    DisplayTrace("станьте в очередь на склад");
    end;
    end;
end;
end;
if a>0 then
k=GetIGlobalVar("temp.supply",0)+a;
SetIGlobalVar("temp.supply",k);
end;
l=GetIGlobalVar("temp.supplyLimit",0); -- лимит заправок (где-то нужно обьявить в начале сценария)
if k>=l then
RunScript("noSupply",2000);
DisplayTrace("Снабжение войск приостановлено");
--Suicide(); --нельзя отключать, скрипт должен продолжать параллельно работать с noSupply
end;
end;

Для специальных задач, удачную идею подсказал наш легендарный simeo, спасибо ему; если по какому-то событию нужно ограничить игрока в одной или нескольких возможностях автотехники, тогда по сценарному условию нужно запустить вот такой скрипт:

выборочное отключение возможностей автотехники

function noMine()
local b,s;
for b=501,510 do -- инженерки, первый и последний
s=GetUnitState(b);
if s==41 then -- индикатор
Cmd(9,b);
DisplayTrace("Мин нет, но вы держитесь! (с)");
end;
end;
end;

весь список индикаторов(нужный подставить в скрипт выше)

-- 41 минировать
-- 40 строить ежи
-- 39 строить мост
-- 35 ремонтировать здания
-- 19 рыть траншеи
-- 18 строить колючую проволоку
-----------------------------
-- 36 ремонтировать юниты
-- 31 разминировать
--------------------------------
-- 38 пополнять солдат
-- 37 пополнять боезапас
-- 32 двигаться

Хорошего ГЗМ!

+3

18

Как показывает практика, полезны не только сложные кубики (хе-хе.. выдаем желаемое за действительное :) ), но и решение простых, казалось бы задач. Например вот таких:

Нужно контролировать, чтобы события состоялось в нескольких разных функциях, после подтверждения - сюжет развивается дальше:

Атаки

function checkAttack()
local a,b,c;
c=4;-- сколько атак
a=0;
for b=1,c do
a=a+GetIGlobalVar("temp.Attack"..b,0);
if a==c then
ObjectiveChanged(3,1); -- третье задание выполнено
Suicide();
end;
end;
end;
------------ устанавливаем флаги рядом с запуском скрипта на атаку (соотв. флаг рядом с соотв. ранскрипт) --------------
SetIGlobalVar("temp.Attack1",1);
SetIGlobalVar("temp.Attack2",1);
SetIGlobalVar("temp.Attack3",1);
SetIGlobalVar("temp.Attack4",1);
--и т.д.

Скриптовая группа должна побывать в каждой из нескольких зон:

Разведка

function check()
local a,b,c;
c=3;-- сколько всего зон с названием ReconZone1, ReconZone2, ...
a=0;
for b=1,c do
if (GetNScriptUnitsInArea(104,"ReconZone"..b)>0)then -- скриптовый номер юнита(группы) который должен побывать везде
SetIGlobalVar("ReconZone"..b,1);
end;
a=a+GetIGlobalVar("ReconZone"..b,0);
end;
if a==c then
ObjectiveChanged(3,1); -- третье задание выполнено
Suicide();
end;
end;

Колонна бтр с пехотой, при обнаружении противника пехота высаживается и группа продолжает атаковать в направлении цели:

Бтр с пехотой

function infantry()
local k,b,e,r,xU,yU,x1,y1;
x1,y1=GetScriptAreaParams("target"); -- цель атаки
k=0;
r=1700; -- настраиваемая дальность радара
for b=2001,2005 do -- номера танспортеров (первый и последний)
xU,yU=GetObjCoord(b);
k=k+xU;
e=GetNUnitsInCircle(0,xU,yU,r);
if e>0 then
r=r+100;
Cmd(5,b,xU,yU);
QCmd(3,b,x1,y1);
Cmd(3,b+100,x1,y1); -- номер пехоты (+100 от транспорта)
end;
end;
if k<0 then
Suicide();-- самоликвидация при всех убитых транспортах
end;
end;

К сожалению, даже наши почетные картоделы не желают применять циклические проверки в своих скриптах, разворачивая эпические стены кода... помню раньше даже мерились между собою кто больше строк кода выгнал, (если по 10 песо за строку как мне платит штаб тогда ладно, а если бесплатно?) ... это же не эстетично :) ... например вот такая функция, заменит 350 строк (!) скрипта в новой карте уважаемого VautourII:

x_corps_leyte.lua

function Dzot()
local a,b;
local c=0;
for b=0,41 do
a=GetIGlobalVar("temp.dzot"..b,0);
c=c+a;
if a~=1 then
if (GetNUnitsInArea(0,"dzot"..b)>0)then
SetIGlobalVar("temp.dzot"..b,1);
LandReinforcement(b);
end;
end;
end;
if c==42 then
Suicide();
end;
end;

... и запускайте пожалуйста функции с корректным временем, не 0:
RunScript("Dzot",3000);

из нового и интересного (идея маэстро simeo) предлагаю кубик , который поможет определить, из личной артиллерии игрока, крупный или специальный калибр (по РАЗМЕРУ боекомплекта), затем установит лимит на это орудие кратный бк и отследит его расход, выдаст наказание:

учет снарядов

function setupAmmo()
local a,b,m;
for b=601,620 do
a=GetNAmmo(b);
if a<17 then --  определение имб по бк
m=a*2; -- установка комплектов бк у имб = два
SetIGlobalVar("temp.AmmoMax"..b,m);
SetIGlobalVar("temp.Ammo"..b,a);
--DisplayTrace("%g b %g Max",b,m);
end;
end;
RunScript("countAmmo",2000);
Suicide();
end;
function countAmmo()
local a,aa,b,m,mm,s;
for b=601,620 do
mm=GetIGlobalVar("temp.AmmoMax"..b,0);
if mm>0 then
s=GetUnitState(b);
if s>0 then
a=GetNAmmo(b);
aa=GetIGlobalVar("temp.Ammo"..b,0);
SetIGlobalVar("temp.Ammo"..b,a);
d=aa-a;
if d>0 then
m=mm-d;
SetIGlobalVar("temp.AmmoMax"..b,m);
if m<=0 then
DisplayTrace("орудие номер %g закончились кондиционные боеприпасы! Прекратить стрельбу!",b);
Cmd(9,b);
--ChangePlayer(b,2); -- наказание вариант 1
DamageObject(b,50);-- наказание вариант 2
SetIGlobalVar("temp.AmmoMax"..b,1);-- наказание вариант 2
end;
end;
end;
end;
end;
end;
function Init()
DisplayTrace("ws7 test battle");
RunScript("setupAmmo",3000);
end;

и в заключении сегодняшнего выпуска- подарок картоделам :)

Динамическое управление камерой

function Camera()
local x,y=GetObjCoord(777); -- скрипт номер на который должна установится камера при запуске функции
SetIGlobalVar("temp.x",x/1.41);
SetIGlobalVar("temp.y",y/1.41);
x=GetIGlobalVar("temp.x",0);
y=GetIGlobalVar("temp.y",0);
local command=("SetCamera("..x..","..y..")");
AskClient(command);
Suicide();
end;

синтаксис команды AskClient (“SetCamera(x, y)”) изначально убогий, потому как переменные в нее подставить было нельзя, да еще их нужно пересчитывать от координат... но оказывается и это поправимо :)

Хорошего ГЗМ!

+6

19

Модернизации (спасибо уважаемому simeo за ТЗ) подвергся кубик учета топлива, теперь он умеет:

1.различать юниты (в том числе и личные) по типам, устанавливать им соответствующие типу показатели расхода и емкости бака, задавать в начале миссии уровень топлива рандомом (на скрине в диапазоне 40-60% от емкости-настраивается)
http://sh.uploads.ru/t/I5phF.jpg

2.устанавливать емкость цистерн у заправщиков
http://sh.uploads.ru/t/jpZEc.jpg

3.заправлять танки циклично, в течении времени, на определенное кол-во топлива (50 ед./цикл например), до максимума их баков или до опустошения собственных цистерн
http://sd.uploads.ru/t/qB6yk.jpg

4.заправлять сами заправщики из стационарных емкостей (без ограничения количества их), с проверкой существования этих емкостей
http://sg.uploads.ru/t/KpeGP.jpg

5.запрещать заправщикам снабжать юниты снарядами
http://s9.uploads.ru/t/lqh9J.jpg

6.перемещать камеру на машину у которой закончилось топливо, машина отцепляет орудие при этом
http://s9.uploads.ru/t/OxGjN.jpg

7....и высаживает пехоту
http://sg.uploads.ru/t/ItXRS.jpg

8.информировать о наличии топлива и номере машины/заправщика через выделение юнита

9.легко настраиваться, под различный масштаб карт, игровой сценарий и т.п.

Топливо, модернизированный кубик

function setupFuel()
local a,b,c,d,f,z;
local t={"arm_medium","spg_assault","spg_antitank","arm_heavy","arm_super","spg_super"};
local rB={1150,1500,1500,2000,3000,6000};-- полный бак соотв. типу
local rN={2,3,3,4,5,6};-- нормы расхода соотв. типу
local rZ={10000,5000};-- полная цистерна заправщиков 201,202
local tF={};
for b=1,6 do
tF[b]=GetNUnitsOfType(t[b],0);
end;
for a=101,107 do -- скриптовые номера машин (начальный, конечный)
ChangePlayer(a,3);--нейтралам - это важно!
    for b=1,6 do
    z=tF[b]-GetNUnitsOfType(t[b],0);
    if z>0 then
    SetIGlobalVar("temp.rN"..a,rN[b]);
    SetIGlobalVar("temp.rB"..a,rB[b]);
    break;
    end;
    end;
ChangePlayer(a,0);
end;
for d=201,202 do -- скриптовый номер заправщика
f=d-200;--должно получатся 1,2,... из номера заправщика
c=rZ[f];
SetIGlobalVar("temp.fuel"..d,c);
SetIGlobalVar("temp.rB"..d,c);
end;
RunScript("startFuel",2000);
Suicide();
end;
function startFuel()
local a,b,c,f,x,y;
for a=101,107 do -- скриптовые номера машин (начальный, конечный)
c=GetIGlobalVar("temp.rB"..a,1000);-- 1000=полный бак типам ВНЕ списка
f=c*(40+RandomInt(20))/100; -- первоначальная отметка топливомера машин(рандом 40-60% бака)
SetIGlobalVar("temp.fuel"..a,f);
b=GetIGlobalVar("temp.fuel"..a,0);
DisplayTrace("машина %g : топливомер отметка %g/%g",a,b,c);
x,y=GetObjCoord(a);
SetIGlobalVar("temp.coord.x"..a,x);
SetIGlobalVar("temp.coord.y"..a,y);
end;
RunScript("Fuel",10000);
RunScript("FuelZ",60000);
RunScript("SelectedUnits",30000); -- инфо-блок
Suicide();
end;
function Fuel()
local a,b,c,d,f,m,s,t,x,y,bb,bz,rZ,xx,yy,xt,yt;
for a=101,107 do -- скриптовые номера машин
    if (GetNUnitsInScriptGroup(a)>0) then
    xx=GetIGlobalVar("temp.coord.x"..a,0);
    yy=GetIGlobalVar("temp.coord.y"..a,0);
    x,y=GetObjCoord(a);
    SetIGlobalVar("temp.coord.x"..a,x);
    SetIGlobalVar("temp.coord.y"..a,y);
    for d=201,202 do -- скриптовый номер заправщика
    s=GetUnitState(d);
    xt,yt=GetObjCoord(d);
    t=((x-xt)*(x-xt)+(y-yt)*(y-yt));
    if (t>100000) then -- расстояние для заправки
    bb=GetIGlobalVar("temp.fuel"..a,0);
    c=GetIGlobalVar("temp.rN"..a,1);-- 1=расход типам ВНЕ списка
    b=bb+0.4-((x-xx)*(x-xx)+(y-yy)*(y-yy))*c/10000;-- 10000=расход к масштабу карты
        if (b>0) then
        SetIGlobalVar("temp.fuel"..a,b);
        else
        SetIGlobalVar("temp.fuel"..a,0);
        Cmd(9,a);
        QCmd(32,a,x,y);
        QCmd(5,a,x,y);
        ChangePlayer(a,2);-- наказание N1=неактивный юнит
        Camera(x,y);
        DisplayTrace("машина %g : закончилось топливо",a);
        end;
    else
    if s==1 then
    f=GetIGlobalVar("temp.fuel"..d,0);
    bb=GetIGlobalVar("temp.fuel"..a,0);
    rZ=GetIGlobalVar("temp.rB"..a,1000);-- 1000=полный бак типам ВНЕ списка
    m=rZ-bb;
    if m>=50 and f>=50 then-- здесь и ниже, 50=заправка за цикл
    SetIGlobalVar("temp.fuel"..a,bb+50);
    bz=GetIGlobalVar("temp.fuel"..d,0)-50;
    SetIGlobalVar("temp.fuel"..d,bz);
    ChangePlayer(a,0);
    DisplayTrace("машина %g заправлена на 50 ед. топлива",a);
    else
        if f>=m and m>0 then--требуйте долива пива после отстоя пены!
        SetIGlobalVar("temp.fuel"..a,bb+m);
        bz=GetIGlobalVar("temp.fuel"..d,0)-m;
        SetIGlobalVar("temp.fuel"..d,bz);
        ChangePlayer(a,0);
        DisplayTrace("машина %g полностью заправлена",a);
        else
        if m>=1 and f>=1 then--требуйте долива пива после отстоя пены!
        SetIGlobalVar("temp.fuel"..a,bb+1);
        bz=GetIGlobalVar("temp.fuel"..d,0)-1;
        SetIGlobalVar("temp.fuel"..d,bz);
        ChangePlayer(a,0);
        DisplayTrace("машина %g заправлена на 1 ед. топлива",a);
        end;
        end;
    end;
    end;
    end;
    if s==37 then
    Cmd(9,d);
    AskClient("HighlightControl(20027)");
    end;
    end;
end;
end;
end;
function FuelZ()
local a,d,t,s,h,x,y,rZ,xt,yt;
for a=201,202 do -- скриптовый номер заправщика
    s=GetUnitState(a);
    if s==1 then
    for d=301,301 do -- скриптовый номер стационарного резервуара
    h=GetObjectHPs(d);
    if h>0 then
    x,y=GetObjCoord(a);
    xt,yt=GetObjCoord(d);
    t=((x-xt)*(x-xt)+(y-yt)*(y-yt));
    if (t<100000) then -- расстояние для заправки
    rZ=GetIGlobalVar("temp.rB"..a,0);
    SetIGlobalVar("temp.fuel"..a,rZ);
    DisplayTrace("заправщик %g полностью заправлен",a);
    end;
    end;
    end;
    end;
end;
end;
function SelectedUnits()
AskClient("GetSelectedUnits()");
end;
function GetSelectedUnitsFeedBack(n)
local b,c;
if (n >= 101 and n<=107) then --скриптовые номера машин (начальный, конечный)
b=GetIGlobalVar("temp.fuel"..n,0);
c=GetIGlobalVar("temp.rB"..n,1000);-- 1000=полный бак типам ВНЕ списка
DisplayTrace("машина %g : топливомер отметка %g/%g",n,b,c);
end;
if (n >= 201 and n<=202) then-- скриптовый номер заправщика
b=GetIGlobalVar("temp.fuel"..n,0);
c=GetIGlobalVar("temp.rB"..n,0);
DisplayTrace("заправщик %g : цистерна отметка %g/%g",n,b,c);
end;
end;
function Camera(x,y)
SetIGlobalVar("temp.CameraX",x/1.41);
SetIGlobalVar("temp.CameraY",y/1.41);
x=GetIGlobalVar("temp.CameraX",0);
y=GetIGlobalVar("temp.CameraY",0);
local command=("SetCamera("..x..","..y..")");
AskClient(command);
end;
function Init()
DisplayTrace("ws7 test");
RunScript("setupFuel",2000);
end;

Хорошего ГЗМ!

+10

20

WS7 написал(а):

кубик учета топлива

Даже не верится что такое возможно в этой игре. :-) Будем теперь очень ждать карт с таким скриптом, испытать его в деле.  :cool:
А что за зелёные цифры в консоли? (это обратная связь от функции AskClient("GetSelectedUnits()")-определяет скрипт номер выделенного юнита/ов. WS7)

0

21

Леонид88 написал(а):

Даже не верится что такое возможно в этой игре.

И правильно что коснулись этого ) Топливо и ограничение в снарядах даст что то новое старичку Блицу )
https://zen.yandex.ru/nAk2t3857/ccb5baJ5S/wak7iA/pQgY2erhD/1ivRwW/Uw9pChjP/lOLSFdH0/nTovnVSb/yKq0Uy/ZuNjMbr/BdQ9z_Y/O6njH/axC4mf7/0gjLQin/T8SkP6/06KJydl7/IEQJPBIE/0JAdg/0UyMGvmkOG5/tZof95L/g52lx/EQqbHV/vcq0fUP/gh2Rid/kQzXZdUj_Y/VLilIKpZ/NXX/4Fc1jNX/SrGNKlbyHz/9Oa0/o9N9yLy5Qa5/sr3/FjCeDLPCtKl/TJU/9yWXnaWS6PPMsy#DSD
https://zen.yandex.ru/media/id/5c73cf75 … 323c1f2c3d
А то понимаешь ли привыкли к рогу изобилия обстреливать каждый холмик и наматывать км ))) В реалии порой танки и другую технику  приходилось бросить , так как не было топлива и боезапаса ....
Да и учить приходилось жестко и в ограничениях ;)
Интересные и не обычные фото военной техники  том II

0

22

WS7 написал(а):

Колонна бтр с пехотой, при обнаружении противника пехота высаживается и группа продолжает атаковать в направлении цели:

Да полезная штука! Я себе в Б2 тоже на эту тему алгоритм разработал, подсмотрев схему в скриптах разработчиков.
Только есть проблема - маршрут. Если до цели бтр идет по прямой то всё складно выходит. Но вот лично я любитель выстраивать "красивые" колонны, которые как в жизни, движутся по дорогам. Значит маршрут извилистый. Получается завидев врага, БТР пехоту сбросил, скрипт её подхватил, изменил построение на атакующее и отправил в конечную точку (в Вашем примере 'target'). Но, отправил по прямой, а бтр пошел заданным маршрутом. И их пути разошлись  :D

А иначе никак... Разве что "обклеить" весь маршрут скриптовыми зонами и запускать какую-то сложную систему проверки местонахождения сквада, чтобы отправить его по оставшимся точкам маршрута. Но это в теории. А на практике такое выписать ...  %-)

0

23

С десантированием с БТРов. Доработать бы скрипт, что бы выжившие пехотинцы снова садились в транспорт, после атаки.

0

24

Как создать скрипт авиаподдержки сопровождения бомбардировщиков и штурмовиков истребителями? И делается ли это в редакторе карт или в файлах "луа"? К примеру: при вызове бомбардировщиков SM.79 одновременно стартовали (по определённому маршруту) истребители MC.200, которые не подчиняются игроку, а имеют статус "союзника". И так каждый раз, получилось бы прикрытие, пока они есть на аэродроме. Это сложно создать на действующей карте? Было бы не плохо иметь бомбардировочную и штурмовую авиацию с прикрытием с обеих сторон - у немцев до 44-го года, у русских с 43-го, ну и т.д.

0

25

vn-229 написал(а):

Как создать скрипт авиаподдержки сопровождения бомбардировщиков и штурмовиков истребителями? И делается ли это в редакторе карт или в файлах "луа"?

Да, это делается только скриптом.
У меня есть, но это не моё, сам где то взял, так что тут напишу, если не понятно, придется разбираться "всем миром", хотя, не сложно кажется.

4)Вылет бомбардировщиков в зону "stancia", с поддержкой истребителей, летящих в ту же зону

function bomb()     
EnableAviation(1, 3);
GiveCommand(19,10000,1,GetScriptAreaParams("stancia"));
DisableAviation(1, 3);
RunScript("aviares",4000);
Suicide();
end;

function aviares()
EnableAviation(1, 1);
GiveCommand(20,10000,1,GetScriptAreaParams("stancia"));
DisableAviation(1, 1);
Suicide();
end;

Штурмовики по аналогии, только цифра, обозначающая тип самолета, будет другая.

0

26

В канун Дня Нашей Победы, хочу поздравить всех картоделов и подарить им полезную  (надеюсь) утилиту-"счетчик скриптовых номеров".

countID

-- v4 ws7
function stop()
local r,a,b;
a=GetIGlobalVar("temp.qf",0);
b=GetIGlobalVar("temp.ql",0); 
for r=a,b do
Cmd(9,r);
end;
end;
function land()
local r,a,b;
a=GetIGlobalVar("temp.qf",0);
b=GetIGlobalVar("temp.ql",0); 
for r=a,b do
LandReinforcement(r);
end;
RunScript("countID",30000); -- чем больше подкреплений, тем больше нужно дать времени до запуска счетчика
Suicide();
end;
function countID()
local b,q,z,o,oo,m,x,y,s,k,e,w,ww;
local t=1;
local h=1;
local u=0;
local j=0;
local er=0;
local a1,a2,a3,a4,a5,a6,a7,a8,a9,d1,d2,d3,d4,d5,d6,d7,d8,d9,x1,y1;
a=GetIGlobalVar("temp.qf",0);
l=GetIGlobalVar("temp.ql",0); 
for q=a,l do
o=GetObjectHPs(q);
s=GetUnitState(q);
if q==o then
SetIGlobalVar("temp.t"..t,q);
t=t+1;
u=u+1;
end;
if s>=0 and q~=o then
SetIGlobalVar("temp.o"..h,q);
h=h+1;
j=j+1;
oo=GetNUnitsInScriptGroup(q);
if oo>1 then -- if oo>=1 then
er=er+1;
SetIGlobalVar("temp.er"..er,q);
end;
end;
if t==10 then
a1=GetIGlobalVar("temp.t1",0);
a2=GetIGlobalVar("temp.t2",0);
a3=GetIGlobalVar("temp.t3",0);
a4=GetIGlobalVar("temp.t4",0);
a5=GetIGlobalVar("temp.t5",0);
a6=GetIGlobalVar("temp.t6",0);
a7=GetIGlobalVar("temp.t7",0);
a8=GetIGlobalVar("temp.t8",0);
a9=GetIGlobalVar("temp.t9",0);
DisplayTrace("юниты - %g - %g - %g - %g - %g - %g - %g - %g - %g - ",a1,a2,a3,a4,a5,a6,a7,a8,a9);
t=1;
end;
if h==10 then
d1=GetIGlobalVar("temp.o1",0);
d2=GetIGlobalVar("temp.o2",0);
d3=GetIGlobalVar("temp.o3",0);
d4=GetIGlobalVar("temp.o4",0);
d5=GetIGlobalVar("temp.o5",0);
d6=GetIGlobalVar("temp.o6",0);
d7=GetIGlobalVar("temp.o7",0);
d8=GetIGlobalVar("temp.o8",0);
d9=GetIGlobalVar("temp.o9",0);
DisplayTrace("обьекты - %g - %g - %g - %g - %g - %g - %g - %g - %g - ",d1,d2,d3,d4,d5,d6,d7,d8,d9);
h=1;
end;
end;
for b=1,t-1 do
a9=GetIGlobalVar("temp.t"..b,0);
DisplayTrace("юниты - %g -",a9);
end;
for m=1,h-1 do
d9=GetIGlobalVar("temp.o"..m,0);
DisplayTrace("обьекты - %g -",d9);
end;
x,y=GetMapSize();
x1=x/1024;
y1=y/1024;
DisplayTrace("карта %g на %g , ID юнитов - %g  последний - %g , ID обьектов - %g  последний - %g ",x1,y1,u,a9,j,d9);
for e=1,er do
w=GetIGlobalVar("temp.er"..e,0);
ww=GetNUnitsInScriptGroup(w);
local xc,yc=GetObjCoord(w);
DisplayTrace(" Внимание! обьектов ID %g более одного ( %g шт) - координата первого %g %g ",w,ww,xc,yc);
end;
RunScript("units",5000);
Suicide();
end;
function units()
local q,o,s,um,ux,hm,hx,a,b,t;
local u=0;
local h=0;
a=GetIGlobalVar("temp.qf",0);
b=GetIGlobalVar("temp.ql",0);
for q=a,b do
o=GetObjectHPs(q);
s=GetUnitState(q);
if q==o then
u=u+1;
SetIGlobalVar("temp.u"..u,q);
if u==1 then
SetIGlobalVar("temp.umin",q);
end;
end;
if s>=0 and q~=o then
h=h+1;
SetIGlobalVar("temp.h"..h,q);
if h==1 then
SetIGlobalVar("temp.hmin",q);
end;
end;
end;
SetIGlobalVar("temp.uс",u);
um=GetIGlobalVar("temp.umin",0);
ux=GetIGlobalVar("temp.u"..u,0);
SetIGlobalVar("temp.hс",h);
hm=GetIGlobalVar("temp.hmin",0);
hx=GetIGlobalVar("temp.h"..h,0);
DisplayTrace("ID юнитов - %g шт первый N %g последний N %g , ID обьектов - %g шт  первый N %g последний N %g ",u,um,ux,h,hm,hx);
KillScript("stop");
t=5000;-- время показа юнитов
SetIGlobalVar("temp.tunits",t);
RunScript("cameraU",t);
--RunScript("cameraJ",5000); -- можно включить показ обьектов, пропустив юниты (выключите строку выше)
Suicide();
end;
function cameraU()
local a,b,q,h,xc,xy,k,l,qf;
h=GetIGlobalVar("temp.uс",0);
k=GetIGlobalVar("temp.k",0);
k=k+1;
SetIGlobalVar("temp.k",k);
qf=GetIGlobalVar("temp.qf",0);
l=GetIGlobalVar("temp.ql",0);
if k<=h+1 then
--for r=qf,l do
--ChangeSelection(r,0);
--end;
--ChangeSelection(q,0); --ddd
for b=k,l do
q=GetIGlobalVar("temp.u"..b,0);
if q>0 then
p=GetPartyOfUnits(q);
ChangeWarFog(p);
ChangeSelection(q,1);
a=GetNUnitsInScriptGroup(q);
xc,yc=GetObjCoord(q);
DisplayTrace(" выбран/ы юнит/ы ID %g - %g шт - координата %g %g ",q,a,xc,yc);
SetIGlobalVar("temp.xc",xc/1.41);
SetIGlobalVar("temp.yc",yc/1.41);
xc=GetIGlobalVar("temp.xc",0);
yc=GetIGlobalVar("temp.yc",0);
local command=("SetCamera("..xc..","..yc..")");
AskClient(command);
deselect(q);
break;
end;
end;
else
RunScript("cameraJ",5000); -- время показа обьектов
Suicide();
end;
end;
function deselect(q)
SetIGlobalVar("temp.select",q);
local tt=GetIGlobalVar("temp.tunits",0)/2;
RunScript("change",tt);
end;
function change()
qq=GetIGlobalVar("temp.select",0);
ChangeSelection(qq,0);
Suicide();
end;
function cameraJ()
local a,b,q,h,xc,xy,kk,l;
h=GetIGlobalVar("temp.hс",0);
kk=GetIGlobalVar("temp.kk",0);
kk=kk+1;
SetIGlobalVar("temp.kk",kk);
l=GetIGlobalVar("temp.ql",0);
if kk<=h then
for b=kk-1,l do
q=GetIGlobalVar("temp.h"..b,0);
end;
for b=kk,l do
q=GetIGlobalVar("temp.h"..b,0);
if q>0 then
--p=GetPartyOfUnits(q);
--ChangeWarFog(p);
--ChangeSelection(q,1); -- не включать если есть терра-обьекты
a=GetNUnitsInScriptGroup(q);
xc,yc=GetObjCoord(q);
SetIGlobalVar("temp.xc",xc/1.41);
SetIGlobalVar("temp.yc",yc/1.41);
xc=GetIGlobalVar("temp.xc",0);
yc=GetIGlobalVar("temp.yc",0);
local command=("SetCamera("..xc..","..yc..")");
AskClient(command);
DisplayTrace(" в центре экрана - обьект/ы ID %g - %g шт - координата %g %g ",q,a,xc,yc);
break;
end;
end;
else
DisplayTrace("конец скрипта, удачного картостроения!");
Suicide();
end;
end;
function Init()
DisplayTrace("ws7 countID");
Password("www.dtf.ru");
God(0,1);
God(1,1);
God(2,1);
DisableAviation (1,-1);
SetIGlobalVar("temp.qf",0); -- здесь задаем диапазон просмотра скрипт номеров, от
SetIGlobalVar("temp.ql",100000); -- и до
RunScript("land",1000);
RunScript("stop",5000);
end;

Это не кубик, в привычном для этой темы понимании, т.е. не часть ваших  скриптов для миссий, это отдельный скрипт, который заменяет родной для ЛЮБОЙ карты и после запуска, посчитает ВСЕ обьявленные скрипт номера, разложит на юниты и обьекты (в том числе и терра-обьекты, окопы, невидимые мины, ежи и т.п.), и покажет их на карте с указанием количества в каждой пачке и координаты.

например, список скрипт-номеров юнитов из новой карты Залив Свиней почетного картодела  VautourII
https://forumupload.ru/uploads/0000/38/bf/1621/t375977.jpg

всего задействовано 449 скрипт номеров юнитов, и три обьекта, обратите внимание на предупреждения выделенные после списка "Внимание обьектов более одного.."
https://forumupload.ru/uploads/0000/38/bf/1621/t815217.jpg

так вот на карте со скриптномером 260 расположено 10 обьектов: два грузовика, два орудия и шесть терра-обьектов... в принципе это рабочая схема (карта загружается), но настоятельно рекомендую картоделам избегать вот таких обьединений, некоторые функции работают некорректно или могут привести к аварийному завершению программы, если их применять к обьектам вместо юнитов, к терра-обьектам. В идеале всем обьектам (да и юнитам) нужно присваивать уникальные ID.

Распространенной проблемой являются "забытые" или случайные присвоения скрипт номеров, например выделяет картодел рамочкой юниты, а в выделение попал терра-обьект, что может приводить к "необьяснимым" вылетам, скрипт миссии должен работать, ошибок в синтаксисе нет, а не работает ... "мистика"... вроде танки с номером 100 все убиты, а функция не срабатывает... а песочек с номером 100 "жив" :) Будьте внимательны.

карта ger_to_stalingrad скрипт номером личных орудий присвоен терра-обьекту рядом
https://forumupload.ru/uploads/0000/38/bf/1621/t136931.jpg

отличный кадр: наглядно показано,что в домах заскриптованы номером "24" восемь сквадов пехоты, обычными методами "искать" такую пехоту дело не быстрое :)
https://forumupload.ru/uploads/0000/38/bf/1621/t807362.jpg
к сожалению, глюк движка не позволяет таким же образом "подсветить" технику стороны "1" - только пехоту, а для юнитов Игрока "подсветка" работает нормально. Не подсвечиваю так же обьекты (они всегда расположены в центре экрана), потому что "выбор" терра-обьекта - гарантированный краш (как и говорилось выше).

в утилиту встроена автоматическая высадка ВСЕХ подкреплений, ведь чтобы их посчитать, они должны быть представлены на игровой карте, но так как сам процесс может быть растянут во времени для больших карт и большого числа подкреплений, внутри скрипта (строка 17) настраивается время запуска счетчика после высадки, по умолчанию 30 секунд, это для небольших карт... для абсолютно точного подсчета, рекомендую перед запуском удалить в редакторе в закладке "подкрепления" все строки (таким образом, все юниты окажутся на карте со старта).

Хорошего ГЗМ! С наступающим Праздником!
Особая благодарность офицеру-топографу Tyrion за помощь в формировании ТЗ.

+9

27

Столкнулся со следующей проблемой. Мне нужно сделать так, чтобы мотопехота двигалась по узкому и извилистому ущелью, высаживаясь при контакте с врагом и далее следуя от одной зоны к другой, т.к. напрямую там не пройти - ИИ непременно застрянет. Собственно, проблема состоит в том, чтобы как-то проверять прохождение той или иной зоны с тем, чтобы при высадке и команде "атаковать" юниты не возвращались к первой точке маршрута. Взяв за основу предоставленный ув. WS7 шаблон, я попробовал написать вот такую функцию:
function Mechinf_1_attack()
local a,c,d,k,e,r,x,y;
k=0;
r=1350;
for a = 2101,2103 do
x,y=GetObjCoord(a);
k=k+x;
c=0;
e=GetNUnitsInCircle(0,x,y,r);
d=GetNScriptUnitsInArea(a,"b"..c);
if e>0 then
Cmd(3,a,GetScriptAreaParams("b"..c));
Cmd(3,a*10,GetScriptAreaParams("b"..c));
end;
end;
if d>0 then
c=c+1;
end;
if k<0 then
Suicide();
end;
end;
Но она не работает так, как надо - пехота и БТРы все равно двигаются к первой точке маршрута...

Отредактировано VautourII (2020-05-22 17:04:35)

0

28

VautourII написал(а):

чтобы мотопехота двигалась по узкому и извилистому ущелью, высаживаясь при контакте с врагом и далее следуя от одной зоны к другой, т.к. напрямую там не пройти

как раз доработал скрипт к Рурскому котлу (карта и ТЗ - Мастер топографии Алекс ) на отход колонны немцев, может быть подойдет такой алгоритм:

- рисуем маршрут - зоны "w0,w1,w2..." сколько хотите.... до развилки w7 в нашем случае (любая)... затем случайно(или как хотите) выбирается маршрут из продолжения - w8,w9,w10 или из другой ветки w18,w19,...w24... любой протяженности... можно больше двух веток...

- скриптуем технику 200-249 (можно пропускать, скрипт определит наличие или отсутствие юнита)
- скриптуем пехоту 250-299 ... любой формации... скрипт задаст ей атакующую форму (если хотите)

технику и сквады скриптуем уникальными номерами!

-важный пункт! - рассаживаем пехоту строго +50 к транспорту, в танк/машину 200 садится пехота 250, 212-262 и т.д. ...

функция маршрута

function run()
local q,qi,zi,d,rw,w,k,e,s,si,a;
k=0;
rw=GetIGlobalVar("temp.randomway",0);
for q=200,299 do -- 200_249 техника 250_299 пехота
local x,y=GetObjCoord(q);
if x>0 then
zi=GetIGlobalVar("temp.zi"..q,0);
local xi,yi=GetScriptAreaParams("w"..zi);
si=GetIGlobalVar("temp.carrier"..q,0);
if si>0 then
local xx,yy=GetObjCoord(q-50);
e=GetNUnitsInCircle(0,xx,yy,2000);-- дальность обнаружения=2000,  врага здесь игрока 0
s=GetUnitState(q-50);
if e>0 and s>0 then
qi=q-50;
Cmd(9,qi);
Cmd(5,qi,xx,yy);
SetIGlobalVar("temp.carrier"..q,0);
a=GetIGlobalVar("temp.zi"..qi,0);
SetIGlobalVar("temp.zi"..q,a);
end;
else
Cmd(0,q,xi,yi);
Cmd(3,q,xi,yi);
end;
d=(x-xi)*(x-xi)+(y-yi)*(y-yi);
if d<120000 then
SetIGlobalVar("temp.zi"..q,zi+1);
if zi==7 then -- указать зону развилки
if rw>0 then
SetIGlobalVar("temp.zi"..q,zi+1); -- продолжаем путь 7-8-9
else
SetIGlobalVar("temp.zi"..q,zi+11); -- изменяем путь 7-18-19
end;
elseif zi==10 or zi==24 then -- последние зоны в маршрутах
Cmd(1007,q);
w=GetIGlobalVar("temp.loose",0)+1;
SetIGlobalVar("temp.loose",w);
DisplayTrace("конечная q= %g  всего сбежало= %g",q,w);
if w>4 then -- счетчик убежавших, наказание
DisplayTrace("Ожидайте расстрела (с)!");
RunScript("loose", 5000);
Suicide();
end;
end;
end;
else
k=k+1;
end;
end;
if k>77 then -- считаем убитых (+ выбывшие), награждение
RunScript("Win",5000);
Suicide();
end;
end;

кстати в функции использована новая команда не отмеченная в наших руководствах - Cmd(1007,ID) - удаление юнита (аналогично DeleteReinforcement), но работает более удобно, еще и в связке Сmd0,Cmd3+QCmd1007 - приехал в нужную точку, сработал Q - юнит удалился, не нужно проверять зоны, подгадывать время и т.п. ... это видимо заготовка для второго блица из сталинградского Аилогика... работает в нашей сборке ГЗМ, как и команда телепорта пехоты в транспорт Cmd(1001,IDinf,IDtruck) на ее основе будет кубик с высадкой танкистов из подбитой машины в произвольном месте (чего стандартными методами сделать было нельзя)...

вернемся к нашему маршруту, для корректного запуска функции нужна еще одна -установочная, в которой мы укажем какой алгоритм выбора варианта маршрута, установим начальные точки следования для юнитов, если колонна сильно растянута и не все начинают с w0, как в нашем случае:

установка маршрута

function setuprun()
local q,i,s;
i=RandomInt(2);
SetIGlobalVar("temp.randomway",i); -- выбор пути отхода
---- этот блок не нужен если все стартуют от зоны w0-----
for q=200,226 do -- только для техники НЕ от зоны w0, пехоте не нужно вообще
s=GetUnitState(q);
if s>0 and q<210 then
SetIGlobalVar("temp.zi"..q,4); -- устанавливаем первую точку пути w4 для 200-209
elseif s>0 and q<222 then
SetIGlobalVar("temp.zi"..q,3); -- w3 210-221
elseif s>0 and q<227 then
SetIGlobalVar("temp.zi"..q,1);--w1 222-226
end;
end;
-------- конец блока -------------------------------------
for q=250,299 do -- для пехоты (номера +50 от транспорта)
s=GetUnitState(q);
if s==3 then
ChangeFormation(q,3); -- пехоте атакующую форму
SetIGlobalVar("temp.carrier"..q,3); -- отметка "в траснпорте"
end;
end;

двигаясь по маршруту, определяем врага в радиусе 2000 от каждого загруженного пехотой юнита, появился враг - высаживаемся и атакуем (с точки высадки и по маршруту колонны, далее в пешем порядке ), по приходу к последним зонам в маршруте можно настроить сценарий, что должно происходить, в нашем примере - если беспрепятственно доехало и исчезло более 4 юнитов или сквадов (w>4), то игрок наказывается поражением, напротив если счетчик отсутствующих на карте юнитов и сквадов превысит 77 (k>77), то игрок победил... что раньше сработает... легко перенастроить под ваши задачи!

Хорошего ГЗМ!

+5

29

WS7 написал(а):

... работает в нашей сборке ГЗМ, как и команда телепорта пехоты в транспорт Cmd(1001,IDinf,IDtruck) на ее основе будет кубик с высадкой танкистов из подбитой машины в произвольном месте (чего стандартными методами сделать было нельзя

Телепортация в транспорт – очень полезная команда!
Когда я переносил в свой мод (а это движок ванильного Б2), немецкие миссии из аддона «Великие битвы: Высадка в Нормандии», при адаптации под Б2 скриптов, столкнулся с такой проблемой: в движке «Нормандии» реализована возможность высадки подкреплений в любой точке карты, а в Б2 такого нет – только в точках подкреплений (Reinforcement Point). И была там миссия на тему «выступления» Михаэля Виттмана 13.06.44 г. По сценарию той миссии, в определенный момент, Тигр Виттмана подбивают (гусеница), экипаж покидает танк и его надо провести в безопасное место … Суть проблемы – когда приходит время «повреждения», танк может находится в ЛЮБОЙ точке карты. Возить экипаж в танке с самого начала миссии – не вариант, т.к. танк принадлежит играющему и он просто может взять и высадить «непонятных солдатиков», тем самым нарушив сценарий …
Сделал так:
- на краю карты разместил точку подкреплений, принадлежащую нейтральной стороне;
- когда по сценарию танк повреждается, он становится нейтральным;
- в тот же момент, от нейтрального игрока, приходит подкреп (тот самый экипаж). Играющий его на краю карты не видит, т.к. нейтральная сторона не подсвечивается. И по приходу, этот экипаж сразу телепортируется в танк;
- и сразу после телепортации, скрипт дает танку команду сбросить экипаж (в качестве точки высадки – координаты самого танка );
- после высадки, экипаж передается игроку.
Весь процесс занимает пару секунд. А в миссии играющий просто видит, как экипаж покинул вышедший из строя танк и перешел под его командование. И это может произойти в любой точке карты

Отредактировано Brummbar (2020-05-22 23:31:20)

+1

30

Трудно переоценить любимую многими картоделами команду RandomInt(n) - генератор псевдослучайных чисел, ведь действительно многие интересные игровые ситуации, развития событий, ветвления сюжета - зависят от функционирования этой строки скрипта... тем обиднее нашим сетевым бойцам, что  рандом не работает у трех из четырех игроков (за наводку и ТЗ спасибо товарищу Barmaley185)

Сейчас мы попытаемся исправить такое досадное недоразумение, заменив процедуру собственным кубиком случайных чисел

генератор случайных подкреплений для мультиплеера

-- переменные--
trg=99; -- скриптномер всем! юнит-триггерам
zone=16; -- всего триггер зон у четырех сторон "z1","z2",...,"z16"
wave=6; -- всего волн подкреплений
rand=4; -- вариантов подкреплений (максимум 20)
--01-20;21-40;41-60;61-80 = подкрепы сторон соотв, след.волна+100 = 101-120;121-140;141-160;161-180 и т.д.
temp=20;-- базовое время между подкреплениями, минут
rat=4;-- максимально доп. время (определится случайно каждому игроку и добавится к базовому),!!!должно быть МЕНЬШЕ базового!!!, минут
--используемые глобалки для певдорандома (!!!не занимать основным скриптом!!!)--
--"temp.irandom"--
--"temp.wrandom"--
--"temp.rrandom0"-"temp.rrandom4"--
--"temp.trandom"--
--"temp.random0"-"temp.random9999"--
-- псевдорандом для мультиплеера --
function landrandom()
local a,b,bb,r,w,wm,p,pp,pl,t,tt;
wm=wave*4;
w=GetIGlobalVar("temp.wrandom",0);
for a=0,3 do
pl=IsPlayerPresent(a);
if pl>0 then
b=w+a+1;
p=GetIGlobalVar("temp.random"..b,0);
r=p+a*20+w*25;
SetIGlobalVar("temp.rrandom"..a,r);
bb=b+wm;
pp=GetIGlobalVar("temp.random"..bb,0);
tt=pp/rand*rat;
SetIGlobalVar("temp.trandom",tt);
t=GetIGlobalVar("temp.trandom",0)*60000+1000;
RunScript("landreinf"..a,t);
end;
end;
w=w+4;
SetIGlobalVar("temp.wrandom",w);
if w==wm then
DisplayTrace(" последнее подкрепление ");
Suicide();
end;
end;
function landreinf0()
local r=GetIGlobalVar("temp.rrandom0",0);
LandReinforcement(r);
Suicide();
end;
function landreinf1()
local r=GetIGlobalVar("temp.rrandom1",0);
LandReinforcement(r);
Suicide();
end;
function landreinf2()
local r=GetIGlobalVar("temp.rrandom2",0);
LandReinforcement(r);
Suicide();
end;
function landreinf3()
local r=GetIGlobalVar("temp.rrandom3",0);
LandReinforcement(r);
Suicide();
end;
function erandom()
local b,bm,n,n2,t,x,i,p,z,k;
x=2;
for z=1,zone do
k=GetNScriptUnitsInArea(trg,"z"..z);
if k>0 then
x=x*z;
end;
end;
n=math_sqrt(x);
n2=0;
SetIGlobalVar("temp.irandom",n);
bm=wave*4*2;
for b=1,bm do
i=GetIGlobalVar("temp.irandom",0);
n=(n-i);
p=n*rand+1;
SetIGlobalVar("temp.random"..b,p);
n=n*10;
SetIGlobalVar("temp.irandom",n);
n2=n2+i;
if n==0 then
n=math_sqrt(n2);
SetIGlobalVar("temp.irandom",n);
end;
end;
t=temp*60000;
RunScript("landrandom",t);
Suicide();
end;
-- математика --
function math_mod(x)
if(x >= 0) then
    return x;
else
    return -1*x;
end;
end;
function math_sqrt(x)
if(x <= 0) then
    return 0;
end;
local y_ = 1;
local y = 0.5*(y_+(x/y_));
while(math_mod(y-y_) > 0.001) do
    y_ = y;
    y = 0.5*(y_+(x/y_));
end;
return y;
end;
-- псевдорандом для мультиплеера --
function Init()
RunScript("erandom",5000);
end;

Кубик легко настраивается количеством волн подкреплений (неограниченно), вариантами подкреплений каждому игроку (от 2 до 20), базовым временем между подкреплениями и случайной (каждому отдельно) добавкой минут к времени между подкреплениями (если 0 - то игроки получат подкрепления одновременно через базовое время)...

Работает это просто: скриптуются одним номером четыре юнита (по одному каждому игроку) так называемые юнит-триггеры (ваши сетевые офицеры, кони и т.п.), которые в начале игры должны занять зону из нескольких доступных (z1,z2,....), расположение триггеров  в зонах и создаст "зерно" или seed как это принято в игрострое, для последующих вычислений бесконечного ряда псевдослучайных чисел.

Рекомендуется сразу размещать триггеры в триггер-зоне, чтобы избежать доп-проверок,... перемещая триггеры - игроки создают новую последовательность на всю игру, т.е. после срабатывания функции - весь необходимый ряд для этой сессии будет создан и триггеры больше не будут нужны (отправьте их в атаку) :)

Скрипт работает на любое количество зон, т.е. вариантов бесконечных рядов, но наверное будет достаточно по четыре зоны на каждого игрока (z1-z16) что дает нам 4*4*4*4=256 вариантов ... 5 зон на игрока дадут уже 625 вариантов и т.д.... надеюсь понятно, что если триггеры у всех четырех игроков расположены одинаково в двух играх, то и подкрепления они получат те же самые и через точно такие же временные интервалы....

При тестировании сценария для шести волн из четырех доступных подкреплений ( всего 6144 единиц выбора) функция выдала прекрасную сходимость в пределах 0,35% от математического ожидания (вариант 3 был чуть чаще чем другие)
https://forumupload.ru/uploads/0000/38/bf/1621/t293976.jpg

Варианты подкреплений скриптуются по след. формуле 01-20; 21-40; 41-60; 61-80 = подкрепы сторон соотв, след.волна+100 = 101-120;121-140;141-160;161-180 и т.д.

В итоге у вас получится что-то вроде этого:
https://forumupload.ru/uploads/0000/38/bf/1621/t627364.jpg

За помощь в математике отдельное спасибо греческому товарищу Герону и его другу :)офицеру форума dima32rus

Хорошего ГЗМ в мультиплеере!

+2


Вы здесь » Союз | Union » Картостроение и скриптовка » Как перестать бояться и начать скриптить