PDA

View Full Version : Further custom NPC problems.



Nero
12-04-2016, 07:33 AM
The after looking through the SDK a bit I have come to the conclusion that the Zombie really is the most basic of monsters.
So I decided to try something harder: The Headcrab. This includes some scheduling, tasks, ranged checks 'n shiet.

I've converted everything but ran into some problems:



// headcrab - tiny, jumpy alien parasite
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
const int HLHC_AE_JUMPATTACK = 2;//#define HC_AE_JUMPATTACK ( 2 )

const int HLHEADCRAB_HEALTH = 20;
const int HLHEADCRAB_DMGBITE = 10;
const int TASKSTATUS_RUNNING = 1;

array<ScriptSchedule@>@ custom_monster_schedules;

ScriptSchedule slHCRangeAttack1(
bits_COND_ENEMY_OCCLUDED |
bits_COND_NO_AMMO_LOADED,
0,
"HCRangeAttack1" );

ScriptSchedule slHCRangeAttack1Fast(
bits_COND_ENEMY_OCCLUDED |
bits_COND_NO_AMMO_LOADED,
0,
"HCRAFast" );

void InitSchedules()
{
slHCRangeAttack1.AddTask( ScriptTask(TASK_STOP_MOVING, 0.0f) );
slHCRangeAttack1.AddTask( ScriptTask(TASK_FACE_IDEAL, 0.0f) );
slHCRangeAttack1.AddTask( ScriptTask(TASK_RANGE_ATTACK1, 0.0f) );
slHCRangeAttack1.AddTask( ScriptTask(TASK_SET_ACTIVITY, ACT_IDLE) );
slHCRangeAttack1.AddTask( ScriptTask(TASK_FACE_IDEAL, 0.0f) );
slHCRangeAttack1.AddTask( ScriptTask(TASK_WAIT_RANDOM, 0.5f) );

slHCRangeAttack1Fast.AddTask( ScriptTask(TASK_STOP_MOVING, 0.0f) );
slHCRangeAttack1Fast.AddTask( ScriptTask(TASK_FACE_IDEAL, 0.0f) );
slHCRangeAttack1Fast.AddTask( ScriptTask(TASK_RANGE_ATTACK1, 0.0f) );
slHCRangeAttack1Fast.AddTask( ScriptTask(TASK_SET_ACTIVITY, ACT_IDLE) );

array<ScriptSchedule@> scheds = {slHCRangeAttack1, slHCRangeAttack1Fast};

@custom_monster_schedules = @scheds;
}

const array<string> pHLHCIdleSounds =
{
"headcrab/hc_idle1.wav",
"headcrab/hc_idle2.wav",
"headcrab/hc_idle3.wav"
};

const array<string> pHLHCAlertSounds =
{
"headcrab/hc_alert1.wav"
};

const array<string> pHLHCPainSounds =
{
"headcrab/hc_pain1.wav",
"headcrab/hc_pain2.wav",
"headcrab/hc_pain3.wav"
};

const array<string> pHLHCAttackSounds =
{
"headcrab/hc_attack1.wav",
"headcrab/hc_attack2.wav",
"headcrab/hc_attack3.wav"
};

const array<string> pHLHCDeathSounds =
{
"headcrab/hc_die1.wav",
"headcrab/hc_die2.wav"
};

const array<string> pHLHCBiteSounds =
{
"headcrab/hc_headbite.wav"
};

