Jump to content
Sign in to follow this  
SicSemperTyrannis

iniDB - Save and Load data to the server or your local computer without databases!

Recommended Posts

It's stored in /@inidb/db/

The first parameter of the write/read function is the file name, just not the entire file name (since it's not needed and can cause exploits).

So,

_fileName = format["PlayerID_%1", getPlayerUID _unit];

Would be like:

/@inidb/db/PlayerID_123456.ini

hm. i figured that.

must be something with my mission code. i kept it really simple just for testing purpose. just want to get the saving to work first. please have a look.

init.sqf:

call compile preProcessFile "\iniDB\init.sqf";

to keep it simple i run this script from a user action:

_unit = _this select 0;

_puid = getPlayerUID _unit;

if(!isServer) exitWith {};

if(_puid == "_SP_PLAYER_" || _puid == "") exitWith {};

// Allow users with multiple profiles to make new profiles and have new lives

// We want to use CRC hashes for the name because some people have spaces, weird characters or some other stuff so it's just better this way.

_profileName = _unit getVariable["profileName", ""];

if(_profileName == "") exitWith {};

_unitFileName = format["%1_%2", _puid, (_profileName call iniDB_CRC32)];

// We will save to the same file, but use different sections for each side

// We don't want cop uniforms/pos/etc saving over to insurgent or civilian sides

// This will mean persistent data will carry over _per occupation_, pretty neat right?

_sectionTitle = format["%1", side _unit];

// Actually save global data

[_unitFileName, _sectionTitle, "pos", position _unit] call iniDB_write;

//[_unitFileName, _sectionTitle, "loadout", ([_unit] call getLoadout)] call iniDB_write;

all i did is comment out the gear saving and change _unit to _this select 0 to make it work with addaction. for some reason there still isn't a file being created in the \db folder. what am i missing?

Share this post


Link to post
Share on other sites
hm. i figured that.

must be something with my mission code. i kept it really simple just for testing purpose. just want to get the saving to work first. please have a look.

init.sqf:

to keep it simple i run this script from a user action:

all i did is comment out the gear saving and change _unit to _this select 0 to make it work with addaction. for some reason there still isn't a file being created in the \db folder. what am i missing?

Try changing

[_unitFileName, _sectionTitle, "pos", position _unit] call iniDB_write;

To

[_unitFileName, _sectionTitle, "pos", position player] call iniDB_write;

I use it on mine and it works perfectly fine server-wide, for everyone. I also noticed you dont have "_this" defined? Atleast not in that code bit.

Share this post


Link to post
Share on other sites

hm. _unit is defined at the top like _unit = _this select 0; referring to the unit that the action i use for saving is assigned to. it's just changed from _this to _this select 0 because of what the addaction sends to the script. shouldn't be a problem but i will try what you suggested. would you mind uploading your mission? since it seems to work it might be a good example for me to look at. maybe i'm just missing a step. but from what i read in here what i have should be enough but i get no errors but nothing happens either, also worth mentioning i am testing while hosting from my PC. testing just for me just to see if the saving creates a file.

Edited by Bad Benson

Share this post


Link to post
Share on other sites

If you're adding an action to the player in-game, something for them to scroll and select, then you should be using "player addAction"

Share this post


Link to post
Share on other sites

i'm not sure what you mean. i add the action via

this addaction ["save", "saveaction.sqf"]

in the init line of my character. the addaction command sends the following array to the script it executes [target, caller, ID]. http://community.bistudio.com/wiki/addAction

since the action is applied to myself and i'm also the one calling it i am "target" and also "caller". so by defining _unit as _unit = _this select 0 and _unit = _this select 1 should both work. as you can see the player ID is fetched from _unit like this _puid = getPlayerUID _unit; so i don't see how that would change anything.

still for lack of options i tried both your suggestions and get the same result as before.

EDIT: if you have something that works it would really help me if you could upload it. that way i can see if the mission itself is the cause of the problem.

Share this post


Link to post
Share on other sites

Ya, see, I don't put my actions into the init, I execute a specific script for adding and removing the actions so what I suggested is more tailored to my system. lol sorry about that.

EDIT: Not sure if it will help or not, depends what type of mission you have in my opinion. I run a Takistan Life, Island Life, and Wasteland, and they all save perfectly fine with just setting the options and things to save and executing the script in the init.sqf that executes per player, on a loop while the player is connected.

if (isServer) then 
{
	while {true} do 
	{
		sleep 30;
			_Profile		= format["%1", getPlayerUID player];
			//============	Save Stats
			[_Profile, "playerData", "Location", position player] call iniDB_write;
			[_Profile, "playerData", "Weapons", weapons player] call iniDB_write;
			[_Profile, "playerData", "Ammo", magazines player] call iniDB_write;
			[_Profile, "playerData", "Money", player getVariable "cmoney"] call iniDB_write;
			[_Profile, "playerData", "Food", player getVariable "canfood"] call iniDB_write;
			[_Profile, "playerData", "Water", player getVariable "water"] call iniDB_write;
			[_Profile, "playerData", "MedKits", player getVariable "medkits"] call iniDB_write;
			[_Profile, "playerData", "Fuel", player getVariable "fuel"] call iniDB_write;
			[_Profile, "playerData", "RepairKits", player getVariable "repairkits"] call iniDB_write;
			[_Profile, "playerData", "FullFuel", player getVariable "fuelFull"] call iniDB_write;
			[_Profile, "playerData", "EmptyFuel", player getVariable "fuelEmpty"] call iniDB_write;
			[_Profile, "playerData", "SpawnBeacons", player getVariable "spawnBeacon"] call iniDB_write;
			[_Profile, "playerData", "CamoNets", player getVariable "camonet"] call iniDB_write;
			[_Profile, "playerData", "Hunger", hungerLevel] call iniDB_write;
			[_Profile, "playerData", "Thirst", thirstLevel] call iniDB_write;
			sleep 1;
			player groupChat "Player Stats Saved";
	};
};

sleep 30; is obviously so it saves every 30 seconds.

Edited by HellsGuard

Share this post


Link to post
Share on other sites

no problem:)

