Sending an NPC on walkabout (and sound volume)

Started by FortressCaulfield, Thu 04/07/2024 15:05:50

Previous topic - Next topic

FortressCaulfield

I need to send a security guard on a walk for several rooms that the player is not necessarily in, but might walk through and needs to be able to encounter him on his journey.

I added a flag to check if he's moving and then put code in global repeatedly execute that moves him from room to room, but it gets crashy if I try to make him walk in rooms the player isn't in. What's the solution? Do I just have to give up on him having naturalistic pathing and add a simple frame counter to do the room swaps, only worrying about his position if the player enters a room he's in or vice versa?

Also is there a way to adjust the volume of sound effects without having to tie it to a char or channel or object?
"I can hear you! My ears do more than excrete toxic mucus, you know!"

-Hall of Heroes Docent, Accrual Twist of Fate

Khris

You can wrap the code in
Code: ags
  if (cNPC.Room == player.Room)
to avoid these crashes.

Only a channel's volume can be adjusted afaik; the only other solution I can see is to import the sound at different volumes (which is probably not a terribly good idea).

Snarky

#2
Quote from: FortressCaulfield on Thu 04/07/2024 15:05:50Do I just have to give up on him having naturalistic pathing and add a simple frame counter to do the room swaps, only worrying about his position if the player enters a room he's in or vice versa?

Quick answer: yes. Only the room the player is currently in is loaded, so the engine doesn't have access to walkable area masks etc. for other rooms, and cannot do pathfinding there.

It would probably be possible to do some quick math when the player enters the room the roaming character is currently in to determine where they should be based on the timing, to achieve the same effect from the player's POV.

Quote from: FortressCaulfield on Thu 04/07/2024 15:05:50Also is there a way to adjust the volume of sound effects without having to tie it to a char or channel or object?

Sure. If you group all sound effects under a particular AudioType, you can adjust the default volume for that AudioType (Game.SetAudioTypeVolume); or you can adjust it individually as you play the sound:

Code: ags
  AudioChannel* ac = myClip.Play();
  ac.Volume = 80;

This code is technically unsafe because AudioClip.Play() will return null if the clip fails to play for some reason—for example that you've exceeded the number of available AudioChannels and the currently playing audio is all higher-priority than the clip you're trying to play. So to be on the safe side you should do a null-check, but this is pretty tedious to do every time, and I think most game makers tend to cross their fingers and hope to get away with it.

Or you could write a helper function:

Code: ags
Channel* PlayVolume(this AudioClip*, int vol)
{
  AudioChannel* ac = myClip.Play();
  if(ac != null)
    ac.Volume = vol;
}

And then you can just call:

Code: ags
  myClip.PlayVolume(80);

Crimson Wizard

#3
I was not sure which volume it is in this question, but if it's footsteps volume, then there's a Character.AnimationVolume property since AGS 3.6.0, purposed specifically for the frame sounds, as you cannot access their audio channels directly (at least not without workarounds).

On another hand, if you have a AudioChannel, there's a AudioChannel.SetRoomLocation() function that adjust the volume depending on the distance between the location and player character. I'd mention this just for the reference.

Somehow I cannot remember if it's possible to make frame sounds act like that automatically. But it's possible to script a similar thing with Character.AnimationVolume.

FortressCaulfield

Quote from: Khris on Fri 05/07/2024 09:56:34You can wrap the code in
Code: ags
  if (cNPC.Room == player.Room)
to avoid these crashes.

Only a channel's volume can be adjusted afaik; the only other solution I can see is to import the sound at different volumes (which is probably not a terribly good idea).

The problem is I want the game to know how far along he is without the player being in the room.

What I ended up having to do was put a frame counter in global rep exec which triggers his room changes, then a check in each room's rep exec which told him where to move if the player happened to be in that room when he moved there AND a check in each room's load to set him up properly if the player walked into a room where he already was.

Pain in the butt. I'm just going to avoid designing this type of scenario again.
"I can hear you! My ears do more than excrete toxic mucus, you know!"

-Hall of Heroes Docent, Accrual Twist of Fate

Crimson Wizard

#5
Quote from: FortressCaulfield on Fri 05/07/2024 22:06:58a check in each room's load to set him up properly if the player walked into a room where he already was.

You dont have to add this to each room though, there's on_event for doing something on common events like room load:
https://adventuregamestudio.github.io/ags-manual/Globalfunctions_Event.html#on_event

I doubt if it's necessary to put something in each room's rep-exec either, if it's a common action then likely you may have everything in the global rep-exec under some condition (such as aforementioned "if (cNPC.Room == player.Room)").

heltenjon

Quote from: FortressCaulfield on Fri 05/07/2024 22:06:58Pain in the butt. I'm just going to avoid designing this type of scenario again.
Not an advice at all, but this reminded me of the game All Demons Must Go to Hell by The AdventureThinkTank (Space Quest Historian and friends).

