Jump to content
IndeedPete

Concept Question - User-Maintained Shop Iventory

Recommended Posts

G'day!

 

As few of you might know, I have created a shop system as part of my earlier work (see fancy logos in my sig). It was able to give players access to pre-indexed third party assets in my playable content without making these assets mandatory. That means if I indexed/priced (whitelisted), for example, Toadie's weapon packs in my shop configs, a player who runs Toadie's packs can see and buy them in the shop, whereas a player who doesn't can't see them but is still able to play my content. This optional use of mod packs is something that should be integrated into the game at the core, but there have been a few discussion threads about that particular idea around.

 

Anyway, as I said, third party assets need to be indexed manually as I haven't found a way to dynamically price them. I just can't find enough config values in CfgWeapons which would allow me to create some sort of function that dynamically prices all entries and sorts them into the matching categories. Hence manual efforts are required to maintain my shop configs and it gets more and more with a growing number of user requests ("Can you please add pack x and pack y as well? These are the best!"). Now, maintaining these configs is annoying and exhausting as it's basically just copy-pasting classnames around with a bit of googling for calibers, types (carbine vs. long rifle etc.). All in all, I hate doing that and I'm a lazy piece of shit, but I love the overall benefit it grants when done properly.

 

This consideration led me to the idea of user-maintained configs, where every user can add his favourite guns (or items, uniforms, vests, etc.) to the shop without breaking anything or having to rip-open and re-pack addon PBOs. Before I'm starting to query you fellow readers, I want to show you a snippet of a config and the shop's way to read and interprete it. Let's take a look at this weapon config which is being #included into the missionConfigFile:

class ShopWeapons // Main config class for weapons displayed in the "Weapons" section of the shop.
{	
	class Rifles // Rifle category, can be used to filter for rifles vs. carbines vs. rifles with GL, etc.
	{
		displayName = "Rifles"; // Category's display name.
		
		class hlc_rifle_ak47 // Mod weapon.
		{
			origin = "Russia"; // Additional info, irrelevant.
			price = 3000; // Shop price.
		};
		class arifle_TRG21_F // Vanilla weapon.
		{
			origin = "Israel";
			price = 3500;
		};
		class IP_arifle_TRG21_FBlack: arifle_TRG21_F {}; // Mod weapon which inherits price and origin attributes from vanilla TRG21.
		
// ...

As you can see in the comments, it's a quite simple, multi-layered config structure, following the principle "Shop Category" >> "Item Category" >> "Item" >> "Item Attribute". And now, let's take a look at a snippet of the selector function which evaluates these configs and creates dynamic arrays suited to the user's mod configuration and availability:

_weaponCategories = "((if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1) && {count _x > 0}" configClasses (missionConfigFile >> "ShopWeapons"); // Get category class paths from ShopWeapons config.
IP_WeaponCategories = _weaponCategories call _getClasses; // Get category class names from class paths.
IP_WeaponFilters = ["All"] + IP_WeaponCategories; // Set up filters based on categories.
IP_AvailableWeapons = []; // Initialise weapons array.
{
	private "_weapons";
	_weapons = [];
	_weaponClasspaths = "((isNumber(_x >> 'price')) && {(if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1})" configClasses (missionConfigFile >> "ShopWeapons" >> _x); // Get "raw" data / class paths from ShopWeapons config.
	_weaponClasses = _weaponClasspaths call _getClasses; // Get weapon class names from class paths.
	
	{	// Evaluate which of the weapon classes are available in the currently running Arma 3 instance.
		if ((isNumber(configFile >> "CfgWeapons" >> _x >> "scope")) && {getNumber(configFile >> "CfgWeapons" >> _x >> "scope") == 2}) then {
			_weapons pushBack _x;
		};
	} forEach _weaponClasses;
	
	IP_AvailableWeapons pushBack _weapons; // Add selected sub-set of available weapons to the amount of weapons shown in the shop, sorted by category.
} forEach IP_WeaponCategories;

The global vars filled in this snippet are then fed to the shop once it's opened by the player. In MP, these values can also be determined by the server and then published to the clients in order to maintain consistency between players with different mod configurations. This approach allows customisation without enforcing the use of mod packages.

 

Finally, I'm getting to the point of all this. How can I give users the possibility to extend these configs from above without having to dePBO / re-pack anything?

 

Before I let you jump in with hopefully brilliant ideas, I could think of using the userconfig approach Arma offers. I could think of structures like this:

class ShopWeapons
{	
	class Rifles
	{
		displayName = "Rifles";
		
		class arifle_TRG21_F
		{
			origin = "Israel";
			price = 3500;
		};
		class IP_arifle_TRG21_FBlack: arifle_TRG21_F {};
		// ...
		
