Jump to content
Sign in to follow this  
groshnak

Adjusting AI skill based on distance with setskill and distance to assignedtarget?

Recommended Posts

So after trying every possible setting with the different setskill aimingaccuracy and aimingshake numbers i found out that it's hopeless.

It's simply not possible to make the AI act even remotely human-like unless adjusted on the fly depending on the conditions, like lowering spottingdistance when AI walks in to a forest or depending how dark it is outside, these are quite easy to script and i got those to work well.

However, the tricky part is adjusting these values quickly depending how far the target is. I need to make them more accurate at longer ranges and less accurate at shorter ranges. Otherwise they wont hit anything at over 100 meters or they always get instant 360 headshots in towns.

So would someone mind helping me turn these things in to a working sqf script. Please excuse the nonsense i'm about to write because im still trying to move to sqf from sqs and i'm quite positive i'm doing this wrong in many ways.

[codethatlikelywontwork]
_unit = _this select 0 //AI Unit to get adjusted

while {alive _unit} do { 
waitUntil {!isnull assignedtarget _unit}; //Wait for the AI to actually target something.Does this equal the target hes going to shoot?
_target = assignedtarget _unit; //Define the target AI is hopefully going to shoot soon
if (_unit distance _target >10) then {_unit setSkill [aimingaccuracy 0.15]};
if (_unit distance _target >20) then {_unit setSkill [aimingaccuracy 0.2]};
if (_unit distance _target >30) then {_unit setSkill [aimingaccuracy 0.25]};
if (_unit distance _target >50) then {_unit setSkill [aimingaccuracy 0.3]};
if (_unit distance _target >100) then {_unit setSkill [aimingaccuracy 0.4]};
if (_unit distance _target >200) then {_unit setSkill [aimingaccuracy 0.5]};
sleep 2; // Guessing 2 seconds is enough to adjust when AI changes targets?
};

[/codethatlikelywontwork]

So the script is supposed to keep going until the unit dies. As long as the unit is alive, it will wait for the unit to target something. Then it will adjust the AI units accuracy based on the distance and wait for 2 seconds and then it will check again, if there is no targets it will wait for the next target. I'm quite sure the script i made in my head is full of crap? For one, if target is 300 meters away it will adjust the skill 6 times because the condition is met on every line. It needs to be defined better, like over 100 but less than 200.

Edited by Groshnak

Share this post


Link to post
Share on other sites
For one, if target is 300 meters away it will adjust the skill 6 times because the condition is met on every line. It needs to be defined better, like over 100 but less than 200.

You could use switch.

_targetdistance = _unit distance assignedtarget _unit;
switch true do {
case (_targetdistance < 20): { _unit setSkill ["aimingaccuracy", 0.15] };
case (_targetdistance < 30): { _unit setSkill ["aimingaccuracy", 0.2] };
case (_targetdistance < 50): { _unit setSkill ["aimingaccuracy", 0.25] };
case (_targetdistance < 100): { _unit setSkill ["aimingaccuracy", 0.3] };
case (_targetdistance < 200): { _unit setSkill ["aimingaccuracy", 0.4] };
case (_targetdistance < 2000): { _unit setSkill ["aimingaccuracy", 0.5] };
default {};
};

Only first met condition is executed and the rest are skipped.

Edited by Greenfist

Share this post


Link to post
Share on other sites
You could use switch.

_targetdistance = _unit distance assignedtarget _unit;
switch _targetdistance do {
case (_targetdistance < 20): { _unit setSkill [aimingaccuracy 0.15] };
case (_targetdistance < 30): { _unit setSkill [aimingaccuracy 0.2] };
case (_targetdistance < 50): { _unit setSkill [aimingaccuracy 0.25] };
etc...
default {};
};

Only first met condition is executed and the rest are skipped.

Yeah, indeed. Does it still loop back to the beginning if i use switch?

Share this post


Link to post
Share on other sites

Few problems. I can get a hint reading from the editor exec window for enemy distance assignedtarget enemy, but it wont start giving readings until quite some time after the enemy starts shooting, i guess it doesnt assign a target to itself immedeatly. Any other command we could use for this, there should be some way to read what the enemy is trying to shoot at?

And i also cant find a way to make the script work on the distance even after i fixed all the typing errors i found, it doesn't do anything. Hmm..

Share this post


Link to post
Share on other sites

I edited my post above.

It does change the accuracy fine for me, but the assignedtarget doesn't trigger immediately like you said.

Share this post


Link to post
Share on other sites
I edited my post above.

It does change the accuracy fine for me, but the assignedtarget doesn't trigger immediately like you said.

Cheers, think i could work with this for now. I could give them accuracy of 0.1 to begin with incase someone runs into them around a corner and they will likely miss one or two shots at first. And if they open fire on a target further away they just seem to take a few pot shots at the enemies until they get more accurate. Then i'll just add a condition to return the accuracy back to 0.1 when they have no assigned targets left.

I guess this is the best we can do atm. Other way would be to check for the nearest enemy, but that would get messed up if he was shooting someone else than the nearest person. Maybe a check for the nearest enemy in the direction where the AI unit is looking at? Hmm..

Heres the script if anyone else is interested. These skill levels are for enemies of low skill or hipfiring, conscripts etc

private ["_unit","_targetdistance"];

_unit = _this select 0;

