VGUI Task List
January 2024
You can help by adding links to this article from other relevant articles.
January 2024
A task list for the player might be useful for your mod. This tutorial shows how to implement a simple task list that can display from 1-4 tasks, each with individual text and priority level. Priority and status is indicated by text color, with low and medium priority tasks shown in yellow, and high priority tasks shown in red. Tasks that have been completed are shown in green. A new companion entity called an "env_hudtasklist" allows map I/O to add and remove tasks, set text and change priority levels.
Creating the VGUI panel
Start by creating the file src/cl_dll/hl2_hud/hud_tasklist.h...
#ifndef HUD_TASKLIST_H
#define HUD_TASKLIST_H
#define TASKLIST_MAX_TASKS 4
#define TASKLIST_TASK_INACTIVE 0
#define TASKLIST_TASK_COMPLETE 1
#define TASKLIST_TASK_LOWPRIORITY 2
#define TASKLIST_TASK_MEDPRIORITY 3
#define TASKLIST_TASK_HIGHPRIORITY 4
#define TASKLINE_NUM_FLASHES 8.0f
#define TASKLINE_FLASH_TIME 5.0f
#define TASKLINE_FADE_TIME 1.0f
class CHudTaskList : public CHudElement , public vgui::Panel
{
DECLARE_CLASS_SIMPLE( CHudTaskList, vgui::Panel );
public:
CHudTaskList( const char *pElementName ); //The constructor.
void Init( void ); //This gets called when the element is created.
void VidInit( void ); //Same as above, this may only gets called when you load up the map for the first time.
void Reset(); //This gets called when you reset the HUD.
void Paint( void ); //This gets called every frame, to display the element on the screen!
// void OnThink(void); //This gets called also almost every frame, so we can update things often.
void MsgFunc_TaskList( bf_read &msg );
private:
// CHudTexture *m_pIcon; // Icon texture reference
wchar_t m_pText[TASKLIST_MAX_TASKS][256]; // Unicode text buffer
int m_iPriority[TASKLIST_MAX_TASKS]; // 0=inactive, 1=complete, 2=low, 3=medium, 4=high
float m_flStartTime[TASKLIST_MAX_TASKS]; // When the message was recevied
float m_flDuration[TASKLIST_MAX_TASKS]; // Duration of the message
vgui::HFont m_hSmallFont, m_hLargeFont;
protected:
virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
private:
// vgui::Label *m_pNameLabel; //The vgui label that will hold our name.
vgui::HScheme scheme; //The Scheme object to hold our scheme info.
};
#endif
Now create the file src/cl_dll/hl2_hud/hud_tasklist.cpp...
//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============
//
// Purpose: Task List element
//
//=============================================================================
#include "cbase.h"
#include "hudelement.h"
#include "hud_macros.h"
#include "iclientmode.h"
#include "view.h"
using namespace vgui;
#include <vgui_controls/Panel.h>
#include <vgui_controls/Frame.h>
#include <vgui/IScheme.h>
#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>
using namespace vgui;
#include "c_baseplayer.h"
#include "hud_tasklist.h"
DECLARE_HUDELEMENT( CHudTaskList );
DECLARE_HUD_MESSAGE( CHudTaskList, TaskList );
// ===================================================================
CHudTaskList::CHudTaskList( const char *pElementName ) : CHudElement( pElementName ), vgui::Panel( NULL, "HudTaskList" )
{
DevMsg (2, "CHudTaskList::CHudTaskList - constructor sent %s\n", pElementName);
scheme = vgui::scheme()->LoadSchemeFromFile("resource/ClientScheme.res", "ClientScheme");
SetScheme(scheme); // Here we load up our scheme and set this element to use it. Using a different scheme than ClientScheme doesn't work right off the bat anyways, so... :)
vgui::Panel *pParent = g_pClientMode->GetViewport();
SetParent( pParent ); // Our parent is the screen itself.
SetHiddenBits( 0 ); // Never Hidden. You could make it so that it gets hidden when health is, with HIDEHUD_HEALTH, or any other combination of HIDEHUD flags.
SetBgColor(Color( 0,0,0,100 ));
SetPaintBackgroundEnabled( true );
SetPaintBackgroundType (2); // Rounded corner box
}
void CHudTaskList::Init( void )
{
HOOK_HUD_MESSAGE( CHudTaskList, TaskList );
DevMsg (2, "CHudTaskList::Init\n" );
Reset();
}
void CHudTaskList::Reset( void )
{
DevMsg (2, "CHudTaskList::Reset (clearing out list)\n" );
for (int i=0; i<TASKLIST_MAX_TASKS; i++)
{
m_pText[i][0] = '\0';
m_iPriority[i] = TASKLIST_TASK_INACTIVE;
}
}
void CHudTaskList::VidInit( void )
{
DevMsg (2, "CHudTaskList::VidInit\n" );
Reset();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHudTaskList::ApplySchemeSettings( vgui::IScheme *pScheme )
{
DevMsg (2, "CHudTaskList::ApplySchemeSettings\n" );
m_hSmallFont = pScheme->GetFont( "HudHintTextSmall", true );
m_hLargeFont = pScheme->GetFont( "HudHintTextLarge", true );
BaseClass::ApplySchemeSettings( pScheme );
}
/*
virtual int GetFontTall(HFont font) = 0;
virtual int GetFontAscent(HFont font, wchar_t wch) = 0;
virtual bool IsFontAdditive(HFont font) = 0;
virtual void GetCharABCwide(HFont font, int ch, int &a, int &b, int &c) = 0;
virtual int GetCharacterWidth(HFont font, int ch) = 0;
virtual void GetTextSize(HFont font, const wchar_t *text, int &wide, int &tall) = 0;
*/
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHudTaskList::Paint( void )
{
// KLUDGE! DISABLE TEMPORARILY
// return;
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if ( !pPlayer )
return;
int x, y;
int textSizeWide, textSizeTall;
int iShown = 0; // number of lines shown
wchar_t unicode[256]; // scratch space for text to print
// --- Set up default font and get character height for line spacing
vgui::surface()->DrawSetTextFont( m_hLargeFont );
int fontTall = vgui::surface()->GetFontTall( m_hLargeFont );
// --- Don't actually draw the task list at first, but instead
// --- calculate the total width & height of the text.
swprintf(unicode, L"Active Tasks");
vgui::surface()->GetTextSize (m_hLargeFont, unicode, textSizeWide, textSizeTall);
iShown++;
int border = 20;
int maxWidth = textSizeWide;
int maxHeight;
for (int i=0; i<TASKLIST_MAX_TASKS; i++)
{
if (m_iPriority[i] != TASKLIST_TASK_INACTIVE)
{
// --- Calculate coordinates of text (right justify)
swprintf(unicode, L"%s", m_pText[i]);
vgui::surface()->GetTextSize (m_hLargeFont, unicode, textSizeWide, textSizeTall);
maxWidth = (textSizeWide > maxWidth) ? textSizeWide : maxWidth;
iShown++;
}
}
// If no text to show except the title, show nothing
if (iShown == 1) {
SetPaintBackgroundEnabled( false );
return;
}
maxHeight = iShown * fontTall;
SetPos( ScreenWidth() - border - maxWidth, border);
SetSize( maxWidth + border, maxHeight );
SetPaintBackgroundType (2); // Rounded corner box
SetPaintBackgroundEnabled( true );
// ----------------------------------------------------------
// --- Prep text for a title on the task list. It actually
// --- will get drawn in the for loop below on the first pass.
// ----------------------------------------------------------
vgui::surface()->DrawSetTextColor( 255, 170, 0, 220 );
swprintf(unicode, L"Active Tasks");
// ----------------------------------------------------------
// --- Get text width and right justify
// ----------------------------------------------------------
vgui::surface()->GetTextSize (m_hLargeFont, unicode, textSizeWide, textSizeTall);
// ----------------------------------------------------------
// --- Calculate coordinates of title text
// ----------------------------------------------------------
iShown = 0;
x = border / 2; // ScreenWidth() - border - textSizeWide;
y = iShown * fontTall; // border + iShown * fontTall;
// ----------------------------------------------------------
// --- Draw all tasks that aren't inactive.
// ----------------------------------------------------------
for (int i=0; i<TASKLIST_MAX_TASKS; i++)
{
if (m_iPriority[i] != TASKLIST_TASK_INACTIVE)
{
// ----------------------------------------------------------
// --- If this is the first task item, show task list title
// ----------------------------------------------------------
if (iShown == 0) {
iShown++;
vgui::surface()->DrawSetTextPos(x, y);
vgui::surface()->DrawPrintText( unicode, wcslen(unicode) );
}
// ----------------------------------------------------------
// --- Calculate coordinates of text (right justify)
// ----------------------------------------------------------
swprintf(unicode, L"%s", m_pText[i]);
vgui::surface()->GetTextSize (m_hLargeFont, unicode, textSizeWide, textSizeTall);
x = border / 2; // ScreenWidth() - border - textSizeWide;
y = iShown * fontTall; // border + iShown * fontTall;
iShown++;
vgui::surface()->DrawSetTextPos(x, y);
int lr=255, lg=255, lb=255;
// DevMsg (2, "CHudTaskList::Paint task %d, priority %d: %s\n", i, m_iPriority[i], m_pText[i][0] );
// ----------------------------------------------------------
// --- Set text color based on priority
// ----------------------------------------------------------
switch (m_iPriority[i])
{
case TASKLIST_TASK_COMPLETE :
// vgui::surface()->DrawSetTextColor( 100, 255, 0, brightness );
lr = 0;
lg = 255;
lb = 0;
break;
case TASKLIST_TASK_LOWPRIORITY :
case TASKLIST_TASK_MEDPRIORITY :
// vgui::surface()->DrawSetTextColor( 255, 220, 0, 255 );
lr = 255;
lg = 255; // 220;
lb = 0;
break;
case TASKLIST_TASK_HIGHPRIORITY :
// vgui::surface()->DrawSetTextColor( 255, 0, 0, 255 );
lr = 255;
lg = 0;
lb = 0;
break;
default :
// ----------------------------------------------------------
// --- We should never get here!
// ----------------------------------------------------------
DevMsg ("hud_tasklistdisplay: Task List item %d (%s) with unknown priority %d!\n", i, m_pText[i], m_iPriority[i]);
break;
}
// ----------------------------------------------------------
// --- Calculate text flash color and fade for completed tasks
// ----------------------------------------------------------
float curtime = gpGlobals->curtime;
if ( curtime >= m_flStartTime[i] && curtime < m_flStartTime[i] + m_flDuration[i] )
{
// ----------------------------------------------------------
// --- FLASH TEXT
// ----------------------------------------------------------
float frac1 = ( curtime - m_flStartTime[i] ) / m_flDuration[i];
float frac = frac1;
frac *= TASKLINE_NUM_FLASHES;
frac *= 2 * M_PI;
frac = cos( frac );
frac = clamp( frac, 0.0f, 1.0f );
frac *= (1.0f-frac1);
int r = lr, g = lg, b = lb;
r = r + ( 255 - r ) * frac;
g = g + ( 255 - g ) * frac;
b = b + ( 255 - b ) * frac;
int alpha = 63 + 192 * (1.0f - frac1 );
alpha = clamp( alpha, 0, 255 );
vgui::surface()->DrawSetTextColor( r, g, b, 255 );
// InsertColorChange( Color( r, g, b, 255 ) );
}
else if ( m_iPriority[i] == TASKLIST_TASK_COMPLETE &&
curtime <= m_flStartTime[i] + m_flDuration[i] + TASKLINE_FADE_TIME &&
curtime > m_flStartTime[i] + m_flDuration[i] )
{
// ----------------------------------------------------------
// --- FADE TEXT
// ----------------------------------------------------------
float frac = ( m_flStartTime[i] + m_flDuration[i] + TASKLINE_FADE_TIME - curtime ) / TASKLINE_FADE_TIME;
int alpha = frac * 255;
alpha = clamp( alpha, 0, 255 );
vgui::surface()->DrawSetTextColor( lr * frac, lg * frac, lb * frac, alpha );
}
else
{
// ----------------------------------------------------------
// --- NORMAL TEXT
// ----------------------------------------------------------
vgui::surface()->DrawSetTextColor( lr, lg, lb, 255 );
}
// ----------------------------------------------------------
// --- Draw the text
// ----------------------------------------------------------
vgui::surface()->DrawPrintText( unicode, wcslen(unicode) ); // print text
// ----------------------------------------------------------
// --- Remove fully faded out completed tasks
// ----------------------------------------------------------
if (m_iPriority[i] == TASKLIST_TASK_COMPLETE && gpGlobals->curtime > m_flDuration[i]+m_flStartTime[i]+TASKLINE_FADE_TIME)
{
m_iPriority[i] = TASKLIST_TASK_INACTIVE;
}
}
}
BaseClass::Paint();
}
// ----------------------------------------------------------
// ----------------------------------------------------------
void CHudTaskList::MsgFunc_TaskList( bf_read &msg )
{
char szString[256];
int task_index;
int task_priority;
task_index = msg.ReadByte();
task_priority = msg.ReadByte();
msg.ReadString( szString, sizeof(szString) );
DevMsg (2, "CHudTaskList::MsgFunc_TaskList - got message for task %d, priority %d, string %s\n", task_index, task_priority, szString );
// ----------------------------------------------------------
// --- Convert it to localize friendly unicode
// ----------------------------------------------------------
g_pVGuiLocalize->ConvertANSIToUnicode( szString, m_pText[task_index], sizeof(m_pText[task_index]) );
// ----------------------------------------------------------
// --- Setup a time tracker for tasks just added or updated
// ----------------------------------------------------------
if ( m_iPriority[task_index] != task_priority )
{
m_flStartTime[task_index] = gpGlobals->curtime;
m_flDuration[task_index] = TASKLINE_FLASH_TIME;
}
m_iPriority[task_index] = task_priority;
}
Commanding the new Task List VGUI
OK - so that creates our VGUI panel to display the task list. Now we just need some way to communicate with it. We'll do that by creating a new entity.
Go ahead and create the file src/dlls/EnvHudTasklist.cpp...
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "engine/IEngineSound.h"
#include "baseentity.h"
#include "entityoutput.h"
#include "recipientfilter.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define TASKLIST_MAX_TASKS 4
#define TASKLIST_TASK_INACTIVE 0
#define TASKLIST_TASK_COMPLETE 1
#define TASKLIST_TASK_LOWPRIORITY 2
#define TASKLIST_TASK_MEDPRIORITY 3
#define TASKLIST_TASK_HIGHPRIORITY 4
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CEnvHudTasklist : public CPointEntity
{
public:
DECLARE_CLASS( CEnvHudTasklist, CPointEntity );
void Spawn( void );
void Precache( void );
private:
void InputShowHudTasklist( inputdata_t &inputdata );
void InputHideHudTasklist( inputdata_t &inputdata );
void InputTask1Message( inputdata_t &inputdata );
void InputTask2Message( inputdata_t &inputdata );
void InputTask3Message( inputdata_t &inputdata );
void InputTask4Message( inputdata_t &inputdata );
void InputTask1Urgency( inputdata_t &inputdata );
void InputTask2Urgency( inputdata_t &inputdata );
void InputTask3Urgency( inputdata_t &inputdata );
void InputTask4Urgency( inputdata_t &inputdata );
void SendTaskData (int index);
string_t m_iszTaskmsg[TASKLIST_MAX_TASKS];
int m_iUrgency[TASKLIST_MAX_TASKS]; // 0=complete, 1=low, 2=medium, 3=high
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( env_hudtasklist, CEnvHudTasklist );
BEGIN_DATADESC( CEnvHudTasklist )
DEFINE_KEYFIELD( m_iszTaskmsg[0], FIELD_STRING, "task1message" ),
DEFINE_KEYFIELD( m_iUrgency[0], FIELD_INTEGER, "task1urgency" ),
DEFINE_KEYFIELD( m_iszTaskmsg[1], FIELD_STRING, "task2message" ),
DEFINE_KEYFIELD( m_iUrgency[1], FIELD_INTEGER, "task2urgency" ),
DEFINE_KEYFIELD( m_iszTaskmsg[2], FIELD_STRING, "task3message" ),
DEFINE_KEYFIELD( m_iUrgency[2], FIELD_INTEGER, "task3urgency"),
DEFINE_KEYFIELD( m_iszTaskmsg[3], FIELD_STRING, "task4message" ),
DEFINE_KEYFIELD( m_iUrgency[3], FIELD_INTEGER, "task4urgency" ),
// Show/hide entire task list
DEFINE_INPUTFUNC( FIELD_VOID, "ShowHudTasklist", InputShowHudTasklist ),
DEFINE_INPUTFUNC( FIELD_VOID, "HideHudTasklist", InputHideHudTasklist ),
// Set individual task list strings
DEFINE_INPUTFUNC( FIELD_STRING, "Task1Message", InputTask1Message ),
DEFINE_INPUTFUNC( FIELD_STRING, "Task2Message", InputTask2Message ),
DEFINE_INPUTFUNC( FIELD_STRING, "Task3Message", InputTask3Message ),
DEFINE_INPUTFUNC( FIELD_STRING, "Task4Message", InputTask4Message ),
// Set individual task urgency values
DEFINE_INPUTFUNC( FIELD_INTEGER, "Task1Urgency", InputTask1Urgency ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "Task2Urgency", InputTask2Urgency ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "Task3Urgency", InputTask3Urgency ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "Task4Urgency", InputTask4Urgency ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Spawn the new ent
//-----------------------------------------------------------------------------
void CEnvHudTasklist::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvHudTasklist::Precache( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for showing the task list
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputShowHudTasklist( inputdata_t &inputdata )
{
for (int i=0; i<TASKLIST_MAX_TASKS; i++) {
SendTaskData (i);
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputHideHudTasklist( inputdata_t &inputdata )
{
}
//-----------------------------------------------------------------------------
// Send a task data message to the client. This gets caught by the task HUD
// display element and shown.
//-----------------------------------------------------------------------------
void CEnvHudTasklist::SendTaskData (int index)
{
CBaseEntity *pPlayer = NULL;
pPlayer = UTIL_GetLocalPlayer();
if ( pPlayer )
{
if ( !pPlayer->IsNetClient() )
{
return;
}
CSingleUserRecipientFilter user( (CBasePlayer *)pPlayer );
user.MakeReliable();
UserMessageBegin( user, "TaskList" );
WRITE_BYTE( index );
WRITE_BYTE ( m_iUrgency[index] );
WRITE_STRING( STRING (m_iszTaskmsg[index]) );
MessageEnd();
DevMsg (2, "Sent msg %d, %d, %s\n", index, m_iUrgency[index], m_iszTaskmsg[index]);
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 1 urgency
// DEFINE_INPUTFUNC( FIELD_STRING, "Task1Urgency", InputTask1Urgency ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask1Urgency ( inputdata_t &inputdata )
{
m_iUrgency[0] = inputdata.value.Int();
SendTaskData (0);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 2 urgency
// DEFINE_INPUTFUNC( FIELD_STRING, "Task2Urgency", InputTask2Urgency ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask2Urgency ( inputdata_t &inputdata )
{
DevMsg (2, "Got req. to set task2 urgency to %d\n", inputdata.value.Int());
m_iUrgency[1] = inputdata.value.Int();
SendTaskData (1);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 3 urgency
// DEFINE_INPUTFUNC( FIELD_STRING, "Task3Urgency", InputTask3Urgency ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask3Urgency ( inputdata_t &inputdata )
{
m_iUrgency[2] = inputdata.value.Int();
SendTaskData (2);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 4 urgency
// DEFINE_INPUTFUNC( FIELD_STRING, "Task4Urgency", InputTask4Urgency ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask4Urgency ( inputdata_t &inputdata )
{
m_iUrgency[3] = inputdata.value.Int();
SendTaskData (3);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 1 text
// DEFINE_INPUTFUNC( FIELD_STRING, "TaskMessage1", InputTaskMessage1 ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask1Message( inputdata_t &inputdata )
{
m_iszTaskmsg[0] = MAKE_STRING( inputdata.value.String() );
SendTaskData (0);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 2 text
// DEFINE_INPUTFUNC( FIELD_STRING, "TaskMessage2", InputTaskMessage2 ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask2Message( inputdata_t &inputdata )
{
m_iszTaskmsg[1] = MAKE_STRING( inputdata.value.String() );
SendTaskData (1);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 3 text
// DEFINE_INPUTFUNC( FIELD_STRING, "TaskMessage3", InputTaskMessage3 ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask3Message( inputdata_t &inputdata )
{
m_iszTaskmsg[2] = MAKE_STRING( inputdata.value.String() );
SendTaskData (2);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting task 4 text
// DEFINE_INPUTFUNC( FIELD_STRING, "TaskMessage4", InputTaskMessage4 ),
//-----------------------------------------------------------------------------
void CEnvHudTasklist::InputTask4Message( inputdata_t &inputdata )
{
m_iszTaskmsg[3] = MAKE_STRING( inputdata.value.String() );
SendTaskData (3);
}
That implements the new entity. But to use it in a map, we'll need to create an entry in the FGD file for the mod.
Adding the new entity to the FGD
@PointClass base(Targetname) size(-8 -8 -8, 8 8 8) = env_hudtasklist: "An entity to display a set of tasks for the player on the screen." [ task1message(string) : "Task 1 Text " : "" : "This is the text of the first task." task1urgency(choices) : "Task 1 Urgency" : 0 : "Urgency of this task" = [ 0 : "INACTIVE" 1 : "COMPLETE" 2 : "LOW PRIORITY" 3 : "MEDIUM PRIORITY" 4 : "HIGH PRIORITY" ] task2message(string) : "Task 2 Text " : "" : "This is the text of the second task." task2urgency(choices) : "Task 2 Urgency" : 0 : "Urgency of this task" = [ 0 : "INACTIVE" 1 : "COMPLETE" 2 : "LOW PRIORITY" 3 : "MEDIUM PRIORITY" 4 : "HIGH PRIORITY" ] task3message(string) : "Task 3 Text " : "" : "This is the text of the third task." task3urgency(choices) : "Task 3 Urgency" : 0 : "Urgency of this task" = [ 0 : "INACTIVE" 1 : "COMPLETE" 2 : "LOW PRIORITY" 3 : "MEDIUM PRIORITY" 4 : "HIGH PRIORITY" ] task4message(string) : "Task 4 Text " : "" : "This is the text of the fourth task." task4urgency(choices) : "Task 4 Urgency" : 0 : "Urgency of this task" = [ 0 : "INACTIVE" 1 : "COMPLETE" 2 : "LOW PRIORITY" 3 : "MEDIUM PRIORITY" 4 : "HIGH PRIORITY" ] // Inputs input ShowHudTasklist(void) : "Shows the task list" input HideHudTasklist(void) : "Hides the task list" input Task1Message(string) : "Set task 1 message" input Task2Message(string) : "Set task 2 message" input Task3Message(string) : "Set task 3 message" input Task4Message(string) : "Set task 4 message" input Task1Urgency(integer) : "Set task 1 urgency (0-4)" input Task2Urgency(integer) : "Set task 2 urgency (0-4)" input Task3Urgency(integer) : "Set task 3 urgency (0-4)" input Task4Urgency(integer) : "Set task 4 urgency (0-4)" ]
Registering the Custom Message
Because we are sending a custom message type (e.g. TaskList) we need to register this with the engine's user message cache.
Open up gameshared/hl2/hl2_usermessages.cpp
Add the following at the bottom of the RegisterUserMessages(void) method:
usermessages->Register( "TaskList", -1 );
This registers the TaskList message as a bona fide message of variable length.
Using the new Task List
Add a new tasklist entity to a test map. You can set it's initial set of tasks and priorities if you'd like. You can also have other entities directly set task text and priorities via entity outputs.
Improvements and Brainstorms
There is a lot that could be done to improve on this task list.
- Visually it's pretty simple, and there is definitely room for improvement to it's look.
- The display of task urgency plus and task state transitions (fading when removed, flashing when new) could be improved or modified. Make the text of new tasks "slide in" from the right?
- The current implementation also only allows for 4 tasks. Obviously one might want to add more.
- Another option might be to allow the task list to only be displayed when a key is held down. Examining the code for the scoreboard would be a good start, since it draws the scoreboard only when the (default) Tab key is held down.
- It's also not very good that the colors, fonts and other text is all hardwired into the code. It would be great to put that out into a .res file, plus localize the text!
- Allow for a "percent done" to be displayed for each task
- Allow a task to be shown as "active" - meaning you are actively working on that task
- Allow NPCs to be "active" in a task
- Show number of NPCs active on task
- Port to multiplayer and have "team" task status and "individual" task status
- Show number of players on team tasks active on task
- Some kind of symbol next to tasks active and tasks completed. Checkbox anyone?
- Customize task list title via entity
- Optionally don't remove completed tasks from task list