Jump to content
Sign in to follow this  
beaar

Hostile AI using Say3D (or similar) near players

Recommended Posts

Hey. I'd like to have hostile AI randomly use say3D (like they're shouting) when they're close to a player. Is this feasible?

Thanks :)

Solved see post #4

Edited by rzon

Share this post


Link to post
Share on other sites

First you need to register your sounds in description.ext to use them in the game.

Then it is very possible with something like this:

_object = NAMEOFMAN;
_distance = 10; //player must be within this many meters before the sound will play
_random = 5; //equal chance for 0, 1, 2, 3, or 4

if (player distance _object <= _distance) then // <= means 'is less then or equal to', it's not an arrow.
{
_randomResult = floor(random _random);
switch (_randomResult) do
{
	case 0: {_object say3D "sound1";};
	case 1: {_object say3D "sound2";};
	case 2: {_object say3D "sound3";};
	case 3: {_object say3D "sound4";};
	case 4: {_object say3D "sound5";};
	//you need to manually add more "cases" if you want more than 5 sounds
};
};

Personally, I would use playSound3D (click me) over say3D any day because of the control you get over the sound and you don't need to do any extra work except fill out a few more parameters in the command.

Edited by DreadedEntity

Share this post


Link to post
Share on other sites

Thanks! So if I'd like it to execute only for clients, could I execute the script in the init-line of the AI, with "if (isServer) exitWith {}" on top of the code?

Additionally, I'd like it to run in a loop every 20 seconds or so. I've made some changes to the script to reflect what I'm trying to do. Am I going in the right direction? How will performance fare with the script running on every client, for every AI? Is there a prettier way to run it? Should have stated it's multiplayer in the op, sorry.

From reading the wiki it looks like I'll have to do some extra work for the playSound3D bits before it'll work, but I should be able to do that without problems. It's mostly the "execute for every client"-part that I don't understand.

if (isServer) exitWith {}; //execute only on clients
_object = this select 0; //so I think I can execute with "_nul = [this] execVM "script.sqf" in AI init-fields
_distance = 20; //player must be within this many meters before the sound will play
_random = 5; //equal chance for 0, 1, 2, 3, or 4
while {alive _object} do //keep running script while AI unit is alive, so he can shout more than once
{
 if (player distance _object <= _distance) then // <= means 'is less then or equal to', it's not an arrow.
 {
   _randomResult = floor(random _random);
   switch (_randomResult) do
   {
     case 0: {_object playSound3D "sound1";};
     case 1: {_object playSound3D "sound2";};
     case 2: {_object playSound3D "sound3";};
     case 3: {_object playSound3D "sound4";};
     case 4: {_object playSound3D "sound5";};
   };
   sleep 20; //wait 20 seconds until loop runs again
 };
};

Maybe I'm taking the wrong approach to this? Maybe I could have just one script on the server, working through AI on side X one by one and checking if they're close to a unit from side Y (all players would be side Y)?

Here's what I'm thinking.

while (true) do
{
 foreach (_unit where side X) //for each AI on enemy side -- somewhat pseudo-ish code
 {
    _nearestTargets = _unit nearTargets 25; //count targets for the AI soldier within 25m
    _validNearestTargets = [];

    if (alive _x && side _x == west) then
    {
      _validNearestTargets set [(count _validNearestTargets),_x];
    }; forEach _nearestTargets; //for every target, check if they're alive and on side west and then add to _validNearestTargets

    if (count _vanlidNearestTargets > 0) //check if there's actually any valid targets/players in the array
    {
      //random chance to yell + random sound
      playSound3D(....); //yell something
    };
   sleep 2; //sleep 2sec before checking next AI
 };
};

and then execute it in init.sqf with

if (isServer) //check if server
{
 [] spawn { //use spawn so not to halt init.sqf(?)
    _nul = [] execVM "playsound.sqf"; //execute script
 }
};

Is this a realistic way to do it?

Edit: just found another one, findNearestEnemy, perhaps it would be even simpler to use that?


Edit 2: Okay, I've puzzled some stuff together and this is what I've come up with. Is it a good idea?

 //This should get all groups on east side_eastGroups = [];
{
   if (count units _x > 0 and side _x == east) then
   {
       _eastGroups set [(count _eastGroups),_x];
   }
} forEach allGroups;