i really appreciate you trying to help. did you do anything else than adding the preprocess command in the init.sqf and installing the addon itself to set everything up apart from you custom saving method?

Share this post


Link to post
Share on other sites
First, this is a great system, works very well, but I have 1 problem and hopefully someone can help me out with it.

This makes no sense to me as to why this doesn't work, and is regarding weapons & magazines loading, everything else loads perfectly fine and has no issue.

First My Save variable

_Profile		= format["%1", getPlayerUID player];
[_Profile, "playerData", "Weapons", weapons player] call iniDB_write;
[_Profile, "playerData", "Ammo", magazines player] call iniDB_write;

Then my loading info which executes in the same file, same time, as all the other variables which load perfectly fine.

_Weps		= [_Profile, "playerdata", "Weapons", "ARRAY"] call iniDB_read;
_Mags		= [_Profile, "playerdata", "Ammo", "ARRAY"] call iniDB_read;

And currently I have it as

player addWeapon _Weps;

I've tried (_Weps); , this addWeapon _Weps; , (_Weps); , I've literally tried everything I know, and it just will not give the weapons or mags, if anyone has gotten it to load the mags and weapons without issues... please share :(

Couple of things... first, you can't call iniDB_read on the client. Once again guys, the server. The server. The server.

Also as far as I'm aware, addWeapon does not accept arrays. Try using the script I mentioned early on to get/set loadout.

Not sure if it will help or not, depends what type of mission you have in my opinion. I run a Takistan Life, Island Life, and Wasteland, and they all save perfectly fine with just setting the options and things to save and executing the script in the init.sqf that executes per player, on a loop while the player is connected.

Um, question. How would "player" be a valid variable on the server... ? I feel like a lot of you guys should read up on code locality in ARMA.

I'm not trying to make fun of any of you guys it's just... that has to be broken. If you're running it on a test server, it might save YOUR stats since isServer == true and player is valid on a local, non-dedicated server.

If you try running it with actual players i have a strong feeling it will break.

Protip: If iniDB_read/iniDB_write and "player" are used nearby one another, it's likely going to be broken.

Share this post


Link to post
Share on other sites

well yea i know how "player" works. i also know how locality works. could i get an answer if my very simple code has any problems in it or if i am missing something? i won't need any help at all to set up my own system. all i need is to get simple saving by an addaction to work as a confirmation the @iniDb is set up right.

i am not using "player" at all. i am just fetching the unit i want to save from the addaction array. simple stuff. so there shouldn't be an issue there. just trying to save myself for a start.

so having the preprocess in the init should initialize the database functions, right? so if i now run my action script with this version of your script (2 simple changes eexplained above).

_unit = _this select 0;

_puid = getPlayerUID _unit;

if(!isServer) exitWith {};

if(_puid == "_SP_PLAYER_" || _puid == "") exitWith {};

// Allow users with multiple profiles to make new profiles and have new lives

// We want to use CRC hashes for the name because some people have spaces, weird characters or some other stuff so it's just better this way.

_profileName = _unit getVariable["profileName", ""];

if(_profileName == "") exitWith {};

_unitFileName = format["%1_%2", _puid, (_profileName call iniDB_CRC32)];

// We will save to the same file, but use different sections for each side

// We don't want cop uniforms/pos/etc saving over to insurgent or civilian sides

// This will mean persistent data will carry over _per occupation_, pretty neat right?

_sectionTitle = format["%1", side _unit];

// Actually save global data

[_unitFileName, _sectionTitle, "pos", position _unit] call iniDB_write;

//[_unitFileName, _sectionTitle, "loadout", ([_unit] call getLoadout)] call iniDB_write;

why doesn't it create an ini in my db folder? i would really appreciate an answer since this is about the system itself and not me trying to get my own code to work. just give me a pointer i can work with...please?

Share this post


Link to post
Share on other sites

I got my load sorting working via varibles, exept that people port onto my location not their own so close! .. yet so far

Share this post


Link to post
Share on other sites

hm. seems like i was trusting in your code example too much. i guess it's for dedicated servers maybe?

anyways. commenting out the following line made it work. seems like it only works for a certain set up i don't have here.

_unit = _this select 0;

_puid = getPlayerUID _unit;

if(!isServer) exitWith {};

if(_puid == "_SP_PLAYER_" || _puid == "") exitWith {};

// Allow users with multiple profiles to make new profiles and have new lives

// We want to use CRC hashes for the name because some people have spaces, weird characters or some other stuff so it's just better this way.

_profileName = _unit getVariable["profileName", ""];

//if(_profileName == "") exitWith {};

_unitFileName = format["%1_%2", _puid, (_profileName call iniDB_CRC32)];

// We will save to the same file, but use different sections for each side

// We don't want cop uniforms/pos/etc saving over to insurgent or civilian sides

// This will mean persistent data will carry over _per occupation_, pretty neat right?

_sectionTitle = format["%1", side _unit];

// Actually save global data

[_unitFileName, _sectionTitle, "pos", position _unit] call iniDB_write;

//[_unitFileName, _sectionTitle, "loadout", ([_unit] call getLoadout)] call iniDB_write;

maybe you could elaborate on this line more. the comments sound like it's useful stuff. anyways it will do it without it for now.

EDIT: ah i just noticed a line i missed before...so many comments lol. could you just drop a short sentence what the following line is for and how it's used?

_profileName = _unit getVariable["profileName", ""];
Edited by Bad Benson

Share this post


Link to post
Share on other sites

In the player init (on the client) I do:

player setVariable["profileName", profileName, true]; //3rd var "true" reports it to the server

It's explained why I do that in the comments (if a player uses a different profile it will make a new db file), I guess I should have been more clear.

https://community.bistudio.com/wiki/profileName

Share this post


Link to post
Share on other sites

ah i thought so. thank you for making it clear. you might wanna add this player init thing to the original post where you pasted your save and load script. that will make that post the working template people were asking for. just a suggestion.

anyways i don't want more than one profile per player so i should be fine leaving it out.

just wanted to say how great and easy to use (once figured out :p) this is. great move releasing it and not keeping it to yourself/your community. this adds a lot to arma's sandbox features. now i can make those infinite campaigns i always wanted to make without a huge chunk of extra stuff like a database server.

this will help people make a lot of interesting stuff apart from dayZ clones.

Share this post


Link to post
Share on other sites

I hope so, that was the plan I guess. I also hope there's some C/++ coders (or other languages) that can learn a bit about the plugin format and how to send/return data to/from the extension DLLs.

There's a lot more you can make. I plan on making a couple more extension modules myself.

Share this post


Link to post
Share on other sites

Is is possible for implementing the string compression?

Since I, myself, am not a native programmer, then it is quite hard for me to understand some codes.

Can you give me some advice for implementing the string-data compression (zlib, glib, lzw) to your codes?

Share this post


Link to post
Share on other sites

*thumbs up*

Excellent work, SicSemperTyrannis.

I just tested it. The next step for me is to find out how I can save the status of vehicles in a certain area.

Share this post


Link to post
Share on other sites

i can't for the life of me figure out how to load data for clients. saving works perfectly and i can look into everyone's profiles and see that all items are there. but when they reconnect the loading doesn't work. can everybody share their methods for loading please? i'm kinda stuck at the moment.

shouldn't it work like this in the init.sqf?

0 = player execVM "loadPlayerData.sqf";

since inside the loadPlayerData.sqf there's a server check i thought this would be the best way to make the server fetch the data from the ini and setpos the unit. but for some reason this doesn't work for clients. works for myself hosting the game perfectly. the init.sqf of a mission is ran by every client that connects, right? so on his machine "player" should work and then inside the load script the server takes over due to if (!isServer) exitwith {};. still no success:(

Edited by Bad Benson

Share this post


Link to post
Share on other sites
Is is possible for implementing the string compression?

Since I, myself, am not a native programmer, then it is quite hard for me to understand some codes.

Can you give me some advice for implementing the string-data compression (zlib, glib, lzw) to your codes?

Why would you need string compression if you don't mind my asking?

I can try to make a separate extension to meet your needs if I know what it is for.

EDIT:

@Bad Benson: No, it doesn't belong in the client init. If the server isn't running the code, it will not work. You have to execute the code on the server-side and I explain how to do that in this topic in detail.

Share this post


Link to post
Share on other sites
@Bad Benson: No, it doesn't belong in the client init. If the server isn't running the code, it will not work.

thx for your answer! i hope you mean the init.sqf by "client init" and not the init line of the client. because that is not where i'm running it from. if so this will confirm my suspicion that i my logic was flawed. i assumed the init.sqf would run everywhere when a client connects. which is obviously wrong now that i think about it:D

You have to execute the code on the server-side and I explain how to do that in this topic in detail.

i guess you mean setting up a initplayer.sqf that will run the load script to make sure it runs on the server.

anyways thx for the hint.

Share this post


Link to post
Share on other sites

just wanted to say.. thanks for this.. works great and is perfect for many things.

:)

Share this post


Link to post
Share on other sites
Why would you need string compression if you don't mind my asking?

I can try to make a separate extension to meet your needs if I know what it is for.

I just wanted to make the string-data be small as possible. Since the inventory data contains a lot of string value which is cost almost 2kb (flat) per a public variable, then compressed and reduced the size of buffer to 1kb or 800 bytes and send over the net later would be good in both terms of transferring and security.

I don't know how the arma 3 handle for sending the string value across the net, but when judging from my experience with DayZ private... arma 2 seems doesn't handle the flat data file well enough.

Anyway, thanks for your concern.

EDIT:

May be I've forgot the most important part. I've modified your module extension into 2 versions, which are: 1)The server version which is almost the same as original and... 2) client version which has an ability for decoding only (can not write any file on disk). That's why I need a string compression.

