Queue System

Started by bulka_tarta, Wed 05/07/2017 22:43:45

Previous topic - Next topic

bulka_tarta

Hi, I thought this question will be more complex and it felt like advanced section of the forum would be suitable - sorry if that's not the case!

I was playing around with AGS for a little while now, constantly trying to do some more complex bits of code.
I'm currently planning out on how to do a queue system in my project. Imagine a restaurant-type game, where you need to prepare burgers etc. You have various customers that come from left side, stop, the queue gets bigger and you can call each customer to talk to him. He walks up to the counter, orders stuff (through dialogue) and then goes away. There's a bunch of tricks though. I would like the customers to be different every time, and they order different stuff each time, so each customer needs to "hold" some (also random) information.

I'm trying to plan things ahead (I heard that's what real programmers do ;) ), and see what would be the best method to do all of this. I'm not sure if this questions isn't too broad at the moment, so I can always come back here later.

I'm thinking of creating a struct for the characters, so for example:
Code: ags

struct Client {
  String name;
  String foodType;
  int sprite;
  int gender;
};
import Client clients[MAX_CLIENTS];


And then set things up:
Code: ags

clients.name = "Bob";
clients.foodType = "burger";
clients.sprite = Random(10); //select random sprite from 10
clients.gender = Random(2); //1 = male   2 = female


I was thinking of creating two characters in the game: cMale and cFemale. Based on "gender" I decide (TBC how) if we are using cMale or cFemale, and based on the "sprite" I was thinking to use the number to attach the view to the character. I was thinking of using something like: vMale1, vMale2 etc and same for female. That way I could attach a random view to the character. My worry is that I won't be able to have cMale multiple times on the screen, right? In that case I would need cMale1, cMale2 etc? I'm planning to have up to 10 people in the queue at once.

Would a queue system like the one I described (or something similar) be possible in AGS?
I would really appreciate any pointers in the right direction.

dayowlron

I don't see anything you questioned that would not be able in AGS. I don't think you could have multiple occurrences of cMale, but you could create cMale1, cMale2, etc. The rest of what you suggested sounds like a good start. There will be more involved in processing the queus, but a good start.
Pro is the opposite of Con                       Kids of today are so much different
This fact can clearly be seen,                  Don't you know?
If progress means to move forward         Just ask them where they are from
Then what does congress mean?             And they tell you where you can go.  --Nipsey Russell

Snarky

#2
Yes, you're on the right track. This is sort of how the AGS Awards Client works as well: there's a pool of "blank" characters and an array of structs with data for what different characters are like (notably the views to use), and when someone logs on, the client grabs a free character and customizes it according to the data. (Actually there are several different structs in different arrays, one with avatars and one with the different users that are logged on, but the principle is the same.)

A few points:

- Note that clients.gender = Random(2); can return 0 as well as 1 or 2. (The AGS Random() function is one of the stupidest design decisions in the engine.) So you should call it as Random(1); and distinguish between 0 and 1. (And obviously you also need to keep this in mind for selecting random sprites.)

- There is no need to have separate cMale and cFemale characters. You can just have cClient0, cClient1, cClient2... and then populate them with the appropriate gender according to the data in your struct.

- Think about what information you actually need in the struct. Does the game need to define what food they want as soon as they get in line, or only for the current customer? If it's not tied to their character, you shouldn't store it, but just pick it (randomly) when it's time for them to order.

- Instead of "sprite", I would put "view" if the characters are going to animate at all. You might need to store both a walk view and talk view.

- To generate clients, there are two ways you can go. You can predefine a bunch of characters, and then just pick one of them at random, which will set the view, name, gender, and everything else about them accordingly. In that case, you only generate one random number to pick the character from your "cast" â€" all the other variables you just look up. (There's then a complication to ensure that you don't have multiple clones of the same client in line at the same time.) Or you can pick each characteristic randomly each time, to dynamically generate a new character with a random combinations of traits. That appears to be what you're trying to do in your code example. However, in that case you don't need a master array of clients, you just need the ones that are currently in line. And you also shouldn't hard-code anything (like name), but always pick it from a list. And finally, the gender will presumably determine what sort of names and views you want to use, so you should pick that first:

Code: ags
// Header
enum Gender
{
  eMale,
  eFemale
};

