Всякие записи. Выложу тут, может кому-то интересно будет, и за одним нужным людям ответы на конкретные вопросы ))
Всякие записи Прочитать все совместимые с оружием магазины.
Казалось бы проще простого:
Код:
_getMagazines = {
getArray (configFile >> "CfgWeapons" >> _this >> "magazines")
}
В первом приближении это так, но возможно это не очень надежно, и вот почему.
Представим, что нам нужно узнать все совместмые с оружием магазины; но это и магазины подствольников и других muzzles. Следующая функция учитывает этот момент:
Код:
_getAnyMagazines = { private ["_weapon", "_mags"];
_weapon = configFile >> "CfgWeapons" >> _this; // точка входа в класс нашего ствола
_mags = [];
{ // для всех muzzles нашего ствола
_mags = _mags + getArray (
// если очередной (обычно единственный) muzzle -- это this, то читаем магазины у себя,
// иначе -- у подкласса с указанным muzzle classname.
( if(_x == "this")then{ _weapon }else{ _weapon >> _x } ) >> "magazines"
)
} foreach getArray (_weapon >> "muzzles");
_mags
};
Ок, с этим разобрались, но все-же, почему про первый способ сказано, что он не надежен ? :-)
Представим, что некто выпустил мега-популярный аддон бластера:
Код:
class Blaster : AK74 {
... // поскипано
muzzles[] = {"RealBlastMuzzle", "SubBlastMuzzle"}; class RealBlastMuzzle : AK74 {
... // поскипано
magazines[] = {"BlasterMag", "SuperBlasterMag", "GiperBlasterMag"};
};
class SubBlastMuzzle : GrenadeLauncher {
... // поскипано
magazines[] = {"NuclearMag", "SuperNuclearMag", "GiperNuclearMag"};
};
};
Заметим, что Blaster не переопределяет магазины наследуемые от AK74, но (!) это ему и не нужно, так как это делается в RealBlastMuzzle.
Можно поспорить, что это не самый умный стиль, но это корректный конфиг! Ведь игра _всегда_ читает magazines (вообще все свойства) "исходя из показаний" свойства muzzles.
Наверное не стоит говорить какой набор магазинов покажет вызов _getMagazines.
Получается, что мы всегда должны читать магазины вторым способом? Я думаю да, ведь если так поступает движок игры, то почему мы должны делать это иначе.
Еще серьезнее дело обстоит при чтении свойств задаваемых в подклассах modes.
В доброй половине случаев значение этих свойств полученных напрямую из класса оружия будет ложным, так как оно наследуется от rifle, а реальное значение находится в классах перечисленных в modes.
Чтобы сделать всю эту мороку тривиальной, написал библиотечку для работы с наиболее (на мой взгляд) часто потребными вещами.
Работа со слотами
Иногда мне бывает надо работать со слотами.
Например тогда, когда хочу корректно добавлять юниту оружие.
Чтение слотов имеет некоторую специфику, поэтому об этом моменте подробнее.
У оружия и магазинов тип используемого слота задается свойством type.
В офп для этого использовались свойства weaponType и magazineType, но поскольку в арма появился новый раздел конфига CfgMagazines, все описания магазинов вынесены в этот раздел.
Чтение свойства "type" оружия:
Код:
getNumber ( configFile >> "CfgWeapons" >> _ourWeapon >> "type" )
Чтение свойства "type" магазина:
Код:
getNumber ( configFile >> "CfgMagazines" >> _ourWeapon >> "type" )
Возможные типы слотов имеет смысл определять (в конфигах аддонов) следующим образом:
Код:
#define WeaponNoSlot 0 // Dummy weapons
#define WeaponSlotPrimary 1 // Primary weapon
#define WeaponSlotHandGun 2 // Handgun slot
#define WeaponSlotSecondary 4 // Secondary weapon (launcher)
#define WeaponSlotHandGunMag 16 // Handgun magazines (8x)(or grenades for M203/GP-25)
#define WeaponSlotMag 256 // Magazine slots (12x / 8x for medics)
#define WeaponSlotGoggle 4096 // Goggle slot (2x)
#define WeaponHardMounted 65536 //
// На основании "рекомендаций"
) :
http://community.bistudio.com/wiki/C...ce#weaponSlots // Примечание: Значения слотов армы несколько отличаются от таковых в офп: гранатомет переехал на два разряда вправо (16 -> 4), а пистолетный магазин на один (32 -> 16).
То, что все эти определения являются степенями двойки, говорит о том, что возможна комбинация (по или) этих значений, например:
WeaponSlotSecondary | WeaponSlotGoggle | WeaponSlotHandGun
И действительно, этот способ используется для описания комбинации слотов у солдата (любого vehicle, в общем случае).
Свойство класса описывающее слоты называется weaponSlots. Такой пример:
Код:
weaponSlots = WeaponSlotPrimary + 4 * WeaponSlotMag + WeaponSlotGoggle + WeaponSlotHandGun + 4 * WeaponSlotHandGunMag;
задает по одному слоту под винтовку, бинокль и пистолет, четыре под обычные магазины, и еще четыре под магазины пистолетные. Ну да вы это и так знаете, все так-же как в офп ))
Пример чтения этого параметра:
Код:
_weaponSlots = getNumber ( configFile >> "CfgVehicles" >> (typeOf _ourMan) >> "weaponSlots" );
Кстати, действительно ли мы можем задать солдату любое количество любых слотов? Конечно нет.
Например: "WeaponSlotMag * 16 == 4096", а это уже значение WeaponSlotGoggle.
Для наглядного представления сей битовой механики вот табличка:
Код:
-------------------------------------------------------------------------------------
бит диапазон
макро представление поля битов на возможных
десятичное бинарное поле значений
--------------------- ---------- ----------------- ----------------- ---- ---------
WeaponSlotPrimary 1 0000000000000001 ................x 1 0..1
WeaponSlotHandGun 2 0000000000000010 ...............x. 1 0..1
WeaponSlotSecondary 4 0000000000000100 .............xx.. 2 0..3
WeaponSlotHandGunMag 16 0000000000010000 .........xxxx.... 4 0..15
WeaponSlotMag 256 0000000100000000 .....xxxx........ 4 0..15
WeaponSlotGoggle 4096 0001000000000000 ..xxx............ 3 0..7
WeaponHardMounted 65536 10000000000000000 x................ 1 0..1
-------------------------------------------------------------------------------------
Таким нехитрым способом в одно число упаковывается несколько разных значений.
Примечание: Сам способ описания слотов накладывает определенные ограничения, но ограничения движка игры могут быть жестче -- так например WeaponSlotSecondary, это все-таки не четыре, а два возможных значения: он либо есть, либо его нет. В функах, что приведены ниже, я использую битовые маски такими, как они нарисованы здесь, хотя возможно стоит "заузить" некоторые поля.
Теперь чтобы получить количество, например, WeaponSlotMag'ов нужно выделить его битовое поле по маске и сдвинуть результат вправо на восемь разрядов:
(WeaponSlots & 0F00h) >> 8
или для наглядности с маской в бинарном виде:
(WeaponSlots & 00000111100000000b) >> 8
Можно действовать и наоборот -- вначале сдвинуть поле вправо, а затем обнулить старшие биты поразрядным "и"
(WeaponSlots >> 8) & 1111b
Следующая условная схемка демонстрирует почему это так:
Код:
------------------------------------------------------------------------------
.....xxxx........ // вначале: точки -- неинтересные биты, иксы -- интересные
>>>>>>>> // на восемь разрядов вправо
00000000.....xxxx // теперь: интересные биты на своем месте, но биты неинтересные все портят (для нас это мусор)
00000000000001111 // выделить по маске
0000000000000xxxx // в итоге: только интересные биты
------------------------------------------------------------------------------
Например есть определение:
Код:
weaponSlots = 1 + 4 + 12*256 + 2*4096 + 2 + 8*16
итого: 11399, или в двоичном виде: 10110010000111b.
Чтобы узнать количество магазинных слотов (WeaponSlotMag), сдвигаем weaponSlots вправо на 8 разрядов
Код:
10110010000111b
>> 8
равно: 00000000101100b
и выделяем поразрядным and:
00000000101100b
&& 00000000001111b
равно: 00000000001100b
Получаем 1100, или в десятичной записи 12. (вы можете повторить все эти манипуляции на калькуляторе)
Теперь дело осталось за малым -- поскольку в скриптах нет поразрядных операций, надо нарисовать это в обычной арифметике.
Правый сдвиг будем изображать "целочисленным" (отбросим все что справа от запятой с помощью floor) делением на степень двойки.
Дальше нам надо получить несколько (N) младших разрядов результата, отбросив (обнулив) все остальные, это можно сделать взяв остаток от деления на 2_в_степени_N.
Теперь можно легко написать нужные функции:
Код:
_readSlots = { getNumber ( configFile >> "CfgVehicles" >> _this >> "weaponSlots" ) };
_slotPrimary = { (_this call _readSlots) % 2 };
_slotHandGun = { floor((_this call _readSlots) / 2 ) % 2 };
_slotSecondary = { floor((_this call _readSlots) / 4 ) % 4 };
_slotHandGunMag = { floor((_this call _readSlots) / 16 ) % 16 };
_slotMag = { floor((_this call _readSlots) / 256 ) % 16 };
_slotGoggle = { floor((_this call _readSlots) / 4096 ) % 8 };
_sardMounted = { floor((_this call _readSlots) / 65536 ) % 2 };
Все эти штуки есть в той же либе.
Несколько причин для того чтобы читать конфиги скриптом.
Читать из конфига можно не только "родные" классы и свойства, но и любые определенные нами.
Например:
Код:
//config.cpp или description.ext
class CfgMyMegaClass {
my_super_prop = "some value";
};
//sqf
// чтение из config.cpp
hint ( getText ( configFile >> "CfgMyMegaClass" >> "my_super_prop" ) )
// чтение из description.ext
hint ( getText ( missionConfigFile >> "CfgMyMegaClass" >> "my_super_prop" ) )
Возможность создавать такие userdefined классы и свойства в конфигах сложно переоценить.
Взять средний аддон техники, необходимые для его работы данные, это часто:
1) координаты источников звука и классы звуков для альтернативной озвучки аддона,
2) дополнительные наборы текстур, например, сменных номеров, других знаков,
3) названия дополнительных анимаций экипажа,
и наверное многое другое.
При этом, раньше все эти данные приходилось держать прямо в скриптах, и порой вынуждало писать одни и те же вещи для каждого аддона (мода) в отдельности.
Теперь есть возможность однажды написать систему озвучки или установки номеров, в общем случае -- систему, выполняющую типичные действия, достаточно универсальную, чтобы работать с любым аддоном.
Все нужные для работы данные она будет брать из конфига, что позволит не лазить лишный раз в скрипты, а также уменьшит их количество.
Можно использовать свои свойства прямо в классе аддона, но если не хочется "портить" или боимся за совместимость с будущими версиями игры, то можно располагать все такие штуки в отдельных классах:
Код:
class CfgMySuperModName
{
class MySuperM60
{
signTextures[] = {"sign1.paa","sign2.paa","sign3.paa", etc };
}
}
И читать свойства не из CfgVehicles, а из класса CfgMySuperModName.
В целом, это дает огромный простор для творчества и приложения фантазии.
ЗЫ.
Здорово, что дебажить это можно в description.ext, а потом заменив missionConfigFile на configFile засунуть в config.cpp.
Определить положение юнита Причем, не просто текущую анимацию, это как раз проще простого, ведь теперь есть комадна animationState, а именно положение -- стоя, на колене, или лежа, с пистолетом, и т.д.
Это может быть полезным во многих случаях, например, это будет удобным всегда когда мы расширяем возможности анимаций.
Первое, что приходит на ум -- это простой и очевидный способ:
Код:
animationState _unit in check_anim_list_1
где check_anim_list_1 -- здровенный список всех анимаций относящихся к одному состоянию.
Ряд таких проверок позволит выяснить действительное положение юнита.
Однако суммарный размер массивов при таком подходе, будет весьма значительным, так что этот способ вряд-ли кажется разумным. Еще один резон отказаться от этого подхода -- это его плохая совместимость с возможными модификациями конфига анимаций.
Действительно хороший способ сделать это -- это прочитать параметр upDegree в интересующем нас "CfgMovesMC >> Actions >> SomeClass". Этот параметр непосредственно используется аи игры для определения того в какое положение следует перевести юнита, и, надо думать, более корректного способа нет:
Код:
_readActionsProp = {
private "_moves";
_moves = getText ( configFile >> "CfgVehicles" >> arg(0) >> "moves" );
configFile >> _moves >> "Actions" >> getText (
configFile >> _moves >> "States" >> arg(1) >> "actions" ) >> arg(2)
};
getNumber ( [typeOf _unit, animationState _unit, "upDegree"] call _readActionsProp )
Мы делаем это примерно так-же как это делает сама игра (точнее ее аи) -- вначале узнаем анимации для нашего юнита (_unitMoves), затем используя название текущей анимации (animationState _unit) выясняем название "класса-категории" (текущая анимация ссылается на него при помощи "actions") к которой относится текущая (_unitMoves >> "States" >> "текущая анимация" >> "actions"). И уже перейдя в "класс-категорию" мы читаем ее "upDegree". Немного путанно, но если вы знакомы со структурой конфига анимаций, это не будет слишком сложным для вас.
Возможные значения upDegree определены в конфиге:
Код:
enum {
MANPOSNOWEAPON = 12,
MANPOSHANDGUNSTAND = 9,
MANPOSCROUCH = 6,
MANPOSHANDGUNLYING = 5,
MANPOSHANDGUNCROUCH = 7,
MANPOSBINOCSTAND = 14,
MANPOSDEAD = 0,
MANPOSCOMBAT = 8,
MANPOSBINOCLYING = 2,
MANPOSSTAND = 10,
MANPOSSWIMMING = 11,
MANPOSWEAPON = 1,
MANPOSLYING = 4,
MANPOSLYINGNOWEAPON = 3,
MANPOSBINOC = 13
}
Теперь, при желании мы можем сделать switch для нужных нам значений:
Код:
switch ( _upDegree ) do
{
case 12: { hint "MANPOSNOWEAPON" };
case 09: { hint "MANPOSHANDGUNSTAND" };
case 06: { hint "MANPOSCROUCH" };
case 05: { hint "MANPOSHANDGUNLYING" };
case 07: { hint "MANPOSHANDGUNCROUCH" };
case 14: { hint "MANPOSBINOCSTAND" };
case 00: { hint "MANPOSDEAD" };
case 08: { hint "MANPOSCOMBAT" };
case 02: { hint "MANPOSBINOCLYING" };
case 10: { hint "MANPOSSTAND" };
case 11: { hint "MANPOSSWIMMING" };
case 01: { hint "MANPOSWEAPON" };
case 04: { hint "MANPOSLYING" };
case 03: { hint "MANPOSLYINGNOWEAPON" };
case 13: { hint "MANPOSBINOC" };
}
Ок, если мы теперь собираемся использовать полученный _upDegree для того чтобы воспроизвести какой либо эффект -- звук, партикл, или анимацию, не стоит-ли еще немного подумать?
Всегда имеет смысл задать себе вопрос -- зачем мне это надо? Может то, что я пытаюсь решить некоторым образом, проще, быстрее и интереснее решается как-то иначе?
Вначале говорилось о возможности добавления в классы своих (userdefined) свойств.
Что если нужный эффект определять при помощи такого userdefined property, непосредственно в "классе-категории"? Затем наш скрипт мог-бы читать его аналогично тому как он читает сейчас upDegree, и основываясь на прочитанном играл-бы нужный эффект.
Вот фрагменты конфига анимаций взятые наугад:
Код:
class RifleKneelActions : RifleStandActions {
........ // поскипано, оставлено немного для ориентировки
Lying = "AmovPpneMstpSrasWrflDnon";
Stand = "AmovPknlMstpSrasWrflDnon";
Crouch = "AmovPknlMstpSrasWrflDnon";
FireNotPossible = "AmovPknlMstpSrasWrflDnon";
strokeFist = "";
strokeGun = "AmovPknlMstpSrasWrflDnon";
upDegree = "ManPosCrouch"; // тот самый upDegree
// а это добавлено нами:
_superPuperUserdefinedProperty = "some effect for Kneel";
}; class RifleStandActions : RifleLowStandActions {
............. // поскипано
upDegree = "ManPosCombat";
_superPuperUserdefinedProperty = "some effect for Stand";
};
//
// и так для всех нужных Actions-классов, и не забываем про наследование свойств.
//
Теперь можно читать "_superPuperUserdefinedProperty" так-же как мы читали "upDegree", и без лишних проверок выполнять нужные действия.
Это хороший способ определять самые разные штуки реализуемые скриптованием: рукопашные удары, метание ножей, атаки бензопилами и т.д. -- все они будут воспроизводить нужные эффекты для нужных положений бота, без дополнительных выяснений этого положения.