Viable looting system?

Started by lafouine88, Sun 25/02/2024 21:31:17

Previous topic - Next topic

lafouine88

Hi guys, some more RPG issues here :) This is more of a logical/structural question than coding I guess.

I've been struggling dor the past two weeks with a good looting system, meaning something flexible and not to greedy in ressource (I don't want to create 300 Invitems).
So basically this was my idea (I'll try not make it simple but can be more accurate if needed):

30 inventoryitems with no sprites or anyhting else, kind of the matter that would embody the different items (in the example lets say it correponds to 5 max equipped item+15 max items in the bag+5 max loot items+5 extras)

1 struct of arrays
Code: ags
Struct loot{
int sprite;
String Nameitem;
int bonus;
...
}
describing the icon,name etc of all the items. This one on the other hand could be big since I guess it doesn't take too much memory, say 200 different items (this is ambitious "^^).
so we have for instance :
loot[1].sprite=1;              loot[2].sprite=2;
loot[1].Nameitem=Blade;        loot[2].Nameitem=Shield;
loot[1].bonus=5;               loot[2].bonus=3;
....

So far I guess it's quite clear. Now this is where it becomes confused, and not so good :
Ennemies are defined with the same method
Code: ags
  ennemis[k].HP=200;
//...
and I added these lines
Code: ags
    ennemis[k].loot1=0;
  ennemis[k].loot2=0;
  ennemis[k].loot3=0;
  ennemis[k].loot4=0;
  ennemis[k].loot5=0;
  ennemis[k].baseloot=0;

Then,at map load, I attribute the loot to the different mobs with a function randomingly giving them the items with given probabilities.
Code: ags
attribuloot(k, 40,60, 100, 20, 20); //-->so 40% to have item1, 60% to have item 2...

So when the map is loaded, each munster has something like this :
ennemi[0].loot1=0;          ennemi[1].loot1=0;    ... 
ennemi[0].loot2=1;          ennemi[1].loot1=0;
ennemi[0].loot3=0;          ennemi[1].loot1=1;    ...
ennemi[0].loot4=0;          ennemi[1].loot1=1;
ennemi[0].loot5=1;          ennemi[1].loot1=0;

Anf finally when I loot the monster another function gives the image and attributes to the invitem based on the loot struct:(this becomes really messy :s)

Code: ags
  function Loot_attribution(Character*corps, int l){
    //l number of first loot item
//inventaireused is a global int to keep track of the number of inventory items I've used.
    nmobjets=ennemis[corps.ID].loot1+ennemis[corps.ID].loot2+ennemis[corps.ID].loot3+ennemis[corps.ID].loot4+ennemis[corps.ID].loot5;//how many more inv items are we using
   if(ennemis[corps.ID].loot1==1){
  corps.AddInventory(inventory[inventaireused]);//adds the next unused item inventory to the mob
   inventaireused++;
   inventory[inventaireused].Graphic=butin[l].sprite;    //gives the attributes
   inventory[inventaireused].CursorGraphic=butin[l].sprite;
   Lblloot1.Text= String.Format("%s",butin[l].Nomobjet);    //this label is next to the item to display its name
   Lblloot1.TextColor=butin[l].couleur;
   Lblloot1.Visible=true;
   }
      if(ennemis[corps.ID].loot2==1){
        corps.AddInventory(inventory[inventaireused]);
   inventaireused++;
//and same fo the 4 other items

In clear, each type of monster could carry a maximum of 5 items, and for each monster all 5 items must be defined each time, this is what the 'l' variable is here for-->monster type1 can loot loot[0],loot[1],loot[2],loot[3],loot[4] (l==0).monster2 can loot loot[5]...loot[9](l==5) etc. This is particularly bad because if i want monster1 to be able to carry loot[6] I must define this item twice, more if I want this item to be carried by more monsters. It seems like a huge waste of space...

Besides the whole thing is actually extremely heavy to manipulate and I start to get lost when I want to add items descriptions or some kind of icon overlay when the mouse is over it. When I started to make the items pickable this became nuts and I realized there might be some easier way to do this. problem is that now my mind is stuck on this idea and I can't find anything else X3

Would some of you have some suggestions I could use? I can give more details if needed but I feel like I've already given too many and lost most of you "^^

Thanks


eri0o

From the code it also looks like there is a mix of the AGS inventory system and a different one that was coded from scratch.

It's not clear in your system what concept means what, and it also appears that your functions are doing way too many things at the same time.

I would look into what is the minimum I need to make it work and work from scratch to up.

Say, an item can be either usable or "equipable", and it can do an effect in some status (if it's equipable this effect is continuous, usable, it happens once and the item is lost). The item would probably also require a name and description. You could make then this item concept using either some struct or adding properties to inventory items - it depends how you want to structure things.

Now let's say loot is what a monster can drop, you need to come up with what sort of rules you want for this, either a specific monster gets a list that is the pairs of item name and probability, or a monster type gets such a list.

And so on. Once the concepts are well defined, I would write the code with such separations.


Khris

#2
I'd use what's called "third normal form" in database theory.

You have a list of numbered enemies and a list of numbered items, so to assign an item to an enemy, you use a 3rd list.

Code: ags
// header
#define MAX_ENEMY_ITEMS 1000

struct str_enemy_item {
  int enemyId;
  int itemId;
  int count;
}

// main
str_enemy_item enemy_item[MAX_ENEMY_ITEMS];
int enemy_items;

int EiLookup(int enemyId, int itemId) {
  int i;
  for (i = 0; i < enemy_items; i++) {
    if (enemy_item[i].enemyId == enemyId && enemy_item[i].itemId == itemId) return i;
  }
  if (i == MAX_ENEMY_ITEMS) {
    Display("MAX_ENEMY_ITEMS limit reached");
    return -1; // this will cause an out of array bounds error
  }
  // i is now the first free list entry
  // list entry doesn't exist yet so create it
  enemy_item[i].enemyId = enemyId;
  enemy_item[i].itemId = itemId;
  enemy_items++;
  return i;
}

To add item #5 to enemy #3, you can now do:
Code: ags
  enemy_item[EiLookup(3, 5)].count++;

lafouine88

Wow... thanks Khris...This seems good...but to be honest I don't understand it all "^^Sorry I'm not used to reading someone else's code and I realize I also don't have all the tools to understand this.

So line by line, could you tell me if I got things wrong?
Code: ags
// header
#define MAX_ENEMY_ITEMS 1000  //easier to change the settings without hunting for pieces of code-->clever^^

struct str_enemy_item {
  int enemyId;          //self explanatory 
  int itemId;           //self explanatory 
  int count;            //number of items in the ennemy's inventory?
}

// main
str_enemy_item enemy_item[MAX_ENEMY_ITEMS];    //1000 of the struct above
int enemy_items;                               //tricky cause the name is similar but is the number of inventoryitems slots already used

int EiLookup(int enemyId, int itemId) {
  int i;                                        //this i is the one I don't really get...
  for (int i = 0; i < enemy_items; i++) {
    if (enemy_item[i].enemyId == enemyId && enemy_item[i].itemId == itemId) return i;  //and this line here is too tricky for me. I guess the idea is to check what is the next available slot on the list.
  }
  if (i == MAX_ENEMY_ITEMS) {
    Display("MAX_ENEMY_ITEMS limit reached");
    return -1; // this will cause an out of array bounds error---->ok
  }
  // i is now the first free list entry
  // list entry doesn't exist yet so create it
  enemy_item[i].enemyId = enemyId;
  enemy_item[i].itemId = itemId;
  enemy_items++;
  return i;
}
So the idea is to create a list that would be something like:
Code: ags
  enemy_item[0].enemyId == 0;
  enemy_item[0].itemId == 3;//-->invitem[0] takes the form of item[3] and is put in inventory of character[0]?
enemy_item[0].count==//? then I don't get this line :s
  enemy_item[1].enemyId = 3;
  enemy_item[1].itemId = 5;//-->invitem[1] takes the form of item[5] and is put in inventory of character[3]?
//...

Or did I get lost on the way?^^
Thanks again, and sorry for being slow to understand

Khris

You don't need to manually provide the index of the enemy_item[] array. The whole point of my approach is to never have to write code like in your 2nd snippet, at all.

When you want to change or read the number of items an enemy has, you always use
Code: ags
  enemy_item[EiLookup(3, 5)].count
like any other integer variable and simply enter the appropriate enemy id and item id. The function will look up the corresponding table entry and return the array index.

The for loop inside the function searches for a match; the i variable is simply the counter.

Say you want enemy[0] to have one each of item[2], item[6] and item[9]:
Code: ags
  enemy_item[EiLookup(0, 2)].count = 1;
  enemy_item[EiLookup(0, 6)].count = 1;
  enemy_item[EiLookup(0, 9)].count = 1;

That is all you need.

To iterate over an enemy's inventory you would use something like this:
Code: ags
function DoSomethingWithEnemyInv(int enemyId) {
  for (int i = 0; i < enemy_items; i++) {
    if (enemy_item[i].enemyId == enemyId && enemy_item[i].count > 0) {
      // has at least one of item[enemy_item[i].itemId]
    }
  }
}

lafouine88

OK! I wanted to give it a try and figure out on tests how it works, and what I can do with it. Problem is it tells me "i is already defined" and since I don't really get the whole function I can't correct it myself... :cry: sorry again"^^

Khris

Yeah, sorry, typo on my part.
The for line in the EiLookup function should not try to redeclare i:
Code: ags
  for (i = 0; i < enemy_items; i++) {

lafouine88

#7
@Khris Hello again, I spent the last days struggling with this system. It seems really promising but I still don't get it completely and it makes it more complicated to adapt it to what I want to do. I did what you suggested and this is what happens :

-at room start, when I configure my ennemies it goes like this :
Code: ags
function room_Load()
{
  //-wolf-------------------
  int k=0;
  while(k<10){///---first 10 characters are wolves
  ennemis[k].HP=20;
  ennemis[k].damage=2;
  ennemis[k].skin=17;
//....the rest of the attributes for this kind of mob
//so thats where I write this : meaning each wolf has item number 1.
  enemy_item[EiLookup(k, 1)].count++;
}

and then when you loot the ennemy, based on your second post I created this function to materialize the loot, meaning it creates the invitem from the characs I specified for each item:

Code: ags
  function Materialize_loot(int enemyId) {
    nmobjets=0;///---this is the number of items the ennemy carries, just another variable to adapt the size of the ennemy inventory, not relevant here
  for (int i = 0; i < enemy_items; i++) {
    if (enemy_item[i].enemyId == enemyId && enemy_item[i].count > 0) {
      character[enemyId].AddInventory(inventory[inventaireused+1]);   //gets the next free invitem
      inventory[inventaireused+1].Graphic=butin[i].sprite;            //takes the sprite from the struct butin,which carries all the characs
         inventory[inventaireused+1].CursorGraphic=butin[i].sprite;
         inventory[inventaireused+1].SetProperty("numero",i);         //I used this for the pickup action which goes later, to be able to remove the right item once it's picked up
         //player.Say("%d",i);                                           //debugging device I ll explain later
      
    }
    nmobjets++;///if there were more items to give, this counter goes up(to enlarge the size of the inventory)
    inventaireused++;///and this is the counter to know how many invitems I've used so far
  }
}

But the problem is (I found out using my "device : player.say) if I understand your code,"i" should be equal to 1 for each ennemy, because it corresponds to the number of the item I gave at room start. But instead it keeps going, meaning ennemy[0] has item no1 as asked, but ennemy[1]has item no2(and the player says i=2 instead of one) and so on until ennemy[9] who carries item n°10 and i=10...
so I must have missed a point in your code and don't use it correctly, or this is normal I just didn't configure my loot attribution right.

Something else is weird, when I use my function 'materialize_loot', it doesn't just attribute the invitems to the specific target, but to all the ennemies. Which is not what I understood from your explanations and creates chaos in the invitem management(when I loot one mob, it creates 10 invitems, if i kill another one it creates 9 more etc. when I would like it to create just one invitem for the target I'm currently looting)

Could you give me a hand here please? I'm reaching my coding limit here and I'm out of ideas :/ I can be more specifi if needed, obviously

Thanks again

Khris

Your Materialize_loot function doesn't use my code at all it seems. It also doesn't use your loot array.

Your original code had lines like this:
ennemi[0].loot1=0;
ennemi[0].loot2=1;
ennemi[0].loot3=0;
ennemi[0].loot4=1;
ennemi[0].loot5=0;

Translated into a table (think MS Excel), this is akin to having five columns in your enemy table to store what loot they have, and this doesn't even allow for also stating how many of each item they have.
I provided a way to get rid of these columns by using a third table/array, which also allows to put, say, 3 items of the same type into an enemy's inventory, instead of just one.

You also had
loot[1].Nameitem=Blade;

I assumed you would use my system like this:
enemy_item[Eilookup(0, 1)].count = 3; // enemy #0 has 3 of loot item #1 (Blade)

This means your Materialize_loot function has to iterate over your own custom loot array, use the index as the second parameter for Eilookup(enemyId, i) and use the result:

Code: ags
  int count = enemy_item[Eilookup(enemyId, i)].count; // enemy enemyId has "count" number of loot item #i

This count is now used to populate the actual inventory window by translating loot item #i into an actual AGS inventory item.

lafouine88

#9
Quote from: Khris on Sun 03/03/2024 16:49:15Your Materialize_loot function doesn't use my code at all it seems. It also doesn't use your loot array.
It doesn't in this function but it does at one point,and there is no problem with this part of the structure. at room load, it allocates each mob with its item using enemy_item[Eilookup(k, 1)].count = 1; // for each ennemy using the k counter
and then the materialize function uses the information to create the invitem caracs.
Code: ags
inventory[inventaireused+1].Graphic=butin[i].sprite; 
And i didn't write it all here but it also copies the properties etc. so there is no problem with this part.

The problem is, when I use
enemy_item[Eilookup(k, 1)].count = 1;
it doesn't seem toi give them the #1 item for each, but #1 for the first ennemy to be set(ennemy[0]) and then item#2 for the second ennemy and so on :confused: so I guess there is a misuse in the first part of my materialize function :

Code: ags
  for (int i = 0; i < enemy_items; i++) {
    if (enemy_item[i].enemyId == enemyId && enemy_item[i].count > 0) {
      character[enemyId].AddInventory(inventory[inventaireused+1]);   //gets the next free invitem
      inventory[inventaireused+1].Graphic=butin[i].sprite;  //--->because here 'i' is supposed to be 1 all the time to correspond the enemy_item[Eilookup(k, [b]1[u][/u][/b])].count = 1; , but instead it keeps getting higher 

You see what I mean? the 'materialize_item' function does translate as instructed and uses my butin[] settings as planned. But what I don't understand is why it doesn't create the right item.

Khris

Ok, I think I understand what the problem is.

Each time your loop finds an item (*any* item), it adds inventory[inventaireused+1] then increases inventaireused by one.
Naturally, this will simply iterate over all AGS inventory items of your game, regardless of what the enemy actually possesses.

However,
Code: ags
enemy_item[i].enemyId == enemyId && enemy_item[i].count > 0
means that the enemy possesses at least one of
Code: ags
loot[enemy_item[i].itemId]
which is one of your loot items.
So the next step would be to use the loot item's information and add the correct AGS inventory item to the enemy.

I'm not sure how you're getting from the info stored in
Code: ags
Struct loot{
int sprite;
String Nameitem;
int bonus;
...
}
to an actual AGS inventory item though.

lafouine88

Quote from: Khris on Sun 03/03/2024 21:39:07I'm not sure how you're getting from the info stored in
Code: ags
Struct loot{
int sprite;
String Nameitem;
int bonus;
...
}
to an actual AGS inventory item though.

Actually I mixed my code and the translation I did on the forum to make things more understandable for english speakers. This was a bad idea.
 so butin and loot are the same struct. So the materialisation into an invitem is quite easy using lines like :

Code: ags
inventory[inventaireused+1].Graphic=loot[i].sprite
inventory[inventaireused+1].SetProperty("Name",loot[i].Nameitem);
inventory[inventaireused+1].SetProperty("Strenght",loot[i].Strenght);
inventory[inventaireused+1].SetProperty("Agi",loot[i].Agi);
//etc.
And then I can use the property variables to affect my character's stats, this seems to work alright.

On the first part of your message I really feel like I'm doing exactly what you are saying :s I do use the .count int. and it seems to me that on our previous posts we say the same thing  ???
 So one last thing before I stop annoying you(I already posted this message but it seems that it got deleted) :

If I go :
Code: ags
enemy_item[EiLookup(2, 1)].count++;
does it entail that :
Code: ags
Materialize_loot(2); 
//-- with

  function Materialize_loot(int enemyId) {
  for (int i = 0; i < enemy_items; i++) {
    if (enemy_item[i].enemyId == enemyId && enemy_item[i].count > 0) {
      //this here will be true for i==1
      //to give values it means that : enemy_item[1].enemyId==2 && enemy_item[1].count>0
    }

Thanks again for your patience, I really feel like I understand what I'm supposed to do with your function but I miss something somewhere :/

Khris

Try this instead:

Code: ags
  for (int li = 0; li < 50; li++) {
    int count = enemy_item[Eilookup(enemyId, li)].count;
    if (count > 0) {
      // add loot[li] to inventory
    }
  }

lafouine88

YEEEEES that's what I was looking for. It's perfect!! Thanks a million for everything Khris <3<3<3

SMF spam blocked by CleanTalk