struct Client {
  String name;
  int walkView;
  int talkView;
  Gender gender;
};

// Script
// These values are used to calculate what views to use
#define VIEWS_MAX 10 // How many different views there are for each gender
#define MALE_WALK_0 123 // The index of the first male walk view (the next VIEWS_MAX are others)
#define MALE_TALK_0 133 // The index of the first male talk view
#define FEMALE_WALK_0 148 // First female walk view
#define FEMALE_TALK_0 158 // First female talk view

#define NAMES_MAX 50
String maleNames[NAMES_MAX];
String femaleNames[NAMES_MAX];

#define MAX_QUEUE 10
Client clientQueue[MAX_QUEUE];

void initClientData()
{
  int i=0;
  maleNames[i] = "Dave"; i++;
  maleNames[i] = "Bob"; i++;
  maleNames[i] = "Ranesh"; i++;
  // ...

  i=0;
  femaleNames[i] = "Alicia"; i++;
  femaleNames[i] = "Roberta"; i++;
  femaleNames[i] = "Jane"; i++;
  // ... 
}

void generateClient(int index)
{
  if(Random(1)) // This is true if the value is not 0
  {
    clientQueue[index].gender = eMale;
    clientQueue[index].name = maleNames[Random(NAMES_MAX-1)];

    int vNum = Random(VIEWS_MAX - 1);
    clientQueue[index].walkView = MALE_WALK_0 + vNum;
    clientQueue[index].talkView = MALE_TALK_0 + vNum;
  }
  else
  {
    clientQueue[index].gender = eFemale;
    clientQueue[index].name = femaleNames[Random(NAMES_MAX-1)];

    int vNum = Random(VIEWS_MAX - 1);
    clientQueue[index].walkView = FEMALE_WALK_0 + vNum;
    clientQueue[index].talkView = FEMALE_TALK_0 + vNum;
  }
}


You also need to assign this to your blank characters, of course, but I'll skip that part. (Depending on exactly how you organize it, some of the data in the struct might be redundant, as you can just assign it to the character directly.)

Edit: In fact, unless you're using gender somewhere else, there's really no need to store it in the struct either. Just use it to pick an appropriate name and view. (Of course, if you think there's a chance you might use it, e.g. to generate appropriate dialogue like "What's your order sir/madam?", there's no harm in keeping it.)

monkey0506

Quote from: Snarky on Thu 06/07/2017 10:05:28(The AGS Random() function is one of the stupidest design decisions in the engine.)

In C++, std::rand uses a fixed inclusive min of 0 and a fixed inclusive max of RAND_MAX. std::uniform_int_distribution's constructor takes an inclusive min and inclusive max.

Java (Random.nextInt(int)) and C# (Random.Next(int)) use exclusive max, with a fixed min of 0. C# supplies Random.Next(int min, int max) (inclusive min, exclusive max). Additionally, Java supplies Random.nextInt() which returns any random 32-bit integer (AFAICT, this means the value could be negative and is inclusive from Integer.MIN_VALUE to Integer.MAX_VALUE).

php has rand() and rand(int min, int max). For the former, min is defaulted as 0 and max is defaulted as getrandmax(). Both functions use inclusive min and inclusive max.

There really isn't a general consensus in the programming community of what range "random" should return, even if both min and max are explicit. In AGS, one can achieve an inclusive range [min, max] by using:

Code: ags
int rand = Random(max) + min;


Which is comparable to the same code in other languages. Some languages you may have to specify max + 1 to make max inclusive, but your statement about the design of AGS' Random seems a bit melodramatic.

dayowlron

I like Snarky's reply and code snippet, however I do have 2 comments about the function initClientData.
1) There is no need to have a variable "i" at all and (2) you can't redeclare the same variable in a single function.
I would change it to:
Code: ags

void initClientData()
{
  maleNames[0] = "Dave"; 
  maleNames[1] = "Bob"; 
  maleNames[2] = "Ranesh"; 
  // ...
 
  femaleNames[0] = "Alicia"; 
  femaleNames[1] = "Roberta"; 
  femaleNames[2] = "Jane"; 
  // ... 
}

Pro is the opposite of Con                       Kids of today are so much different
This fact can clearly be seen,                  Don't you know?
If progress means to move forward         Just ask them where they are from
Then what does congress mean?             And they tell you where you can go.  --Nipsey Russell

