Persistent Time for PW
#1
Posté 18 août 2010 - 10:53
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.
#2
Posté 18 août 2010 - 11:18
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..)
#3
Posté 18 août 2010 - 11:26
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 .
#4
Posté 18 août 2010 - 11:51
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 .
#5
Posté 19 août 2010 - 12:03
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 .
#6
Posté 19 août 2010 - 12:55
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?
#7
Posté 19 août 2010 - 03:30
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 .
#8
Posté 19 août 2010 - 10:17
#9
Posté 19 août 2010 - 10:44
#10
Posté 19 août 2010 - 11:29
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.
#11
Posté 19 août 2010 - 07:12
#12
Posté 19 août 2010 - 07:23
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.
#13
Posté 19 août 2010 - 07:32
Modifié par ShaDoOoW, 19 août 2010 - 07:32 .
#14
Posté 19 août 2010 - 08:23
#15
Posté 19 août 2010 - 08:26
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?
#16
Posté 19 août 2010 - 09:06
nDate =( nYear )* 8064 + (nMonth -1)* 672 + (nDay -1)*24 + nHour
#17
Posté 19 août 2010 - 09:21
Modifié par ShaDoOoW, 19 août 2010 - 09:28 .





Retour en haut







