SOLVED Multiple invincibility frames, linked to timers

Feel

Member
Hi developers ! πŸ₯°

Before anything else, sorry i'm a bit bad with english, so i will try to do my best to explain what i try to do.. ! πŸ˜‡

Some context to begin :
-
I have multiple projectiles that cannot be destroyed. (permanent in the room)
- These projectiles belong to the player.
- The number of projectiles is unknow. (can be 1, 2, 3... or a way more 20+)
- They can (and must !) damage enemies.

This is where the trouble begins ... 🧠

Imagine we have 2 projectiles A and B, and 2 enemies Y and Z.

When projectile A hit enemy Y.
Y become invincible to projectile A for a short duration, but enemy Y still can be damaged by projectile B.
And projectile A still can hit enemy Z. (B can also hit Z).

If my reflexion is right (doubt is allowed πŸ˜†)
The idee it's to have a timer related to an id projectile.
If an enemy is hit by a projectile, he keep the projectile id, create timer related to it, and become imune to this id, until the timer ends.
But i rly don't know how to do that ...

I hope it's clear, i won't lie i've tried so many dumb things since ... more than 6hours now ... rofl, i just want to go take a nap right now.
Thank you if you had the courage to read so far .. πŸ˜„
 

SoapSud39

Member
You could do it with a (or some) DS list(s) to keep track of a list of instance ids.

Here's what I'm imagining:
For every enemy instance:
- use one DS list to keep track of projectile ids
- use another DS list to keep track of timers
- every time a projectile collision is detected, check whether the projectile id is on the first list
- if not, take damage and add the id to the list and add a timer to the second list (these are i-frames)
- if so, do nothing
- for the second list, every timer that exists in the list will count down
- every time a timer reaches 0, delete the list entry from the appropriate position on both lists
Now, you could use some normal arrays or maybe a DS grid, but DS lists get the benefit of built-in sorting and automatic shifting, and it's easy to just randomly add an entry to the list.

Here is some code I've written just now to go along with it (if you're one to take free code)
I don't know how familiar you are with DS lists, so I've included some helpful comments
GML:
///Enemy Object (or parent)
//Create
projectile_hit = ds_list_create(); //list of current unaffected projectiles //you can leave these empty
projectile_timer = ds_list_create(); //list of timers
projectile_collision = ds_list_create(); //to check for current step collisions

//Step
If place_meeting(x, y, obj_projectile) { //or other collision code
    var num_hit = instance_place_list(x, y, obj_projectile, projectile_collision, false);
        //this puts all currently colliding projectiles into a list so we can check it
        //num_hit is the count (function return)
    for (var i = 0; i < num_hit; i++) { //loop to check for id in current unaffected projectiles
        var proj_id = projectile_collision[| i]; //list[| i] to return value in list
        if ds_list_find_index(projectile_hit, proj_id) == -1 { //if not on unaffected projectiles list
            ds_list_add(projectile_hit, proj_id); //add id to list
            ds_list_add(projectile_timer, 20); //add i-frames length to second list
            hit_points -= 5; //take damage
        }    //else do nothing
    }
    ds_list_clear(projectile_collision); //clear this list for the next step
}
var num_inv = ds_list_size(projectile_hit); //number of bullets invincible to
for (var i = 0; i < num_inv; i++) { //loop to count down timers
    projectile_timer[| i] -= 1; //list[| i]= to change value in list
    if projectile_timer[| i] <= 0 {
        ds_list_delete(projectile_hit, i); //remove these entries from the list
        ds_list_delete(projectile_timer, i);
            //now the relevant projectile should do damage again
    }
}

//Clean Up (don't forget!)
ds_list_destroy(projectile_hit);
ds_list_destroy(projectile_timer);
ds_list_destroy(projectile_collision);
You'll have to test it. The two lists should line up, but of course I haven't tested it.
 

Nidoking

Member
You could do it with a (or some) DS list(s) to keep track of a list of instance ids.
A better idea might be a DS map: it's easy enough to check whether an instance ID is in the map, and you don't need to juggle pairs of anything. When the timer hits zero, you delete the map entry for that instance ID. I like to think of a map as effectively an array where the indices are anything other than low, consecutive numbers.