Snarky

The arguments for exclusive Random() range are:

-It's much more intuitive for Random(x) to provide a range of x possible values than x+1 ones. The first post in this thread demonstrates that.
-Consistency with array declarations. It's idiotic to define the function so that...

Code: ags
int array[ARRAY_SIZE];
int index = Random(ARRAY_SIZE);
int x = array[index];


... will be buggy.

So in languages that are 1-indexed, it's reasonable for random(x) to give values from 1 to x. In languages that are 0-indexed, it should be 0 to x-1.

dayowlron:

I think using incremental i-indexing is better than hardcoding the index value because (a) when you copy-paste the lines it's less to change (and less potential for making a mistake), and (b) it's easier to edit the initialization after the fact, e.g. if you want to remove a name in the middle of the list you can just delete the line without it leaving a gap in the array â€" and in other cases you might want to insert things in the middle of the list (e.g. if you're listing the names somewhere in the game), which is also much easier. (Also it reduces the temptation to write hacks where you refer directly to an entry by hardcoding the index reference.)

Of course you're right that you can't redeclare the variable â€" for the second iteration it should just be set to 0. I'll edit the sample

bulka_tarta

#6
Thank you very much for the replies! Most of this makes perfect sense, but I have a few additional questions/problems.

- I don't understand how "Gender gender;" works - are you basically adding a new property to the struct Gender? Can you not simply declare it in the struct itself?

- I thought a little bit more about how to handle the people in queue. I think that the queue is nothing else than just a representation of quests in the game. I'm thinking of creating a quest struct which will determine all the info I need for the character.

What I mean is: imagine a card game where each card is a quest. When you start the game, you have all the cards available. To generate the "queue" you draw 3 cards each turn at random from the card pile. In the game, the card is represented with a person in the queue, so at the start of the day/turn you draw the cards (people) into the queue and you deal with them one by one.

At first I thought that the person in the queue determines what quests you should have, but after Snarky's post I think it's better to let the quests determine the person it needs to display.

Code: AGS

// new module header
#define MAX_QUESTS 10
struct Quest {
  int questID; //to help determine which quest is picked, not sure if necessary?
  String gender; //I guess this one shouldn't be a string, but the "Gender gender thing" Snarky used in his example?
  String name; //to pick random name from the list (like from Snarky's sample)
  String surname; //same here

  //things below would probably need to be hard-coded 
  String title; //quest title
  String description; //quest description
  int objective; //sample quest would be: get 5 carrots, make a burger etc. not exactly sure how to store this later on
  int dialogueID; //which dialogue to load
  int timeLimit; //how many turns to complete
};
import Quest quests[MAX_QUESTS];


Objective is a tricky one: I imagine it could be an int, but not exactly sure how to determine that you need for example 5 carrots, or tomatoes? Ideally in game you would see: Get 5 carrots. So would I just put these in the struct: "int objectiveNumberOf;, String objectiveItemType;". Later in the quest-log display: "lblObjective.Text = String.Format("Get %d %s", objectiveNumberOf, objectiveItemType);".

I guess I have an idea of setting everything up now, the problem is how to actually call the things in the game. I understand the "void generateClient(int index)" part, but I can't think of a good way to actually use it when the person shows up in the queue. Currently I'm thinking of having something like "CurrentQuest Struct" that will access all the info from the pre-defined quest and which is determined at the beginning of the day, but even then I feel like I'm missing some programming knowledge to fill in the gap from setting up the names, quests etc and later to accessing them in the game. How do you spawn the character with all his info to the room? Would I do "generateClient()" at the beginning of the turn, but tell that void to also show the character on screen, move it etc?


I understand that this suddenly became a lot larger thing than I initially asked, so I will keep looking through the forums and see what I can put togehter.

Khris

Just to address "Gender gender;":
Snarky declared an enum called Gender. It's basically an integer.

So instead of
Code: ags
  int gender; // 0: male, 1: female

you use
Code: ags
  Gender gender;

And instead of
Code: ags
  person[0].gender = 0; // male

you use
Code: ags
  person[0].gender = eMale;


It's just to make code more readable, since instead of having to use meaningless numbers you can use descriptive words.

AGS has lots of built-in enums; eBlock and eModeInteract are part of enum definitions.

