PDA

View Full Version : Timed events


benjy355
06-18-2007, 06:29 PM
I know source has this code/class in it, but I have NO idea how to use it. I'm just trying to make something run a function every x seconds... Anybody know how I would go about doing this? If so, <3 you.:D

Penumbra
06-19-2007, 01:44 AM
OK, check out the logic_timer entity. This will cheer you up!!! :D

Marine
06-19-2007, 05:22 AM
OK, check out the logic_timer entity. This will cheer you up!!! :D

If the class already has a think function, you can just do this.

Declare a variable like float m_iNextTime;

Then after that do this


if(gpGlobals->curtime > m_iNextTime)
{
Do Function stuff here
m_iNextTime = gpGlobals->curtime + 2.0f; // Run this if block every two seconds
}


That should work. I can't test it cos i dont have VC++ installed on this computer.

Penumbra
06-19-2007, 06:36 AM
OK,

I always do this by means of the SetNextThink method.

My think method becomes something like this:

void Think( void )
{
// Do Function stuff here

SetNextThink( gpGlobals->curtime + 2.0f );
}

Well, Source is flexible enough to handle both ways so choose the one that fits you (if you are in code that is)! :p

benjy355
06-19-2007, 08:23 AM
I can't test this lol, my script in the first place is broken... D: I suck at using arrays.
const char *ChatterSounds[ 12 ];
ChatterSounds[ 0 ] = "npc/metropolice/vo/searchingforsuspect.wav";
ChatterSounds[ 1 ] = "npc/metropolice/vo/novisualonupi.wav";
...

BTW, I'm using Penumbra's method :D

Marine
06-19-2007, 08:54 AM
OK,

I always do this by means of the SetNextThink method.

My think method becomes something like this:

void Think( void )
{
// Do Function stuff here

SetNextThink( gpGlobals->curtime + 2.0f );
}Well, Source is flexible enough to handle both ways so choose the one that fits you (if you are in code that is)! :p

I find my way is more versitile, because if the think event is run for other things as well you cannot use SetNextThink :P

Nial
06-19-2007, 09:55 AM
I find my way is more versitile, because if the think event is run for other things as well you cannot use SetNextThink :P


Well, yes, you can. You just have to make sure that you're using SetNextThink to set a relatively small interval, one that's regular enough for any situation you're timing within the Think function!

Marine
06-19-2007, 10:35 AM
Yeh ;)

10 charz

Penumbra
06-19-2007, 10:55 AM
Well, yes, you can. You just have to make sure that you're using SetNextThink to set a relatively small interval, one that's regular enough for any situation you're timing within the Think function!

Or create another Think function. From the top of my head, I did this:
DEFINE_THINKFUNC( PlayerDistanceThink ),
DEFINE_THINKFUNC( SoundThink ),

