Hello guys!

So, I have this Xml Attachment that I'm using for a leveling system "a la" RPG, with levels and xp, attach to every player.

I want this Attachment to have a Timer that give a amount of xp every x minutes. But I'm not quite sure how to do this, since I'm fairly new to coding.

I read a couple of existing attachment with timer, but they don't really do what I want, since their timer are not reapetable.

Maybe you guys know a example of code that I can use to write the effect that I want?

Thank you for your time!
 
From https://github.com/ServUO/ServUO/blob/master/Server/Timer.cs
There's a timer with Delegate that you can use to deal with this easily.

Code:
public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerCallback callback)

Using this is pretty simple. The first argument is the delay for the first tick, the second is the repeat interval and the call back is a delegate method that's executed when the timer ticks. You can find more information about delegates if needed here https://www.servuo.com/tutorials/delegates.20/ as I've already made a tutorial about them.
 
That starts to get more complicated. For that you'll want to create a new class based on timer class, then you have access to the start and stop methods of the timer, and you use the ontick method to make it do what you want it to do.
 
So! As advised I created an internal timer for my attachment.

I added a statement that look if the player is in map.internal or not. Now everything work.

Thanks for the help!
 
If it can help other people, here's what I did:

I added this timer to my attachment :
Code:
//Method to start the timer
public void DoTimer()
        {
            if (m_Timer != null)
                m_Timer.Stop();

            m_Timer = new InternalTimer(this);
            m_Timer.Start();
        }

//The timer
private class InternalTimer : Timer
        {
            private XmlCharManager m_attachment;

            public InternalTimer(XmlCharManager attachment)
                : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(5.0))
            {
                Priority = TimerPriority.OneSecond;
                m_attachment = attachment;
            }

            protected override void OnTick()
            {
                if (m_attachment == null) return;
               
                Mobile m = m_attachment.AttachedTo as Mobile;

                //If the player is online
                if (m.Map != Map.Internal)
                {
                    //What the Timer do OnThick
                    m_attachment.GiveXp();
                    if (m_attachment.Xp >= 100)
                    {
                        m_attachment.LevelUp();
                    }
                }

                else
                {
                    Stop();
                    return;
                }
            }
        }

And I added this to LoginStats.cs

Code:
//Look for the attachment with the timer
XmlCharManager c = (XmlCharManager)XmlAttach.FindAttachment(m, typeof(XmlCharManager));

            if (c != null)
            {
                //Start the timer when the player login.
                c.DoTimer();
            }
 
Doing well so far. I want to give you another bit of advice, instead of having a new instance of the timer for each attachment you could have a static timer... This timer would have a list for playermobiles and using the events (LoginEvent and LogoutEvent) update the list.

If the timer shouldn't be global you can use a dictionary instead of a list to keep track of the playermobile and the last DateTime that was ticked for that person. Then when you tick you check against the value to make sure if it applies or not.

The reason I'm suggesting this is because every new instance of a timer slowly generates more and more lag.

Let me know if you want more info on how to apply this.
 
This is a bit more advance for me, since I never worked with dictionaries or lists before, but that might be a great opportunity to do it. It is by doing difficult things that you become better!

The strange thing about this Timer is that I need to be able to set a different TimeSpan on some player (the better your roleplaying is, the quicker you gain xp). Would a Static Timer with a dictionary still work?
 
my suggestion works by having one timer that fires every so often, so you'd have it fire at the fastest you'd ever to want it to trigger. Then checking when fired against the dictionary's values (date time) to see if it should apply to the player (the dictionaries keys). It requires a bit of thought but it's not really that difficult, Question, do you understand arrays?
 
Well, after reading your post, I went on to read about it! It took me a couple of try, but I managed to write a class using Arrays (a JaggedArray).

So, know I understand them enough to work with them!
 
For a first try, I made a list to see If I were able to make It work.

And It seems to work!

So here's what I did, If someone need something similare :

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Server.Engines.XmlSpawner2;

namespace Server.Services.XmlSpawner2
{
    public class CoteTimer : Timer
    {
        private static readonly TimeSpan m_Delay = (TimeSpan.FromSeconds(5.0));

        public CoteTimer()
            : base(m_Delay, m_Delay)
        {
            this.Priority = TimerPriority.OneSecond;
        }

        private static readonly List<Mobile> m_PlayersList = new List<Mobile>();

        public static void Initialize()
        {
            new CoteTimer().Start();
            EventSink.Login += new LoginEventHandler(EventSink_Login);
            EventSink.Logout += new LogoutEventHandler(EventSink_Logout);
        }

        private static void EventSink_Login(LoginEventArgs e)
        {
            Mobile mob = e.Mobile;
            m_PlayersList.Add(mob);
        }

        private static void EventSink_Logout(LogoutEventArgs e)
        {
            Mobile mob = e.Mobile;
            m_PlayersList.Remove(mob);
        }

        protected override void OnTick()
        {
            foreach (Mobile player in m_PlayersList)
            {
                XmlCharManager c = (XmlCharManager)XmlAttach.FindAttachment(player, typeof(XmlCharManager));
                if (c != null)
                {
                    c.GiveXp();
                }
                else
                {
                    player.SendMessage("Erreur : Il vous manque un module");
                }
            }
        }
    }
}

The next part is to change the list for a Dictionnary with DateTime, so, the most difficult is behind.

I'll add it to the post when I'll finish it.

Talow, thank you so much for your advice, you were of a big help!
 
Simple yet effective code; excellent use of lists, eventsinks, and static methods.

Personally I would add a null check for player somewhere as its possible for one to be deleted but not be removed from the list.
 
Back