Edited by Zatan13th
added

Share this post


Link to post
Share on other sites

I'm still not entirely understanding your need for it, if it's to store inventory only, the server has that information and you don't really need to specifically send your loadout from the client to the server.

The server has that data explicitly, meaning, it'd be there whether you specifically sent it or not.

That being the case, I could look into making a gzip extension, but it might not work well with the ini format.

Share this post


Link to post
Share on other sites
you don't really need to specifically send your loadout from the client to the server.

The server has that data explicitly, meaning, it'd be there whether you specifically sent it or not.

Yes, that's right. However, the method for sending back the stored data from server to clients is that what I'm looking for.

for example:

["H_HelmetB_paint","G_Sport_Blackred","U_B_CombatUniform_mcam_vest",["FirstAidKit","30Rnd_65x39_caseless_mag"],"V_PlateCarrierGL_rgr",["30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","16Rnd_9x21_Mag","HandGrenade","HandGrenade","1Rnd_HE_Grenade_shell","1Rnd_HE_Grenade_shell","1Rnd_HE_Grenade_shell","1Rnd_Smoke_Grenade_shell","1Rnd_SmokeRed_Grenade_shell","1Rnd_SmokeGreen_Grenade_shell","1Rnd_SmokeYellow_Grenade_shell","1Rnd_SmokePurple_Grenade_shell","1Rnd_SmokeBlue_Grenade_shell","1Rnd_SmokeOrange_Grenade_shell"],"",[[],[]],[[],[]],[],["","",""],[],["","acc_pointer_IR","optic_Hamr"],["30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","16Rnd_9x21_Mag","HandGrenade","HandGrenade","1Rnd_HE_Grenade_shell","1Rnd_HE_Grenade_shell","1Rnd_HE_Grenade_shell","1Rnd_Smoke_Grenade_shell","1Rnd_SmokeRed_Grenade_shell","1Rnd_SmokeGreen_Grenade_shell","1Rnd_SmokeYellow_Grenade_shell","1Rnd_SmokePurple_Grenade_shell","1Rnd_SmokeBlue_Grenade_shell","1Rnd_SmokeOrange_Grenade_shell"],["arifle_MX_GL_Hamr_point_mzls_F","hgun_P07_F","Binocular","Throw","Put"],["FirstAidKit","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","30Rnd_65x39_caseless_mag_Tracer","16Rnd_9x21_Mag","HandGrenade","HandGrenade","1Rnd_HE_Grenade_shell","1Rnd_HE_Grenade_shell","1Rnd_HE_Grenade_shell","1Rnd_Smoke_Grenade_shell","1Rnd_SmokeRed_Grenade_shell","1Rnd_SmokeGreen_Grenade_shell","1Rnd_SmokeYellow_Grenade_shell","1Rnd_SmokePurple_Grenade_shell","1Rnd_SmokeBlue_Grenade_shell","1Rnd_SmokeOrange_Grenade_shell"],["ItemMap","ItemCompass","ItemWatch","ItemRadio","ItemGPS","G_Sport_Blackred","NVGoggles","H_HelmetB_paint","Binocular"]]

This is quite a bit very-long string which I aware the game-engine can't handle well much.

My idea is not to compress the whole text file, but just to compress the string data in buffer memory (for saving net-packet bandwidth) and send to specific client then decompress it to original text.

I think compress2 function in zlib library should be ok, but I don't know to implement it. :)

Edit:

May be in another way: is it possible for you to adding the index service?

Just like the example above can be transformed into

["A001","A002","A003",["B001","B002"]...["C006"]]

(modder can define what is A001/A002/Z300 or whatever from inside dll or external file).

You don't need to put yourself much about my idea, it is just some suggestions from the whom can't programming.;)

Edited by Zatan13th
added

Share this post


Link to post
Share on other sites

Well, again, the engine would just send that data explicitly (although I guess you are trying to avoid that?) because I'm pretty sure addWeapon/addBackpack/addItem/etc works on the server.

If you're trying to avoid it purposefully, maybe it's better you add me to skype (will put in my profile in a moment).

Share this post


Link to post
Share on other sites

I'm sorry which I haven't any skype account. A Forum's personal message would be acceptable?

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
Sign in to follow this  

×