Author Topic: Persistent Time for PW  (Read 709 times)

Legacy_GhostOfGod

  • Hero Member
  • *****
  • Posts: 1490
  • Karma: +0/-0
Persistent Time for PW
« on: August 18, 2010, 11:53:43 pm »


               What is the best way to do this using the NWN database.

This is one of those things that I have always been curious about how to do best. I've searched many times for this topic but can't seem to find any scripts for it to compare to what I've come up with.

I know that to keep time persistent in a PW you need to store the year, month, day and hour every so often. And then when the module loads you need to retrieve that info and set it back into the mod.
One must also make sure that the starting month and day for the PW are set to 1 and that the starting hour must be set to 0.

When we first did this we were getting each integer and storing them(4 writes to the database, year, month, day, hour) every 2 minutes(game hour). But there was a noticible lag/flicker every time it would save.

So I decided to optimize this the only way I knew how. Using string parsing and IntToString you can convert all of the 4 integers to one string and store it that way. Then when the module loads it grabs the string and breaks it up into it's separate integers and resets the mods date and time like so:

//Function to save current calendar date and time to a database.
#include "x3_inc_string"
void SaveCurrentDateTime()
{
string sDBName = "PERSISTENT_DATE_TIME";
string sYear = IntToString(GetCalendarYear());
string sMonth = IntToString(GetCalendarMonth());
string sDay = IntToString(GetCalendarDay());
string sHour = IntToString(GetTimeHour());

string sDateTime = sYear + "," + sMonth + "," + sDay + "," + sHour;

SetCampaignString(sDBName, "STRING_DATE_TIME", sDateTime);
object oPC = GetFirstPC();
SendMessageToPC(oPC, "Time Saved!");
}

void RecursiveStoreDateTime()
{
DelayCommand(2.0, SaveCurrentDateTime());
DelayCommand(120.0, RecursiveStoreDateTime());
}

//Function to set the stored calendar date and time onto the PW.
void SetPWDateTime()
{
string sDBName = "PERSISTENT_DATE_TIME";
int iStored = GetCampaignInt(sDBName, "DATE_TIME_STORED");
string sDateTime = GetCampaignString(sDBName, "STRING_DATE_TIME");
string sYear = StringParse(sDateTime, ",");
string sNew;
sNew = StringRemoveParsed(sDateTime, sYear, ",");
string sMonth = StringParse(sNew, ",");
sNew = StringRemoveParsed(sNew, sMonth, ",");
string sDay = StringParse(sNew, ",");
sNew = StringRemoveParsed(sNew, sDay, ",");
string sHour = sNew;

int iYear = StringToInt(sYear);
int iMonth = StringToInt(sMonth);
int iDay = StringToInt(sDay);
int iHour = StringToInt(sHour);

//Used for testing
DelayCommand(10.0, SendMessageToPC(GetFirstPC(),"The year is: " + sYear +
                                                "\\nThe month is: " + sMonth +
                                                "\\nThe day is: " + sDay +
                                                "\\nThe hour is: " + sHour));

if (iStored != TRUE)
    {
    SetCampaignInt(sDBName, "DATE_TIME_STORED", TRUE);
    RecursiveStoreDateTime();
    }

else
    {
    SetCalendar(iYear, iMonth, iDay);
    SetTime(iHour, GetTimeMinute(), GetTimeSecond(), GetTimeMillisecond());
    RecursiveStoreDateTime();
    }
}

//void main()
//{
//}

This way does work much better. And we hardly see any lag/flicker when it saves.

But anyway back to the first question at the top. Is there a better, easier,  more efficient way to do this?
I would love to see what you guys are using and compare.

Thanks in advance.
               
               

               
            

Legacy_Genisys

  • Hero Member
  • *****
  • Posts: 961
  • Karma: +0/-0
Persistent Time for PW
« Reply #1 on: August 19, 2010, 12:18:07 am »


               Why not just use local variables?  I mean if you restart your module that is...



If it's for a single player module, I'd probably store the module second count into the database...



