This has been one of the bigger hurdles for me when I tried to do this some time ago. They would spawn in fine the first time, but depending on the player behaviour (player leaves the area while it’s still day, doesn’t come back during the night for them to despawn, comes back the next day etc.) there would be multiple copies of NPCs or they’d never be there at all and things like that.
Scripts on entering area give me problems in general, especially since, say, loading the game is also considering entering an area (which, for instance, defeats autosaves on enter) and other stuff like that.
It doesn’t help that I only have a sort of vague suspicion what “storing a day/night flag” might mean and no idea how to really implement it. I, uh, really mean the “dummies” part.
BIG EDIT because I’m grappling with this stuff right now due to some unexpected free time.
I experimented a bit with the ultima schedule system, which looks pretty nice. It has an include file where the various actions for NPCs are defined, and a hartbeat script which actually makes them do it, using a nifty combination of waypoints nad variables.
I tried editing the include file, for some starter cosmetic changes, such as making creatures walk instead of run or speak different strings (let’s be honest, children running around saying “Tag! Thou art it!” is really silly). I think I’m supposed to recompile the script that uses the include in order for the changes to the include to be implemented, right? But the actual heartbeat script gives me a compiling error and given how complicated it is, I have no idea what to do about it.
The script:
Summary
#include “_actions_inc”
void main()
{
if (GetLocalInt(OBJECT_SELF,“WAITING”) == TRUE) return;
if (GetLocalInt(OBJECT_SELF,"INPARTY")==TRUE) return;
if (IsInConversation(OBJECT_SELF) || GetIsInCombat()) return;
SetAILevel(OBJECT_SELF,AI_LEVEL_LOW); // Faster Offscreen Action
int iAction = ACT_FOLLOW; // For Henchmen and Other Followers
string sMyName = GetTag(OBJECT_SELF);
iAction = 0;
// Build Schedule
int i12AM = GetLocalInt(OBJECT_SELF,"12AM");
int i3AM = GetLocalInt(OBJECT_SELF,"3AM");
int i6AM = GetLocalInt(OBJECT_SELF,"6AM");
int i9AM = GetLocalInt(OBJECT_SELF,"9AM");
int i12PM = GetLocalInt(OBJECT_SELF,"12PM");
int i3PM = GetLocalInt(OBJECT_SELF,"3PM");
int i6PM = GetLocalInt(OBJECT_SELF,"6PM");
int i9PM = GetLocalInt(OBJECT_SELF,"9PM");
if (i12AM == 0) // Get Midnight
if (i9PM > 0)i12AM = i9PM;
else if (i6PM > 0)i12AM = i6PM;
else if (i3PM > 0)i12AM = i3PM;
else if (i12PM > 0)i12AM = i12PM;
else if (i9AM > 0)i12AM = i9AM;
else if (i6AM > 0)i12AM = i6AM;
else if (i3AM > 0)i12AM = i3AM;
else if (i12AM == 0) // empty schedule, abort
{
if (AUTO_FIX_SCHED == TRUE)
{
if ((GetRacialType(OBJECT_SELF) != RACIAL_TYPE_UNDEAD) &&
(GetRacialType(OBJECT_SELF) != RACIAL_TYPE_CONSTRUCT))
{
SetLocalInt(OBJECT_SELF,"9PM",14); // sleep
CreateObject(OBJECT_TYPE_WAYPOINT,"newwaypoint",GetLocation(OBJECT_SELF),FALSE,sMyName+"_9PM");
}
SetLocalInt(OBJECT_SELF,"6AM",11); // loiter
CreateObject(OBJECT_TYPE_WAYPOINT,"newwaypoint",GetLocation(OBJECT_SELF),FALSE,sMyName+"_6AM");
}
else
{
if (DEBUG_SCHED > 0) SendMessageToPC(GetFirstPC(),sMyName+" in "+GetName(GetArea(OBJECT_SELF))+" has a blank schedule!");
return;
}
}
// Fill in the Blanks
if (i3AM == 0) i3AM = i12AM;
if (i6AM == 0) i6AM = i3AM;
if (i9AM == 0) i9AM = i6AM;
if (i12PM == 0) i12PM = i9AM;
if (i3PM == 0) i3PM = i12PM;
if (i6PM == 0) i6PM = i3PM;
if (i9PM == 0) i9PM = i6PM;
// The Time
int iTheTime = GetTimeHour()+1; // Odee-Odee-O!
if (iTheTime == 24) iTheTime = 0;
// Determine the proper course of Action
if ((iTheTime >= 0) && (iTheTime < 3)) iAction = i12AM;
if ((iTheTime >= 3) && (iTheTime < 6)) iAction = i3AM;
if ((iTheTime >= 6) && (iTheTime < 9)) iAction = i6AM;
if ((iTheTime >= 9) && (iTheTime < 12)) iAction = i9AM;
if ((iTheTime >= 12) && (iTheTime < 15)) iAction = i12PM;
if ((iTheTime >= 15) && (iTheTime < 18)) iAction = i3PM;
if ((iTheTime >= 18) && (iTheTime < 21)) iAction = i6PM;
if ((iTheTime >= 21) && (iTheTime < 24)) iAction = i9PM;
SetLocalInt(OBJECT_SELF,"ACTIVITY",iAction);
string sMyJob = "";
// Get a real job
if ((iTheTime >= 21) && (GetIsObjectValid(GetObjectByTag(sMyName+"_9PM")))) sMyJob = sMyName+"_9PM";
else if ((iTheTime >= 18) && (GetIsObjectValid(GetObjectByTag(sMyName+"_6PM")))) sMyJob = sMyName+"_6PM";
else if ((iTheTime >= 15) && (GetIsObjectValid(GetObjectByTag(sMyName+"_3PM")))) sMyJob = sMyName+"_3PM";
else if ((iTheTime >= 12) && (GetIsObjectValid(GetObjectByTag(sMyName+"_12PM")))) sMyJob = sMyName+"_12PM";
else if ((iTheTime >= 9) && (GetIsObjectValid(GetObjectByTag(sMyName+"_9AM")))) sMyJob = sMyName+"_9AM";
else if ((iTheTime >= 6) && (GetIsObjectValid(GetObjectByTag(sMyName+"_6AM")))) sMyJob = sMyName+"_6AM";
else if ((iTheTime >= 3) && (GetIsObjectValid(GetObjectByTag(sMyName+"_3AM")))) sMyJob = sMyName+"_3AM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_12AM"))) sMyJob = sMyName+"_12AM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_9PM"))) sMyJob = sMyName+"_9PM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_6PM"))) sMyJob = sMyName+"_6PM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_3PM"))) sMyJob = sMyName+"_3PM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_12PM"))) sMyJob = sMyName+"_12PM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_9AM"))) sMyJob = sMyName+"_9AM";
else if (GetIsObjectValid(GetObjectByTag(sMyName+"_6AM"))) sMyJob = sMyName+"_6AM";
else sMyJob = sMyName+"_3AM";
object oMyWork = GetObjectByTag(sMyJob); // posible non-wp obj?
SetLocalObject(OBJECT_SELF,"WORKPLACE",oMyWork);
if (GetIsObjectValid(oMyWork) != TRUE) // no work
if (AUTO_FIX_SCHED == TRUE) CreateObject(OBJECT_TYPE_WAYPOINT,"newwaypoint",GetLocation(OBJECT_SELF),FALSE,sMyJob);
else
{ // abort
SendMessageToPC(GetFirstPC(),sMyName+" in "+GetName(GetArea(OBJECT_SELF))+" can't find "+sMyJob+".");
return;
}
float fWorkRange = 3.0;
if (iAction == ACT_WAITER) fWorkRange = 20.0;
if (iAction == ACT_WANDER) fWorkRange = 40.0;
object oMyExit = GetNearestObject(OBJECT_TYPE_DOOR,oMyWork,1);
int iMyExit = 1;
if (GetArea(OBJECT_SELF) != GetArea(oMyWork)) //Offsite Work
{
if (GetLocalInt(OBJECT_SELF,"SITTING")==TRUE) SetLocalInt(OBJECT_SELF,"SITTING",FALSE);
ClearAllActions();
if (DEBUG_SCHED > 1) SendMessageToPC(GetFirstPC(),sMyName+" in "+GetName(GetArea(OBJECT_SELF))+" exiting to "+GetTag(oMyWork)+" in "+GetName(GetArea(oMyWork)));
if (GetArea(GetFirstPC())==GetArea(OBJECT_SELF))
{
MoveToNewLocation(GetLocation(oMyWork));
return;
}
else
{
if (GetArea(GetFirstPC()) == GetArea(oMyWork))
{
while (GetTransitionTarget(oMyExit)==OBJECT_INVALID)
{
iMyExit = iMyExit + 1;
oMyExit = GetNearestObject(OBJECT_TYPE_DOOR,oMyWork,iMyExit);
if (oMyExit==OBJECT_INVALID)
{
ActionJumpToObject(oMyWork);
return;
}
}
ActionJumpToObject(oMyExit);
DelayCommand(0.2,ActionMoveToObject(oMyWork));
return;
}
else
{
ActionJumpToObject(oMyWork);
return;
}
}
}
else if (GetDistanceToObject(oMyWork)>fWorkRange) //Too Far
{
if (GetLocalInt(OBJECT_SELF,"SITTING")==TRUE) SetLocalInt(OBJECT_SELF,"SITTING",FALSE);
ClearAllActions();
if (GetArea(GetFirstPC())==GetArea(OBJECT_SELF))
{
if (DEBUG_SCHED > 1) SendMessageToPC(GetFirstPC(),sMyName+" in "+GetName(GetArea(OBJECT_SELF))+" moving to "+GetTag(oMyWork));
MoveToNewLocation(GetLocation(oMyWork));
return;
}
else
{
ActionJumpToObject(oMyWork);
return;
}
}
int iAsleep = GetLocalInt(OBJECT_SELF,"ASLEEP");
if ((iAsleep == TRUE)&&(iAction != ACT_SLEEP))
{ // The sleeper must awaken!
ClearAllActions();
SetLocalInt(OBJECT_SELF,"ASLEEP",FALSE);
iAsleep = FALSE;
effect eSleep = GetFirstEffect(OBJECT_SELF);
while(GetIsEffectValid(eSleep))
{
if (GetEffectType(eSleep) == EFFECT_TYPE_SLEEP)
{
RemoveEffect(OBJECT_SELF, eSleep);
break;
}
eSleep = GetNextEffect(OBJECT_SELF);
}
}
if ((GetArea(GetFirstPC())!=GetArea(OBJECT_SELF))&& (iAction!=ACT_SLEEP))
if ((iAction!=ACT_DESK_WORK)&&((iAction!=ACT_EAT_AT_INN)&&(iAction!=ACT_MAJOR_SIT))) return;
if (DEBUG_SCHED > 1) SendMessageToPC(GetFirstPC(),sMyName+" in "+GetName(GetArea(OBJECT_SELF))+" : action #"+IntToString(iAction)+" during "+IntToString(iTheTime));
DoActivity(iAction);
}
The bit where it says
float fWorkRange = 3.0;
if (iAction == ACT_WAITER) fWorkRange = 20.0;
if (iAction == ACT_WANDER) fWorkRange = 40.0;
gives me a “variable defined without type” error, and while I know how to deal with that in my own simplistic scripts (usually means I misspelled a previously defined variable or that I forgot to say it’s an object or whatever), this one seems to throw that sort of thing about everywhere just fine and I have no idea how to handle that. I’d like to use this system, because I sort-of get how, and it’s not hard to slap onto an already messily complicated module, but without being able to tweak the scripts to my setting it’s not possible.