GML:
///Enemy Object (or parent)
for (var i = 0; i < num_inv; i++) { //loop to count down timers
    projectile_timer[| i] -= 1; //list[| i]= to change value in list
    if projectile_timer[| i] <= 0 {
        ds_list_delete(projectile_hit, i); //remove these entries from the list
        ds_list_delete(projectile_timer, i);
            //now the relevant projectile should do damage again
    }
}
This loop won't work properly - you can't manipulate the indices in the list while you're iterating from 0 to end. Either start at the last index and iterate backwards, or (ugh) manipulate the loop variables as you delete entries. Please do the first thing.
 

Feel

Member
You could do it with a (or some) DS list(s) to keep track of a list of instance ids.

Here's what I'm imagining:
For every enemy instance:
- use one DS list to keep track of projectile ids
- use another DS list to keep track of timers
- every time a projectile collision is detected, check whether the projectile id is on the first list
- if not, take damage and add the id to the list and add a timer to the second list (these are i-frames)
- if so, do nothing
- for the second list, every timer that exists in the list will count down
- every time a timer reaches 0, delete the list entry from the appropriate position on both lists
Now, you could use some normal arrays or maybe a DS grid, but DS lists get the benefit of built-in sorting and automatic shifting, and it's easy to just randomly add an entry to the list.

Here is some code I've written just now to go along with it (if you're one to take free code)
I don't know how familiar you are with DS lists, so I've included some helpful comments
GML:
///Enemy Object (or parent)
//Create
projectile_hit = ds_list_create(); //list of current unaffected projectiles //you can leave these empty
projectile_timer = ds_list_create(); //list of timers
projectile_collision = ds_list_create(); //to check for current step collisions

//Step
If place_meeting(x, y, obj_projectile) { //or other collision code
    var num_hit = instance_place_list(x, y, obj_projectile, projectile_collision, false);
        //this puts all currently colliding projectiles into a list so we can check it
        //num_hit is the count (function return)
    for (var i = 0; i < num_hit; i++) { //loop to check for id in current unaffected projectiles
        var proj_id = projectile_collision[| i]; //list[| i] to return value in list
        if ds_list_find_index(projectile_hit, proj_id) == -1 { //if not on unaffected projectiles list
            ds_list_add(projectile_hit, proj_id); //add id to list
            ds_list_add(projectile_timer, 20); //add i-frames length to second list
            hit_points -= 5; //take damage
        }    //else do nothing
    }
    ds_list_clear(projectile_collision); //clear this list for the next step
}
var num_inv = ds_list_size(projectile_hit); //number of bullets invincible to
for (var i = 0; i < num_inv; i++) { //loop to count down timers
    projectile_timer[| i] -= 1; //list[| i]= to change value in list
    if projectile_timer[| i] <= 0 {
        ds_list_delete(projectile_hit, i); //remove these entries from the list
        ds_list_delete(projectile_timer, i);
            //now the relevant projectile should do damage again
    }
}

//Clean Up (don't forget!)
ds_list_destroy(projectile_hit);
ds_list_destroy(projectile_timer);
ds_list_destroy(projectile_collision);
You'll have to test it. The two lists should line up, but of course I haven't tested it.
Hi !
Thank you very much for your time, i understand what you are trying to do, but i must admit, is a bit too high level for me,
i mean i could not write something like this my self yet, but i understand when i read your code !
I tried to implement in my game, and Nidoking say right

A better idea might be a DS map: it's easy enough to check whether an instance ID is in the map, and you don't need to juggle pairs of anything. When the timer hits zero, you delete the map entry for that instance ID. I like to think of a map as effectively an array where the indices are anything other than low, consecutive numbers.
This loop won't work properly - you can't manipulate the indices in the list while you're iterating from 0 to end. Either start at the last index and iterate backwards, or (ugh) manipulate the loop variables as you delete entries. Please do the first thing.
Even if i understand why use a DS map like Nidoking saied, i am too newbie to do it properly i think :confused:
I will try tho !
Thanks to you two for your time !