But that's checked every heartbeat, therefore you would only get beats as an integer, e.g. 1 beat, 2 beats, where beats = 6 seconds, you can still use this efficiently in scripting to tell time / day / month etc, if you use a formula like that which time is setup on the module (Module Properties)...



Just an idea..  (This is how I keep track of time myself..)
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Persistent Time for PW
« Reply #2 on: August 19, 2010, 12:26:15 am »


               LINK - Im using the same approach. All four values in string, then in module load convert it.

However there is bug in your script. First set time and then date reason is simple. You can advance time only forward so if your module has starting hour 13, and you set new hour to 12, the game date will advance by one day. So either call time first or set starting hour to 0.
               
               

               


                     Modifié par ShaDoOoW, 18 août 2010 - 11:26 .
                     
                  


            

Legacy_GhostOfGod

  • Hero Member
  • *****
  • Posts: 1490
  • Karma: +0/-0
Persistent Time for PW
« Reply #3 on: August 19, 2010, 12:51:58 am »


               

ShaDoOoW wrote...

However there is bug in your script. First set time and then date reason is simple. You can advance time only forward so if your module has starting hour 13, and you set new hour to 12, the game date will advance by one day. So either call time first or set starting hour to 0.


Thanks for the reply shadow.

The mods starting hour is 0 so no problems there. But yeah, I see what you are saying if somone didn't have their starting hour set to 0 and then fired the script. I guess it would'nt hurt anything to switch it.  Although the same thing applies to setting calendar too. Which is why we have the month and day in the mod set to 1.

Genisys wrote...

Why not just use local variables?  I mean if you restart your module that is...


This is for a Persistent World. Thus the PW in the title. When NWN shuts down and restarts, local variables are not saved on the module. So we need to use the database to store it. Thank you for the reply though Genisys.
               
               

               


                     Modifié par GhostOfGod, 18 août 2010 - 11:52 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Persistent Time for PW
« Reply #4 on: August 19, 2010, 01:03:20 am »


               There might be a way to avoid pseudo HB, but this needs NWNX. Using real time it would be possible to workaround this, something like:

load old real time value from database
get new real time value via NWNX
substract new from old and divide it by hour/minutes ratio in your module
this will give you ingame hours passed by the server was last time ran
load old date/time string from database
decode it into ints and add hours passed into it
recreate date/time string again from new values
set this date and time
save the new date/time string into database
save the current real time into database

its just fast idea, there will be some issues, but should work without pseudo heartbeats
[/quote]
               
               

               


                     Modifié par ShaDoOoW, 19 août 2010 - 12:04 .
                     
                  


            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Persistent Time for PW
« Reply #5 on: August 19, 2010, 01:55:36 am »


               I Really do not like the use of a string Var for this if you are using the NWN DB. The reason is that this date is going to be stored many times into the DB and with the nwn DB the old string is never overwritten. The nwn DB makes a new entry into the DB and marks the old entry as no longer used. Now that will not make a much of a difference if you do regular maintenance on the DB to clean out the unused strings and other complex data types.





An Integer on the other hand is a simple data type and will be replaced in the data base and not cause any bloating. So to answer your question. yes using only one variable to store the information but using an integer.  



As with many date systems in computer sciences few of them actually use day 0 year 0 as the date they start counting from.  The reason is simply that most things the date is going to be used for will never use the dates before a given date.  



So the best bet is to not start you server at year 0 day 0.  but to use a day closer to the start date of the server.  This will decrease the chance of your server running into a y2k bug when packing data into a limited data type.  



Packing the date into a integer  as hours from your server start date has another benefit. Normal math will work on the dates.  



First lets look at limitations of the date ranges  when counting  by hours from server start and packing it into an integer.  



NWN uses a 32 bit singed integer with a range of  +or -  2147483647.



numbers of hours in a NWN year is 24hours/day*28days/month*12months/year = 8064 hours per year for the nwn date system. (the NWN calendar only has 336 day in a year)



This means the  data type can handle   2,147,483,647/8,064 =  266505 years of dates.



hmm  I’m Showing my age now.  I’m use to having to use smaller data types. Looks like a start date of year 0 day 0 hour 0 will work just fine.