class monster_hlheadcrab : ScriptBaseMonsterEntity
{
//=========================================================
// Spawn
//=========================================================
void Spawn()
{
Precache();

g_EntityFuncs.SetModel( self, "models/headcrab.mdl" );
g_EntityFuncs.SetSize( self.pev, Vector(-12, -12, 0), Vector(12, 12, 24) );

self.pev.solid = SOLID_SLIDEBOX;
self.pev.movetype = MOVETYPE_STEP;
self.m_bloodColor = BLOOD_COLOR_GREEN;
self.pev.effects = 0;
self.pev.health = HLHEADCRAB_HEALTH;
self.pev.view_ofs = Vector( 0, 0, 20 );// position of the eyes relative to monster's origin.
self.pev.yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim?
self.m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
self.m_MonsterState = MONSTERSTATE_NONE;
g_EntityFuncs.DispatchKeyValue( self.edict(), "displayname", "HL Headcrab" );

@this.m_Schedules = @custom_monster_schedules;

self.MonsterInit();
}

//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void Precache()
{
uint i;

for( i = 0; i < pHLHCIdleSounds.length(); i++ )
g_SoundSystem.PrecacheSound( pHLHCIdleSounds );

for( i = 0; i < pHLHCAlertSounds.length(); i++ )
g_SoundSystem.PrecacheSound( pHLHCAlertSounds[i] );

for( i = 0; i < pHLHCPainSounds.length(); i++ )
g_SoundSystem.PrecacheSound( pHLHCPainSounds[i] );

for( i = 0; i < pHLHCAttackSounds.length(); i++ )
g_SoundSystem.PrecacheSound( pHLHCAttackSounds[i] );

for( i = 0; i < pHLHCDeathSounds.length(); i++ )
g_SoundSystem.PrecacheSound( pHLHCDeathSounds[i] );

for( i = 0; i < pHLHCBiteSounds.length(); i++ )
g_SoundSystem.PrecacheSound( pHLHCBiteSounds[i] );

g_Game.PrecacheModel( "models/headcrab.mdl" );
}

//=========================================================
// RunTask
//=========================================================
void RunTask( Task@ pTask )
{
switch( pTask.iTask )
{
case TASK_RANGE_ATTACK1:
case TASK_RANGE_ATTACK2:
{
if( self.m_fSequenceFinished )
{
self.TaskComplete();
SetTouch( null );
self.m_IdealActivity = ACT_IDLE;
}
break;
}
default:
{
BaseClass.RunTask(pTask);
}
}
}

void StartTask( Task@ pTask )
{
self.m_iTaskStatus = TASKSTATUS_RUNNING;

switch( pTask.iTask )
{
case TASK_RANGE_ATTACK1:
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, pHLHCAttackSounds[0], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
self.m_IdealActivity = ACT_RANGE_ATTACK1;
SetTouch( TouchFunction(this.LeapTouch) );
break;
}
default:
{
BaseClass.StartTask( pTask );
}
}
}

//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
void SetYawSpeed()
{
int ys;

switch( self.m_Activity )
{
case ACT_IDLE:
ys = 30;
break;
case ACT_RUN:
case ACT_WALK:
ys = 20;
break;
case ACT_TURN_LEFT:
case ACT_TURN_RIGHT:
ys = 60;
break;
case ACT_RANGE_ATTACK1:
ys = 30;
break;
default:
ys = 30;
break;
}

self.pev.yaw_speed = ys;
}

//=========================================================
// LeapTouch - this is the headcrab's touch function when it
// is in the air
//=========================================================
void LeapTouch( CBaseEntity@ pOther )
{
if( pOther.pev.takedamage == 0 )
return;

if( pOther.Classify() == Classify() )
return;

// Don't hit if back on ground
if( !self.pev.FlagBitSet(FL_ONGROUND) )
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, pHLHCBiteSounds[Math.RandomLong(0,(pHLHCBiteSounds.length - 1))], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );

pOther.TakeDamage( self.pev, self.pev, GetDamageAmount(), DMG_SLASH );
}

SetTouch( null );
}

//=========================================================
// Center - returns the real center of the headcrab. The
// bounding box is much larger than the actual creature so
// this is needed for targeting
//=========================================================
Vector Center()
{
return Vector( self.pev.origin.x, self.pev.origin.y, self.pev.origin.z + 6 );
}


Vector BodyTarget( const Vector& in posSrc )
{
return Center();
}

//=========================================================
// PainSound
//=========================================================
void PainSound()
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, pHLHCPainSounds[Math.RandomLong(0,(pHLHCPainSounds.length - 1))], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
}

//=========================================================
// DeathSound
//=========================================================
void DeathSound()
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, pHLHCDeathSounds[Math.RandomLong(0,(pHLHCDeathSounds.length - 1))], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
}

