1. For issues you find with the Switch and Console releases, please follow this link and give as much detail as possible. This is the speediest way to get info to Pipeworks and get a hotfix in the works.
    Dismiss Notice
  2. For issues you find with the Mobile 1.3 update, please follow this link and give as much detail as possible. This is the speediest way to get info to DR Studios to look at your issue. Also, some troubleshooting hints here.
    Dismiss Notice
  3. TCF will be undergoing a scheduled software and server upgrade on September 26, 2019 beginning at 9:00AM GMT. TCF may be unavailable for an extended period after this time. More information can be found here. We apologize in advance for any inconvenience this may cause, we will try to keep the downtime short.

tModLoader Spawning a projectile dust trail a set distance behind the projectile

Discussion in 'General Mod Discussion' started by POCKETS, Sep 10, 2019.

  1. POCKETS

    POCKETS Terrarian

    I figured I would ask in these forums (as well as the tMod Discord) about the code for the Chlorophyte dust trail, and what I'm attempting to do with it.

    I have a custom projectile that behaves like the Chlorophyte bullet, in that it homes in on targets, and leaves a dust trail. The code is working fine, and the trail is working fine. However, I am attempting to edit the dust trail so that the trail starts to spawn at a set distance behind the projectile, and not on the projectile itself.

    This is the code for the dust trail (TealArrowDust4 is my custom dust)

    Code:
    for (int num163 = 0; num163 < 10; num163++) // Spawns 10 dust every ai update (I have projectile.extraUpdates = 1; so it may actually be 20 dust per ai update)
                        {
                            float x2 = projectile.position.X - projectile.velocity.X / 10f * (float)num163;
                            float y2 = projectile.position.Y - projectile.velocity.Y / 10f * (float)num163;
                            int num164 = Dust.NewDust(new Vector2(x2, y2), 1, 1, mod.DustType<TealArrowDust4>(), 0f, 0f, 0, default(Color), 1f);
                            Main.dust[num164].alpha = projectile.alpha;
                            Main.dust[num164].position.X = x2;
                            Main.dust[num164].position.Y = y2;
                            Main.dust[num164].velocity *= 0f;
                            Main.dust[num164].noGravity = true;
                            Main.dust[num164].fadeIn *= 1.8f;
                            Main.dust[num164].scale = 2f;
                        }
    I believe this code works as follows -

    The position of the spawning of the dust is determined by the current position of the projectile. Then, the current velocity of the projectile is subtracted, which makes the dust spawn in a set location (doesn't move or fall to the ground with noGravity set to true). There is some extra math involved so that it can spawn the dust over and over again, but that's basically it.

    In order to get the dust to start spawning a set distance behind the projectile, and not at the projectiles current position, I'm going to need to change how the position is calculated, but I'm at a loss when it comes to how. I've tried several things, but I'm still new to C# and coding/modding. I've been thinking about a way to track where the projectile has already been, using that as it's spawn location, but again, I wouldn't know where to start.

    Any suggestions or hints would be awesome!


    It has been suggested that I use an array to store the projectiles positions, which could work. I understand how simple arrays work, but I think I'd first need to convert the positions (Vectors) into integers, and I'm a bit lost on how to do that.
     
    Last edited: Sep 10, 2019
  2. Kazzymodus

    Kazzymodus Moderator Staff Member Moderator

    Hi @POCKETS! Welcome to the forums!

    So for any linear projectile, you'd just subtract the position by the velocity multiplied by an arbitrary number of frames, so that the dust appears where your projectile used to be. However, with a homing projectile that wouldn't work, as you can't account for curved paths.

    The array idea would actually work, and seems the simplest way to go about it, so let's do that!

    So the good news is that you don't actually need to convert Vectors to ints, as you can just make a Vector array. We are going to have to make some more variables, though, as we're going to have to make a ring buffer to get this working properly.

    A ring buffer is essentially an array that, through clever use of some extra variables, "wraps around", forming a closed ring. To start off, declare these fields at the top of the class declaration (so not local variables!):
    Code:
    private const int BufferSize = 10; // This is the amount of positions we'll store. 10 is one sixth of a second's worth, if you want more than that, just increase this value.
    private Vector2[] positionBuffer = new Vector2[BufferSize] // Initialises a Vector2 array of size BufferSize (so 10).
    private int bufferTail = 0; // This keeps track of the 'tail' of the buffer. Don't worry, I'll explain below.
    private bool bufferFull; // This keeps track of whether or not the buffer has been filled yet.
    
    Having done that, you are going to have to actually track the positions. Put this in your AI function:
    Code:
    positionBuffer[bufferTail] = projectile.position;
    bufferTail++;
    if (bufferTail >= BufferSize)
    {
         bufferFull = true;
         bufferTail = 0;
    }
    So what happens here is that every frame, you store the current position of the projectile in the tail of the buffer, then move the tail by one element. If you move past the last element, the tail gets reset to the first element of the array. This means that once you've filled the array, it starts overwriting the old data, but that's not a problem since we're only interested in the last 10 frames anyway.

    So now we have an array that continuously keeps track of the last 10 positions of your projectile. Now to spawn the dust!
    Code:
    int dustAmount = bufferFull ? BufferSize : bufferTail;
    
    for (int i = 0; i < dustAmount; i++)
    {
        int dustId= Dust.NewDust(positionBuffer[i], 1, 1, mod.DustType<TealArrowDust4>(), 0f, 0f, 0, default(Color), 1f);
        Main.dust[dustId].alpha = projectile.alpha;
        Main.dust[dustId].velocity *= 0f;
        Main.dust[dustId].noGravity = true;
        Main.dust[dustId].fadeIn *= 1.8f;
        Main.dust[dustId].scale = 2f;
    }
    
    Finally, we create a dust particle for every entry in the buffer. First, we have to determine how many dust particles we can spawn: if the buffer hasn't been filled at least once yet (which will be the case in the first nine frames of the projectile's life), there will be some default values in the array, and we don't want to spawn any dust there! So we either draw as many values as we have yet, or all of them if we've filled the buffer at least one (we use the bufferFull variable to keep track of that).

    Then, we create a dust for every position we have. This not in order of their age, but that doesn't matter, because all dust particles are independent of each other! This should give you a nice trail along the projectile's path!

    -

    I haven't tested this in tModLoader (or even written it in an IDE), but in theory this should work. Give it a whirl, and let me know if it works. ;)
     
    Last edited: Sep 10, 2019
    POCKETS likes this.
  3. POCKETS

    POCKETS Terrarian


    First off, I want to thank you for the VERY detailed explanation of how this works. I'm going to need to study this a bit to truly understand it, and I'm rather old, so it may take me some time ;-) This explanation is VERY much appreciated.

    I don't know that this will work as I had intended, and testing it in game gives me some strange results. The dust is spawning in a staggered pattern, instead of in a nice smooth line. I increased the scale to show it better.

    OK, so the first image is what's happening in game.

    The second image will explain what I am trying to do. The code for the Chlorophyte bullets works perfectly if I want to spawn the dust at the projectile's current location minus the velocity of the projectile, as you mentioned above. I understand the math behind the original Chlorophyte dust trail code - I had to sit down and really study it ;-)

    The second image will really explain what my end goal is. I don't want the dust to spawn at the projectiles current location. I want to spawn the dust after the projectile has already been there and gone (by a distance that I specify). I hope this makes things more clear. Perhaps the above array code does actually do this, and I screwed something up?

    In the first image, it does actually appear that they are spawning slightly behind the projectile, but in game it doesn't appear this way.

    I'm sorry that I didn't specify this in my original post. I should have been more specific about what I was trying to achieve. I do appreciate the help, nonetheless!

    Example.png Example2.png
     
    Last edited: Sep 10, 2019
    Kazzymodus likes this.
  4. Kazzymodus

    Kazzymodus Moderator Staff Member Moderator

    Ah, gotcha. Well, that's a bit more complicated, but still manageable. Basically we're just going to do the same as we're doing now, except we're going to not draw a couple of the oldest entries.

    I'm going to write three code blocks again like last time. Some of it you've already seen (but may have changed values along the line), some of it is new. If you replace what was in the old blocks by what it in the new ones, you should be golden. ;)
    Code:
    private const int BufferSize = 15; // This is now 15 instead of 10, to account for the lagging (see below).
    private const int DustLag = 5; // The amount of frames we're not drawing, as to create the trail 'delay'.
    private Vector2[] positionBuffer = new Vector2[BufferSize];
    private int bufferTail = 0;
    
    So here are your fields. DustLag is new, and I removed bufferFull since we don't need it anymore.
    Code:
    positionBuffer[bufferTail] = projectile.Center; // We're now storing the center of the projectile, as part of getting rid of the jitter.
    bufferTail++;
    if (bufferTail >= BufferSize)
    {
         bufferTail = 0;
    }
    Also pretty much the same, I again removed the bufferFull tidbit.
    Code:
    // This is all rather complex to explain in comments, so I'll do it in the running text.
    
    int firstDust = (bufferTail + DustLag) % BufferSize;
    int lastDust = firstDust + BufferSize - DustLag;
    
    for (int i = firstDust; i < lastDust; i++)
    {
        int dustId= Dust.NewDustPerfect(positionBuffer[i % BufferSize], mod.DustType<TealArrowDust4>(), Vector2.Zero, projectile.alpha, default(Color), 1f);
        dust.alpha = projectile.alpha; // Pretty sure this is redundant, but leaving it just in case.
        dust.velocity *= 0f; // Ditto.
        dust.noGravity = true;
        dust.fadeIn *= 1.8f;
        dust.scale = 2f;
    }
    This is where it gets complex. We can not draw the entire buffer anymore, because we don't want to draw the oldest five. However, those five keep changing position, so before we draw the buffer we have to figure out which those are.

    Figuring out the first dust to draw is not complicated: we know it has to be the sixth oldest in the buffer, so the index is tailBuffer (because that's the oldest entry, and the next entry we would overwrite) plus whatever our delay is (in our case, it's five) (so [bufferTail + DustLag]). However, because our buffer wraps around, we have to modulate the index so that if it passes the last element, it jumps back to 0 again (if the first dust was 12, and we'd add 5, we'd get 17, but because our array only has 15 elements, we wouldn't have an element with index 17). Hence we get [int firstDust = (bufferTail + DustLag) % BufferSize;].

    For our last dust, we know it's the one before the tail index (because we just wrote our newest position to that index), but if we just do [tailBuffer - 1], the for loop would break down if the last dust has a lower index than the first dust (e.g. the first dust is 8, and the last is 3. Because 8 > 3, the for loop would immediately exit). So instead, we pretend that our array doesn't wrap around, and get what would be the index of the last element if our array was infinitely long. Which is [firstDust + BufferSize - DustLag]: firstDust is the first dust we're drawing, but as we've already skipped five, we're going to draw ten more. Then, within the for-loop, we modulate the index counter by the size of the buffer, so it wraps around and starts at the beginning again if it passes the last element of the buffer. Don't worry, it sounds a lot more complicated than it actually is. :)

    We also replace NewDust by NewDustPerfect: this should get of the jittering you're experiencing. NewDust draws a dust at a random position within a rectangle, NewDustPerfect places at the pixel perfect coordinates you specify.

    -

    I think this should work for what you're trying to achieve, but if not, do please let me know. ;)
     
    POCKETS likes this.
  5. POCKETS

    POCKETS Terrarian

    Once again, the detailed response and the overwhelmingly detailed explanation is very much appreciated. I haven't had the chance to wrap my head around what's taking place here just yet, but I promise I'll take some time and go over it once I get it working in game.

    I have a small issue, though. Visual Studio is giving me an error when using NewDustPerfect and it's arguments.

    OK so here's the code I'm using -

    Code:
                for (int i = firstDust; i < lastDust; i++)
                {
                    int dustId = Dust.NewDustPerfect(positionBuffer[i % BufferSize], mod.DustType<TealArrowDust4>(), Vector2.Zero, projectile.alpha, default(Color), 1f);
                    //Main.dust[dustId].alpha = projectile.alpha;
                    //Main.dust[dustId].velocity *= 0f;
                    Main.dust[dustId].noGravity = true;
                    Main.dust[dustId].fadeIn *= 1.8f;
                    Main.dust[dustId].scale = 2f;
                }
    The error tells me that you cannot implicitly convert `Terraria.Dust` to an `int`

    I'm not familiar enough with these errors yet. I know I need to change something, but I'm not sure what. Without that, I can't test this to ensure it actually works ;-)
     
    Kazzymodus likes this.
  6. Kazzymodus

    Kazzymodus Moderator Staff Member Moderator

    Oh, whoops, that must've slipped past me in the edit. :eek:

    Replace that entire block by this:
    Code:
    for (int i = firstDust; i < lastDust; i++)
    {
        Dust dust = Dust.NewDustPerfect(positionBuffer[i % BufferSize], mod.DustType<TealArrowDust4>(), Vector2.Zero, projectile.alpha, default(Color), 1f);
        // dust.alpha = projectile.alpha;
        // dust.velocity *= 0f;
        dust.noGravity = true;
        dust.fadeIn *= 1.8f;
        dust.scale = 2f;
    }
    The difference is that while NewDust returns the id of the new dust, NewDustPerfect (and NewDustDirect, but we're not using that here) return the actual dust. I've changed the code to accommodate that, so now you should be good. ;)
     
    POCKETS likes this.
  7. POCKETS

    POCKETS Terrarian

    Unfortunately, we're back to the same original issue. I'm beginning to think my request is a bit more complicated to pull off than I had anticipated...

    I have to apologize that this problem has taken so much of your time, and we're not really making any progress. I suppose we might be moving in the right direction, but I can't see a solution at this point.

    Here's an image of what it looks like now. It's still staggered as before, and it's not delayed at all. Also, I tried changing the array size and the DustLag to higher numbers, but that only made the trail longer, and still started right at the projectile's current position.

    Example.png
     
  8. Kazzymodus

    Kazzymodus Moderator Staff Member Moderator

    So I ended up actually booting Visual Studio for a change and actually tested my code, and figured out the problem:
    Code:
    int firstDust = (bufferTail + DustLag) % BufferSize;
    int lastDust = firstDust + BufferSize - DustLag;
    
    for (int i = firstDust; i < lastDust; i++)
    {
       // Stuff
    }
    So what I did in my infinite genius was to skip the oldest positions, rather than the newest, which is what we need. If you replace that code by this:
    Code:
    int lastDust = bufferTail + BufferSize - DustLag;
    
    for (int i = bufferTail; i < lastDust; i++)
    {
       // Stuff again
    }
    
    ...then you should finally have a working trail (and I actually tested it this time!).

    Let me know if that works for you. :)
     
    POCKETS likes this.
  9. POCKETS

    POCKETS Terrarian

    Success! Well, sort of.

    The trail delay is working now, but the staggered dust is still there. I posted my code below, so perhaps I have something out of place, or incorrect. There's been a few rewrites, so maybe I made a mistake in the iteration process.

    Code:
    private const int BufferSize = 25;
    private const int DustLag = 15;
    private Vector2[] positionBuffer = new Vector2[BufferSize];
    private int bufferTail = 0;

    Code:
    positionBuffer[bufferTail] = projectile.Center;
    bufferTail++;
         if (bufferTail >= BufferSize)
         {
               bufferTail = 0;
         }

    Code:
    int lastDust = bufferTail + BufferSize - DustLag;
    
    for (int i = bufferTail; i < lastDust; i++)
    {
    
    Dust dust = Dust.NewDustPerfect(positionBuffer[i % BufferSize], mod.DustType<TealArrowDust4>(), Vector2.Zero, projectile.alpha, default(Color), 1f);
    dust.noGravity = true;
    dust.fadeIn *= 1.8f;
    dust.scale = 2f;
                
    }

    I believe what's happening is that we got rid of the for loop that spawned 10 dust for every update, which means the trail is nice and smooth. Once that's gone, it's just a staggered trail, since we're no longer spawning 10, we're spawning 1. At least, that's my assessment. I'll try to mess with this and see what I can do on my end.
     

    Attached Files:

    Last edited: Sep 11, 2019
  10. Kazzymodus

    Kazzymodus Moderator Staff Member Moderator

    Glad that at least worked! I'm not sure what you mean by 'staggering', though. Do you mean the regular spaces inbetween dusts? If so, we can fix that by expanding our dust spawning code a bit:
    Code:
    int lastDust = bufferTail + BufferSize - DustLag;
    
    int previousIndex = bufferTail - 1;
    if (previousIndex < 0)
    {
        previousIndex += BufferSize;
    }
    Vector2 oldPosition = positionBuffer[previousIndex];
    
    for (int i = bufferTail; i < lastDust; i++)
    {
        Vector2 newPosition = positionBuffer[i % BufferSize];
                   
        if (newPosition == Vector2.Zero)
        {
            continue;
        }
    
        Vector2 spawnPosition = oldPosition != Vector2.Zero ? Vector2.Lerp(newPosition, oldPosition, Main.rand.NextFloat()) : newPosition;
    
        Dust dust = Dust.NewDustPerfect(spawnPosition, mod.DustType<TealArrowDust4>(), Vector2.Zero, projectile.alpha, default(Color), 1f);
    
        // This here we already had.
    
        // dust.alpha = projectile.alpha;
        // dust.velocity *= 0f;
        dust.noGravity = true;
        dust.fadeIn *= 1.8f;
        dust.scale = 2f;
    
    
        oldPosition = newPosition; // Don't forget this!
    }
    
    What this does is instead of spawning the dust at the position the projectile was at, it spawns it at a random point between those two points, giving you a more chaotic tail. It also prevents drawing the buffer elements that hadn't been written to yet (not strictly necessary if you stay away from the top left corner off the map, but we might as well be professional here ;) ).

    This is a slightly more precise version of simply using NewDust with a width and height, so let me know if this is what you wanted. Otherwise, we'll try that instead.
     
    POCKETS likes this.
  11. POCKETS

    POCKETS Terrarian

    I tried your new code and posted an image below

    I wanted to take a moment and talk about the original Chlorophyte dust trail code. I've posted it below -

    Code:
    for (int num163 = 0; num163 < 10; num163++)
                   {
                        float x2 = projectile.position.X - projectile.velocity.X / 10f * (float)num163;
                        float y2 = projectile.position.Y - projectile.velocity.Y / 10f * (float)num163;
                        int num164 = Dust.NewDust(new Vector2(x2, y2), 1, 1, mod.DustType<TealArrowDust1>(), 0f, 0f, 0, default(Color), 1f);
                        Main.dust[num164].velocity *= 0f;
                        Main.dust[num164].noGravity = true;
                        Main.dust[num164].fadeIn = 1.8f;
                        Main.dust[num164].scale = 2f;
                   }

    So this basically spawns 10 dust for every AI update at the projectile's location minus the projectile's velocity, meaning it just stays in one spot. I know that you probably understand this quite well, but I wanted to be sure I added this so that I can be as clear as possible.

    By removing the for loop, we're now back to just 1 dust per update. This can be seen in the image I posted in my last reply. So, spawning 10 dust per update removes the "staggered" look to the dust (spaces in between the dust). It sort of "fills in" those gaps (I think?). At least, that's what I've been told is happening here with this code.

    Again, by removing that loop, we're back to just 1 dust per projectile's "old" location using the array that you built. (this is just my assessment, given what I know of that code)

    I'm almost positive you understand all this (and maybe I don't understand it), seeing as you are much more experienced than I am, but again, I just wanted to post this to be extra clear about what I had intended with all this.

    My original intent was to make the dust trail exactly like the Chlorophyte bullet trail, only delayed slightly (which we've done with the array). As a consequence, however, we removed the extra dust being spawned in those gaps.

    I hope that makes it more clear. I can show you images if that would help, but I doubt you need any of that. You've been so helpful that I don't know how to thank you for all the work you put into this. That's a lot of time and effort.

    I will continue to mess with things on my end and see if I can make any improvements to the trail itself.

    Here's an image of the trail with the new, expanded code. It's not perfect, but it's getting closer to what was intended. There is an oddity happening, though. It's spawning dust in the gap between where it's supposed to spawn, and the projectile. It's only a very small amount, but noticeable.

    At this point, I don't know that this method will ever recreate what the original trail was doing, but this certainly does give me something to mess with and try and understand. That's always a good thing ;-)

    Example.png



    I'd just like to say thank you again for all of your help with this. Even though it may not be, or ever be what I had intended, you put in a lot of effort here, and I'd like to make sure you know how much it is appreciated ;-)
     
    Last edited: Sep 11, 2019
  12. Kazzymodus

    Kazzymodus Moderator Staff Member Moderator

    I had some trouble getting this to work, due to some weird placement issue I couldn't figure out. In the end, the result looks something like this:
    [​IMG]
    It's not exactly as clean as the Chlorophyte Bullet, but it looks random enough to work.

    You'll have to add one new field to the top of your class:
    Code:
    private const float TrailDensity = 2; // The amount of particles per position. I found 2 works well enough, but you can always tweak it a bit.
    Now I can't really remember what parts of the code I have edited and which I haven't, so I'll just post the entire thing. :p
    Code:
    positionBuffer[bufferTail] = projectile.position; // We're now storing the center of the projectile, as part of getting rid of the jitter.
    bufferTail++;
    if (bufferTail >= BufferSize)
    {
        bufferTail = 0;
    }
    
    int lastDust = bufferTail + BufferSize - DustLag;
    
    int previousIndex = bufferTail - 1;
    if (previousIndex < 0)
    {
        previousIndex += BufferSize;
    }
    Vector2 oldPosition = positionBuffer[previousIndex];
    
    for (int i = bufferTail; i < lastDust; i++)
    {
        Vector2 newPosition = positionBuffer[i % BufferSize];
        Vector2 spawnDomain = Vector2.Normalize(oldPosition - newPosition) * 5;
    
        if (newPosition != Vector2.Zero && oldPosition != Vector2.Zero)
        {
            for (int j = 0; j < TrailDensity; j++)
            {
                float randomX = Main.rand.NextFloat(-spawnDomain.X, spawnDomain.X);
                float randomY = Main.rand.NextFloat(-spawnDomain.Y, spawnDomain.Y);
    
                Dust dust = Dust.NewDustPerfect(newPosition, 75, default(Vector2), projectile.alpha, default(Color), 1f); // This uses the Chloro dust, you can substitute your own here.
    
                dust.position.X += randomX;
                dust.position.Y += randomY;
                           
                // This here we already had.
    
                // dust.alpha = projectile.alpha;
                // dust.velocity *= 0f;
                dust.noGravity = true;
                dust.fadeIn *= 1.8f;
                dust.scale = 1f;
            }
        }
    
        oldPosition = newPosition; // Don't forget this!
    }
    The only additions here are quite simple: instead of spawning single dusts at the stored positions, we spawn two in a random area around that point, based on the difference between that point and the previous point (as to 'smear' them out). Simple, and reasonable effective. ;)

    I'm not sure this is what you want; it's not really what I was going for, but I was getting a weird bug I couldn't really figure out. I'll try to tinker with it some more, but in the meantime, you at least have something that works. :p
     
    POCKETS likes this.
  13. POCKETS

    POCKETS Terrarian

    I'm not sure why, but it still has small gaps between the spawning of the dust. I thought it might be because I have projectile.extraUpdates set to 1, but I disabled it, and it's still there. Maybe it has to do with the speed of my projectile? It's rather fast, as you'll see below.

    I changed the private const float TrailDensity = 2; and Vector2 spawnDomain = Vector2.Normalize(oldPosition - newPosition) * 5; to something higher, and it does make it better. I suppose I can add a few more instances of the dust to compensate, and that should even it out a bit.

    There's also another weird problem. The dust trail will be thin at certain shooting angles, and then thicker at other angles. I've seen this before when I was messing with the original Chlorophyte code.

    I know that it may seem like I am complaining, but that is far from the truth. I can't even begin to thank you for all this help. It's very cool to take a problem like this and work through some solutions. When you see it come together, you know all the effort was worth it ;-)

    I'm going to post my entire projectile code for you, so that you know exactly what you are dealing with here. That way, if there's anything in my code that is interacting with yours, perhaps you'll have some suggestions on what to change in order to get this working properly.

    Projectile code - hastebin
     
    Kazzymodus likes this.
  14. POCKETS

    POCKETS Terrarian

    Unfortunately, I have been unable to make any progress on this. I'm sure there's a solution out there to my small issues with this, but I'm unable to figure them out.

    As mentioned in my previous post, there's still gaps in between the dust for some strange reason, and the dust "area" gets thinner or thicker depending on the angle at which you shoot the weapon. Very odd problem, indeed.

    I was also wondering if the code I posted in the my previous post brought up any ideas or suggestions as to why the above issues may be happening? A lot of that code wasn't written by me. I had a lot of fantastic help with it, and I'm only just now beginning to understand some of it.

    Ever since I started modding Terraria, it's been one gigantic learning experience. I was told that modding Terraria really isn't the best way to go about learning to code, OR learning a coding language for that matter. Still, it has helped me understand some of the fundamental principles behind programming. It has been a great way to learn some of the very basics.

    Anyhow, your effort won't go to waste. Even though I don't understand "ring" arrays, or a lot of the code you posted, I will continue to do my best to decipher all of it when the time is right ;-)