_validEnemies = [];
while (true) do
{
   {
       {
           _nearestEnemy = _x findNearestEnemy (position _x); //find enemy closest to unit _x
           if (!isNull(_nearestEnemy) && _x distance _nearestEnemy <= 30) //check if  aware of enemy, and if he's within 30m of enemy
           {
               _validEnemies set [(count _validEnemies),_x]; //put unit in _validEnemies array if he's within 30m of a known enemy
           };
       } forEach units group _x; //for every unit in group _x
   } forEach _eastGroups; //for each group on east side


   {
       if (floor(random 10) == 1) //10% chance to play sound
       {
           switch (floor(random 20)) do //select sound to play
           {
             case 0: {_x playSound3D "sound1";};
             case 1: {_x playSound3D "sound2";};
             case 2: {_x playSound3D "sound3";};
             case 3: {_x playSound3D "sound4";};
             case 4: {_x playSound3D "sound5";};
             ...
             case 19: {_x playSound3D "sound20";}
           };
       };
   } forEach _validEnemies; //run for every unit that has known enemies close to them

   _validEnemies = []; //reset array of valid units when done
   sleep 5; //wait 5 sec until repeat process
};

init.sqf:

waitUntil {time > 1};
if (isServer) {
   [] spawn {
       _nul = execVM "playsound.sqf";
   };
};

Problem so far is that it doesn't seems to halt when it gets to the while(true) loop. I put 'vehicle setdamage 1' commands in different places to blow up vehicles when the next lines of code is executed (I replaced the whole switch block with one), which works, but nothing inside that while loop is executed.


Edit 3: I think I have solved the problem, I had made quite a few syntax errors which I managed to fix after a while.

I greatly appreciate your help. Most of my knowledge comes from putting different bits of scripts together, but I hope to learn properly soon enough :)

Edited by rzon

Share this post


Link to post
Share on other sites

For anyone wanting to do the same thing, here's the "finished" thing which appears to be working pretty good for me:

playsound.sqf:

_eastGroups = [];//This should get all groups on east side
{
   if (count units _x > 0 and side _x == east) then
   {
       _eastGroups set [(count _eastGroups),_x];
   }
} forEach allGroups;




_validEnemies = [];


while {true} do
{
   {
       {
           _nearestEnemy = _x findNearestEnemy (position _x); //find enemy closest to unit _x
           if (!isNull(_nearestEnemy) && _x distance _nearestEnemy <= 50) then //check if  aware of enemy, and if he's within 50m of enemy
           {
               _validEnemies set [(count _validEnemies),_x]; //put unit in _validEnemies array if he's within 30m of a known enemy
           };
       } forEach units _x; //for every unit in group _x
   } forEach _eastGroups; //for each group on east side


   {
       _random = 20;
       _randomResult = floor(random _random);
       if (_randomResult == 1) then // chance to play sound
       {
           _random = 36;
           _randomResult = floor(random _random);
           switch (_randomResult) do
           {
               case 0: {_x say3D "ljud0";};
               case 1: {_x say3D "ljud1";};
               case 2: {_x say3D "ljud2";};
               case 3: {_x say3D "ljud3";};
           };
           //exitWith{};
       };
   } forEach _validEnemies; //run for every unit that has known enemies close to them

   _validEnemies = []; //reset array of valid units when done
   sleep 3; //wait 5 sec until repeat process
};

init.sqf:

waitUntil {time > 1};if (isServer) then
{
   [] spawn {
       _nul = execVM "playsound.sqf";
   };
};

description.ext:

class CfgSounds{
   sounds[] = {ljud0,ljud1,ljud2,ljud3};
   class ljud0 {name = "ljud0";sound[] = {"\ljud\0.ogg", db+38, 1.0, 200};titles[] = {};};
   class ljud1 {name = "ljud1";sound[] = {"\ljud\1.ogg", db+38, 1.0, 200};titles[] = {};};
   class ljud2 {name = "ljud2";sound[] = {"\ljud\2.ogg", db+38, 1.0, 200};titles[] = {};};
   class ljud3 {name = "ljud3";sound[] = {"\ljud\3.ogg", db+38, 1.0, 200};titles[] = {};};
};

Edited by rzon
  • Like 1

Share this post


Link to post
Share on other sites

The problem with random numbers is that every client will get a different result, so sometimes one player might hear the enemy yell, and another one won't.

Another problem with putting the code in a unit's initialization is that I'm pretty sure unit inits are non-scheduled enviornments (I could be wrong though), this means that using pauses won't work (sleep, waitUntil, etc, it will throw an error). You'll have to use either spawn {code} or execVM "script.sqf"

Last thing I saw, you don't have to spawn a thread in your init.sqf if you're going to execVM "script.sqf", execVM already spawns a new thread to not interrupt with script operation.

Great work though, and glad you were able to get it done

Share this post


Link to post
Share on other sites

Alright, thanks :) I'll probably change the script to use playSound3D instead, as it seems to be a prettier solution.

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  

×