With your dates set as the number of hours  from your start point it is not that hard to pack and unpack your data, as long as you know how to use the Modulo(%) operator.



To pack your date you just multiply each of your  years, months, day  by how many hours  they are and add them all together.  



nDate =( nYear )* 8064 + (nMonth -1)* 672 + (nDay -1)*24 + nHour



the -1 are just to change the normal 1  base start numbers int 0 base start numbers  (ex. months of 1 - 12 to months of 0-11)  so that   day 1,  hour 1  come out to be  hour 1 not hour 25,



to unpack your year from your nDate you just need to divide by the number of  hours in a year and add one. since we are using an int data type  there will be no decimal to the number and we will be left with just the years.  





nYear  = (nDate / 8064)+1



To get the month from the nDate we take the remainder(%) from our  division of years and devide that by the number of hours in a month   and add 1.



nMonth = ((nDate%8064)/672)+1



or divide by the number of hours in a month  then find the remainder of the division by the number of months .  

 

nMonth = ((ndate /672)%12)+1



Both return the same result.  



finding the day is about the same.



nDay =(( nDate%672) / 24) +1



or

nDay = ((nDate/24) % 28 ) +1







To find the hour you just take the remainder of the division of the number of hours in a day.  Hours already start at hour 0  so there in no need for the +1 adjustment.



nHour = nDate%24





Hope you don’t mind the long answer with no code to your question.



L8





EDIT:  I dont think I would use a HB.  I think I would use something more like OnClient leave.  Who really cares if time passes or not if no one is on the server?



               
               

               
            

Legacy_GhostOfGod

  • Hero Member
  • *****
  • Posts: 1490
  • Karma: +0/-0
Persistent Time for PW
« Reply #6 on: August 19, 2010, 04:30:01 am »


               Hey there Lightfoot. This is perfect. Thank you for the reply. I was thinking about how to store it as one big int and then breaking it up but couldn't figure out the math. Still can't, even though you just showed me. ':lol:'

EDIT: The modules month and day can not be a 0. They have to start on 1. Not sure if that adds any problems to this math at all. So the mod would start with the standard year 1372. The month and day can both start on 1 if that is necessary and the hour can be 0. Would i just get rid of the -1's and +1's?

So this is what I did with what you explained. I changed it slightly to allow for keeping the year at its current number and not having to set it to 0. I hope I didn't screw anything up. I only tested this for the passing of 2 hours so I don't know whether or not there will be any issues when numbers get smaller again, y2k or what not.  Seems to work ok so far. But please let me know if I messed it up.

//Function to save current calendar date and time to a database.
void SaveCurrentDateTime()
{
string sDBName = "PERSISTENT_DATE_TIME";
int iYear = GetCalendarYear();
int iMonth = GetCalendarMonth();
int iDay = GetCalendarDay();
int iHour = GetTimeHour();
int iDate = (iYear * 8064) + ((iMonth - 1) * 672) + ((iDay -1) * 24) + iHour;

SetCampaignInt(sDBName, "INT_DATE_TIME", iDate);

//test lines
object oPC = GetFirstPC();
SendMessageToPC(oPC, "Time Saved!");
//

}

void RecursiveStoreDateTime()
{
DelayCommand(2.0, SaveCurrentDateTime());
DelayCommand(120.0, RecursiveStoreDateTime());
}

//Function to set the stored calendar date and time onto the PW.
void SetPWDateTime()
{
string sDBName = "PERSISTENT_DATE_TIME";
int iStored = GetCampaignInt(sDBName, "DATE_TIME_STORED");
int iDate = GetCampaignInt(sDBName, "INT_DATE_TIME");
int iYear = iDate / 8064;
int iMonth = ((iDate % 8064) / 672) + 1;
int iDay =((iDate % 672) / 24) + 1;
int iHour = iDate % 24;

//test lines. This will show 0,1,1,0 until database entry is added.
string sYear = IntToString(iYear);
string sMonth = IntToString(iMonth);
string sDay = IntToString(iDay);
string sHour = IntToString(iHour);
DelayCommand(10.0, SendMessageToPC(GetFirstPC(),"The year is: " + sYear +
                                                "\\nThe month is: " + sMonth +
                                                "\\nThe day is: " + sDay +
                                                "\\nThe hour is: " + sHour));
//

if (iStored != TRUE)
    {
    SetCampaignInt(sDBName, "DATE_TIME_STORED", TRUE);
    RecursiveStoreDateTime();
    }

else
    {
    SetCalendar(iYear, iMonth, iDay);
    SetTime(iHour, GetTimeMinute(), GetTimeSecond(), GetTimeMillisecond());
    RecursiveStoreDateTime();
    }
}