void Spawn( void )
{
ThinkSet( (static_cast< void (CBaseEntity::*) (void)>( &PlayerDistanceThink ), 0, "player_focus" );
SetNextThink( gpGlobals->curtime + 2.0f, "player_focus" );

ThinkSet( (static_cast< void (CBaseEntity::*) (void)>( &SoundThink ), 0, "sound_step" );
SetNextThink( gpGlobals->curtime + 0.5f, "sound_step" );
}
Again, I don't have any idea if this is correct (since I don't have a computer with my code on it) but it's in the right direction... :p

But again one think func with multiple checks is also doable without any big disadvantages :cool:

Penumbra
06-19-2007, 11:03 AM
To reply on benjy355:

A string is represented by a char[].
An array can be represented by a pointer (char[] <=> char*)
An array of strings = an array of character arrays

Hence my dear friend :p :

// Initialize the string-array
char **m_pStrings = new char * [ 2 ];
// Populate the array
m_pStrings[ 0 ] = "Bladibla";
m_pStrings[ 1 ] = "FOO";

Hope this is correct, cause I'm not at a computer with my own code... :cool:

benjy355
06-19-2007, 03:41 PM
Lol, apparently incorrect:
char **ChatterSounds = new char * [ 12 ];
ChatterSounds[ 0 ] = "npc/metropolice/vo/searchingforsuspect.wav";
//OMGERROR - C2466: cannot allocate an array of constant size 0
Yes, there are 11 more array defines in thar >_>

benjy355
06-19-2007, 06:07 PM
Fixed:
static const char *ChatterSounds[12] =
{
"npc/metropolice/vo/searchingforsuspect.wav",
"npc/metropolice/vo/novisualonupi.wav",
"npc/metropolice/vo/citizensummoned.wav",
"npc/metropolice/vo/404zone.wav",
"npc/metropolice/vo/airwatchsubjectis505.wav",
"npc/metropolice/vo/anyonepickup647e.wav",
"npc/metropolice/vo/bugsontheloose.wav",
"npc/metropolice/vo/catchthatbliponstabilization.wav",
"npc/metropolice/vo/citizensummoned.wav",
"npc/metropolice/vo/gota10-107sendairwatch.wav",
"npc/metropolice/vo/investigating10-103.wav",
"npc/metropolice/vo/possible10-103alerttagunits.wav"
};

lodle
06-19-2007, 06:41 PM
if you want to get gritty what he has done (char *array[]) is really the same as char array[][] or char **array. the reason behind this is because an array is an adt which is basically a list of pointers. So its still good.

Just remember because it is a list of pointers to use the str functions when dealing with them other wise crashes and weird stuff will occur.

benjy355
06-19-2007, 07:25 PM
The game keeps crashing when I go to load any map... :confused: (No errors)
BTW, I have no idea how I would debug, considering I can't directly launch a mod from hl2.exe (For some reason.. :mad:)

//Insert Timer here
#include "cbase.h"
#include "AI_ScriptConditions.h"
//#include "convar.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

static const char *ChatterSounds[12] =
{
"npc/metropolice/vo/searchingforsuspect.wav",
"npc/metropolice/vo/novisualonupi.wav",
"npc/metropolice/vo/citizensummoned.wav",
"npc/metropolice/vo/404zone.wav",
"npc/metropolice/vo/airwatchsubjectis505.wav",
"npc/metropolice/vo/anyonepickup647e.wav",
"npc/metropolice/vo/bugsontheloose.wav",
"npc/metropolice/vo/catchthatbliponstabilization.wav",
"npc/metropolice/vo/citizensummoned.wav",
"npc/metropolice/vo/gota10-107sendairwatch.wav",
"npc/metropolice/vo/investigating10-103.wav",
"npc/metropolice/vo/possible10-103alerttagunits.wav"
};
float LAST_PLAYED_SOUND = -1;
float m_iNextTime = gpGlobals->curtime + 30.0f;

CBasePlayer *pPlayer;
void DoRandomChatter( void )
{
if(gpGlobals->curtime > m_iNextTime)
{
m_iNextTime = gpGlobals->curtime + 30.0f;
int SoundToPlay = rand()%11;
if (LAST_PLAYED_SOUND != SoundToPlay) {
//Sound wasn't played last time, play it.
if (ChatterSounds[ SoundToPlay ] != NULL) {
UTIL_EmitSoundSuit( pPlayer->edict(), ChatterSounds[ SoundToPlay ] );
LAST_PLAYED_SOUND = SoundToPlay;
} else
DoRandomChatter();
} else
DoRandomChatter();
}
}
static ConCommand cc_Chatter("DoChatter", DoRandomChatter, "Hear some chatter! (Will not work if HearRandomChatter = 0)");
To be compiled to server.dll

Penumbra
06-20-2007, 01:16 AM
Lol, apparently incorrect:
char **ChatterSounds = new char * [ 12 ];
ChatterSounds[ 0 ] = "npc/metropolice/vo/searchingforsuspect.wav";
//OMGERROR - C2466: cannot allocate an array of constant size 0
Yes, there are 11 more array defines in thar >_>

HMMMMM, I checked it with my code, but there are no errors...
Maybe you should have initialized the arrays first:

char **m_pStrings = new char * [ nNum ];
for( int i = 0; i < nNum; i++ )
{
m_pStrings[ i ] = new char[ MAX_CHARS ];
}

m_pStrings[ 0 ] = "Blaat";
I use it slightly different, but without the errors ;)

As for your next problem: Maybe you should create and declare a class around the whole methode.

