Timer Ser/Des

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
I'm having some issues with Serializing and Deserializing Timers.
I've read some stuff on them and done some searches, but for the most part... I just don't get it. Ser/Des is a tough subject for me so be gentle.

Can someone Explain/Show me how someone would go about doing it?

Below is my item and timer i am trying to Ser/Des. The item is mostly done. I just need to save and load the time on server restart. If I don't, this wont work properly as most of you know.
Code:
namespace Server.Items
{
	[FlipableAttribute( 0xE41, 0xE40 )] 
	public class AccountDepositBox : BaseContainer 
	{
		public override int MaxWeight{ get{ return 1200; } }
		public virtual int MaxItems{ get{ return 250; } }
	#region Account Info
        public string m_Account;	
        [CommandProperty(AccessLevel.GameMaster)]
        public string Account
        {
            get
            {
				return this.m_Account;
			}
            set
            {
				this.m_Account = value;
			}
        }
	#endregion
		Random random = new Random();
		[Constructable]
        public AccountDepositBox() : this(null){}
        [Constructable]
        public AccountDepositBox(string account) : this(account, 0xE41, 0xE40){}
        public AccountDepositBox(string account, int itemID) : this(account, itemID, itemID){}	
		public AccountDepositBox(string account, int inactiveItemID, int activeItemID) : base(inactiveItemID)
		{
			this.m_Account = account;
			Name = "Account Deposit Box";
			Movable = false;
		}		
		public AccountDepositBox( Serial serial ) : base( serial ) 
		{ 
		} 
	#region Ser/Des
		public override void Serialize( GenericWriter writer ) 
		{ 
			base.Serialize( writer ); 
			writer.Write( (int) 0 );
			writer.Write((string)this.m_Account);
		} 

		public override void Deserialize( GenericReader reader ) 
		{ 
			base.Deserialize( reader ); 
			int version = reader.ReadInt();
			this.m_Account = reader.ReadString();
		}
	#endregion
	}
#region Timer
	public class AccountDBoxTimer : Timer
	{
		AccountDepositBox adbox;
		Mobile player;
		
		public AccountDBoxTimer( AccountDepositBox item, Mobile from ) : base( TimeSpan.FromSeconds( 59 ) )
		{
			adbox = item;
			player = from;
			Priority = TimerPriority.OneSecond;
		}

		protected override void OnTick()
		{
			...
		}
	}
}
#endregion
Please save us lost ones from the torture of timer Ser/Des!
 

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
You don't really serialize the timer. You want to compute the end time for the timer and then have the timer start with the time remaining.

Take a look at sheep.cs and see how the NextWoolTime property is created for the item and then Ser / Des and then called in the OnThink method(which is really just a timer).
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
is OnThink only used in mobiles? just curious.
 

Dian

Knight
Nov 7, 2013
626
61
28
Shard Name
Ancient Legends
Donate
Donate money to this user
OnThink is Mobiles, yes. It is Core Mobiles.cs or BaseAI method.
 

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
Yes, it is only Mobiles, I just meant its an example of how the timer is being restarted, you wouldn't actually use the OnThink Method, just the technique use inside that method.
 

zerodowned

Moderator
ServUO Developer
Jun 28, 2014
1,829
250
83
Take a look at this. Basically when you equip the item, it starts a timer, the timer stops if you take the item off. So obviously something has to be serialized.

C#:
/*
  script by zerodowned
  credits to: Murzin's Hearthstone script, Peoharen's SelfDeletingItem script (part of the Mobile Abilities Package), and Soteric for help on Script Support boards - pointed out that I forgot to use ": base()" in the Constructable.
  Edited by Bittiez
*/

using System;
using Server;
using Server.Mobiles;
using Server.Items;

namespace Server.Items
{
  public class HalfLifeBow : CompositeBow
  {
  public override int InitMinHits { get { return 255; } }
  public override int InitMaxHits { get { return 255; } }


  // Initiate the starting variables to be used
  public int time_left;
  public int start = 18; //Minutes for delay
  public Timer halflifetimer;
  public Mobile m;


  // Setup the GM access/viewable variables for the item
  [CommandProperty(AccessLevel.GameMaster)]
  public int TimeLeft
  {
  get { return time_left; }
  set { time_left = value; }
  }


