*UPD: Рабочий вариант в комментариях внизу.
Дисклеймер: я только начал изучать скриптовый язык SQF. Если у вас есть способ сделать это проще и эффектней, прошу поделитесь мыслями/кодом. Это не пример рабочего кода, это описание проблемы, с которой я столкнулся.
Цель: написать скрипт, который будет отслеживать передвижение игрока по карте и спавнить цивилов на заранее обозначенных позициях в поселениях, как только игрок приблизится к поселению на 250 метров, а также удалять этих цивилов, когда игрок от поселения на 250 метров отойдёт.
Первое, что приходит в голову - создать в каждом поселении по триггеру и повесить код с респавном цивилов на них. В чём проблема: на карте около 40 поселений - это 40 триггеров, каждый из которых обновляется раз в 0.5 секунд. Это слишком затратно.
Что я хочу, так это создать триггер и прикрепить его к игроку, чтобы триггер перемещался вместе с ним. А в каждом поселении мы разместим объекты типа LOGIC (почему именно их, напишу дальше). Как только игрок приблизится к объекту LOGIC на 250 метров - триггер сработает (сработает условие: объект LOGIC внутри триггера) и сработает наш код по спавну цивилов.
Код:
// Создаём триггер, настраиваем размер, прикрепляем триггер к игроку
tr_playerOne = createTrigger ["EmptyDetector", position player];
tr_playerOne attachTo [player, [0,0,0],"Pelvis"];
tr_playerOne setTriggerArea [250, 250, 0, false, 1000];
// Почему LOGIC? У нас на выбор есть: "EAST", "WEST", "GUER", "CIV", "LOGIC", "ANY", "ANYPLAYER",
// "STATIC", "VEHICLE", "GROUP", "LEADER", "MEMBER".
// Из всего этого мы можем использовать только LOGIC,
// ведь всё остальное и так будет присутствовать на карте и ошибочно триггерить наш тригер.
tr_playerOne setTriggerActivation ["LOGIC", "PRESENT", true];
// при активации вызываем функцию спавнющую цивилов, а при деактивации - удаляющую заспавненных цивилов.
tr_playerOne setTriggerStatements ["this", "[thisList, 'one'] call respawnCivReadyFnc", "['one'] call deleteCivFnc"];
// сюда добавятся активные цивилы (которые были заспавненны, но ещё не были удалены)
activeCivArrayOne = [];
// это массивы с цивилами. Каждый массив содержит цивилов из одного конкретного поселения.
civArray_1 = [
// "название модельки, позиция, азимут на который ориентирован цив (его direction)"
["1", [883.64,2749.38,0], 337]
];
civArray_2 = [
["2", [879.059,2739.4,0], 20]
];
respawnCivReadyFnc = {
// список наших LOGIC-ов, которые попадают в триггер; название триггера (в нашем случае просто 'one')
params ["_pointsList", "_trigger"];
// для всех LOGIC-ов попавших в триггер:
{
// узнаём название LOGIC-а, попавшего в триггер. Все LOGIC-и имеют названия типа: point_*номер*
_pointStr = format ["%1", _x];
// узнаём номер поинта
_pointNum = parseNumber (_pointStr trim ['point_', 1]);
// вызываем след. функцию передавая ей номер поинта и название триггера
[_pointNum, 'one'] call respawnCivCompileFnc;
} forEach _pointsList;
};
respawnCivCompileFnc = {
params ["_civArrayNum", "_trigger"];
_code = "";
// вызываем функцию спавнющую цивилов, передавая ей в аргументы массив с цивилами определённого поинта и имя триггера
// все массивы с цивилами имеют названия типа: civArray_*номер*. civArray_1 будет принадлежать point_1, civArray_2 - point_2 и т.д.
if (_trigger == "one") then {
_code = compile (format ["
[civArray_%1, 'one'] call respawnCivFnc;
", _civArrayNum]);
};
call _code;
};
respawnCivFnc = {
params ["_civArray", "_trigger"];
// спавним цивов из переданного массива и заносим их в массив activeCivArrayOne
//(это нужно, чтобы знать каких именно цивов нужно удалить, когда произойдет деактивация триггера)
{
_gr = createGroup civilian;
_civ = _gr createUnit [
format ["C_CUPAFRCIV_Civ_%1_01", ((_civArray select _forEachIndex) select 0)],
((_civArray select _forEachIndex) select 1),
[],
0,
"FORM"
];
_civ setFormDir ((_civArray select _forEachIndex) select 2);
if (_trigger == 'one') then {
activeCivArrayOne pushBack _civ;
};
} forEach _civArray;
};
// При деактивации триггера (когда игрок отходит на 250 метров от LOGIC-объекта) - удаляем всех цивов из массива activeCivArrayOne
deleteCivFnc = {
params ["_trigger"];
if (_trigger == 'one') then {
{
deleteVehicle _x;
} forEach activeCivArrayOne;
// после удаления обнуляем массив активных цивов
activeCivArrayOne = [];
};
};
Это работает. Но есть проблема: иногда случается такая ситуация, когда один LOGIC-объект расположен слишком близко к другому (это случается когда поселения находятся рядом друг с другом). В таком случае наш триггер перекрывается полем действия сразу двух LOGIC-объектов, или другими словами, наш триггер триггерится сразу на два LOGIC-а.
Что происходит в таком случае? Триггер срабатывает на один из них, а на другой - нет. Точнее он срабатывает на оба. Но условие активации (респавн цивов) происходит только один раз - для одного LOGIC-а. Другой же LOGIC просто игнорируется. И даже больше. Если был активирован левый (на картинке) LOGIC, а правый был проигнорирован, то даже если игрок сместится вправо - на территорию правого LOGIC-а, полностью уйдя с территории левого - то ничего не произойдёт. Точнее не произойдет спавна цивов на правом LOGIC-е, произойдёт лишь удаление цивов с территории левого. Что же делать? Ну, мне пришла идея - вместо одного триггера, висячего на игроке, сделать два триггера, висящих на игроке. И тут главный вопрос: как заставить эти два триггера работать обособленно друг от друга. Чтобы первый триггер стриггерился на левый LOGIC, а второй - на правый? Я много вариантов перебрал и не нашёл ни одного рабочего. Точнее, я нашел один вариант, но его сложно считать рабочим. Код:
// Итак, что изменилось?
tr_playerOne = createTrigger ["EmptyDetector", position player];
tr_playerOne attachTo [player, [0,0,0],"Pelvis"];
tr_playerOne setTriggerArea [250, 250, 0, false, 1000];
tr_playerOne setTriggerActivation ["LOGIC", "PRESENT", true];
tr_playerOne setTriggerStatements ["this", "[thisList, 'one'] call respawnCivReadyFnc", "['one'] call deleteCivFnc"];
// теперь у нас два триггера
tr_playerTwo = createTrigger ["EmptyDetector", position player];
tr_playerTwo attachTo [player, [0,0,0],"Pelvis"];
tr_playerTwo setTriggerArea [250, 250, 0, false, 1000];
// Единственный "рабочий" способ, который я нашёл, - задать второму триггеру что-то другое, чем LOGIC.
// Например WEST (любой юнит стороны синих).
tr_playerTwo setTriggerActivation ["WEST", "PRESENT", true];
tr_playerTwo setTriggerStatements ["this", "[thisList, 'two'] call respawnCivReadyFnc", "['two'] call deleteCivFnc"];
activeCivArrayOne = [];
// У нас теперь два активных массива - каждый для своего триггера.
activeCivArrayTwo = [];
civArray_1 = [
["1", [883.64,2749.38,0], 337]
];
civArray_2 = [
["2", [879.059,2739.4,0], 20]
];
respawnCivReadyFnc = {
params ["_pointsList", "_trigger"];
_pointStr = format ["%1", _pointsList select 0];
_pointNum = parseNumber (_pointStr trim ['point_', 1]);
// теперь мы проверяем, какой триггер вызвал функцию и передаём имя этого триггера дальше
// (чтобы знать, в какой массив заносить активных цивов)
if (_trigger == "one") then {
[_pointNum, 'one'] call respawnCivCompileFnc;
};
if (_trigger == "two") then {
[_pointNum, 'two'] call respawnCivCompileFnc;
};
};
respawnCivCompileFnc = {
params ["_civArrayNum", "_trigger"];
_code = "";
// тут тоже передаём имя массива
if (_trigger == "one") then {
_code = compile (format ["
[civArray_%1, 'one'] call respawnCivFnc;
", _civArrayNum]);
};
if (_trigger == "two") then {
_code = compile (format ["
[civArray_%1, 'two'] call respawnCivFnc;
", _civArrayNum]);
};
call _code;
};
respawnCivFnc = {
params ["_civArray", "_trigger"];
{
_gr = createGroup civilian;
_civ = _gr createUnit [
format ["C_CUPAFRCIV_Civ_%1_01", ((_civArray select _forEachIndex) select 0)],
((_civArray select _forEachIndex) select 1),
[],
0,
"FORM"
];
_civ setFormDir ((_civArray select _forEachIndex) select 2);
// после спавна цива, заносим его в массив активных цивов, в зависимости от имени триггера
if (_trigger == 'one') then {
activeCivArrayOne pushBack _civ;
};
if (_trigger == 'two') then {
activeCivArrayTwo pushBack _civ;
};
} forEach _civArray;
};
deleteCivFnc = {
params ["_trigger"];
// при удалении также проверяем имя триггера, чтобы знать из какого активного массива удалять цивов при деактивации триггера.
if (_trigger == 'one') then {
{
deleteVehicle _x;
} forEach activeCivArrayOne;
activeCivArrayOne = [];
};
if (_trigger == 'two') then {
{
deleteVehicle _x;
} forEach activeCivArrayTwo;
activeCivArrayTwo = [];
};
};
С одной стороны это работает, но с другой - полностью лишает нас возможности использовать WEST (синих) юнитов на карте, ведь на них триггер также будет ошибочно срабатывать. Возможно есть смысл заменить WEST на CIV. Но я надеюсь, что найдётся знаток, который подскажет как сделать это всё "по уму". Мне кажется, что решение кроется в настройке условия триггера:
tr_playerOne setTriggerStatements ["ВОЗМОЖНО РЕШЕНИЕ КРОЕТСЯ ЗДЕСЬ?", "[thisList, 'one'] call respawnCivReadyFnc", "['one'] call deleteCivFnc"];
Я много чего перепробовал, клепал различные переменные, пытался запоминать "активный триггер" и т.п. Но ничего не помогло. Подскажите, ребята, как сделать респавн цивов, на заданных позициях, при подходе к поселению на ~200-250 метров, с возможным перекрытием этих областей (поселений) друг-другом, а также удаление заспавленных цивов при отходе игрока от поселения. И всё это, используя 1-2 триггера, прикреплённых к игроку.