Here is my code, i run it in the endstep event of my projectile parent : (i need precise collision because projectile can sometimes going really fast)
GML:
with oParEnemy
{
    if collision_line(other.xprevious, other.yprevious, other.x, other.y, id, 0, 0) || place_meeting(x, y, other)
    {
        var num_hit = instance_place_list(x, y, other, projectile_collision, false);
        //this puts all currently colliding projectiles into a list so we can check it
        //num_hit is the count (function return)
        for (var i = 0; i < num_hit; i++) //loop to check for id in current unaffected projectiles
        {
            var proj_id = projectile_collision[| i]; //list[| i] to return value in list
            if ds_list_find_index(projectile_hit, proj_id) == -1 //if not on unaffected projectiles list
            {
                ds_list_add(projectile_hit, proj_id); //add id to list
                ds_list_add(projectile_timer, 60); //add i-frames length to second list
                /*
                    damage
                    effects
                    flash, ect ...
                */
            }//else do nothing
            ds_list_clear(projectile_collision); //clear this list for the next step
        }  
    }
    var num_inv = ds_list_size(projectile_hit); //number of bullets invincible to
    for (var i = 0; i < num_inv; i++)
    { //loop to count down timers
        projectile_timer[| i] -= 1; //list[| i]= to change value in list
        if projectile_timer[| i] <= 0
        {
            ds_list_delete(projectile_hit, i); //remove these entries from the list
            ds_list_delete(projectile_timer, i);//now the relevant projectile should do damage again
        }
    }  
}
The error message :
(line are not exact, i clean up a bit the code for the forum)
Code:
___________________________________________
############################################################################################
ERROR in
action number 1
of  Step Event2
for object oProj:

DoSub :2: undefined value
at gml_Object_oProj_Step_2 (line 43) -                      projectile_timer[| i] -= 1; //list[| i]= to change value in list
############################################################################################
gml_Object_oProj_Step_2 (line 43)
 
Last edited:

Nidoking

Member
This loop won't work properly - you can't manipulate the indices in the list while you're iterating from 0 to end. Either start at the last index and iterate backwards, or (ugh) manipulate the loop variables as you delete entries. Please do the first thing.
I was right when I said this too, and you didn't fix it. When a timer expires, you're iterating right past the end of the list and getting an undefined value.
 

Feel

Member
I was right when I said this too, and you didn't fix it. When a timer expires, you're iterating right past the end of the list and getting an undefined value.
Yes exactly ! Sorry i was a bit confuse, many things in mind 😢
I have done this, and it looks like it works in game, for now, but to be honnest i'am not 100% sure of what i've done ... πŸ‘€

GML:
    var num_inv = ds_list_size(projectile_hit); //number of bullets invincible to
    //show_debug_message("num_inv : "+string(num_inv));
    for (var i = num_inv; i > -1; i--)
    { //loop to count down timers
        show_debug_message(i);
        if projectile_timer[| i] >= 0
            projectile_timer[| i] -= 1; //list[| i]= to change value in list
        if projectile_timer[| i] <= 0
        {
            ds_list_delete(projectile_hit, i); //remove these entries from the list
            ds_list_delete(projectile_timer, i);//now the relevant projectile should do damage again
        }
    }
Does it looks correct to you ? πŸ˜‡
 

Geners

Member
Hi !
Thank you very much for your time, i understand what you are trying to do, but i must admit, is a bit too high level for me,
i mean i could not write something like this my self yet, but i understand when i read your code !
I tried to implement in my game, and Nidoking say right



Even if i understand why use a DS map like Nidoking saied, i am too newbie to do it properly i think :confused:
I will try tho !
Thanks to you two for your time !