while {alive _unit} do {

waitUntil {!isnull assignedtarget _unit};

_targetdistance = _unit distance assignedtarget _unit;

switch true do {

case (_targetdistance < 20): {_unit setSkill ["aimingaccuracy", 0.13]};

case (_targetdistance < 30): {_unit setSkill ["aimingaccuracy", 0.15]};

case (_targetdistance < 50): {_unit setSkill ["aimingaccuracy", 0.17]};

case (_targetdistance < 70): {_unit setSkill ["aimingaccuracy", 0.19]};

case (_targetdistance < 100): {_unit setSkill ["aimingaccuracy", 0.21]};

case (_targetdistance < 150): {_unit setSkill ["aimingaccuracy", 0.22]};

case (_targetdistance < 200): {_unit setSkill ["aimingaccuracy", 0.24]};

case (_targetdistance < 250): {_unit setSkill ["aimingaccuracy", 0.25]};

case (_targetdistance > 250): {_unit setSkill ["aimingaccuracy", 0.26]};

default {_unit setSkill ["aimingaccuracy", 0.1];};

};

sleep 3;

};

And i call it for all guerilla units with this in init.sqf:

{

if (side _x == independent) then {

[_x] execvm "ai.sqf";

_x setSkill ["aimingaccuracy", 0.1];

};

} foreach allunits;

Edited by Groshnak

Share this post


Link to post
Share on other sites
This code makes me want to vomit.

Not only willwhile (true) is going to kill your mission's frames like nobody's business, but you're also going to have each unit in the mission do this constantly on top of that? Oh my god, RIP your server.

If you have a better solution, then please do share it.

I'm total scripting noob and never done any MP missions, but this doesn't sound like a very frame killing script to me. It runs on each unit only every 3 seconds if they have a assigned target. Altough you could add some sleep to the waituntil so it doesn't run on every frame.

Share this post


Link to post
Share on other sites

Didn't affect my framerates at all. With about 300 AI running this script and 10 players, its steady fps limited to 60 and rarely goes under, so i don't know where your rant is coming from.

But back to the topic, squint gives me an error if theres no "private" set to _unit and _targetdistance but i don't see why not having it would cause any issues because the definitions for those dont change in the switch?

Heres an idea for a bit tuned version incase someone is having performance problems running this on an old AMD Thunderbird or something:

_unit = _this select 0;
while {alive _unit} do { 
waitUntil{sleep 1; (!isnull assignedtarget _unit)};
_targetdistance = _unit distance assignedtarget _unit;
switch true do {
case (_targetdistance < 20): {_unit setSkill ["aimingaccuracy", 0.13]};
case (_targetdistance < 30): {_unit setSkill ["aimingaccuracy", 0.15]};
case (_targetdistance < 50): {_unit setSkill ["aimingaccuracy", 0.17]};
case (_targetdistance < 70): {_unit setSkill ["aimingaccuracy", 0.19]};
case (_targetdistance < 100): {_unit setSkill ["aimingaccuracy", 0.21]};
case (_targetdistance < 150): {_unit setSkill ["aimingaccuracy", 0.22]};
case (_targetdistance < 200): {_unit setSkill ["aimingaccuracy", 0.24]};
case (_targetdistance < 250): {_unit setSkill ["aimingaccuracy", 0.25]};
case (_targetdistance > 250): {_unit setSkill ["aimingaccuracy", 0.26]};
default {_unit setSkill ["aimingaccuracy", 0.1];};
sleep 1;
};   
sleep 3;
};

Edited by Groshnak

Share this post


Link to post
Share on other sites

Only thing that private ["_var"] does is define the scope of the variable. Squint is just being pedantic about your function syntax but the engine doesn't care.

You could have this:

_unit = _this select 0;

while{true}do{

private "_unit";

_unit = "LOL";

hint _unit; //Displays "LOL"

};

hint _unit; //Displays object or something

Without private "_unit" inside the while loop, _unit = "LOL" will override _unit = _this select 0;

Hope this clears it up.

And lol at the troll screaming about while{true}... How else you supposed to run code over and over? You could use a recursive function but doubt it will make any performance difference.

I like this idea a little tho, could you share the script you made for forests?

Share this post


Link to post
Share on other sites
Only thing that private ["_var"] does is define the scope of the variable. Squint is just being pedantic about your function syntax but the engine doesn't care.

You could have this:

_unit = _this select 0;

while{true}do{

private "_unit";

_unit = "LOL";

hint _unit; //Displays "LOL"

};

hint _unit; //Displays object or something

Without private "_unit" inside the while loop, _unit = "LOL" will override _unit = _this select 0;

Hope this clears it up.

And lol at the troll screaming about while{true}... How else you supposed to run code over and over? You could use a recursive function but doubt it will make any performance difference.

I like this idea a little tho, could you share the script you made for forests?

For forests the most simple way was to use a repeated trigger covering the forest and using thislist to set spottingdistance lower on activation and returning it back up on deactivation. However i still need to make a script version of it if i want to use different spottingdistance for night and day or otherwise they will get too high values in the forest at night, but it seems like the ai can't see crap in the dark even if they have a higher value so that doesn't seem to be a problem.

This one made the biggest difference, now you get a bit of spraying effect when they start shooting but they get deadly after a few seconds so you need to take cover :p

Share this post


Link to post
Share on other sites

I see, I was hoping for some clever trick to auto-detect when they're in forests/shrubs.. Busy working on a script only SP game mode. Getting the AI to be fun is always a mission, bCombat helps a lot tho but their accuracy is still a problem. I usually just run a script on all my units that sets their skills with some random value like (random 0.1) + 0.15 for accuracy.

The initial spraying effect is good :P Gives some warning and time to react, without getting 360deg spin around lazer tagged.

Share this post


Link to post
Share on other sites

Most, if not all, trees will match the ": t_". And bushes are ": b_" for example.

But beware, it might be a quite heavy command, so use sparingly.

Edited by Greenfist

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  

×