Snarky

I think this goes to show once again that the right way of doing something depends very much on the precise details of what you want to do. And you're being vague about some of the important points. For example:

Quote from: bulka_tarta on Sat 08/07/2017 12:10:02
What I mean is: imagine a card game where each card is a quest. When you start the game, you have all the cards available. To generate the "queue" you draw 3 cards each turn at random from the card pile. In the game, the card is represented with a person in the queue, so at the start of the day/turn you draw the cards (people) into the queue and you deal with them one by one.

At first I thought that the person in the queue determines what quests you should have, but after Snarky's post I think it's better to let the quests determine the person it needs to display.

If you draw quests/cards from a stack, that implies that the quests are statically pre-defined, and it's only a matter of choosing some at random. That's very different from dynamically generating them at random. At various times you've given different impressions of what you actually want there.

Do you want to...

a. Custom-create a bunch of quests, like:
-1. Bob's Quest: pick 10 carrots, 20 potatoes, 3 sprigs of parsley (10 turns)
-2. Liza's Quest: collect 5 gallons of milk and 15 eggs (5 turns)
-3. Ezra's Quest: ...
-4. Danielle's Quest: ...
- ...
-18. Ivan's Quest: ...
-19. Helen's Quest: ...
-20. Omar's Quest: catch 3 chickens and pick 5 onions (8 turns)

... and now when you play, draw three at random; say 2, 4 and 18, so you get Liza, Danielle and Ivan's quests. Or...

b. Dynamically generate the quests. So you:

- Pick a name at random [... Ezra] and any other personal characteristics [... character sprite/view]
- Pick a recipe at random [... 3 eggs, 1 gallon of milk, 2 oz flour], and an amount [... x2]
- ... pick any other relevant characteristics at random [deadline: ... 5 turns]

... for each of the quests.

In this second case, you probably shouldn't create them ahead of time and keep them in a list, but only generate them as they come up.

Quote from: bulka_tarta on Sat 08/07/2017 12:10:02
- I don't understand how "Gender gender;" works - are you basically adding a new property to the struct Gender? Can you not simply declare it in the struct itself?

"Gender" is not a struct, it's an enum (enumeration). To add to Khris's explanation, an enumeration is a predefined list of options. For example:

Code: ags
enum Direction {
  eUp,
  eDown,
  eLeft,
  eRight,
  eForward,
  eBackward
};


