Script people in a town area going to sleep at night?

Alright, in my final test I only found one NPC that didn’t manage to get to sleep. So the system seems pretty solid, I must say. Sure, if it could be totally bullet proof that would of course be even better, but now at least most NPCs seems to find their way home in the night.

if you’re good i’m good. but Here’s some stuff anyway  :)

object GetRandomHomeDoor(float fMaxDistance)
{
    object oDoor = OBJECT_INVALID;
    int iDoor = 0;

    object oNpc = OBJECT_SELF;

    int i = 1;
    object oObject = GetNearestObject(OBJECT_TYPE_DOOR, oNpc, i);
    while (GetIsObjectValid(oObject)
        && GetDistanceToObject(oObject) <= fMaxDistance)
    {
        if (GetLocalInt(oObject, "nCAIUseOK") && !Random(++iDoor))
            oDoor = oObject;

        oObject = GetNearestObject(OBJECT_TYPE_DOOR, oNpc, ++i);
    }
    return oDoor;
}


// debug funct.
void Tell(string tell){ SendMessageToPC(GetFirstPC(FALSE), tell); }

const string VAR_MOVECOUNT = "iMoveCount";  // how many times (heartbeats) the NPC has tried to get home
const int iMOVECOUNT_LIMIT = 6;             // the maximum count of tries to get home before jumping there

const float fMAXDISTHOME = 30.f;

// Moves a creature toward a door on its Heartbeat and makes it go scripthidden
// when it gets there. Sets a bunch of vars to sleep at night.
// - oNpc   : OBJECT_SELF
// - oDoor  : an object to move to (usually a door)
// - iSpeak : not used.
void SimpleNpcGoHomeAndSleepUntilWakeTime(object oNpc, object oDoor, int iSpeak)
{
    oNpc = OBJECT_SELF; // IMPORTANT: Overrides 'oNpc' arg

    Tell(" ");
    Tell("SimpleNpcGoHomeAndSleepUntilWakeTime() " + GetName(oNpc) + " ( " + GetTag(oNpc) + " )");

    if (!GetIsObjectValid(oDoor))
        oDoor = GetRandomHomeDoor(fMAXDISTHOME);

    if (GetIsObjectValid(oDoor))
    {
        ClearAllActions(); // Don't stack the action queue

        ClearFlags(oNpc);

        float fDist;

        int iMoveCount = GetLocalInt(oNpc, VAR_MOVECOUNT) + 1;
        SetLocalInt(oNpc, VAR_MOVECOUNT, iMoveCount);
        Tell(". iMoveCount= " + IntToString(iMoveCount));

        if (iMoveCount >= iMOVECOUNT_LIMIT)
        {
            Tell(". FORCE GO HOME");
            JumpToObject(oDoor);
            fDist = 0.f;
        }
        else
            fDist = GetDistanceToObject(oDoor);

        if (fDist > 3.f) // move to door if not already there.
        {
            Tell(". moving to door tag= " + GetTag(oDoor));
            ActionMoveToObject(oDoor);
        }
        else
        {
            Tell(". arrived at door tag= " + GetTag(oDoor));
            DeleteLocalInt(oNpc, VAR_MOVECOUNT);

            SetLocalInt(oNpc, "nStartedMovingHome", FALSE);
            SetLocalInt(oNpc, "nNpcMovingHome",     FALSE); // arrived at home
            SetLocalInt(oNpc, "nNpcIsAsleep",       TRUE);

            vector pSelf = GetPositionFromLocation(GetLocation(oNpc));
            SetLocalFloat(oNpc, "fLocationHomeX", pSelf.x);
            SetLocalFloat(oNpc, "fLocationHomeY", pSelf.y);
            SetLocalFloat(oNpc, "fLocationHomeZ", pSelf.z);
            SetLocalFloat(oNpc, "fFacingAtSleep", GetFacing(oNpc));


            PlayAnimation(ANIMATION_LOOPING_GET_MID, 1.f, 1.f); // open the door

            DelayCommand(2.f, SetScriptHidden(oNpc, TRUE, FALSE));

            // have to restore default ai level so we can continue to process while
            // hidden in prep for waking.
            DelayCommand(3.f, SetAILevel(oNpc, AI_LEVEL_DEFAULT));
        }
    }
    else Tell(". oDoor is INVALID");
}
1 Like

Ah, looks interesting. I think I will try your code tomorrow. I like that you have implemented the iMoveCount. I’ll just have to make sure, since this is an include script, that I change every script that uses that include script and compile again. Lance told me that you have to do it like that unfortunately, otherwise it won’t work, I believe.