  // starting the item itself
  [Constructable]
  public HalfLifeBow()
  : base()
  {
  time_left = start*60;
  Name = "Half Life Piercer";
  Hue = 2988;
  Attributes.WeaponDamage = 40;
  Attributes.WeaponSpeed = 20;
  WeaponAttributes.HitLeechHits = 25;
  WeaponAttributes.HitLeechMana = 40;
  WeaponAttributes.HitFireball = 35;
  WeaponAttributes.SelfRepair = 5;
  Attributes.SpellChanneling = 1;
  Attributes.CastSpeed = 1;
  Attributes.Luck = 20;
  Attributes.RegenMana = 5;
  Attributes.SpellDamage = 15;
  }

  public HalfLifeBow(Serial serial)
  : base(serial)
  {
  }

  private void Expire()
  {
  if (Deleted)
  return;

  Delete();
  if (m is Mobile) m.SendMessage("The " + this.Name + " turns to dust.");
  }

  //this controls the list you see when you mouse-over the item
  public override void AddNameProperty(ObjectPropertyList list)
  {
  base.AddNameProperty(list);
  string lisths = String.Format("{0} Minutes.", (time_left / 60));
  list.Add("<BASEFONT COLOR=#00FF00>Half Life Remaining: {0}<BASEFONT COLOR=#FFFFFF>", lisths); //FFFFFF
  }

  public override bool OnEquip(Mobile from)
  {
  base.OnEquip(from);
  m = from;
  from.SendMessage("You feel your " + this.Name + " start to decay.");
  Start_Timer(TimeSpan.FromSeconds(1));
  return true;
  }

  public void Start_Timer(TimeSpan s)
  {
  Server.Timer.DelayCall(s, new Server.TimerCallback(decaying));
  }

  private void decaying()
  {
  Mobile from = m;
  if (from != null)
  {
  if (time_left != null) minusone();

  if (from.FindItemOnLayer(this.Layer) == this)
  {
  Start_Timer(TimeSpan.FromSeconds(1));
  }
  InvalidateProperties();
  }
  }


  private void minusone()
  {
  time_left -= 1;
  if (time_left < 1)
  {
  Expire();
  }
  InvalidateProperties();
  }

  public override void Serialize(GenericWriter writer) // generic info to write and save
  {
  base.Serialize(writer);
  writer.Write((int)0);

  writer.Write((int)time_left);
  }

  public override void Deserialize(GenericReader reader) // make sure proper variables are read
  {
  base.Deserialize(reader);
  int version = reader.ReadInt();

  if (version >= 0)
  time_left = reader.ReadInt();
  }

  }
}
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
So just having 'time_left = reader.ReadInt();' this in Des, it continues the time left timer on server startup?
^with its Ser counterpart​
 

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
No, not that simple. time_left is a value being saved after a restart, but just serializing doesn't make the value advance.

In the above example:

Line 23: Has an initial value of 18 (designed to be 18 minutes)

Line 42: Takes the value of 18 and multiples it by 60 and sets the result to equal time_left (this is our value of time_left when the item is constructed)

Line 85: Starts a timer that checks every 1 second when the item is equipped.

Line 94: This is a method that is called by the above timer every 1 second.

Line 99: This above method checks to make sure the value of time_left is not null and then calls another method called minusone

Line 110: This is the method called minusone, it takes the value of time_left and subtracts from the value 1 second. If the time_left is less than a second, then the Expire method is called and the item deletes.

So, the way this works:

Lets pretend you have this bow and have equipped it. You use it for a few minutes and then the shard does a restart. Lets pretend at that moment, the time_left has a value of 2000 seconds remaining.

After the restart, the ser /des will make sure that value of 2000 seconds is stored on your bow in the time_left property. However, the time has not been restarted, so this value doesn't change. Once you equip the bow, a timer is again started, but this time it uses your value of 2000 seconds as a starting point and begins ticking away the seconds until it reaches zero.

He has chosen for the timer to not begin until the bow is equipped in this case, but the principle is the same. You store a value of time remaining for the timer to expire and then use something to start the timer going again (it could be logging in, equipping, whatever you want). But the idea is the same, you store the time remaining and then restart the timer using that value.

Hope that makes sense!
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
Ok, i know I'm doing something wrong but i cant tell what it is. I'm trying a second route of using DateTime because I'm not sure how i would code the line to set the NextRenewTime

But heres what i have 'trying' to use datetime.

Can someone point out my mistakes?
 

Attachments

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
Need a bit of background on this script. I looked at it and you have a method called DoRenew that sets the TimeDate. However, you don't have anything that is calling that method and your CheckRenew doesn't do anything (nor is it called anywhere).