benjy355
06-20-2007, 09:03 AM
...>_> Explain this, class declaration...

Penumbra
06-20-2007, 02:28 PM
A Class can (and it's wise to) be declared so it is visible in Source.

In the tutorial Authoring a Model Entity (http://developer.valvesoftware.com/wiki/Authoring_a_Model_Entity) you can see how to declare a class:

public:
DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
DECLARE_DATADESC();in header and:
// Start of our data description for the class
BEGIN_DATADESC( CMyModelEntity )

// Save/restore our active state
DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),

// Links our input name from Hammer to our input member function
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),

// Declare our think function
DEFINE_THINKFUNC( MoveThink ),

END_DATADESC() in the cpp-file.

Now, DECLARE_CLASS has multiple forms, so find the right one for your class. (In my opinion DECLARE_CLASS_NOBASE)

But I'm not sure whether this really solves your problem... ;)

benjy355
06-20-2007, 05:28 PM
You know what, maybe you could figure this out better then me, here.
Header + CPP file (http://rapidshare.com/files/38463165/server_dlls.zip.html) download :D

lodle
06-21-2007, 09:10 AM
This is not the first time i have seen this happen. When i was doing the scopes for different res it kept crashing on a 2d array of strings

Penumbra
06-21-2007, 10:33 AM
OK, this is what I made of your class and actually, I am going to post the code down below, instead of dumping it for 90 days at a file-server. If this makes the post too big and someone has a problem with that, please post it so I will edit the posts!!

It's just that I want this piece of code to be here on the forum, so people can see and learn from it! This makes my life easier to! :cool:

OK, first of all .... the header file:
#ifndef SAMPLE_CHATTER_H
#define SAMPLE_CHATTER_H
#ifdef _WIN32
#pragma once
#endif

class CChatter
{
DECLARE_CLASS_NOBASE( CChatter );

public:
void DoRandomChatter( void );
static CChatter *GetInstance( void );
void Precache( void );

private:
CChatter( void );

// The instance of this Singleton-class
static CChatter m_pChatter;

int m_iSoundToPlay;
int m_iLastPlayedSound;

char **m_pChatterSounds;
};

#endif // SAMPLE_CHATTER_H

Penumbra
06-21-2007, 10:35 AM
The cpp-file:
// Sample class for Benjy355.

#include "cbase.h"
#include "ChatterTimed.h"

#include "tier0/memdbgon.h"


CON_COMMAND( do_chatter, "Hear some chatter!" )
{
CChatter::GetInstance()->DoRandomChatter();
}

// Constructor
// NOTE: This constructor will only be called once
// This class is a Singleton-class, meaning there can only be one
// instance (object) of this class.
CChatter::CChatter( void )
{
// Initialize variables
m_iSoundToPlay = -1;
m_iLastPlayedSound = -1;

// Instead of direct wav-files, Source uses sound scripts
// These are a few of your wav's but you'll have to check
// the scripts directory for more!
m_pChatterSounds = new char * [ 3 ];
m_pChatterSounds[ 0 ] = "NPC_MetroPolice.HidingSpeech";
m_pChatterSounds[ 1 ] = "NPC_MetroPolice.Cupcop.Success";
m_pChatterSounds[ 2 ] = "canals.mudcop1_01";
}

// Method for retrieving the one object that there is for this class
CChatter *CChatter::GetInstance( void )
{
static CChatter m_pChatter;
m_pChatter.Precache();

return &m_pChatter;
}

// Method for precaching all the sound files
void CChatter::Precache( void )
{
CBaseEntity::PrecacheScriptSound( m_pChatterSounds[ 0 ] );
CBaseEntity::PrecacheScriptSound( m_pChatterSounds[ 1 ] );
CBaseEntity::PrecacheScriptSound( m_pChatterSounds[ 2 ] );
}

// Method for emitting a random chatter
void CChatter::DoRandomChatter( void )
{
m_iSoundToPlay = RandomInt( 0, 2 );
if( m_iLastPlayedSound != m_iSoundToPlay )
{
//Sound wasn't played last time, play it.
if( m_pChatterSounds[ m_iSoundToPlay ] )
{
// Retrieve the player (this is only for single player games)
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
if( pPlayer )
{
// Emit the sound and mark it as last played
UTIL_EmitSoundSuit( pPlayer->edict(), m_pChatterSounds[ m_iSoundToPlay ] );

/*
// This is an alternative way to get a sound playing
// The only thing I don't know is what this value does....
int index = 1;
const char *szSound = m_pChatterSounds[ m_iSoundToPlay ];
const Vector *vecOrigin = &pPlayer->GetAbsOrigin();

CBaseEntity::EmitSound( CBroadcastRecipientFilter(), index, szSound, vecOrigin );
*/

m_iLastPlayedSound = m_iSoundToPlay;
}
}
else
{
// Try again
DoRandomChatter();
}
}
else
{
// Try again
DoRandomChatter();
}
}

Penumbra
06-21-2007, 11:06 AM
OK, this is it!!!

Now for some advice:

The ConCommand 'do_chatter' cannot be typed into console. I'm lying, the command can be typed, but you will not hear a sound, since you are in the console.

The way to hear a sound is to create a point_clientcommand entity in Hammer. Name it so you can reference it. Create a brush (on the floor or something) and texture it with the tools\trigger texture.
Now, select the brush and press <Ctrl>+<T> to tie a brush entity to this brush. Search the list for a trigger_proximity and give it a name. Set point to measure from to the name of the trigger_proximity.
Go to the Output tab, and add a new output: OnStartTouch; the name of the point_clientcommand entity; Via input Command; with parameters 'do_chatter'.

Compile and once in game if you walk into the brush a sound will be played.

In Source, you can't emit a sound without precaching it. This is also in the code above. Another thing is that Source only works with SoundScripts instead of paths. The scripts are in the scripts directory and you should find the rest of your wav-files in them. I've found a few to get you started.

OK, my good deed for today is completed and I wish you more knowledge!! :cool:

benjy355
06-21-2007, 12:13 PM
Lewl, I can't compile. Errors. (Like always, FSK Windows)
m_pChatterSounds = new char * [ 3 ];
ChatterTimed.cpp(21) : error C2440: '=' : cannot convert from 'char **' to 'char *'

Penumbra
06-22-2007, 02:25 AM
Please!!!!

Copy BOTH the header and the CPP-file.

In your header file it should be char **m_pChatterSounds;!!!!!!

A simple copy-paste. ;)