ok will write up some points to consider … am busy eating ice cream atm

1 Like

I’m not actually a good coder. I just manage to take code other people share and get it adapted for what I hope to do with it. It can wind up not exactly optimized or perfect because of me.

3 Likes

@andgalf Compiling is just like baking: you have to get all the ingredients (code) together and mixed up just right before putting it in the oven (compiling).

Because once it’s compiled, that’s that; that’s the bytecode. It ain’t gonna go looking for #includes anymore.

So if an #include gets changed, any script that uses those changes need to be recompiled. ( Turtles all the way down, re. nested #includes aka dependencies within dependencies – but i don’t think that’s an issue here. )

But notice that doesn’t mean that everything that uses a changed #include needs recompiling. Only scripts that (you want to) make use of the changes need to be recompiled.

So if the only script you’re using from kamal’s CAI pack is your OnHeartbeat script, only the OnHeartbeat script needs to be recompiled.

After replacing the older versions of the functions and/or variables in the #include.

And yes i suggest simply commenting out the older functions w/

/*
// this function has been updated/replaced below
[old function]
*/

[new function]

but don’t delete the old ones. Mine are untested.

 
Of course it’s a good idea to recompile everything to keep everything up to date w/ current code. But it’s not strictly necessary. Just saying that not doing so can come back to haunt ya …

 
:camel: for the pack @kamal

3 Likes

I have implemented your code now and everything works! Now, I just have to remove the debug messages. It was, however, interesting to see what happened and when. It really takes quite a lot of time before everyone has gone home to sleep, but that looks and seems natural, so I like that.

When waiting to see what would happen next, being slightly worried that the NPCs wouldn’t come back in the morning, everyone eventually did. However, there’s one particular lady that I want to end up exactly where she was before going to bed, and she didn’t manage to go back to where she was. I don’t have any waypoints for the NPCs so that may be understandable. Maybe I would need a slightly different heartbeat script for her.