		#include "userconfig\shop_extensions\weapons\rifles.hpp"

However, that would probably require file patching to be enabled? It could also cause CTDs when these files are missing or users misconfigured something? Is there a possibility to make a conditional #include? What about the steam workshop? As far as I know, it cannot access user configs (yet).

 

You see, these are the questions I'm hoping to discuss in this thread. Please share your ideas in general, I appreciate every input I can get on this matter.

 

Thanks for your attention! ;)

Share this post


Link to post
Share on other sites

Sorry for the data dump....
 

 

third party assets need to be indexed manually as I haven't found a way to dynamically price them.

How about off of the weapon stats like shown in the arsenal?

Heres a few pulled from a quick browse through the weapon configs
weapons and accessories
    swayDecaySpeed = 2;
    "reloadtime"
    opticsZoomMax = 1.25;
    +modes[] = {"Single","FullAuto","single_medium_optics1","single_medium_optics2","fullauto_medium"};
    +muzzles[] = {"this","EGLM"};
    minRangeProbab = 0.3;
    minRange = 1;
    midRangeProbab = 0.58;
    midRange = 150;
    maxRangeProbab = 0.04;
    maxRange = 500;
    maxRecoilSway = 0.008;
    +weaponslotsInfo mass
    lockAcquire = 1;
    laser = 0;
    irDistance = 0;
    inertia = 0.2;
    htMax = 600; not actually sure what this represents
    fireSpreadAngle = "3.0f";
    fireLightIntensity = 0.2;
    fireLightDuration = 0.05;
    distanceZoomMin = 400;
    distanceZoomMax = 400;
    dispersion = 0.002;
    cursor
    canShootInWater = 0;
    +LinkedItems does it already have pointer,muzzle,cows,bipod and their stats like maxrange etc values that change a weapons behaviour
    +"arifle_TRG21_GL_ACO_pointer_F" >> "single_medium_optics1" >> "StandardSound" >> "SoundTails" >> "TailHouses" >> "volume" - how quiet is it, how quiet is it with a muzzle
    recoil class values

magazines
    initSpeed - automatically checked via configextremes of a weapon via magazines > initSpeed

ammo
    hit - automatically checked via configextremes of a weapon via magazines > ammo > hit

Arsenal uses ["reloadtime","dispersion","maxrange","hit","mass","initSpeed"]

armour
    ["armor","maximumLoad","mass"]

They are just the three arsenal uses but im sure there is more you could pull to base pricing off of.

using BIS_fnc_configExtremes, see BIS_fnc_arsenal for ideas lines 128-134 & 1880 - 1915 for how it does the weapons stats.

see this post for some more info on arsenal and configextremes.
configextremes is a little picky on some of the data it reaches into so also worth a read and could probably create your own.

Query each weapon which has an author of "Bohemia Interactive" and is not dlc, then you have your base extremes that every other weapon can be based off of to work out a pricing algorithm.
 

 

Not sure about your main question though, i have not looked into the changes from filePatching other than reading the changes. As you say i believe workshop only manages PBOs. If it were only for dedicated implementation and your shop was all handled server side i would suggest it wouldnt be a problem, yes its going to crash if the include is not found but tbh if the server admins on his game then this is unlikely to happen. Although i would guess this is ment for anyone so then you have joe blogs needing to know about filePatching and including a blank file for the include etc. TBH i would go the algorithm route and if people want to create their own cost list then they need to un/repack the pbo but hey that just me :)

Share this post


Link to post
Share on other sites

Thanks for your reply!

 

Even if I manage to come up with some sort of mathematical model for automated pricing, there would be certain pitfalls from my perspective:

  • Arma offers different weapons suited for different tasks. Hence the price of the weapon partly relies on the mission profile if we follow common market rules. (Higher demand of a good leads to a higher price.) I could of course track purchases and add them as coefficient to the model. However, I still think mission designers should be able to do the pricing manually.
  • It's a huge hassle, and community authors' configs are sometimes not state of the art. I remember that some weapon packs have totally imbalanced weight values for their guns so players can carry 20 rifles in a backpack. That could easily tamper with the algorithm.
  • Automated categorisation is still a problem. Without looking at the gun or googling real world data, I cannot tell whether it's a carbine, a rifle, a marksmen rifle, an MG, or something completely different. Bohemia's weapons follow a rather strict naming pattern, but community packs often apply different naming conventions.
  • BI's config structure is, well, not ideal to say the least. CfgWeapons contains everything from weapon to item, whereas backpacks are in CfgVehicles, and facewear in CfgGlasses. Without manual indexing it's hard to tell what's what, especially with community content.
  • Blacklisting contents in the shop would be needed as well in this approach to allow designers to customise the offer. And that results in the same problems as whitelisting has.

As userconfig extension problems are hard to counter, I can only think of creating a dedicated shop window ("use at your own risk") which aggregates content not listed in the other categories and assigns them a base price. Mission designers could just deactivate that shop window and only allow whitelisted content. Not pretty, but it might work.

 

Oh, and the offer can be determined server side, but the transactions happen client side. So, the server can grab whitelisted classnames from the configs and broadcast them to the clients, based on the server's mod layout. But the clients still require the original configs in order to browse for prices. Originally, this system was designed to support my SP campaigns, and not necessarily MP environments. Fortuantely, it works just fine in MP, only the customisation possibilites with regards to third party content are limited. But you know the players, they wish for a stand-alone version to integrate into their missions. And one of the first questions will be, "Does it work on dedicated servers?"

Share this post


Link to post
Share on other sites

I remember that some weapon packs have totally imbalanced weight values for their guns so players can carry 20 rifles in a backpack.

Set a minimum weight based on gun type? decide what you think is a perceivable minimum weight for a gun class and if it comes in way under this use your minimum.

 

I cannot tell whether it's a carbine, a rifle, a marksmen rifle, an MG,

configfile >> "CfgWeapons" >> _weapon >> "cursor"

case "arifle": {"AssaultRifle"};

case "bomb": {"BombLauncher"};

case "cannon": {"Cannon"};

case "gl": {"GrenadeLauncher"};

case "laserdesignator": {"LaserDesignator"};

case "mg": {"MachineGun"};

case "missile": {"MissileLauncher"};

case "mortar": {"Mortar"};

case "rocket": {"RocketLauncher"};

case "sgun": {"Shotgun"};

case "throw": {"Throw"};

case "smg": {"SubmachineGun"};

case "srifle": {"SniperRifle"};

I suppose this just leaves what do you consider a carbine and a marksman rifle? The definition of a carbine has changed through out history but to me it is the sight length, which could easily be defined by range distance/probability and initial muzzle velocity compared to its base parent. Same with a marksman weapon, its not quite a sniper rifle but is likely(not always) to have a larger sight length than its assault rifle variant, better accuracy at longer range and possibly equipped with a sight and bipod.

 

CfgWeapons contains everything from weapon to item, whereas backpacks are in CfgVehicles, and facewear in CfgGlasses. Without manual indexing it's hard to tell what's what, especially with community content.

configFile >> "CfgWeapons" >> _item >> "simulation"

case "weapon":

case "binocular": {"Binocular"};

case "nvgoggles": {"NVGoggles"};

case "itemcompass": {"Compass"};

case "itemgps": {"GPS"};

case "itemmap": {"Map"};

case "itemminedetector": {"MineDetector"};

case "itemradio": {"Radio"};

case "itemwatch": {"Watch"};

case "cmlauncher": {_itemCategory = "VehicleWeapon"; "CounterMeasuresLauncher"};

configfile >> "CfgWeapons" >> _item >> "type"

case 1 : primaryWeapon

case 2 : handgunWeapon

case 4 : secondaryWeapon

case 16 : hangun magazine

case 256 : items

case 4096 : binoculars

case 65536 : vehicle weapon

case 131072 : inventory items see "itemInfo" >> "type"

configfile >> "CfgWeapons" >> _item >> "itemInfo" >> "type"

case 101: {"AccessoryMuzzle"};

case 201: {"AccessorySights"};

case 301: {"AccessoryPointer"};

case 302: {"AccessoryBipod"};

case 401: {"FirstAidKit"};

case 605: {_itemCategory = "Equipment"; "Headgear"};

case 619: {"Medikit"};

case 620: {"Toolkit"};

case 621: {"UAVTerminal"};

case 701: {_itemCategory = "Equipment"; "Vest"};

case 801: {_itemCategory = "Equipment"; "Uniform"};

configfile >> "CfgVehicles" >> _item >> "isBackpack" == 1

Im sorry but if an addon does not include this required information for how the game interprets its items then its broken and does not deserve to be handled by your shop.

Your wording has changed from your OP, in your OP you explain things as if its a sealed mission with which you wish people to be able to specify their own cost tables hence the dilemma of where to put user defined configs. Yet in your latest post you refer to mission makers, in which case if it is a shop based addon that people can incorporate then they have access to the description.ext which your scripts/mod/addon can read from and incorporate into its own tables. Im a little lost now, what is it? a full mod, a mission, a script package, an addon script package, a combination of both?

How about using a mission parameter for a sealed mission "Use user cost table for IP Shop" true/false 1/0, then people can play the mission with your calculated/predetermined costs or they can start their server with -filePatching and add a cost table in the userConfigs, set the parameter to true and only then do you incorporate a loaded user config. Instead of a class type set out, use arrays so you can compile the loadFile straight into a variable? This is only going to be done server side, you dont want clients joining each determining their own cost tables.

Description.ext

class Params
{
    class IP_Shop
    {
        title = "Use user cost table for IP Shop";
        values[] = {1,0};
        texts[] = {"True","False"};
        default = 0;
    };
};

userconfig\IP\IP_shopLists.hpp

//ShopLists
[
    //Whitelist
    [
        //type, cost, description
        [ "arifle_TRG21_F", 2000, "Israel" ]
    ]
    //Blacklist
    [
        "arifle_MX_F"
    ]
];
initServer.sqf or where ever your server script initialises after mission start.

if!( isServer ) exitWith {};
private [ "_paramsIndex", "_lists" ];

//Find what param, mission creator could of placed it any where
_cfg = ( missionConfigFile >> "Params" );
for "_i" from 0 to ( count _cfg ) -1 do {
	if ( configName( _cfg select _i ) == "IP_Shop" ) exitWith {
		_paramsIndex = _i;
	};
};

//Has the user asked to import a user cost table
if ( !isNil "_paramsIndex" && { paramsArray select _paramsIndex isEqualTo 1 } ) then {
	//Is the server enabled for loadfile
    if ( isFilePatchingEnabled ) then {
        _lists = call compile loadFile "userConfig\IP\IP_shopLists.hpp";
        if ( _lists == "" ) then {
            "IP_shopLists : user config called for but does not exist or is empty" call BIS_fnc_error;
        };
    }else{
        "IP_shopLists : File patching not enabled - can not access user config" call BIS_fnc_error;
    };
};

//If we have a valid table process it
if ( !isNil "_lists" && { typename _lists isEqualTo typename [] } ) then {
    {
        //Add/Overwrite entry to your preinit defines/calculations
    }forEach ( _lists select 0 ); //Whitelist
    {
        //Remove entry from your preinit defines/calculations
    }forEach ( _lists select [ 1, 1 ] ); //Blacklist
};
Then setup does not matter for either sealed mission, script package or script addon.

If the param has been included and set the server can check whether its allowed to load external content, otherwise warn the user both in RPT and on screen if hosted.

Share this post


Link to post
Share on other sites

Thanks again for your suggestions.

 

I'm still not convinced of an automated pricing algorithm, that might be a feature for the future though. However, automated indexing could be possible, especially when defining base prices for each category. At the end of the day, the difference between a carbine and a rifle is, as you said yourself, a matter of discussion. At least for the Weapons, Magazines, and Items sections, additional automated indexing next to the pre-defined configs should be possible. There are a few more categories: Missions, Personnel, Static Camp Enhancements (pre-defined compositions), Vehicles, and a Stock Market. These are purely a matter of the mission designer though and should not affect the other categories. I've used the "itemInfo" >> "type" already, but the _item >> "simulation" attribute is new to me. Looks quite useful though. That way, I'm avoiding that user config problem and the client-server issues without having to alter my structure too much. Only thing to do is adjusting the getPrice and the shopInit functions. And I can still properly configurate the packs I use myself. There are a few more config attributes I haven't shown here, such as "show" and "merc" (used for automated unit generation with random gear form the shop). But these shouldn't cause too much trouble.

 

To answer your questions: this thing started out as a script suite used in SP campaign environments, with a defined list of items. It eventually grew to support MP as well, and now I'm trying to implement a few more features (such as automated indexing), write a proper documentation, and then drop it as spin-off on the forums. I'm not really interested in turning it into a full-blown addon, it was created to fit my personal needs only but user requests to release it as script package brought me here. :) What I've explained in a bit of a confusing way were perspectives of the two user types, designer and player.

 

That parameter to allow automated indexing is a good idea. I've also planned to implement a parameter to allow/forbid third party content in general.

Share this post


Link to post
Share on other sites

I'm still not convinced of an automated pricing algorithm, [...]

Well, anything else simply sucks.  :ph34r: 

 

At the end of the day, the difference between a carbine and a rifle is, as you said yourself, a matter of discussion.

Okay, so a bottom-up approach is rather difficult, how about a top-down approach instead?

For each faction you first read out a mapping of soldier roles (e.g. rifelman, machine gunner, sniper dude, ..., you probably also would want to consider CfgVehicleClasses to get at special teams such as divers, recon, ..., for extra pricy stuff). Then you have a look a their equippment (now you know: "aha, this is a sniper rifle!", "this one a marksmen thingy", etc.) and classify/price stuff accordingly.

Granted, this only works if factions are properly and fully/extensively configured. And clearly you might miss certain weapons alltogether this way (no luck with mere weapon packs).

But on the bright side this could allow for a good balance in pricing (e.g. across factions) due to the high-level of abstraction here (infering weapon types based on soldier roles).

 

Oh well, just some food for thought...

...have you seen this thread over here already? -_- 

 

Share this post


Link to post
Share on other sites

Well, anything else simply sucks.  :ph34r: 

 

 

Hm, I still think tomorrow's capitalist wants to have full control over his prices. Just to raise them for no reason. :D I think I'm going to implement the base-prices and optional listing of not whitelisted contents, where the latter overwrite the base prices. Short time, lazy developer, you get the picture. At some point, I can just replace my old getPrice() with that fancy algorithm.

 

No, bottom-up like Larrow suggested sounds sufficient. At least for weapons. Magazines are sorted by caliber in my system, even though I haven't found explicit caliber values in the configs yet. Items will be a bit harder. For example, I've set up categories for Headgear - Soft (caps, booniehats, etc), Headgear - Combat (helmets), and Headgear - Special (pilot helmets, etc). Automated categorisation could be based on protection values or so. We'll see.

 

And as you said, your idea does not work for stand-alone weapon packs, which I have in use already. This whole shop system was created with irregular mercenaries in mind. And some people just buy certain weapons because they look nice. How should my algorithm address the looks of a gun, aside from gathering historical data?

 

Yes, I saw that thread. In fact, the shop is doing similar and allows for users to define custom randomised units. Here's an example config of a mercenary rifleman:

class Rifleman
{
	backpack = "";
	baseClasses[] = {"B_Soldier_F","I_Soldier_F","O_Soldier_F"}; // West, Independent, East
	costRate = 0;
	faction = "Mercs";
	gogglesPools[] = {"Goggles"};
	gogglesProbability = 0.5;
	handgunAttachments[] = {{"", "", "optic_MRD", "optic_Yorris"}};
	handgunMagazines = 3;
	handgunPools[] = {"Pistols"};
	headgearPools[] = {"HeadgearSoft","HeadgearCombat"};
	headgearProbability = 0.9;
	isMerc = 1;
	items[] = {"FirstAidKit"};
	linkedItems[] = {"ItemMap","ItemWatch","ItemRadio","ItemCompass", {"", "ItemGPS"}, {"NVGoggles_INDEP", "NVGoggles_OPFOR"}};
	magazines[] = {"HandGrenade","HandGrenade","SmokeShell","SmokeShellOrange","Chemlight_yellow","Chemlight_yellow"};
	primaryAttachments[] = {"", {"", "acc_flashlight", "acc_pointer_IR"}, {"", "optic_Aco", "optic_ACO_grn", "optic_Holosight", "optic_MRCO", "optic_Arco", "optic_Hamr", "optic_NVS", "optic_Nightstalker", "optic_tws"}, ""}; // Muzzle, Rail, Optics, Under Barrel
	primaryMagazines = 10;
	primaryWeaponPools[] = {"Carbines","Rifles"};
	skill = 4;
	uniformPools[] = {"Paramilitary","Military"};
	unitInsignias[] = {"ICE_ION","ICE_BA"};
	secondaryWeapon = "";
	vestPools[] = {"VestsMedium","VestsHeavy"};
	weapons[] = {{"", "Binocular", "Rangefinder"}};
};

Most things are selected from given shop categories. Stuff in (sub-)arrays will be selected randomly if applicable. The result are mixed, irregular looking forces, in this case fitted for a snow/woodland environment:

 

2015-10-29_0000567prd.jpg

Share this post


Link to post
Share on other sites

I've now settled for a combined solution. There are defined base prices for all categories. If an item is manually indexed, the dfined price will be used. If an item is not indexed and automated indexing is enabled, an algorithm will determine an addition which will be added to the base price. Unfortunately, the automated categorisation relies on the thoroughness of the modder. CUP for example defines most of its weapons as rifle, even the six-round grenade launcher, making it hard to distinct what's what. Anyway, here's my progress so far for weapons, magazines, items (misc, vests, headgear, backpacks), and uniforms. It's still pretty rough but it works so far.

 

Snippet Categorisation / Indexing

_weaponCategories = "((if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1) && {count _x > 0}" configClasses (missionConfigFile >> "ShopWeapons");
IP_WeaponCategories = _weaponCategories call _getClasses;
IP_WeaponFilters = ["All"] + IP_WeaponCategories;
IP_AvailableWeapons = [];
{
	private "_weapons";
	_weapons = [];
	_weaponClasspaths = "((isNumber(_x >> 'price')) && {(if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1})" configClasses (missionConfigFile >> "ShopWeapons" >> _x);
	_weaponClasses = _weaponClasspaths call _getClasses;
	
	{
		if ((isNumber(configFile >> "CfgWeapons" >> _x >> "scope")) && {getNumber(configFile >> "CfgWeapons" >> _x >> "scope") == 2}) then {
			_weapons pushBack _x;
		};
	} forEach _weaponClasses;
	
	IP_AvailableWeapons pushBack _weapons;
} forEach IP_WeaponCategories;

if (_autoIndexing) then {
	_weaponClasspaths = "((getText(_x >> 'simulation') == 'weapon') && {isNumber(_x >> 'scope')} && {getNumber(_x >> 'scope') == 2})" configClasses (configFile >> "CfgWeapons");
	_weaponClasses = _weaponClasspaths call _getClasses;
	
	{
		if ((([_x] call BIS_fnc_baseWeapon) == _x) && {([(missionConfigFile >> "ShopWeapons"), _x] call IP_fnc_getConfigCategory) == ""}) then {
			private "_category";
			_category = switch (getText(configFile >> "CfgWeapons" >> _x >> "cursor")) do {
				case "laserdesignator": {"Optics"};
				case "hgun": {"Pistols"};
				case "smg": {"SMG"};
				case "sgun": {"Carbines"};
				case "arifle": {"Rifles"};
				case "mg": {"LMG"};
				case "srifle": {"Marksman"};
				case "missile": {"Launchers"};
				case "rocket": {"Launchers"};
				default {""};
			};
			
			_muzzles = (getArray(configFile >> "CfgWeapons" >> _x >> "muzzles")) - ["this"];
			if (count _muzzles > 0) then {
				_weapon = _x;
				
				{
					if (((getText(configFile >> "CfgWeapons" >> _weapon >> _x >> "cursor")) == "EmptyCursor") && {(getText(configFile >> "CfgWeapons" >> _weapon >> _x >> "cursorAim")) == "gl"}) exitWith {
						_category = "RiflesGL";
					};
				} forEach _muzzles;
			};
			
			_index = IP_WeaponCategories find _category;
			if (_index >= 0) then {
				(IP_AvailableWeapons select _index) pushBack _x;
			};
		};
	} forEach _weaponClasses;
};

IP_AvailableMagazines = [];
_magazines = "((isNumber(_x >> 'price')) && {(if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1})" configClasses (missionConfigFile >> "ShopMagazines");
{
	_class = configName _x;
	if ((isClass(configFile >> "CfgMagazines" >> _class)) && {getNumber(configFile >> "CfgMagazines" >> _class >> "scope") == 2}) then {
		IP_AvailableMagazines pushBack _class;
	};
} forEach _magazines;

if (_autoIndexing) then {
	_allWeapons = ["Throw", "Put"] + (IP_AvailableWeapons call IP_fnc_arrayMerge);
	
	{
		private ["_mags"];
		_weapon = _x;
		_mags = getArray(configFile >> "CfgWeapons" >> _weapon >> "magazines");
		_muzzles = getArray(configFile >> "CfgWeapons" >> _weapon >> "muzzles");
		if (count _muzzles > 1) then {
			{
				_raw append (getArray(configFile >> "CfgWeapons" >> _weapon >> _x >> "magazines"));
			} forEach (_muzzles - ["this"]);
		};
		
		{
			_mag = _x;
			if ({_x == _mag} count IP_AvailableMagazines == 0) then {
				IP_AvailableMagazines pushBack _mag;
			};
		} forEach _mags;
	} forEach _allWeapons;
};

_itemCategories = "((if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1) && {count _x > 0}" configClasses (missionConfigFile >> "ShopItems");
IP_ItemCategories = _itemCategories call _getClasses;
IP_ItemFilters = ["All"] + IP_ItemCategories;
IP_AvailableItems = [];
{
	private "_items";
	_items = [];
	_itemClasspaths = "((isNumber(_x >> 'price')) && {(if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1})" configClasses (missionConfigFile >> "ShopItems" >> _x);
	_itemClasses = _itemClasspaths call _getClasses;
	
	{
		if (((isNumber(configFile >> "CfgWeapons" >> _x >> "scope")) && {getNumber(configFile >> "CfgWeapons" >> _x >> "scope") == 2}) OR {(isNumber(configFile >> "CfgVehicles" >> _x >> "scope")) && {getNumber(configFile >> "CfgVehicles" >> _x >> "scope") == 2}} OR {(isNumber(configFile >> "CfgGlasses" >> _x >> "scope")) && {getNumber(configFile >> "CfgGlasses" >> _x >> "scope") == 2}}) then {
			_items pushBack _x;
		};
	} forEach _itemClasses;
	
	IP_AvailableItems pushBack _items;
} forEach IP_ItemCategories;

if (_autoIndexing) then {
	_itemClasspaths = "((isNumber(_x >> 'scope')) && {getNumber(_x >> 'scope') == 2})" configClasses (configFile >> "CfgWeapons");
	_itemClasspaths = _itemClasspaths + ("((isNumber(_x >> 'isBackpack')) && {getNumber(_x >> 'isBackpack') == 1} && {isNumber(_x >> 'scope')} && {getNumber(_x >> 'scope') == 2})" configClasses (configFile >> "CfgVehicles"));
	_itemClasses = _itemClasspaths call _getClasses;
	
	{
		_typeArr = [_x] call BIS_fnc_itemType;
		_cat = _typeArr select 0;
		_type = _typeArr select 1;
		
		_category = switch (_cat) do {
			case "Item": {
				_res = switch (_type) do {
					case "AccessoryMuzzle": {"AttachmentsMuzzle"};
					case "AccessoryPointer": {"AttachmentsRail"};
					case "AccessorySights": {"AttachmentsOptics"};
					case "AccessoryBipod": {"AttachmentsUnderBarrel"};
					case "Compass": {"Misc"};
					case "FirstAidKit": {"Misc"};
					case "GPS": {"Misc"};
					case "Map": {"Misc"};
					case "Medikit": {"Misc"};
					case "MineDetector": {"Misc"};
					case "NVGoggles": {"Misc"};
					case "Radio": {"Misc"};
					case "Toolkit": {"Misc"};
					case "UAVTerminal": {"Misc"};
					case "Watch": {"Misc"};
					default {""};
				};
				
				_res
			};
			
			case "Equipment": {
				_res = switch (_type) do {
					case "Glasses": {"Goggles"};
					case "Headgear": {
						_armour = getNumber(configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Head" >> "armor");
						_pass = getNumber(configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Head" >> "passThrough");
						
						if ((_armour == 0) OR (_pass == 1)) then {
							"HeadgearSoft"
						} else {
							"HeadgearCombat"
						}
					};
					case "Vest": {
						_bodyArmour = if (isNumber(configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Body" >> "armor")) then {
							(getNumber(configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Body" >> "armor"))
						} else {
							1
						};
						_bodyPass = getNumber(configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Body" >> "passThrough");
						_capacity = getNumber(configFile >> "CfgVehicles" >> (getText(configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "containerClass")) >> "maximumLoad");
						
						if (((_bodyArmour == 0) OR (_bodyPass == 1)) && {_capacity <= 140}) then {
							"VestsLight"
						} else {
							private "_cumulated";
							_cumulated = 0;
							_hitParts = "isNumber(_x >> 'armor')" configClasses (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "HitpointsProtectionInfo");
							
							{
								_cumulated = _cumulated + (getNumber(_x >> "armor"));
							} forEach _hitParts;
							
							if (_cumulated < 80) then {
								"VestsMedium"
							} else {
								"VestsHeavy"
							}
						}
					};
					case "Backpack": {
						_res = if ((isText(configFile >> "CfgVehicles" >> _x >> "backpackSimulation")) && {(getText(configFile >> "CfgVehicles" >> _x >> "backpackSimulation")) == "ParachuteSteerable"}) then {
							"BackpacksParachutes"
						} else {
							_res = if ((getNumber(configFile >> "CfgVehicles" >> _x >> "maximumLoad")) <= 0) then {
								"BackpacksStatics"
							} else {
								"Backpacks"
							};
							
							_res
						};
						
						_res
					};
					default {""};
				};
				
				_res
			};
			
			default {""};
		};
		
		_backpackCheck = if (_category == "Backpacks") then {((_x call BIS_fnc_basicBackpack) == _x)} else {true};
		if (_backpackCheck && {([(missionConfigFile >> "ShopItems"), _x] call IP_fnc_getConfigCategory) == ""}) then {			
			_index = IP_ItemCategories find _category;
			if (_index >= 0) then {
				(IP_AvailableItems select _index) pushBack _x;
			};
		};
	} forEach _itemClasses;
};

_uniformCategories = "((if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1) && {count _x > 0}" configClasses (missionConfigFile >> "ShopUniforms");
IP_UniformCategories = _uniformCategories call _getClasses;
IP_UniformFilters = ["All"] + IP_UniformCategories;
IP_AvailableUniforms = [];
{
	private "_uniforms";
	_uniforms = [];
	_uniformClasspaths = "((isNumber(_x >> 'price')) && {(if (isNumber(_x >> 'show')) then {(getNumber(_x >> 'show'))} else {1}) == 1})" configClasses (missionConfigFile >> "ShopUniforms" >> _x);
	_uniformClasses = _uniformClasspaths call _getClasses;
	
	{
		if (!(_x in _uniformsInPossession) && {isNumber(configFile >> "CfgWeapons" >> _x >> "scope")} && {getNumber(configFile >> "CfgWeapons" >> _x >> "scope") == 2}) then {
			_uniforms pushBack _x;
		};
	} forEach _uniformClasses;
	
	IP_AvailableUniforms pushBack _uniforms;
} forEach IP_UniformCategories;

if (_autoIndexing) then {
	_uniformClasspaths = "((getText(_x >> 'simulation') == 'weapon') && {(getNumber(_x >> 'type') == 131072) OR (getText(_x >> 'type') == '131072')} && {getNumber(_x >> 'itemInfo' >> 'type') == 801} && {isNumber(_x >> 'scope')} && {getNumber(_x >> 'scope') == 2})" configClasses (configFile >> "CfgWeapons");
	_uniformClasses = _uniformClasspaths call _getClasses;
	
	{
		if (([(missionConfigFile >> "ShopUniforms"), _x] call IP_fnc_getConfigCategory) == "") then {			
			_index = IP_UniformCategories find "Military";
			if (_index >= 0) then {
				(IP_AvailableUniforms select _index) pushBack _x;
			};
		};
	} forEach _uniformClasses;
};

 

 

 

Function getPrice

params [
	["_shopConfig", "ShopWeapons", [""]],
	["_class", "", [""]],
	"_category",
	"_price"
];

_category = [(missionConfigFile >> _shopConfig), _class] call IP_fnc_getConfigCategory;
_price = switch (_shopConfig) do {
	case "ShopWeapons": {
		_res = if (_category != "") then {
			(getNumber(missionConfigFile >> _shopConfig >> _category >> _class >> "price"))
		} else {		
			private "_index";
			{
				_category = _x;
				if ({_x == _class} count _category > 0) exitWith {
					_index = _forEachIndex;
				};
			} forEach IP_AvailableWeapons;
			
			if (isNil "_index") exitWith {0};
			
			private "_add";
			_category = IP_WeaponCategories select _index;
			_basePrice = getNumber(missionConfigFile >> _shopConfig >> _category >> "basePrice");
			_magazines = getArray(configFile >> "CfgWeapons" >> _class >> "magazines");
			_add = 0;
			
			if (_category != "Optics") then {
				private "_caliberHitPairs";				
				_caliberHitSets = [];
				
				{
					_ammo = getText(configFile >> "CfgMagazines" >> _x >> "ammo");
					_count = getNumber(configFile >> "CfgMagazines" >> _x >> "count");
					_caliber = getNumber(configFile >> "CfgAmmo" >> _ammo >> "caliber");
					_hit = getNumber(configFile >> "CfgAmmo" >> _ammo >> "hit");
					_hitIndirect = getNumber(configFile >> "CfgAmmo" >> _ammo >> "indirectHit");
					_hitIndirectRange = getNumber(configFile >> "CfgAmmo" >> _ammo >> "indirectHitRange");
					_set = [_count, _caliber, _hit, _hitIndirect, _hitIndirectRange];
					if !(_set in _caliberHitSets) then {
						_caliberHitSets pushBack _set;
						_add = _add + (_count * _caliber) + (_hit * 10) + (_hitIndirect * _hitIndirectRange);
					};
				} forEach _magazines;
			} else {
				if (isArray(configFile >> "CfgWeapons" >> _class >> "visionMode")) then {
					{
						if (_x == "NVG") then {
							_add = _add + 250;
						};
						
						if (_x == "TI") then {
							_add = _add + 500;
						};						
					} forEach (getArray(configFile >> "CfgWeapons" >> _class >> "visionMode"));
				};
			};
			
			(_basePrice + _add)
		};
		
		_res
	};
	
	case "ShopMagazines": {
		_res = if (isNumber(missionConfigFile >> "ShopMagazines" >> _class >> "price")) then {
			(getNumber(missionConfigFile >> "ShopMagazines" >> _class >> "price"))
		} else {
			_ammo = getText(configFile >> "CfgMagazines" >> _class >> "ammo");
			_basePrice = if (getNumber(configFile >> "CfgAmmo" >> _ammo >> "explosive") == 1) then {(getNumber(missionConfigFile >> "ShopMagazines" >> "basePriceExplosive"))} else {(getNumber(missionConfigFile >> "ShopMagazines" >> "basePrice"))};
			_count = getNumber(configFile >> "CfgMagazines" >> _class >> "count");
			_caliber = getNumber(configFile >> "CfgAmmo" >> _ammo >> "caliber");
			_hit = getNumber(configFile >> "CfgAmmo" >> _ammo >> "hit");
			_hitIndirect = getNumber(configFile >> "CfgAmmo" >> _ammo >> "indirectHit");
			_hitIndirectRange = getNumber(configFile >> "CfgAmmo" >> _ammo >> "indirectHitRange");
			(_basePrice + (_count * _caliber) + _hit + (_hitIndirect * _hitIndirectRange))
		};
		
		_res
	};
	
	case "ShopItems": {
		_res = if (isNumber(missionConfigFile >> _shopConfig >> _category >> _class >> "price")) then {
			(getNumber(missionConfigFile >> _shopConfig >> _category >> _class >> "price"))
		} else {
			_cfg = missionConfigFile >> "ShopItems";
			_typeArr = [_class] call BIS_fnc_itemType;
			_cat = _typeArr select 0;
			_type = _typeArr select 1;
			
			_res = switch (_cat) do {
				case "Item": {
					_res = switch (_type) do {
						case "AccessoryMuzzle": {(getNumber(_cfg >> "AttachmentsMuzzle" >> "basePrice"))};
						case "AccessoryPointer": {(getNumber(_cfg >> "AttachmentsRail" >> "basePrice"))};
						case "AccessorySights": {
							private "_add";
							_add = 0;
							_basePrice = getNumber(_cfg >> "AttachmentsOptics" >> "basePrice");
							_sightsCfg = (configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "OpticsModes");
							
							if (isArray _sightsCfg) then {
								{
									_add = _add + (getNumber(_sightsCfg >> _x >> "distanceZoomMax"));
									
									{
										if (_x == "NVG") then {
											_add = _add + 250;
										};
										
										if (_x == "TI") then {
											_add = _add + 500;
										};						
									} forEach (getArray(_sightsCfg >> _x >> "visionMode"));
								} forEach (getArray _sightsCfg);
							};
							
							(_basePrice + _add)
						};
						case "AccessoryBipod": {(getNumber(_cfg >> "AttachmentsUnderBarrel" >> "basePrice"))};
						case "Compass": {(getNumber(_cfg >> "Misc" >> "ItemCompass" >> "price"))};
						case "FirstAidKit": {(getNumber(_cfg >> "Misc" >> "FirstAidKit" >> "price"))};
						case "GPS": {(getNumber(_cfg >> "Misc" >> "ItemGPS" >> "price"))};
						case "Map": {(getNumber(_cfg >> "Misc" >> "ItemMap" >> "price"))};
						case "Medikit": {(getNumber(_cfg >> "Misc" >> "Medikit" >> "price"))};
						case "MineDetector": {(getNumber(_cfg >> "Misc" >> "MineDetector" >> "price"))};
						case "NVGoggles": {(getNumber(_cfg >> "Misc" >> "NVGoggles" >> "price"))};
						case "Radio": {(getNumber(_cfg >> "Misc" >> "ItemRadio" >> "price"))};
						case "Toolkit": {(getNumber(_cfg >> "Misc" >> "Toolkit" >> "price"))};
						case "UAVTerminal": {(getNumber(_cfg >> "Misc" >> "B_UavTerminal" >> "price"))};
						case "Watch": {(getNumber(_cfg >> "Misc" >> "ItemWatch" >> "price"))};
						default {0};
					};
					
					_res
				};
				
				case "Equipment": {
					_res = switch (_type) do {
						case "Glasses": {(getNumber(_cfg >> "Goggles" >> "basePrice"))};
						case "Headgear": {
							_armour = getNumber(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Head" >> "armor");
							_pass = getNumber(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Head" >> "passThrough");
						
							_res = if ((_armour == 0) OR (_pass == 1)) then {
								(getNumber(_cfg >> "HeadgearSoft" >> "basePrice"))
							} else {
								_basePrice = getNumber(_cfg >> "HeadgearCombat" >> "basePrice");
								_add = if (_armour > 4) then {
									((_armour - 4) * 250)
								} else {
									0
								};
								
								(_basePrice + _add)
							};
							
							_res
						};
						case "Vest": {
							_bodyArmour = if (isNumber(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Body" >> "armor")) then {
								(getNumber(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Body" >> "armor"))
							} else {
								1
							};
							_bodyPass = getNumber(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "HitpointsProtectionInfo" >> "Body" >> "passThrough");
							_capacity = getNumber(configFile >> "CfgVehicles" >> (getText(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "containerClass")) >> "maximumLoad");
							_capacityAdd = _capacity * 5;
							
							_res = if (((_bodyArmour == 0) OR (_bodyPass == 1)) && {_capacity <= 140}) then {
								_basePrice = getNumber(_cfg >> "VestsLight" >> "basePrice");
								(_basePrice + _capacityAdd)
							} else {
								private "_cumulated";
								_cumulated = 0;
								_hitParts = "isNumber(_x >> 'armor')" configClasses (configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "HitpointsProtectionInfo");
								
								{
									_cumulated = _cumulated + (getNumber(_x >> "armor"));
								} forEach _hitParts;
								
								_cumulatedAdd = _cumulated * 5;
								_res = if (_cumulated < 80) then {
									_basePrice = getNumber(_cfg >> "VestsMedium" >> "basePrice");
									(_basePrice + _capacityAdd + _cumulatedAdd)									
								} else {
									_basePrice = getNumber(_cfg >> "VestsHeavy" >> "basePrice");
									(_basePrice + _capacityAdd + _cumulatedAdd)
								};
								
								_res
							};
							
							_res
						};
						case "Backpack": {
							_capacity = getNumber(configFile >> "CfgVehicles" >> _class >> "maximumLoad");
							_res = if ((isText(configFile >> "CfgVehicles" >> _class >> "backpackSimulation")) && {(getText(configFile >> "CfgVehicles" >> _class >> "backpackSimulation")) == "ParachuteSteerable"}) then {
								(getNumber(_cfg >> "BackpacksParachutes" >> "basePrice"))
							} else {
								_res = if (_capacity <= 0) then {
									(getNumber(_cfg >> "BackpacksStatics" >> "basePrice"))
								} else {
									_basePrice = getNumber(_cfg >> "Backpacks" >> "basePrice");
									_capacityAdd = _capacity * 5;
									(_basePrice + _capacityAdd)
								};
								
								_res
							};
							
							_res
						};
						default {0};
					};
					
					_res
				};
				
				default {0};
			};
			
			_res
		};
		
		_res
	};
	
	case "ShopUniforms": {
		_res = if (_category != "") then {
			(getNumber(missionConfigFile >> _shopConfig >> _category >> _class >> "price"))
		} else {
			_basePrice = getNumber(missionConfigFile >> _shopConfig >> "basePrice");
			_capacity = getNumber(configFile >> "CfgVehicles" >> (getText(configFile >> "CfgWeapons" >> _class >> "ItemInfo" >> "containerClass")) >> "maximumLoad");
			_capacityAdd = _capacity * 5;
			(_basePrice + _capacityAdd)
		};
		
		_res
	};
	
	default {0};
};

_price

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×