benjy355
06-22-2007, 09:21 AM
:D Now it compiles
It works, but it's not playing by itself. :D The console command works though :O

Ging
06-28-2007, 03:51 AM
I've gotta question your choice of using a singleton class - a cleaner (and simpler) approach would be to make the array containing the sound definitions static and add a flag that you can check in the constructor to see if they need to be defined for the first time or not.

As far as I can tell, you're using the singleton purely to ensure there can only be a single instance of the class, which is only working with half the definition of the pattern (which in full is something like "ensure a class has only one instance, and provide global access to it").

I'm purely curious as to why you went with the singleton - the rest of the class is fairly sound (if you'll pardon the pun).

Penumbra
06-28-2007, 04:52 AM
OK, there are several reasons why I did this...

The first thing is that I want to let a ConCommand work with the class. And I want to make sure that the class is 'fully operational' when the ConCommand is called. With the GetInstance method, I have that certainty.
It could be that Source is constructing every class it knows beforehand, but I'm not the one to rely on that.

Also a 'normal' class can be create with just a constructor inside the ConCommand, as long as you delete it afterwards. Well, my experience with this is that it creates an extra overhead for the deletion and re-creation. A Singleton is always there in memory, ready to be called. Of course, for this example, the overhead is minimal. But when you have a lot of classes and objects this becomes a concern.

As far as I've been thaught, the Singleton pattern is there for the sole purpose to make sure that only one instance can be created. In this case, I think this is convient, since I don't want duplications of the string array. To make the class publicly accessible is the point of a singleton.
For instance, if benjy would like to expand this class with another sound option that is attached to a ConCommand (or maybe is called by a server-side class) this pattern is (in my thoughts) ideal for the addition.

As to your simpler approach, would you please further elaborate on this because I can't get a complete view of what your saying?
Are you saying that you prefer a completely static class?