//=========================================================
// IdleSound
//=========================================================
void IdleSound()
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, pHLHCIdleSounds[Math.RandomLong(0,(pHLHCIdleSounds.length - 1))], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
}

//=========================================================
// AlertSound
//=========================================================
void AlertSound()
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, pHLHCAlertSounds[Math.RandomLong(0,(pHLHCAlertSounds.length - 1))], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
}

//=========================================================
// PrescheduleThink
//=========================================================
void PrescheduleThink()
{
// make the crab coo a little bit in combat state
if( self.m_MonsterState == MONSTERSTATE_COMBAT && Math.RandomFloat(0,5) < 0.1 )
{
this.IdleSound();
}
}

//=========================================================
// Classify - indicates this monster's place in the
// relationship table.
//=========================================================
int Classify()
{
return CLASS_ALIEN_PREY;
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void HandleAnimEvent( MonsterEvent@ pEvent )
{
switch( pEvent.event )
{
case HLHC_AE_JUMPATTACK:
{
//ClearBits( pev->flags, FL_ONGROUND );//#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) //?
self.pev.flags &= ~FL_ONGROUND;

g_EntityFuncs.SetOrigin( self, self.pev.origin + Vector( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground
Math.MakeVectors( self.pev.angles );

Vector vecJumpDir;
if( self.m_hEnemy !is null )
{
CBaseEntity@ pEnemy = self.m_hEnemy.GetEntity(); //?
float gravity = g_EngineFuncs.CVarGetFloat( "sv_gravity" );
if( gravity <= 1 )
gravity = 1;

// How fast does the headcrab need to travel to reach that height given gravity?
//float height = ( self.m_hEnemy.pev.origin.z + self.m_hEnemy.pev.view_ofs.z - self.pev.origin.z ); //?
float height = ( pEnemy.pev.origin.z + pEnemy.pev.view_ofs.z - self.pev.origin.z );
if( height < 16 )
height = 16;
float speed = sqrt( 2 * gravity * height );
float time = speed / gravity;

// Scale the sideways velocity to get there at the right time
//vecJumpDir = ( self.m_hEnemy.pev.origin + self.m_hEnemy.pev.view_ofs - self.pev.origin ); //?
vecJumpDir = ( pEnemy.pev.origin + pEnemy.pev.view_ofs - self.pev.origin );
vecJumpDir = vecJumpDir * ( 1.0 / time );

// Speed to offset gravity at the desired height
vecJumpDir.z = speed;

// Don't jump too far/fast
float distance = vecJumpDir.Length();

if( distance > 650 )
{
vecJumpDir = vecJumpDir * ( 650.0 / distance );
}
}
else
{
// jump hop, don't care where
vecJumpDir = Vector( g_Engine.v_forward.x, g_Engine.v_forward.y, g_Engine.v_up.z ) * 350;
}

int iSound = Math.RandomLong(0,2);
if( iSound != 0 )
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, pHLHCAttackSounds[iSound], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );

self.pev.velocity = vecJumpDir;
self.m_flNextAttack = g_Engine.time + 2;
}
break;

default:
BaseClass.HandleAnimEvent( pEvent );
break;
}
}

//=========================================================
// CheckRangeAttack1
//=========================================================
bool CheckRangeAttack1( float flDot, float flDist )
{
if( self.pev.FlagBitSet(FL_ONGROUND) && flDist <= 256 && flDot >= 0.65 )
{
return true;
}
return false;
}

//=========================================================
// CheckRangeAttack2
//=========================================================
bool CheckRangeAttack2 ( float flDot, float flDist )
{
return false;
}

int TakeDamage( entvars_t@ pevInflictor, entvars_t@ pevAttacker, float flDamage, int bitsDamageType )
{
// Don't take any acid damage -- BigMomma's mortar is acid
if ( bitsDamageType == DMG_ACID )
flDamage = 0;

return BaseClass.TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
}

float GetDamageAmount()
{
return HLHEADCRAB_DMGBITE; //gSkillData.headcrabDmgBite; //?
}

int GetVoicePitch()
{
return 100;
}

float GetSoundVolume()
{
return 1.0;
}

Schedule@ GetScheduleOfType ( int Type )
{
switch( Type )
{
case SCHED_RANGE_ATTACK1:
{
return slHCRangeAttack1[ 0 ];
}
break;
}

return BaseClass.GetScheduleOfType( Type );
}
}

/*
DEFINE_CUSTOM_SCHEDULES( CHeadCrab )
{
slHCRangeAttack1,
slHCRangeAttack1Fast,
};

IMPLEMENT_CUSTOM_SCHEDULES( CHeadCrab, CBaseMonster );
*/
/*
class monster_hlbabycrab : ScriptBaseMonsterEntity
{
void CBabyCrab :: Spawn( void )
{
CHeadCrab::Spawn();
SET_MODEL(ENT(pev), "models/baby_headcrab.mdl");
pev->rendermode = kRenderTransTexture;
pev->renderamt = 192;
UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24));

pev->health = gSkillData.headcrabHealth * 0.25; // less health than full grown
}

void CBabyCrab :: Precache( void )
{
PRECACHE_MODEL( "models/baby_headcrab.mdl" );
CHeadCrab::Precache();
}

void CBabyCrab :: SetYawSpeed ( void )
{
pev->yaw_speed = 120;
}

float GetDamageAmount()
{
return HLHEADCRAB_DMGBITE * 0.3; //gSkillData.headcrabDmgBite * 0.3 //?
}

BOOL CBabyCrab :: CheckRangeAttack1( float flDot, float flDist )
{
if ( pev->flags & FL_ONGROUND )
{
if ( pev->groundentity && (pev->groundentity->v.flags & (FL_CLIENT|FL_MONSTER)) )
return TRUE;

// A little less accurate, but jump from closer
if ( flDist <= 180 && flDot >= 0.55 )
return TRUE;
}

return FALSE;
}


Schedule_t* CBabyCrab :: GetScheduleOfType ( int Type )
{
switch( Type )
{
case SCHED_FAIL: // If you fail, try to jump!
if ( m_hEnemy != NULL )
return slHCRangeAttack1Fast;
break;

case SCHED_RANGE_ATTACK1:
{
return slHCRangeAttack1Fast;
}
break;
}

return CHeadCrab::GetScheduleOfType( Type );
}

int GetVoicePitch()
{
return PITCH_NORM + RANDOM_LONG(40,50);
}

float GetSoundVolume()
{
return 0.8;
}
}
*/
string GetHLHeadcrabName()
{
return "monster_hlheadcrab";
}

void RegisterHLHeadcrab()
{
g_CustomEntityFuncs.RegisterCustomEntity( "monster_hlheadcrab", GetHLHeadcrabName() );
}
/*
string GetHLBabycrabName()
{
return "monster_hlbabycrab";
}

void RegisterHLBabycrab()
{
g_CustomEntityFuncs.RegisterCustomEntity( "monster_hlbabycrab", GetHLBabycrabName() );
}
*/


The errors:


WARNING: monster_hlheadcrab.as (162, 2) : Compiling void monster_hlheadcrab::StartTask(Task@)
WARNING: monster_hlheadcrab.as (177, 15) : No matching signatures to 'BaseMonster::StartTask(Task@&)'

WARNING: monster_hlheadcrab.as (310, 2) : Compiling void monster_hlheadcrab::HandleAnimEvent(MonsterEvent@)
WARNING: monster_hlheadcrab.as (323, 23) : Both operands must be handles when comparing identity

WARNING: monster_hlheadcrab.as (419, 2) : Compiling Schedule@ monster_hlheadcrab::GetScheduleOfType(int)
WARNING: monster_hlheadcrab.as (425, 28) : Can't implicitly convert from 'Task@&' to 'Schedule@'.

WARNING: monster_hlheadcrab.as (427, 4) : Unreachable code




No matching signatures to 'BaseMonster::StartTask(Task@&)'
Relevant line in bold.


void StartTask( Task@ pTask )
{
self.m_iTaskStatus = TASKSTATUS_RUNNING;

switch( pTask.iTask )
{
case TASK_RANGE_ATTACK1:
{
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, pHLHCAttackSounds[0], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );
self.m_IdealActivity = ACT_RANGE_ATTACK1;
SetTouch( TouchFunction(this.LeapTouch) );
break;
}
default:
{
BaseClass.StartTask( pTask );
}
}
}


