-
Notifications
You must be signed in to change notification settings - Fork 68
Time and Calendar
The game tracks time by counting minutes. The starting minutes value is set as 346320, which will give a date & time of 12:00 noon, Tirdas, 1st of Hearthfire in the year 3E 389.
The date format string is located @44224
. It takes the weekday, day, ordinal suffix, month and year. To get the short date string, the part starting with in the
is removed.
The values for the date format string are gotten as follows:
string GetDateString(unsigned int gameMinutes)
{
// Get the year
int year = gameMinutes / 518,400; // 518,400 minutes are in an in-game year
year += 389; // Starting year
// Get how many minutes into the year have passed
int minutesPassed = gameMinutes % 518,400;
// Use the number of minutes into the year to get the month
int i = 0;
while (minutesPassed >= NumbersOfMinutesPerMonth[i])
{
minutesPassed -= NumbersOfMinutesPerMonth[i];
i++;
}
// Get the day. At this point "minutesPassed" refers to how many minutes into the month have passed.
int day = (minutesPassed / 1440) + 1; // 1440 minutes in a day
string daySuffix = GetDaySuffix(day);
while (day >= 7)
{
day -= 7; // Day must be 0 through 6
}
// From here, use the numerical values obtained for month and day to get the corresponding strings from the
// string arrays, and combine with year, day suffix and the date format string.
return dateString;
}
NumbersOfMinutesPerMonth is a 12-element DWORD array @442BA
.
string GetDaySuffix(int day)
{
if (day == 11 || day == 12 || day == 13)
{
return DaySuffixes[3]; // "th"
}
day /= 10;
// Return DaySuffixes entry pointed to by DaySuffixAddresses[day]
}
DaySuffixes is a 4-element string array @443E6
.
DaySuffixAddresses is a 10-element WORD array @443D2
that points to the entries of DaySuffixes.
(To translate its values to the addresses being used here, add 0x3F527 to them.)
The time format is %d:02%d %s
, where the hour is 12-based, and the last string is taken from the strings located @44259, 44267, 44276, 4427B, 4428C, 4429B, 442A4
. The mapping is as follows:
00 midnight
01..02 night
03..05 early morning
06..11 morning
12 noon
13..17 afternoon
18..20 evening
21..23 night
Those are controlled by phase <- currentDay mod 32
. Moon1 uses phase
, Moon2 phase + 14
. The phase determines the frame in moon0x.dfa
which is shown.
- Each minute:
- Spell timers are advanced, and if any PC attribute reaches 0, PC dies.
- If there is no enemies around, and not resting, the levelup is checked.
- Stamina decreases and is checked
- Some other things happen
- Quest and random encounters are fired
- Each hour:
- Quests are checked for expiring
- If resting or in wilderness, a random encounter is fired
- Each day:
- Quests are checked for expiring (v2)
- The weather changes
- Knights repair their equipment
Knight equipment repair
void KnightItemRepair(void)
{
int maxRepairAmount = (PlayerLevel + 1) * 10;
int i = 0;
INVENTORYITEM* item = &PlayerInventoryItems[i];
int numberOfUsedInventorySlots = GetNumberOfInventorySlotsInUse();
while (numberOfUsedInventorySlots != 0)
{
byte flags = ((item->Flags) & 0xc) >> 2;
if ((flags != 2 && flags != 3)) // Not a potion or trinket
{
int amountToRepair = item->MaxHealth - item->Health; // Only repair as much as is damaged
if (amountToRepair != 0 && amountToRepair > maxRepairAmount)
{
amountToRepair = maxRepairAmount; // Max repair amount depends on level
}
item->Health += amountToRepair;
}
i++;
item = &PlayerInventoryItems[i];
numberOfUsedInventorySlots--;
}
return;
}
There are 15 holidays. Their names are stored in szlist @4268D
. The info popups for them are resource strings starting with #47
, and their descriptions are resource strings starting with #169
.
The holiday data itself is stored @4277F
. It has the following structure:
BYTE venueType
BYTE services[5]
venueType
is the interior index, and the value of services
is individual for each venue type. Values 1..4 are used as (cost/4)*n
.
0 Is opened on this day
1 Drinks, Heals, Weapons, Spells
2 Rooms, Cures, Armor, Identifies
3 Magical items
4 Blessings, Potions/Spells
Checking whether a service provider is affected by a holiday
bool DoesHolidayServiceAdjustmentApply(void)
{
int holidayIndex;
bool isHoliday = IsHoliday(GameMinutes, holidayIndex);
if (isHoliday && (HolidayData[holidayIndex].venueType == CurrentInteriorType))
{
return true;
}
return false;
}
Displaying holiday text
// Within an update loop function. "UpdateCount" is a guess but this seems to be a global that is used for timing when
// certain updates happen. In this case it might be waiting until 3 updates have happened so the holiday text doesn't show
// immediately on loading a game.
if (UpdateCount > 3 && IsHoliday(GameMinutes) && (PlayerFlags3 & HaveShownHolidayText) == 0
&& (PlayerEnvFlags & InWildernessOrDungeon) == 0 && (PlayerEnvFlags2 & Outdoors) != 0)
{
PlayerFlags3 = PlayerFlags3 | HaveShownHolidayText; // This flag will be cleared when the date changes.
TEMPLATE_DATIndex = holidayIndex + 47;
ShowTEMPLATE_DATMessage();
}
bool IsHoliday(int gameMinutes, int& holidayIndex)
{
int dayOfYear = (gameMinutes % 518400) / 1440; // 518400 minutes in a game year, 1440 minutes in a day
if (dayOfYear < 345) // The last holiday in the game data is on the 344th day.
{
holidayIndex = 0;
while( true )
{
bool isHoliday = (HolidayDates[i] == dayOfYear);
if (isHoliday)
{
return true;
}
if (!isHoliday && dayOfYear <= HolidayDates[i])
{
break;
}
holidayIndex++;
}
}
return false;
}
HolidayDates
(day number in the year) is the WORD
array @427D9
.