Edit: I know I will just have to do another version of sh_hbeat_generic_standardhb by editing it slightly for this to work with the NPC that I need to be at a certain spot when awake, but there’s so much code. If someone could help with this…ugh.
I think it’s here somewhere, in this part of the code, that I need to make a change to jump to a waypoint perhaps.

	//not time to go home,  	   
		if (GetLocalInt(OBJECT_SELF, "nNpcMovingHome")!=1)
			{
			if // determine if time to be active	  
				(((((GetLocalInt(OBJECT_SELF, "nNpcIsAsleep") ==1 ) && (nCurrentTimeHour > GetLocalInt(OBJECT_SELF, "nTimeWakeHour")) )
				|| ((GetLocalInt(OBJECT_SELF, "nNpcIsAsleep") ==1 ) && (nCurrentTimeHour == GetLocalInt(OBJECT_SELF, "nTimeWakeHour")) && (nCurrentTimeMinute == GetLocalInt(OBJECT_SELF, "nTimeWakeMinute"))) )
				//and before sleep time
				&&
				((GetLocalInt(OBJECT_SELF, "nTimeGoHomeHour") > nCurrentTimeHour) || (GetLocalInt(OBJECT_SELF, "nTimeGoHomeHour") == nCurrentTimeHour) && (GetLocalInt(OBJECT_SELF, "nTimeGoHomeMinute") < nTimeGoHomeMinute)))
				//make sure they don't all wake at same time, it causes lag if too many npcs do this at once.
				&&
				(Random(3)==1))
				{
				//jumping back to where we went hidden shouldn't be necessary, but testing has shown it is.
				//for an unknown reason when the npc ends the hidden status, it appears immediately at it's original location
				float fSelfHomeX = GetLocalFloat(OBJECT_SELF, "fLocationHomeX");
				float fSelfHomeY = GetLocalFloat(OBJECT_SELF, "fLocationHomeY");
				float fSelfHomeZ = GetLocalFloat(OBJECT_SELF, "fLocationHomeZ");
				float fFacingAtSleep = GetLocalFloat(OBJECT_SELF, "fFacingAtSleep");
				float fFacingAtWake = fabs(fFacingAtSleep-180.0);
				vector vHome = Vector(fSelfHomeX, fSelfHomeY, fSelfHomeZ);
				location lHome = Location(GetArea(OBJECT_SELF), vHome, 0.0);
				JumpToLocation(lHome);
				SetFacing(fFacingAtWake);
				SetLocalInt(OBJECT_SELF, "nActivity", 0); //no longer doing activity.
				SetLocalInt(OBJECT_SELF, "nNpcIsAsleep", 0);
				SetScriptHidden(OBJECT_SELF, 0);
				SetLocalInt(OBJECT_SELF, "nNpcMovingHome", 0);
				}
	   
 
			// if awake
			int nAsleep = GetLocalInt(OBJECT_SELF, "nNpcIsAsleep");
			int nMovingHome = GetLocalInt(OBJECT_SELF, "nNpcMovingHome");
			if ((nAsleep !=0 ) && (nMovingHome!= 1))
				{
			   	location lCurrent = GetLocation(OBJECT_SELF);
				vector vCurrentLocation = GetPositionFromLocation(lCurrent);
				float fOriginX = 	GetLocalFloat(OBJECT_SELF, "fLocationX");
				float fOriginY = 	GetLocalFloat(OBJECT_SELF, "fLocationY");
				float fOriginZ = 	GetLocalFloat(OBJECT_SELF, "fLocationZ");
				vector vOrigin = Vector(fOriginX, fOriginY, fOriginZ);
				location lOrigin = Location(GetArea(OBJECT_SELF), vOrigin, 0.0);
	
				//if awake and at original position, not in conversation or combat
				if ((vCurrentLocation == vOrigin) && (IsInConversation(OBJECT_SELF) == FALSE) && (GetIsInCombat(OBJECT_SELF) == FALSE))
					{

					SetLocalInt(OBJECT_SELF, "nIsInCombat", 0);
					//we only want to unequip once per time period at origin
					if (GetLocalInt(OBJECT_SELF, "nUnequipDoOnce")== 0)
						{
						SetLocalInt(OBJECT_SELF, "nUnequipDoOnce", 1);
						//remove the vfx for what we were holding while Activity.
						}
					SetLocalInt(OBJECT_SELF, "nActivity", 1); //at origin, mark as acting
					
					//run standard heartbeat
					if (GetAILevel() == AI_LEVEL_VERY_LOW) return;
					
					//insert code block for commoner ai activity. 
					//mark self as in combat.
					SetLocalInt(OBJECT_SELF, "nIsInCombat", 1);
					//mark so we will unequip when combat over and we've returned to origin.
					SetLocalInt(OBJECT_SELF, "nUnequipDoOnce", 0); 
					SetLocalInt(OBJECT_SELF, "nActivity", 0);
					//end inserted code. begin the stock hb script for the rest of the incombat if statement

					if(GetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY))
						{
						if(HenchTalentAdvancedBuff(40.0))
							{
							SetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY, FALSE);
							// TODO evaluate continue with combat
							return;
							}
						}

					if (HenchCheckHeartbeatCombat())
						{
						HenchResetCombatRound();
						}
					if(GetHasEffect(EFFECT_TYPE_SLEEP))
						{
						if(GetSpawnInCondition(NW_FLAG_SLEEPING_AT_NIGHT))
							{
							effect eVis = EffectVisualEffect(VFX_IMP_SLEEP);
							if(d10() > 6)
								{
								ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, OBJECT_SELF);
								}
							}
						}

					// If we have the 'constant' waypoints flag set, walk to the next
					// waypoint.
					else if (!GetIsObjectValid(GetNearestSeenOrHeardEnemyNotDead(HENCH_MONSTER_DONT_CHECK_HEARD_MONSTER)))
						{
						CleanCombatVars();
						if (GetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL))
							{
							HenchDetermineSpecialBehavior();
							}
						else if (GetLocalInt(OBJECT_SELF, sHenchLastHeardOrSeen))
							{
							// continue to move to target
							MoveToLastSeenOrHeard();
							}
						else
							{
							SetLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_POLL, FALSE);
							if (DoStealthAndWander())
								{
								// nothing to do here
								}
							// sometimes waypoints are not initialized
							else if (GetWalkCondition(NW_WALK_FLAG_CONSTANT))
								{
								WalkWayPoints();
								}
							else
								{
								if(!IsInConversation(OBJECT_SELF))
									{
									if(GetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS) ||
										GetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS_AVIAN))
										{
										PlayMobileAmbientAnimations();
										}
									else if(GetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS))
										{
										PlayImmobileAmbientAnimations();
										}
									}
								}
							}
						}
					else if (GetUseHeartbeatDetect())
						{
						//	Jug_Debug(GetName(OBJECT_SELF) + " starting combat round in heartbeat");
						//	Jug_Debug("*****" + GetName(OBJECT_SELF) + " heartbeat action " + IntToString(GetCurrentAction()));
						HenchDetermineCombatRound();
						}
					if(GetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT))
						{
						SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_HEARTBEAT));
						}
1 Like

I think I managed to solve the last bit. I changed the code to this (compare with the code in the previous post if you want):