StartTask is a member of CBaseMonster, but not BaseMonster. So how do I access that? D:


Both operands must be handles when comparing identity
Relevant line in bold.


void HandleAnimEvent( MonsterEvent@ pEvent )
{
switch( pEvent.event )
{
case HLHC_AE_JUMPATTACK:
{
//ClearBits( pev->flags, FL_ONGROUND );//#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) //?
self.pev.flags &= ~FL_ONGROUND;

g_EntityFuncs.SetOrigin( self, self.pev.origin + Vector( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground
Math.MakeVectors( self.pev.angles );

Vector vecJumpDir;
if( self.m_hEnemy !is null )
{
CBaseEntity@ pEnemy = self.m_hEnemy.GetEntity(); //?
float gravity = g_EngineFuncs.CVarGetFloat( "sv_gravity" );
if( gravity <= 1 )
gravity = 1;

// How fast does the headcrab need to travel to reach that height given gravity?
//float height = ( self.m_hEnemy.pev.origin.z + self.m_hEnemy.pev.view_ofs.z - self.pev.origin.z ); //?
float height = ( pEnemy.pev.origin.z + pEnemy.pev.view_ofs.z - self.pev.origin.z );
if( height < 16 )
height = 16;
float speed = sqrt( 2 * gravity * height );
float time = speed / gravity;

// Scale the sideways velocity to get there at the right time
//vecJumpDir = ( self.m_hEnemy.pev.origin + self.m_hEnemy.pev.view_ofs - self.pev.origin ); //?
vecJumpDir = ( pEnemy.pev.origin + pEnemy.pev.view_ofs - self.pev.origin );
vecJumpDir = vecJumpDir * ( 1.0 / time );

// Speed to offset gravity at the desired height
vecJumpDir.z = speed;

// Don't jump too far/fast
float distance = vecJumpDir.Length();

if( distance > 650 )
{
vecJumpDir = vecJumpDir * ( 650.0 / distance );
}
}
else
{
// jump hop, don't care where
vecJumpDir = Vector( g_Engine.v_forward.x, g_Engine.v_forward.y, g_Engine.v_up.z ) * 350;
}

int iSound = Math.RandomLong(0,2);
if( iSound != 0 )
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, pHLHCAttackSounds[iSound], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() );

self.pev.velocity = vecJumpDir;
self.m_flNextAttack = g_Engine.time + 2;
}
break;

default:
BaseClass.HandleAnimEvent( pEvent );
break;
}
}


if( self.m_hEnemy !is null ) Do I need to turn self.m_hEnemy into something else? D:

Can't implicitly convert from 'Task@&' to 'Schedule@'. - Used to be [I]return slHCRangeAttack1[0]; which gave me Type 'ScriptSchedule' doesn't support the indexing operator
Unreachable code - I don't even...


Schedule@ GetScheduleOfType ( int Type )
{
switch( Type )
{
case SCHED_RANGE_ATTACK1:
{
return slHCRangeAttack1.GetTask(0);
}
break;
}

return BaseClass.GetScheduleOfType( Type );
}




So close to finishing it ;_;

EDIT: //? marks things I'm unsure of.

Solokiller
12-04-2016, 04:32 PM
1.) StartTask isn't exposed.
2.) Use self.m_hEnemy.GetEntity() !is null, or self.m_hEnemy.IsValid(). Either will do the trick.
3.) Change slHCRangeAttack1.GetTask(0) to @Schedules::slHCRangeAttack1. For some reason a lot of schedules in GoldSource are implemented as arrays of schedules, despite only having 1 schedule in them. [ 0 ] just means to get the first (and often only) schedule from that array. All of these schedules can be found in the Properties.htm file in the documentation. A poor place to put them, but since there's no way to categorize them, that's the best that could be done.

Nero
13-04-2016, 04:19 AM
Awwww damnit, I should have figured that out and copied it from the SDK ;_;

Thank youuuuuuuuououou! <3