In this behind the scenes youtube video, they talk about how they made Gary, the monster that follows the player around in the labyrinth. (Well, he doesn't really, but it looks that way.) Another approach totally.

FortressCaulfield

Quote from: Crimson Wizard on Fri 05/07/2024 22:41:50
Quote from: FortressCaulfield on Fri 05/07/2024 22:06:58a check in each room's load to set him up properly if the player walked into a room where he already was.

You dont have to add this to each room though, there's on_event for doing something on common events like room load:
https://adventuregamestudio.github.io/ags-manual/Globalfunctions_Event.html#on_event

I doubt if it's necessary to put something in each room's rep-exec either, if it's a common action then likely you may have everything in the global rep-exec under some condition (such as aforementioned "if (cNPC.Room == player.Room)").

Hmmm. I see what you're saying. Basically the same solution as mine except all in one place. If we had the ability to declare structs properly you could set up a universal NPC-go-place function and feed it data and who is doing what and where and handle it all in the globals. Could even have an array of structs with NPC movement data and run through all of them.
"I can hear you! My ears do more than excrete toxic mucus, you know!"

-Hall of Heroes Docent, Accrual Twist of Fate

Crimson Wizard

#8
Quote from: FortressCaulfield on Sat 06/07/2024 03:08:53If we had the ability to declare structs properly you could set up a universal NPC-go-place function and feed it data and who is doing what and where and handle it all in the globals. Could even have an array of structs with NPC movement data and run through all of them.

Could you elaborate, which problems did you have with structs?
AGS v3.* is known to have certain limitations (upcoming AGS v4 version has a new better script compiler), but it's quite possible to have arrays of structs. If you like to be able to pass them into a function (or return from a function) you would need to define a "managed" struct and possibly dynamic array - these may be passed as a pointer.

Sadly, the manual is lacking in organizing this information, but here's some relevant topics:
https://adventuregamestudio.github.io/ags-manual/ScriptKeywords.html#managed
https://adventuregamestudio.github.io/ags-manual/DynamicArrays.html
https://adventuregamestudio.github.io/ags-manual/Pointers.html

OTOH sometimes people don't bother and just make global fixed-size array of regular structs, and pass its indexes as arguments.

FortressCaulfield

Quote from: Crimson Wizard on Sat 06/07/2024 05:42:15but it's quite possible to have arrays of structs

I had a big long reply entered with examples but forum eated it.

LSS, from what I found when I searched this topic, there's no way to declare an array of structs. Like in my mud days the skill list would be such an array, with things like spell name, text shown when casting, level you get it, damage type, mana cost, etc, and there's be a big long entry declaring every skill in the game. Like

struct spell (contents)
spell spell_list[max_spells] = {
{"Acquire Aardvarck", 3, "You summon an aardvarck.", 20, damage-none},
{"Superior Acquire Aardvarck", 13, ... etc. etc,

From what I found when I searched, in ags, I'd have to declare each individual value for each part of the const, for each part of the array.
"I can hear you! My ears do more than excrete toxic mucus, you know!"

-Hall of Heroes Docent, Accrual Twist of Fate

Snarky

#10
That particular way of creating entries doesn't work in AGS, but you can still have arrays.

To set all the values of each entry and add it to the list in one go, make a helper function:

Code: ags
Spell* spell_list[];
int spellCount;

void AddSpell(String name, int val, String description, int otherVal, SpellType type)
{
  if(spellCount == max_spells)
  {
    // Error! Log error and/or quit game 
  }
  spell_list[spellCount] = new Spell;

  // Set the fields
  spell_list[spellCount].Name = name;
  spell_list[spellCount].Value = val;
  spell_list[spellCount].Description = description;
  spell_list[spellCount].OtherValue = otherVal;
  spell_list[spellCount].Type = type;

  spellCount++;
}

Then you can do:

Code: ags
  AddSpell("Acquire Aardvark", 3, "You summon an aardvark.", 20, eDamageNone);
  AddSpell("Superior Acquire Aardvark", 13, "You summon a superior aardvark ", 40, eDamageNone);
  // ...

Which is nearly as easy as the list-declaration style.

Crimson Wizard

#11
Quote from: FortressCaulfield on Sun 07/07/2024 02:35:26LSS, from what I found when I searched this topic, there's no way to declare an array of structs. Like in my mud days the skill list would be such an array, with things like spell name, text shown when casting, level you get it, damage type, mana cost, etc, and there's be a big long entry declaring every skill in the game.

Ah, but you are speaking of initialization, not declaration.

But this is true, AGS script does not support initializing a struct instance with a list of values, similar to C. This makes things less convenient, but arrays of structs may still be done and used in code.

About initialization, this is something that you have to either live with, or find another solution, similar to what Snarky posted above. With regular structs you can make a function that initializes certain entry in a global array by an index.
Then, when it comes to managed structs, the usual solution is to write a "factory" function that allocates an instance, initializes its fields, and returns a pointer to the new instance.

If it's necessary to have a proof that AGS can have and work with structs and arrays of structs I may refer to my own game project which I made some years ago:
https://github.com/ivan-mogilko/ags-lastfurious
Although, I suppose, many people here could post their own code with arrays of structs.


EDIT: @Snarky your example is not suitable for AGS 3, because you have a managed struct with dynamic Strings in it. It may be better to have an example of regular structs.

Snarky

Quote from: Crimson Wizard on Sun 07/07/2024 09:38:53EDIT: @Snarky your example is not suitable for AGS 3, because you have a managed struct with dynamic Strings in it. It may be better to have an example of regular structs.

Managed structs are so much more convenient to work with that even if I need them to have Strings (or other managed types), I usually use a property to access ones stored in an array outside of the struct. Though this does require using a factory method to create them, so the example code isn't fully accurate.

SMF spam blocked by CleanTalk