Here is my code, i run it in the endstep event of my projectile parent : (i need precise collision because projectile can sometimes going really fast)
GML:
with oParEnemy
{
    if collision_line(other.xprevious, other.yprevious, other.x, other.y, id, 0, 0) || place_meeting(x, y, other)
    {
        var num_hit = instance_place_list(x, y, other, projectile_collision, false);
        //this puts all currently colliding projectiles into a list so we can check it
        //num_hit is the count (function return)
        for (var i = 0; i < num_hit; i++) //loop to check for id in current unaffected projectiles
        {
            var proj_id = projectile_collision[| i]; //list[| i] to return value in list
            if ds_list_find_index(projectile_hit, proj_id) == -1 //if not on unaffected projectiles list
            {
                ds_list_add(projectile_hit, proj_id); //add id to list
                ds_list_add(projectile_timer, 60); //add i-frames length to second list
                /*
                    damage
                    effects
                    flash, ect ...
                */
            }//else do nothing
            ds_list_clear(projectile_collision); //clear this list for the next step
        }
    }
    var num_inv = ds_list_size(projectile_hit); //number of bullets invincible to
    for (var i = 0; i < num_inv; i++)
    { //loop to count down timers
        projectile_timer[| i] -= 1; //list[| i]= to change value in list
        if projectile_timer[| i] <= 0
        {
            ds_list_delete(projectile_hit, i); //remove these entries from the list
            ds_list_delete(projectile_timer, i);//now the relevant projectile should do damage again
        }
    }
}
The error message :
(line are not exact, i clean up a bit the code for the forum)
Code:
___________________________________________
############################################################################################
ERROR in
action number 1
of  Step Event2
for object oProj:

DoSub :2: undefined value
at gml_Object_oProj_Step_2 (line 43) -                      projectile_timer[| i] -= 1; //list[| i]= to change value in list
############################################################################################
gml_Object_oProj_Step_2 (line 43)
Hey man, so when you delete an entry in the ds list the whole thing shifts

So if I have
0, 1, 2, 3
And I delete 2, whatever is in 3 will now move to 2


So when you go through a ds list you have to go backwards from the last entry so you don't accidently skip over or go too far in a ds list.

EDIT: oops nevermind you solved it but you need to subtract one from num_inv because a list is offset by 0
 

Feel

Member
Hey man, so when you delete an entry in the ds list the whole thing shifts

So if I have
0, 1, 2, 3
And I delete 2, whatever is in 3 will now move to 2


So when you go through a ds list you have to go backwards from the last entry so you don't accidently skip over or go too far in a ds list.

EDIT: oops nevermind you solved it
Hi !
Yes at first i did not have understood that, but i see what was the problem now, thank you for posting ! πŸ˜‡

EDIT: oops nevermind you solved it
Did I really do it ? πŸ˜„
I mean, in game it works, but i have no confidence in my skills as a programmer..!
 

Geners

Member
Hi !
Yes at first i did not have understood that, but i see what was the problem now, thank you for posting ! πŸ˜‡



Did I really do it ? πŸ˜„
I mean, in game it works, but i have no confidence in my skills as a programmer..!
You may want to subtract 1 from your num_inv, as ds lists are offset by 0

So a ds list of
0,1,2,3
Would have a size of 4
 

Feel

Member
Sorry i'm not sure to understand,
Right now, 0 mean there is no projectile, 1, mean, 1 projectile hit, ect ...

So, if i have 3 projectiles, it seems normal to have a list of 4, or there is something i rly don't get undersood ? πŸ˜„
(size of 4 if the 3 projectiles hit the same target ofc ..)

it display this
ds_list_size(projectile_hit);

2021-03-26_16-38-29.gif
 
Last edited:

Nidoking

Member
You're very close. I don't think you need to check for the timer value >= 0 before subtracting one, since once it hits zero, it will be deleted anyway. But the thing people are trying to tell you is that you need to start at num_inv - 1, not num_inv. num_inv will not point to a valid entry in the list.
 

Feel

Member
You're very close. I don't think you need to check for the timer value >= 0 before subtracting one, since once it hits zero, it will be deleted anyway. But the thing people are trying to tell you is that you need to start at num_inv - 1, not num_inv. num_inv will not point to a valid entry in the list.
Oh, i think i get it..
So it's as easy as that ? πŸ€”
GML:
var num_inv = ds_list_size(projectile_hit); //number of bullets invincible to
    for (var i = num_inv-1; i > -1; i--)
    { //loop to count down timers
        projectile_timer[| i] -= 1; //list[| i]= to change value in list
        if projectile_timer[| i] <= 0
        {
            ds_list_delete(projectile_hit, i); //remove these entries from the list
            ds_list_delete(projectile_timer, i); //now the relevant projectile should do damage again
        }
    }
And yes, you are right, don't need
" to check for the timer value >= 0 before subtracting one "
πŸ˜‡
 
Top