home · browse · search · game entities · user directory · message board · IRC | register

October 27, 2006, 3:43 pm PDT
username  
password  
forgot password?

Popular Resources
  • Half-Life 2 Mod FAQ
  • Valve Hammer Editor
  • Hammer 3.5 beta test
  • Half-Life Utilities
  • game data files
  • ZHLT 2.5.3 custom build
  • Half-Life SDK
  • Feedback
    If you've got any feedback, suggestions, or bugs to report regarding the Collective website, go here!

  • Feedback (301)
  • Newsletter
     
    Enter your email address in the above form to add yourself to the email newsletter list. Click here for more info.

    Hosted Sites
  • Valve ERC
  • Collective
  • TFMapped
  • Spirit of Half-Life
  • Selective Design
  • Pixel Reviews
  • recent articles

    NPC and Item Placement Theory
    17/03/05 11:35pm PST
    Non-Player Character (NPC) and item placement can influence both the gameflow and immersion of a level. This article aims to give some pointers on how to properly place them.
    - Hugh 'Hugh' Lloyd

    Got Props?
    13/03/05 08:32am PST
    A common problem in HL2 mapping is props not showing up in game. This article explains why and offers solutions.
    - Jeff 'Yesukai' Pritchard

    Simulating Randomness
    18/12/04 11:29pm PST
    This article focuses on how to properly simulate random events that should occur at a certain average frequency, or within a certain probability per period of time.
    - Skyler 'Zipster' York

    Adding Single-Player Weapons to Half-Life 2
    15/12/04 06:52pm PST
    Covers the process behind adding weapons to a single-player Half-Life 2 modification.
    - Skyler 'Zipster' York

    Your world in HL2
    06/12/04 12:17am PST
    This article gives tips and advice to anyone wanting to make custom photorealistic textures to be used in Half-Life 2.
    - Oksid

    Hiding in Shadow
    21/08/04 01:11pm PDT
    Describes how to create a function that has monsters disregard you if you are hiding in a certain level of "darkness," which can be set from within map properties.
    - Anders [Wolf] Jenbo (NoBody)

    XSI EXP for Half-Life 2 Tutorial - Camera Control
    23/09/04 12:43am PDT
    A SOFTIMAGE|XSI tutorial explaining all of the camera controls available to you in XSI!
    - Josh Enes

    Bump Mapping in Half-Life
    08/08/04 11:58am PDT
    Details a method of achieving real-time bump mapping in Half-Life, and provides an implementation of the algorithm.
    - Francis 'DeathWish' Woodhouse

    Real-Time "TRON 2.0" Glow For Low-Spec Hardware
    19/06/04 02:06pm PDT
    A sequel to the original "Real-Time 'TRON 2.0' Glow" article, this describes how to implement real-time glow that works on low-spec graphics cards.
    - Francis 'DeathWish' Woodhouse

    Hitboxes and Code
    05/06/04 06:25pm PDT
    How do I make only one part of a monster take damage? Learn about the relationship between model hitboxes and what you can do with them in a characters code.
    - Jonathan 'Teh_Freak' Smith

    Half-Life AI, Schedules and Tasks
    [Mon Apr 08, 2002 / 06:22pm PDT] Caleb 'Ghoul' Delnay - comments (0) comments enabled

    This tutorial assumes you have prior knowledge on how the HL SDK works. If you don't have this knowledge you'll have a hard time understanding this tutorial.

      Note: Some code was slightly modified to allow it to fit on this page better.

    The Half-Life AI is made mostly of two things, tasks and schedules. A task is a specific action performed by a monster, like playing a sequence, running to take cover, or throwing a grenade. A schedule is a set of tasks performed in a specific order, like finding a path to a corpse, running to the corpse, and performing a specific animation. Half-Life has a set of basic AI functions but most of the monsters have there own added schedules and tasks to give them there own unique AI or override the default AI. Below is a schedule of the victory dance a human grunt does.

    //=========================================================
    // Victory dance!
    //=========================================================
    Task_t	tlGruntVictoryDance[] =
    {
    	{ TASK_STOP_MOVING, (float)0	},
    	{ TASK_FACE_ENEMY, (float)0	},
    	{ TASK_WAIT, (float)1.5		},
    	{ TASK_GET_PATH_TO_ENEMY_CORPSE,(float)0	},
    	{ TASK_WALK_PATH, (float)0	},
    	{ TASK_WAIT_FOR_MOVEMENT, (float)0	},
    	{ TASK_FACE_ENEMY, (float)0	},
    	{ TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE	},
    };
    
    Schedule_t	slGruntVictoryDance[] =
    {
    	{ 
    		tlGruntVictoryDance,
    		ARRAYSIZE ( tlGruntVictoryDance ), 
    		bits_COND_NEW_ENEMY		|
    		bits_COND_LIGHT_DAMAGE	|
    		bits_COND_HEAVY_DAMAGE,
    		0,
    		"GruntVictoryDance"
    	},
    };
    

    The slGruntVictoryDance[] is the actual schedule, the tlGruntVictoryDance[] is the list of tasks for that schedule. The stuff after ARRAYSIZE ( tlGruntVictoryDance ), are the different things that can interrupt this schedule. In this case, seeing a new enemy, and taking light or heavy damage. The 0, are different sounds that can stop the schedule. No sounds can stop this schedule. The "GruntVictoryDance" is the name of the schedule, if you do impulse 103 while looking at a monster it will give you a report on its current AI status. Now looking at tlGruntVictoryDance[] you'll see a list of tasks to be performed for this schedule. The (float)0 followed by each task is the value of its data. Most tasks don't need anything here except (float)0 but some need a value, for example: TASK_PLAY_SEQUENCE need an ACT to play, most sequences in a monster a linked to specific ACTs, like ACT_DIE, ACT_RELOAD, etc. Farther down in the hgrunt.cpp file you'll see this:

    DEFINE_CUSTOM_SCHEDULES( CHGrunt )
    {
    	slGruntFail,
    	slGruntCombatFail,
    	slGruntVictoryDance,
    	slGruntEstablishLineOfFire,
    	slGruntFoundEnemy,
    	slGruntCombatFace,
    	slGruntSignalSuppress,
    	slGruntSuppress,
    	slGruntWaitInCover,
    	slGruntTakeCover,
    	slGruntGrenadeCover,
    	slGruntTossGrenadeCover,
    	slGruntTakeCoverFromBestSound,
    	slGruntHideReload,
    	slGruntSweep,
    	slGruntRangeAttack1A,
    	slGruntRangeAttack1B,
    	slGruntRangeAttack2,
    	slGruntRepel,
    	slGruntRepelAttack,
    	slGruntRepelLand,
    };
    
    IMPLEMENT_CUSTOM_SCHEDULES( CHGrunt, CSquadMonster );
    

    That code defines all the custum schedules and implements them into the monster so it can use them.

    Close to the top of the hgrunt.cpp file there is this little chunk of code.

    //=========================================================
    // monster-specific schedule types
    //=========================================================
    enum
    {
    	SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1,
    	SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
    	SCHED_GRUNT_COVER_AND_RELOAD,
    	SCHED_GRUNT_SWEEP,
    	SCHED_GRUNT_FOUND_ENEMY,
    	SCHED_GRUNT_REPEL,
    	SCHED_GRUNT_REPEL_ATTACK,
    	SCHED_GRUNT_REPEL_LAND,
    	SCHED_GRUNT_WAIT_FACE_ENEMY,
    	SCHED_GRUNT_TAKECOVER_FAILED,
    	SCHED_GRUNT_ELOF_FAIL,
    };
    

    That declares all the schedule types which will be used later. It is important that after the first schedule you have the = LAST_COMMON_SCHEDULE + 1, otherwise your schedule numbers will be all wrong and your monsters will be doing stuff they shouldn't. The next part of code looks like this.

    //=========================================================
    // monster-specific tasks
    //=========================================================
    enum 
    {
    	TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1,
    	TASK_GRUNT_SPEAK_SENTENCE,
    	TASK_GRUNT_CHECK_FIRE,
    };
    

    That peice of code declares all the task types. Again its important to have = LAST_COMMON_TASK + 1, after the first task. Now, moving on.

    The code that tells a monster what schedule to do is contained mostly in two functions, GetSchedule and GetScheduleOfType.

    GetSchedule checks the monsters current state (combat, idle, alert, etc.) and picks a schedule based in different situations. When these requirements are met GetSchedule then returns with GetScheduleOfType( SCHEDULE_TYPE ). GetScheduleOfType uses the schedule types to return the corret schedule. Take a look at these two sections of code.

    In AI_BaseNPC_Schedule.cpp (schedule.cpp in SDKs older than 2.2), GetSchedule function.

    if ( HasConditions( bits_COND_ENEMY_DEAD ) 
    	&& LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE )
    {
    	return GetScheduleOfType ( SCHED_VICTORY_DANCE );
    }
    

    And back in hgrunt.cpp, GetScheduleOfType function.

    case SCHED_VICTORY_DANCE:
    {
    	if ( InSquad() )
    	{
    		if ( !IsLeader() )
    		{
    			return &slGruntFail;[ 0 ];
    		}
    	}
    
    	return &slGruntVictoryDance;[ 0 ];
    }
    

    Now heres what that code does. In the GetSchedule code the game checks to see if the monsters enemy is dead and if it has a sequence linked the the ACT_VICTORY_DANCE activity. If it does have the act, then it calls the monsters GetScheduleOfType with SCHED_VICTORY_DANCE. In the hgrunts GetScheduleOfType it looks through all the schedule types. If the grunt is in a squad and is the leader then it returns the fail schedule, otherwise it returns the victory dance schedule (seen farther up this page).

    What? The tutorial is all done? Nah, still have to go over tasks! Like schedules, two functions are used to control tasks, StartTask and RunTask. StartTask is called when the task first starts up. RunTask is then called every time the monster thinks until the task is complete. Lets look at the task that tells the grunt which way to face when he throws a grenade. In the StartTask function we have this.

    case TASK_GRUNT_FACE_TOSS_DIR:
    	break;
    

    As you can see this task doesn't need to do anything when it first starts. Not all tasks use StartTask or RunTask (although they have to use one). Now in RunTask.

    case TASK_GRUNT_FACE_TOSS_DIR:
    {
    // project a point along the toss vector and turn to face that point.
    	MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 );
    	ChangeYaw( pev->yaw_speed );
    
    	if ( FacingIdeal() )
    	{
    		m_iTaskStatus = TASKSTATUS_COMPLETE;
    	}
    	break;
    }
    

    Now this code is called every time the monster thinks. It tells the grunt to turn towards where he wants to throw a grenade and when he finally faces the right direction the task is complete. The code that tells the schedule that this task is complete is the m_iTaskStatus = TASKSTATUS_COMPLETE;. You can also use TaskComplete() and TaskIsComplete(). TaskComplete checks to see if the task failed or not, TaskIsComplete just returns m_iTaskStatus = TASKSTATUS_COMPLETE;. It is important that you complete the task somehow, otherwise the monster just performs the task for an infinate amount of time (aka it becomes a vegie).

    There you have it, a basic synapses of how the HL AI system works. You should probably look around the defaultai.cpp and AI_BaseNPC_Schedule.cpp (schedule.cpp with SDKs older than 2.2) to see a list of all the default tasks and schedules and get a grasp on how the defualt AI works.

    article created on Fri Apr 05, 2002 / 10:19am PST
    this item has been viewed 2975 times
    [Half-Life / coding]

    Only registered users can post comments. Have you registered yet?

    noone has commented on this document

    VERC © 2004. All content copyright its respective owner, all rights reserved.
    script execution time: 0.082414150238 seconds