//not time to go home,  	   
		if (GetLocalInt(OBJECT_SELF, "nNpcMovingHome")!=1)
			{
			if // determine if time to be active	  
				(((((GetLocalInt(OBJECT_SELF, "nNpcIsAsleep") ==1 ) && (nCurrentTimeHour > GetLocalInt(OBJECT_SELF, "nTimeWakeHour")) )
				|| ((GetLocalInt(OBJECT_SELF, "nNpcIsAsleep") ==1 ) && (nCurrentTimeHour == GetLocalInt(OBJECT_SELF, "nTimeWakeHour")) && (nCurrentTimeMinute == GetLocalInt(OBJECT_SELF, "nTimeWakeMinute"))) )
				//and before sleep time
				&&
				((GetLocalInt(OBJECT_SELF, "nTimeGoHomeHour") > nCurrentTimeHour) || (GetLocalInt(OBJECT_SELF, "nTimeGoHomeHour") == nCurrentTimeHour) && (GetLocalInt(OBJECT_SELF, "nTimeGoHomeMinute") < nTimeGoHomeMinute)))
				//make sure they don't all wake at same time, it causes lag if too many npcs do this at once.
				&&
				(Random(3)==1))
				{
				//jumping back to where we went hidden shouldn't be necessary, but testing has shown it is.
				//for an unknown reason when the npc ends the hidden status, it appears immediately at it's original location
				//float fSelfHomeX = GetLocalFloat(OBJECT_SELF, "fLocationHomeX");
				//float fSelfHomeY = GetLocalFloat(OBJECT_SELF, "fLocationHomeY");
				//float fSelfHomeZ = GetLocalFloat(OBJECT_SELF, "fLocationHomeZ");
				//float fFacingAtSleep = GetLocalFloat(OBJECT_SELF, "fFacingAtSleep");
				//float fFacingAtWake = fabs(fFacingAtSleep-180.0);
				//vector vHome = Vector(fSelfHomeX, fSelfHomeY, fSelfHomeZ);
				//location lHome = Location(GetArea(OBJECT_SELF), vHome, 0.0);
				//JumpToLocation(lHome);
				//SetFacing(fFacingAtWake);
				SetLocalInt(OBJECT_SELF, "nActivity", 0); //no longer doing activity.
				SetLocalInt(OBJECT_SELF, "nNpcIsAsleep", 0);
				SetScriptHidden(OBJECT_SELF, 0);
				SetLocalInt(OBJECT_SELF, "nNpcMovingHome", 0);
				}
1 Like

Hi andgalf,

Yes, I have done this in The Scroll. :slight_smile: It covers all things from villagers not being out at night, to NPCs resting in certain places. e.g. Store keepers spend night at the tavern and not at store.

My own system also uses such things as WP_ , WN_ , POST_ , NIGHT_ as @raymondsebas mentions.

It can be quite involved, and it took me some time to get right, and can involve some extra work moving forward just to be sure nothing breaks with it or it breaks anything.

My own system involves checking times when exiting or entering an area. … That is about as much as I can describe as “easy”. Thereafter, you need to consider many other things, including their waypoints, time it occurs, quests involved, etc.

This goes back many years for me when I wrote a post about my NPCs going down the pub, when they should not have done so yet. :slight_smile: CLICK FOR A READ You may want to search for other references to my REAL LIFE system, which this is a part of. It also reiterates some of the potential pitfalls that can occur.

Bottom line … It is involved, and certainly too involved for a simple response. Again, I recommend that you download my module, open it up in the toolset and take a look at some of the scripting to try to understand some of those parts I mean. As @kevL_s says, it’s not too complicated in itself, just involved and requires much consideration. That’s the complication! E.g. Have you set up time to work or are you just going to do such “sleeps” ad hoc? My own system involves proper time, but the easier way would be ad-hoc.

All the best with it and feel free to ask about any of my own scripts if you go that way.

EDIT: I see you have a system that appears to work for you … great! (Just be careful of quest involvements with any NPCs that use this system.)

Thanks, Lance.

2 Likes

ok?

Yes, it appears to work with the changes I did. I just, as you can see, took away the JumpToLocation(lHome) and the floats and vector before that. And just as the description says, it appears that the NPC who I wanted to appear at it’s original location, does so when I did it like this. In this particular case I didn’t want her to reappear at the door she went into (or rather where she got script hidden) since when I tested before she didn’t find her way back to her original position. Now she appeared exactly where I want her. It could maybe look weird that she just teleports there, instead of trying to walk there which failed before, after being script hidden, but I can live with that.

3 Likes