lodle
06-28-2007, 09:41 AM
ok a better approach to this would be define the arrays in shareddefs.h and thus they are global and static.

enum
{
SND_MP_HS=0,
SND_MP_CS,
SND_MC,
};

static const char * s_SoundInfo[] =
{
"NPC_MetroPolice.HidingSpeech",
"NPC_MetroPolice.Cupcop.Success",
"canals.mudcop1_01",
},

then use it like a normal array but useing the enum i.e.: s_SoundInfo[SND_MP_HS] which will be the first sound

Penumbra
06-28-2007, 05:05 PM
Well, I'm slightly against this form of coding.. But if it works, it works :D

The problem for me is that I always have the idea of keeping things where they belong. The class that is called by the ConCommand should have the objects needed for doing his thing and is responsible for those objects.

The above approach from lodle also requires more book keeping as to where this string array was declared when you want to change it later on.

But hey, it's just a matter of opinion! ;)

lodle
06-28-2007, 06:55 PM
well then just add it to the h file of the class you need. Then when you need it some where else include it

Penumbra
06-29-2007, 02:51 AM
Yes, but it's 'cleaner' to create accessor and mutator functions for that.
So what I've been taught is to leave the string array private and a member of the class, and create a GetSounds() and a AddSound( const char * ), RemoveSound( const char * ) and everything else you want.

These methods are then only necessary to implement whenever you need them, instead of just putting the objects globally all the time.

In this example it's probably the case that the string array isn't needed for any other class except the class I wrote it for, so we don't have to create the above-mentioned methods and leave the array private.

But again, I'm not telling you what to do! This is just the way I learned it... :cool:

lodle
06-29-2007, 11:54 AM
Why?

You are declaring it as static meaning it cant be changed at all except in code and it only exists ONCE in memory not as many times as you make instances of the class. So thus you dont need a mutator cause it never changes and an accesor is just more un needed work. This is a very easy way of storing fixed strings and retrieving them with a meaning full name (the enum)

Penumbra
06-30-2007, 03:22 AM
First of all, by providing the design pattern of a singleton, I still have got one instance of the string array. And again, you can do it with a static array!

As you put it, we have the same functionality only with a different approach. If you are sure that you don't want to alter the array, you can use both ways. If you still want to be able to alter the array you use the approach that was in code a few posts ago.

I think the choice of a Singleton class here is still valid... Since I need to call the class from a static method and I don't know whether the class already has an instance.

Is that a good conclusion for this thread, or does Ging has anything to say about the Singleton issue? :cool:

Ging
06-30-2007, 05:18 AM
Accessors (or Get and Setters) should only really be used if they do something meaningful (validate the date they're returning or accepting for instance) if all your "GetString" does is "return variable" than you might as well just make it public.

As for the Singleton - you mentioned that you want to avoid the overhead of constructing / destroying an instance each time the ConCommand is used... I think that's premature optimisation at it's best, the impact of that behaviour is minimal (especially in something as complex as Source). In reality, the usage of the singleton here isn't a major issue, but the singleton seems to be one of the more abused design patterns (reinforced not only by own experiences but also by a lot of other people (see here (http://www.gamedev.net/community/forums/mod/journal/journal.asp?jn=433428&reply_id=2971567))) and I was curious as to what made you choose it here (evidently, the single instance aspect - it doesn't quite meet the global access as it's only accessed in a single place)

In terms of the implementation, I'd have gone for a self contained trigger that either accessed a static list of sounds or allowed the mapper to specify a list (either in a separate file or in the triggers key values). It would've taken longer to write out, but would've been a better learning experience and ultimately, a less complicated system for mappers (something mappers will love you for, in any team is less complicated implementations).

Penumbra
06-30-2007, 09:37 AM
I've read the threads, and they're quite interesting!! Thanks for the ideas. ;)
Although I think the writer is exajurating about the use (or actually misuse) of a Singleton class, he has a point to be critical at what and how you code.

Could you write your own piece of code on the timedchatter class that implements your ideas and thoughts of how to do this? It might be handy to see for everyone.

As for the accessors (getters) and mutators (setters), I disagree with your thoughts. But I don't want to start another discussion about that design issue! :)