Enums are good when you need to pick a single option from a relatively short list of possible (usually mutually exclusive) choices known in advance (and when the choices aren't themselves numbers or just true/false). The way we're handling gender here (where we're ignoring anything other than plain male/female), an enum is the best choice. Another alternative is to redefine it as a yes/no question: bool isMale. However, as recently discussed in another thread, using an enum tends to be more readable ("eFemale" can be understood out of context, while "false" requires you to see what it's being used for).

It's usually a bad idea to use a String for anything other than text. There are several reasons for that, but an important one is that if you make a typo, there's no way for the program to catch it. If you use an enum and accidentally type "eMae", the compiler will throw an error, but if you use a string and type "Male" instead of "male", there won't be any warning that anything's amiss â€" there will just be a mysterious bug in your game and you have to search through your code and hope you spot it yourself.

bulka_tarta

Thank you for the explanation on the enums and I apologise for the confusions and general vagueness. I didn't want to just drop everything at once in the thread, and I thought I can take care of the queue sprites separate from the quests, and figure out the quests at later time (trying to solve the problems one at a time).

Regarding the quests, they would be pre-defined (so like "a"), but I hoped that some parts (like the name or the view) could be picked from random ("b"), hence my initial post was talking about generating people at random. Would this not be efficient thing to do (from programming point of view) and should I just stick to one of the methods (a or b)?

Kitty Trouble

Quote from: monkey0506 on Thu 06/07/2017 12:49:27

There really isn't a general consensus in the programming community of what range "random" should return, even if both min and max are explicit. In AGS, one can achieve an inclusive range [min, max] by using:

Code: ags
int rand = Random(max) + min;


Shouldn't the code be

Code: ags
int rand = Random(max-min) + min;


instead of what you wrote? So for example A range of 0-2 inclusive would be Random(2) + 0 while 1-2 inclusive would be Random(1) + 1

monkey0506

Quote from: Morgan LeFlay on Sat 08/07/2017 14:55:42Shouldn't the code be

Code: ags
int rand = Random(max-min) + min;

Yeah, that's right. Sorry, I was getting carried away focusing on... well, anyway. Yes.

Snarky

Quote from: bulka_tarta on Sat 08/07/2017 14:35:45
Regarding the quests, they would be pre-defined (so like "a"), but I hoped that some parts (like the name or the view) could be picked from random ("b"), hence my initial post was talking about generating people at random. Would this not be efficient thing to do (from programming point of view) and should I just stick to one of the methods (a or b)?

Sure. It all depends on the effect you want.

bulka_tarta

Sigh...
I worked on this over the last weekend and after work during weekday - I just can't seem to figure it out. I tried asking around on the AGS discord chat, and every time I thought "I got this!" I actually didn't...

Anyway, I think I nailed down exactly what I need to do and how things should work. I want to set up a pre-defined quest in a struct, later when you click "new day" button, it generates the clients for you. Each person has a quest "assigned" to it (randomly from the list). It seems to me that I'm just missing a way to connect the quests to the characters, here's what I mean.

Code: ags

//I created the struct for the quests, just including the most important info
struct Quest{
    int view;    //how the character looks
    String name;    //name of the character
    String title;    //title of the quest (to be displayed in quest log later)
    String description;    //same as title
    ???    objective;    //no idea how to determine these
    int dialogueID;    //dialogue ID number determines which dialogue should be loaded for the quest
};
import Quest quests[MAX_QUESTS];


In game_start, I call all the quest structs with views, names (picked random from list) and titles hard-coded in (as the quests are pre-defined, I imagine hard-coding them is not considered "bad"?)

I then made another struct, which is basically a duplicate of the Quest struct, but it's used to select the quest from list at random and store information from the quest.

Code: ags

//the struct is exactly the same as quest one
//I'm calling the function when "new day" button is pressed:
StoreQuest(Random(MAX_STORED_QUESTS));

//and here's the set up of the StoredQuests
void StoreQuest(int index){
  int i = 0;
  index = Random(MAX_STORED_QUESTS);
    storedQuests[i].view = quests[index].view; //index ideally sets the quest from the list at random
    storedQuests[i].name = quests[index].name;;
    storedQuests[i].title = quests[index].title;
    storedQuests[i].description = quests[index].description;
    storedQuests[i].dialogueID = quests[index].dialogueID;
    i++;

    index = Random(MAX_STORED_QUESTS);      
    storedQuests[i].view = quests[index].view;
    storedQuests[i].name = quests[index].name;;
    storedQuests[i].title = quests[index].title;
    storedQuests[i].description = quests[index].description;
    storedQuests[i].dialogueID = quests[index].dialogueID;
    i++; 
    etc...


I then created code (a bit buggy at the moment) to create the queue when you click "new day":
Code: ags

//makes people queue up
function QueueUp(){
  int index = 1;
  int qID;
  int idealSpot = 110; //x value for ideal standing spot in queue
  int reduceSpot = 0; //reduce the ideal spot
  
  
  while(index <= MAX_CHARACTERS){    
    characterValue[index] = storedQuests[qID].dialogueID;    //ideally assigns dialogue (and other info) to this character
    
    character[index].ChangeRoom(1, -8-reduceSpot, 195); //spawns character in
    character[index].Walk(idealSpot-reduceSpot, 195, eNoBlock, eAnywhere); //moves it to the position, with each character it's a bit more to the left
    
    index++;
    reduceSpot += 17;
  }
}


So I got the queue working - people are spawning, even moving to correct spots... The problem is when you click on the client to talk to them:
Code: ags

function ComeForward(this Character*){
  this.Walk(160,180, eBlock, eAnywhere); //walks up to the counter
  dialog[storedQuests[qID]].Start();    //ideally the characters quest should start*
}


*This should ideally recognize that character B has a dialogue from the storedQuests struct, but instead it appears to always give me the same dialogue ID depending on the number of the person in the queue. So for example, character A always gives me dialogue01, character B always has dialogue02 and so on. Ideally, when you select character B from the queue, it should have the dialogue that was assigned to him at the QueueUp stage - so something random, not dialogue02 every time.


Hopefully someone can make some sense out of all this. I feel like if I can find a way to assign all the necessary info to each person, I should be able to move on fairly easily with the extra fluff I have in mind. As always, any suggestions are extremely appreciated, but for now I will try to dig deeper into this programming madness. Thanks in advance!

Snarky

#14
OK, cool: I see what you're trying to do, and it looks like you're making good progress.

There are a few points where the code needs to be fixed or can be streamlined so it's easier to work with, so let's take them one by one:

First, it's not a good idea to have two structs that duplicate the same information. Instead, you should split it so that the first struct only has the "generic" quest information (so no fields for the character names, for example), and the second has the information for that specific assigned quest. Then to you want to reference the struct that already has the generic information. Something like this:

Code: ags
struct Quest
{
  String title;
  String description;
  int dialogueId;
  // ??? objective; // Let's leave this out for now and get back to it later
};
import Quest quests[MAX_QUESTS];

struct StoredQuest
{
  Character* client; // The character that gave you the quest
  int questId; // This is an index into quests[]
  int startTime; // The quests have to be done by a deadline, right? So we need to keep track of when the clock starts ticking
};
import StoredQuest storedQuests[MAX_STORED_QUEST];


And now when you initialize it, you just do this (there's a logic error in your code, you've switched i and index, that probably explains the problem you were seeing):

Code: ags
void SetupStoredQuests()
{
  int i=0; // BTW, arrays start from 0, so you should always initialize indexes to that value
  while(i < MAX_STORED_QUESTS)
  {
    int questIndex = Random(MAX_QUESTS-1);   // Pick a quest at random
    storedQuests[i].questIndex = questIndex;

    storedQuests[i].client = characters[i + QUEST_CHARACTERS_START]; // See below

    storedQuests[i].startTime = timeCounter; // <-- Some variable where you're keeping track of the current time - if the timer on the quest only starts e.g. when you talk to the character, you would put this line there instead; in that case you probably also need a flag to check whether the quest is active - something like "bool isQuestActive;"

    i++;
  }
}


As you see, I've taken out all the references to names and views, because they're really not features of the quest. Instead there's just a reference to a character, which is set using the built-in characters[] array. I'm assuming you've set aside a certain range of characters to be the quest "clients", with MAX_CHARACTERS character slots starting from the index QUEST_CHARACTERS_START.

So instead of storing the names and views in the quest structs, we just assign it directly to the characters:

Code: ags
void SetupQuestCharacters()
{
  int i=0;
  while(i < MAX_CHARACTERS)
  {
    // TODO: the thing where you pick a random name and view, perhaps depending on gender
    characters[QUEST_CHARACTERS_START + i].Name = nameYouJustPicked;      // placeholder
    characters[QUEST_CHARACTERS_START + i].ChangeView(viewYouJustPicked); // placeholder
    i++;
  }
}


You can call this on game start (if you want to use the same characters throughout the game), or at the start of each day, just before SetupStoredQuests(), to get "new" characters each day. If you only call it on game start, the way it currently works the queue will always have the same characters in the same order, every day. You could simply randomize it in SetupStoredQuests(), but that might mean that you get several quests from the same person, and I'm not sure you want that. Picking at random without duplicates (effectively a shuffle) is a bit trickier, so let's not go into it unless necessary.

Now let's decide on how you actually link the quest to the "objective" (I assume that by this you mean the win-condition: what you have to do to complete the quest). And if I understand your examples from before correctly, all the quests ultimately come down to collecting X instances of item A, Y instances of item B, and so forth.

If that's correct, I think there are two good alternatives here.

The easiest (but least extendable) is to have a piece of code somewhere that checks if the quest has been successfully completed:

Code: ags
bool checkQuest(int storedQuestIndex)
{
  int questIndex = storedQuests[storedQuestIndex].questIndex;
  int startTime = storedQuests[storedQuestIndex].startTime;
  if(questIndex == 0)
  {
    // Collect 10 carrots, 20 potatoes, 3 sprigs of parsley within 10 turns
    return (player.InventoryQuantity[iCarrot.ID] >= 10 &&
            player.InventoryQuantity[iPotato.ID] >= 20 &&
            player.InventoryQuantity[iParsley.ID] >= 3 &&
            timeCounter <= startTime + 10);
  }
  else if(questIndex == 1)
  {
    // Collect 5 gallons of milk, 15 eggs within 5 turns
    return (player.InventoryQuantity[iMilkGallon.ID] >= 5 &&
            player.InventoryQuantity[iEgg.ID] >= 15 &&
            timeCounter <= startTime + 5);
  }
  else if(questIndex == 2)
  {
    // etc.
  }
  // ... all the way to MAX_QUESTS - 1
}


This allows us to call checkQuest(x); to see if you've fulfilled the objective of a certain quest.

Now the "objective" is hard-coded into this function. You don't need to represent it in the Quest struct: just match the questIndex with the corresponding entry in quests[]. The advantage of this is that it's pretty simple to code. The drawback is that you're spreading out the information in different places, hard-coding values, and that if you need to use some of the same stuff elsewhere (e.g. to subtract the stuff you're using from your inventory), you'll end up duplicating the same code.

The alternative is to put the information directly into the Quest struct. I'll assume that all the ingredients exist as inventory items, and let's say that each quest can only have MAX_INGREDIENTS different ingredients:

Code: ags
struct Quest
{
  String title;
  String description;
  int dialogueId;

  InventoryItem ingredients[MAX_INGREDIENTS]; // Or possibly "InventoryItem* ingredients[MAX_INGREDIENTS];" - I forget whether you put the asterisk when you declare an array
  int ingredientQuantity[MAX_INGREDIENTS];
  int deadline;
};


Now when you initialize the quest, you simply specify the ingredients, amounts, and the deadline to complete the quest:

Code: ags
  // ...
  quests[i].ingredients[0] = iCarrot;
  quests[i].ingredientQuantity[0] = 10;
  quests[i].ingredients[1] = iPotato;
  quests[i].ingredientQuantity[1] = 20;
  quests[i].ingredients[2] = iParsley;
  quests[i].ingredientQuantity[2] = 3;
  quests[i].deadline = 10;
  // ...
  i++;

  // ...
  quests[i].ingredients[0] = iMilkGallon;
  quests[i].ingredientQuantity[0] = 5;
  quests[i].ingredients[1] = iEgg;
  quests[i].ingredientQuantity[1] = 15;
  quests[i].deadline = 5;


This way, the objective is stored directly in the quest. We can now write a general version of the checkQuest() function:

Code: ags
bool checkQuest(int storedQuestIndex)
{
  int questIndex = storedQuests[storedQuestIndex].questIndex;
  int startTime = storedQuests[storedQuestIndex].startTime;

  // Check if deadline has passed
  if(timeCounter > startTime + quests[questIndex].deadline)
    return false;

  // Check that we have enough of each ingredient
  int i=0;
  while(i < MAX_INGREDIENTS)
  {
    InventoryItem* ingredient = quests[questIndex].ingredients[i];
    if(ingredient != null && player.InventoryQuantity[ingredient.ID] < quests[questIndex].ingredientQuantity[i])
      return false;
    i++;
  }

  // Everything checks out!
  return true;
}


Actually, that's not really much more complicated than hard-coding it. So definitely do this instead!

I think the bug you were having should be solved by the fix to SetupStoredQuests(), so hopefully this will more or less get you there.

bulka_tarta

#15
Thank you Snarky!

I understand most of the code from your samples and I can see how it's mean to work. There's one part I still fail to grasp unfortunately - SetupStoreQuests() and how it's meant to be used. How do I go about making use of "Character* client;"? And if I understand correctly, the storedQuests[].questID is meant to help determine which quest needs to be used/referenced ("index to quests[]"). But how do I determine the index to storedQuests[].questID?

Code: ags

// The problem is when I click the character, 
// I want to call the dialogue from "his" quest:
    dialog[quests[storedQuests[???].questID].dialogueID].Start();


All the array-ception is giving me a headache >:(


Also, if I understand correctly, the bool to check the quests, is being used as a function? So I can just call "checkQuest(1)" to run the entire thing at once?


EDIT:

Would I be correct to use this:
Code: ags

function ComeForward(this Character*){
  dialog[storedQuests[this].questID].Start();

//alternatively:
  dialog[storedQuests[this].client].Start();
}


If so, don't they both kind of do the same thing?

Snarky

#16
My SetupStoredQuests() is meant as a replacement for your void StoreQuest(int index), which is the one part of your code I believe is actually wrong in a serious way.

What it does is simply to set up the storedQuests[] array, in this way:

For each StoredQuest, it
-picks a Quest at random; this is the quest/task that will be given
-picks a character to be the "quest giver" or client, from the subset of characters that have been designated as clients. In the example, it just picks characters in order (first stored quest by first character, second by second, etc.), but this can be changed if needed
-sets the time when the quest starts to the current time

(Note that there's a mistake in my script sample, where I call the built-in character array characters[] instead of character[].)

To use the Character* client field, you do something like:

Code: ags
  storedQuests[storedQuestIndex].client.Walk(idealSpot-reduceSpot, 195, eNoBlock, eAnywhere);


Protip: In order to make the code readable, it's often better to create a temporary variable:

Code: ags
  Character* client = storedQuests[storedQuestIndex].client;
  client.Walk(idealSpot-reduceSpot, 195, eNoBlock, eAnywhere);


You can see how this helps particularly if you have many commands for the client Character.

Quote from: bulka_tarta on Thu 13/07/2017 20:22:54And if I understand correctly, the storedQuests[].questID is meant to help determine which quest needs to be used/referenced ("index to quests[]"). But how do I determine the index to storedQuests[].questID?

Well, if I understand your game right, every day customers line up in a queue, and you have to serve them one by one by fulfilling their "quests"? Then storedQuests[] is effectively your queue, and you have to keep track of where in the queue you are using a variable.

So if you're serving the fifth customer, this variable should be 4 (because we start from 0), and you'd be looking up storedQuests[4].questId to see what the quest is.

Quote from: bulka_tarta on Thu 13/07/2017 20:22:54
Code: ags

// The problem is when I click the character, 
// I want to call the dialogue from "his" quest:
    dialog[quests[storedQuests[???].questID].dialogueID].Start();


All the array-ception is giving me a headache >:(

What you have there is correct once you fill in the ???, but yeah, it gets very messy. Like I said, it's often easier to break the array lookup into several steps by using some intermediate variables:

Code: ags
int currentQueueNumber; // How far into the queue we are

{
  // ...
  int questId = storedQuests[currentQueueNumber].questId;
  int dialogueId = quests[questId].dialogueId;
  dialog[dialogueId].Start();
}


Quote from: bulka_tarta on Thu 13/07/2017 20:22:54Also, if I understand correctly, the bool to check the quests, is being used as a function? So I can just call "checkQuest(1)" to run the entire thing at once?

The "bool" is a function. Usually when you declare functions you put the return type (the kind of value it outputs) before the function name. So checkQuest() is a function that returns true or false. Specifically, checkQuest(n) tells you whether all the conditions for storedQuest[n] have been met. So checkQuest(1) would check StoredQuest 1 (which is the quest for the second person in line, since again, we start counting at 0). These code samples don't have a way to check all the quests in one go (though that would be simple to implement). Since you were talking about a queue, I have been assuming you would be dealing with the quests one at a time, so there'd be no need.

Quote from: bulka_tarta on Thu 13/07/2017 20:22:54Would I be correct to use this:
Code: ags

function ComeForward(this Character*){
  dialog[storedQuests[this].questID].Start();

No, you were right the first time around; this, on the other hand, won't work. The character has nothing to do with the dialog, and you cannot use a Character pointer ("this") as an index into an array.

In fact, this might point to a basic limitation of this approach. Were you planning that the dialog would be held between the player and whoever the character is that's being served? I don't think that's possible, since in the dialog script you have to hard-code the character name. For that to work, you would need to link each quest directly to a specific character after all.

However, I wonder if you need to be using a dialog at all. Unless you're doing a dialog tree with multiple options, there's really no need for it, you can just use Character.Say() commands. Also, it might be possible to generate the dialog automatically from the quest details. So what do these dialogs look like?

Quote from: bulka_tarta on Thu 13/07/2017 20:22:54
Code: ags

//alternatively:
  dialog[storedQuests[this].client].Start();
}


If so, don't they both kind of do the same thing?

No, they really don't, aside from the fact that they're both illegal. The quest (the task you have to complete) and the person giving you that task are two different things, and neither of them are the thing you need to look up the correct dialog.

SMF spam blocked by CleanTalk