I'll be happy to help you with this but I need to know what you are wanting this item to do. I can see that this is a container that is connected to a player account. What is the timing for? Is this a limited time use item or what?
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
This is a Chest connect to the account, like a bank account for every character on that account. I'm wanting to put a time limit of 30 days and then have a choice to renew. The DoRenew and CheckRenew was just stuff i was messing around with along with the datetime stuff. The other version of this script that uses an actual timer works the way i want it to but i cant seem to understand ser/des of timers in a script. i seem to be a ser/des hopeless
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
In the gump file i did the call to DoRenew() which is probably the wrong spot and time to do it.
 

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
What happens after 30 days? What if they are not online when 30 days is reached? What happens to the chest and the stuff in it if they do not renew?

Post your working one as well please and I will see which is closer to what we need to do.
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
Code:
#region Timer
	public class AccountDBoxTimer : Timer
	{
		AccountDepositBox adbox;
		Mobile player;
		
		public AccountDBoxTimer( AccountDepositBox item, Mobile from ) : base( TimeSpan.FromSeconds( 59 ) )
		{
			adbox = item;
			player = from;
			Priority = TimerPriority.OneSecond;
		}

		protected override void OnTick()
		{
			Stop();
			//player.SendMessage("Your Account Box time has run out");
			/*if (player.Account.Username == null)//Has been deleted
			{
				Stop();
				Console.WriteLine("Players Account no longer Exists. Reseting Chest");
				adbox.m_Account = null;
				adbox.Name = "Account Deposit Box";
				adbox.Hue = 0;
				#region Delete Items
				List<Item> Items = new List<Item>();
				foreach (Item stuff in adbox.Items)
					//Item stuff = item as Item;
					Items.Add(stuff);
				for (int x = 0; x < Items.Count; x++)
					Items[x].Delete();
				#endregion
			}*/
			if ( (player.Account.Username != null) && (player.NetState == null) )
			{
				Console.WriteLine("Player is not online, checking for gold...");//Debug Line
				player.BankBox.FindItemsByType( typeof( Gold ) );
				if (player.BankBox.ConsumeTotal( typeof( Gold ), 50000 ) == true)
				{
					Start();
					Console.WriteLine("Player has money and renewal is successful");//Debug Line
				}
				else
				{
					Stop();
					Console.WriteLine("Player has no money and renewal is cancelled");//Debug Line
					adbox.m_Account = null;
					adbox.Name = "Account Deposit Box";
					adbox.Hue = 0;
					#region Move Items to Bank
					List<Item> Items = new List<Item>();

					foreach (Item item in adbox.Items)
						Items.Add(item);

					for (int x = 0; x < Items.Count; x++)
						player.BankBox.DropItem(Items[x]);
					#endregion
				}
			}
			else
			{
				Console.WriteLine("Player is online, Sending Gump...");//Debug Line
				player.SendGump(new AccountDBoxRenewGump(player, adbox));
			}
		}
	}
#endregion
 

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
Here's the script version with that in it.
It works the way i intend it to except the time(testing purpose) and when you restart, the timer is null and no renew option ever
 

Attachments

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
Here, try this, I didn't test it much, but it should do what you want...

I was kind of rushing since my wife wanted the computer so I didn't document the changes much, but you should be able to compare it. I used the first one you posted as a base.
 

Attachments

JBob

Squire
Jul 15, 2014
134
17
18
32
Shard Name
UOAccess
Thank... You... Yeah i can tell what you were doing with it.
I lost a few hours of sleep over not understanding this.
Time to do some more playing around with this. *smiles*
 

Ravenwolfe

Moderator
ServUO Developer
Sep 7, 2013
1,164
145
63
Texas
www.ultimaonlinephoenix.com
Shard Name
UO Phoenix
I pretty much avoid timers unless I need to know EXACTLY how much time remains. Your box does not really fit into this category, you are using a long time frame and you just need to know IF it is expired or what date/time it will expire. Its not like you need a countdown or something.

So, basically I set it to check the expire time when you try to open the box (this insures that the box does not disappear when the owner is logged out. However, if it is expired the owner can't open the chest until he renews it (if he doesn't renew, everything goes to his bank).

Some things I did not solve and you will need to decide how you want to handle it:
1. What happens if the owner never returns (quits the shard or something). Right now the chest will happily sit there forever unless the owner comes back or a Seer (or higher) opens it.

2. If they do not renew, the script will put everything in their bank. This can easily overload their bank.