Oh and on this subject:

Lightfoot8 wrote...

 I dont think I would use a HB.  I think I would use something more
like OnClient leave.  Who really cares if time passes or not if no one
is on the server?


I was thinking about this too. But there is a little problem. If say only like 3 people are on the server and they have been playing for a few hours and suddenly the server crashes, then the date and time will not have had a chance to save. Then when they got back on, time might have gone backwards a couple days and who knows what hour.
               
               

               


                     Modifié par GhostOfGod, 19 août 2010 - 03:49 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Persistent Time for PW
« Reply #7 on: August 19, 2010, 11:17:48 am »


               Excellent Lightfoot, you can also manually substract starting year from the hours total (to get additional 1372 years)
               
               

               
            

Legacy_Genisys

  • Hero Member
  • *****
  • Posts: 961
  • Karma: +0/-0
Persistent Time for PW
« Reply #8 on: August 19, 2010, 11:44:51 am »


               What about writing to a token and just storing the token??  Wouldn't that be the ultimate solution?
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Persistent Time for PW
« Reply #9 on: August 19, 2010, 12:29:37 pm »


               no,  You would still bloat the DB.  If  he is going to save the time on a HB, Saving a token to the DB for the Time would increase the DB bloat by about 100 fold or more.



Oh and on this subject:



Lightfoot8 wrote...



I dont think I would use a HB. I think I would use something more

like OnClient leave. Who really cares if time passes or not if no one

is on the server?







I was thinking about this too. But there is a little problem. If say only like 3 people are on the server and they have been playing for a few hours and suddenly the server crashes, then the date and time will not have had a chance to save. Then when they got back on, time might have gone backwards a couple days and who knows what hour.




The reason I said OnClientExit is that is when the PC get saved to the DB.  At least if nothing extra has been added.  SO if the PC where not saved then the time really should go back because everything they did while on the server never happened and they are reverting back to where they where at that time any way.  



Now if you have added a system to export the characrets in the game.  Just save your time to the DB at the sme time.
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Persistent Time for PW
« Reply #10 on: August 19, 2010, 08:12:18 pm »


               Lightfoot, I just rewrited my persistent module with BIO db to use your algorhythm, but its not +1.
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Persistent Time for PW
« Reply #11 on: August 19, 2010, 08:23:07 pm »


               Hmm  I do see where I made an error above for the year,



nYear = (nDate / 8064)+1

should be

nYear = (nDate / 8064)



The Other ones should be correct.



I'll Double check when I get to the house.







               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Persistent Time for PW
« Reply #12 on: August 19, 2010, 08:32:36 pm »


               month and day are correct without +1 too
               
               

               


                     Modifié par ShaDoOoW, 19 août 2010 - 07:32 .
                     
                  


            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Persistent Time for PW
« Reply #13 on: August 19, 2010, 09:23:14 pm »


               No They really are not.    Here is where they are not.   Try and place a day 28  any given month and year into the date then unpack it.   You will end up with day 0.   same thing with the month without the +1 you will never have a month 12.   the reason is your nNumber%12 is going to return a remainder of 0-11.  It will never return a 12.  

               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Persistent Time for PW
« Reply #14 on: August 19, 2010, 09:26:31 pm »


               Well I tried your script with default dae 1372-6-1-13

when the clocks advanced by one hour I exited the server and ran it again with your alghorythm and get

1373-7-2-14



so where is wrong?