PDA

View Full Version : Troubles to implement SetBits and SetUse, and other issues



Maestro Fénix
27-05-2016, 01:10 PM
Thanks to the help of Nero, we basically have done another complex NPC: the monster_turret. I'm almost done with it, but I have two problems:

-In order to manually activate the turret (through a button for example), the code uses SetUse so the code can recognize when is being called "from the outside".

-To apply flags, they use SetBits, and for remove them, ClearBits.

And the issue is that I can't use them by default as they aren't defined. I found at the SDK their codes (SetUse (https://github.com/ValveSoftware/halflife/blob/5d761709a31ce1e71488f2668321de05f791b405/dlls/cbase.h), SetBits (https://github.com/ValveSoftware/halflife/blob/5d761709a31ce1e71488f2668321de05f791b405/dlls/util.h)) and tried to define them locally, but didn't work. How I could use them.

Another problem that I have is with g_WeaponFuncs.AddMultiDamage, which I believe I'm using it right, yet it errors in game (this line is towards the end, in TraceAttack function).

For the last, if you try the code, you will see that the turret retires fine, but then instead of staying inside, it displays the spin animation. I don't understand why it happens, the logic is apparently fine.


const int TURRET_SHOTS = 2;
const int TURRET_RANGE = (100 * 12);
const Vector TURRET_SPREAD = Vector( 0, 0, 0 );
const int TURRET_TURNRATE = 30; //angles per 0.1 second
const int TURRET_MAXWAIT = 15; // seconds turret will stay active w/o a target
//const int TURRET_MAXSPIN = 5; // seconds turret barrel will spin w/o a target
const float TURRET_MACHINE_VOLUME = 0.5;

const string TURRET_GLOW_SPRITE = "sprites/flare3.spr";

enum TURRET_ANIM
{
TURRET_ANIM_NONE = 0,
TURRET_ANIM_FIRE,
TURRET_ANIM_SPIN,
TURRET_ANIM_DEPLOY,
TURRET_ANIM_RETIRE,
TURRET_ANIM_DIE,
};

class monster_turret_rocket : ScriptBaseMonsterEntity
{


//CSprite@ m_pEyeGlow;
int m_eyeBrightness;

int m_iDeployHeight;
int m_iRetractHeight;
int m_iMinPitch;

int m_iBaseTurnRate; // angles per second
float m_fTurnRate; // actual turn rate
int m_iOrientation; // 0 = floor, 1 = Ceiling
bool m_iOn;
bool m_fBeserk; // Sometimes this bitch will just freak out
bool m_iAutoStart; // true if the turret auto deploys when a target
// enters its range

Vector m_vecLastSight;
float m_flLastSight; // Last time we saw a target
float m_flMaxWait; // Max time to seach w/o a target
int m_iSearchSpeed; // Not Used!

// movement
float m_flStartYaw;
Vector m_vecCurAngles;
Vector m_vecGoalAngles;


float m_flPingTime; // Time until the next ping, used when searching
//float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching

//int m_iStartSpin;


bool KeyValue( const string& in szKey, const string& in szValue )
{
if ( szKey == "maxsleep" )
{
m_flMaxWait = atof( szValue );
return true;
}
else if ( szKey == "orientation" )
{
m_iOrientation = atoi( szValue );
return true;

}
else if ( szKey == "searchspeed" )
{
m_iSearchSpeed = atoi( szValue );
return true;

}
else if ( szKey == "turnrate" )
{
m_iBaseTurnRate = atoi( szValue );
return true;
}
else if ( ( szKey == "style" ) ||
( szKey == "height" ) ||
( szKey == "value1" ) ||
( szKey == "value2" ) ||
( szKey == "value3") )
return true;
else
return BaseClass.KeyValue( szKey, szValue );
}

void Spawn()
{
Precache( );

g_EntityFuncs.SetModel( self, "models/turret.mdl" );
self.pev.health = 100;
self.m_HackedGunPos = Vector( 0, 0, 12.75 );
//m_flMaxSpin = TURRET_MAXSPIN;
pev.view_ofs.z = 12.75;


self.pev.nextthink = g_Engine.time + 1;
self.pev.movetype = MOVETYPE_FLY;
self.pev.sequence = 0;
self.pev.frame = 0;
self.pev.solid = SOLID_SLIDEBOX;
self.pev.takedamage = DAMAGE_AIM;

//self.SetBits (self.pev.flags, FL_MONSTER);
//self.SetUse( self.TurretUse );
/*
if ( ( self.pev.spawnflags & 32 )
&& !( self.pev.spawnflags & 64 ) ) //SF_MONSTER_TURRET_AUTOACTIVATE == 32 SF_MONSTER_TURRET_STARTINACTIVE == 64
{*/
//m_iAutoStart = true;
/*}
*/
self.ResetSequenceInfo( );
self.SetBoneController( 0, 0 );
self.SetBoneController( 1, 0 );
self.m_flFieldOfView = VIEW_FIELD_FULL;
// m_flSightRange = TURRET_RANGE;


m_iRetractHeight = 16;
m_iDeployHeight = 32;
m_iMinPitch = -15;
g_EntityFuncs.SetSize( pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));

SetThink( ThinkFunction( this.Initialize ) );

//m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev.origin, false );
//m_pEyeGlow.SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
//m_pEyeGlow.SetAttachment( edict(), 2 );
m_eyeBrightness = 0;

self.pev.nextthink = g_Engine.time + 0.3;

}


void Precache( )
{
g_SoundSystem.PrecacheSound( "turret/tu_fire1.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_ping.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_active2.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_die.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_die2.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_die3.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_retract.wav" ); // just use deploy sound to save memory
g_SoundSystem.PrecacheSound( "turret/tu_deploy.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_spinup.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_spindown.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_search.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_alert.wav" );

g_Game.PrecacheModel( "models/turret.mdl" );
//g_Game.PrecacheModel( TURRET_GLOW_SPRITE );
}

void Initialize( void )
{
m_iOn = false;
m_fBeserk = false;
//m_iSpin = 0;

self.SetBoneController( 0, 0 );
self.SetBoneController( 1, 0 );

if ( m_iBaseTurnRate == 0 )
m_iBaseTurnRate = TURRET_TURNRATE;

if ( m_flMaxWait == 0 )
m_flMaxWait = TURRET_MAXWAIT;

m_flStartYaw = pev.angles.y;

if ( m_iOrientation == 1 )
{
self.pev.idealpitch = 180;
self.pev.angles.x = 180;
self.pev.view_ofs.z = -pev.view_ofs.z;
self.pev.effects |= EF_INVLIGHT;
self.pev.angles.y = self.pev.angles.y + 180;
if ( self.pev.angles.y > 360 )
self.pev.angles.y = self.pev.angles.y - 360;
}

m_vecGoalAngles.x = 0;

if ( m_iAutoStart == false )
{
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.AutoSearchThink ) );
self.pev.nextthink = g_Engine.time + .1;
}
else
SetThink( ThinkFunction( self.SUB_DoNothing ) );

}

void TurretUse( CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float value )
{
g_Game.AlertMessage( at_console, "Use 1\n" );
if ( !self.ShouldToggle( useType, m_iOn ) )
return;

if (m_iOn)
{
self.m_hEnemy = null;
self.pev.nextthink = g_Engine.time + 0.1;
m_iAutoStart = false;// switching off a turret disables autostart
//!!!! this should spin down first!!BUGBUG
SetThink( ThinkFunction( this.Retire ) );
}
else
{
self.pev.nextthink = g_Engine.time + 0.1; // turn on delay

// if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
/* if ( self.pev.spawnflags & 32 )
{*/
m_iAutoStart = true;
/*}*/
SetThink( ThinkFunction( this.Deploy ) );
}
g_Game.AlertMessage( at_console, "Use 2\n" );
}


void Ping( void )
{
// make the pinging noise every second while searching
if ( m_flPingTime == 0 )
m_flPingTime = g_Engine.time + 1;
else if ( m_flPingTime <= g_Engine.time )
{
m_flPingTime = g_Engine.time + 1;
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_ITEM, "turret/tu_ping.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
//EyeOn( );
}
else if ( m_eyeBrightness > 0 )
{
//EyeOff( );
}
}

/*
void EyeOn()
{
if ( m_pEyeGlow is null )
{
if ( m_eyeBrightness != 255 )
{
m_eyeBrightness = 255;
}
m_pEyeGlow.SetBrightness( m_eyeBrightness );
}
}


void EyeOff()
{
if ( m_pEyeGlow is null )
{
if ( m_eyeBrightness > 0 )
{
m_eyeBrightness = Math.max( 0, m_eyeBrightness - 30 );
m_pEyeGlow.SetBrightness( m_eyeBrightness );
}
}
}*/


void ActiveThink( void )
{
bool fAttack = false;
Vector vecDirToEnemy;

self.pev.nextthink = g_Engine.time + 0.1;
self.StudioFrameAdvance( );

//CBaseEntity@ ent = self.m_hEnemy;

if ( ( !m_iOn ) || ( self.m_hEnemy.GetEntity() is null ) )
{
self.m_hEnemy = null;
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.SearchThink ) );
return;
}

// if it's dead, look for something new
if ( !self.m_hEnemy.GetEntity().IsAlive() )
{
if ( m_flLastSight <= 0.0 )
{
m_flLastSight = g_Engine.time + 0.5; // continue-shooting timeout
}
else
{
if ( g_Engine.time > m_flLastSight )
{
self.m_hEnemy = null;
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.SearchThink ) );
return;
}
}
}

Vector vecMid = self.pev.origin + self.pev.view_ofs;
Vector vecMidEnemy = self.m_hEnemy.GetEntity().BodyTarget( vecMid );

// Look for our current enemy

bool fEnemyVisible = self.m_hEnemy.GetEntity().FVisible( self, false );

vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy
float flDistToEnemy = vecDirToEnemy.Length();

Vector vec = Math.VecToAngles(vecMidEnemy - vecMid);

// Current enmey is not visible.
if ( !fEnemyVisible || ( flDistToEnemy > TURRET_RANGE ) )
{
if ( m_flLastSight <= 0.0 )
m_flLastSight = g_Engine.time + 0.5;
else
{
// Should we look for a new target?
if ( g_Engine.time > m_flLastSight )
{
self.m_hEnemy = null;
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.SearchThink ) );
return;
}
}
fEnemyVisible = false;
}
else
{
m_vecLastSight = vecMidEnemy;
}

Math.MakeAimVectors( m_vecCurAngles );

/*
ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n",
m_vecCurAngles.x, m_vecCurAngles.y,
g_Engine.v_forward.x, g_Engine.v_forward.y, g_Engine.v_forward.z );
*/

Vector vecLOS = vecDirToEnemy;
vecLOS = vecLOS.Normalize();

// Is the Gun looking at the target
if ( DotProduct( vecLOS, g_Engine.v_forward ) <= 0.866 ) // 30 degree slop
fAttack = false;
else
fAttack = true;

// fire the gun
if ( fAttack || m_fBeserk )
{
Vector vecSrc, vecAng;
self.GetAttachment( 0, vecSrc, vecAng );
SetTurretAnim( TURRET_ANIM_FIRE );
Shoot( vecSrc, g_Engine.v_forward );
}
else
{
SetTurretAnim( TURRET_ANIM_SPIN );
}

//move the gun
if ( m_fBeserk )
{
if ( Math.RandomLong( 0, 9 ) == 0 )
{
m_vecGoalAngles.y = Math.RandomFloat( 0, 360 );
m_vecGoalAngles.x = Math.RandomFloat( 0, 90 ) - 90 * m_iOrientation;
TakeDamage( pev, pev, 1, DMG_GENERIC ); // don't beserk forever
return;
}
}
else if ( fEnemyVisible )
{
if ( vec.y > 360 )
vec.y -= 360;

if ( vec.y < 0 )
vec.y += 360;

//ALERT(at_console, "[%.2f]", vec.x);

if ( vec.x < -180 )
vec.x += 360;

if ( vec.x > 180 )
vec.x -= 360;

// now all numbers should be in [1...360]
// pin to turret limitations to [-90...15]

if ( m_iOrientation == 0 )
{
if ( vec.x > 90 )
vec.x = 90;
else if ( vec.x < m_iMinPitch )
vec.x = m_iMinPitch;
}
else
{
if ( vec.x < -90 )
vec.x = -90;
else if ( vec.x > -m_iMinPitch )
vec.x = -m_iMinPitch;
}

// ALERT(at_console, ".[%.2f]\n", vec.x);

m_vecGoalAngles.y = vec.y;
m_vecGoalAngles.x = vec.x;

}

MoveTurret();
}


void Shoot( Vector vecSrc, Vector vecDirToEnemy )
{
self.FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_12MM, 1 );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6, 0, 120);
pev.effects = pev.effects | EF_MUZZLEFLASH;
}

void Deploy( void )
{
self.pev.nextthink = g_Engine.time + 0.1;
self.StudioFrameAdvance( );

if ( self.pev.sequence != TURRET_ANIM_DEPLOY )
{
m_iOn = true;
SetTurretAnim( TURRET_ANIM_DEPLOY );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
self.SUB_UseTargets( self, USE_ON, 0 );
}

if ( self.m_fSequenceFinished )
{
self.pev.maxs.z = m_iDeployHeight;
self.pev.mins.z = -m_iDeployHeight;
g_EntityFuncs.SetSize( self.pev, self.pev.mins, self.pev.maxs );

m_vecCurAngles.x = 0;

if (m_iOrientation == 1)
{
m_vecCurAngles.y = Math.AngleMod( pev.angles.y + 180 );
}
else
{
m_vecCurAngles.y = Math.AngleMod( pev.angles.y );
}

SetTurretAnim( TURRET_ANIM_SPIN );
self.pev.framerate = 0;
SetThink( ThinkFunction( this.SearchThink ) );
}

m_flLastSight = g_Engine.time + m_flMaxWait;
}

void Retire( void )
{
g_Game.AlertMessage( at_console, "Retire 1\n" );
// make the turret level
m_vecGoalAngles.x = 0;
m_vecGoalAngles.y = m_flStartYaw;

self.pev.nextthink = g_Engine.time + 0.1;

self.StudioFrameAdvance( );

//EyeOff( );

if ( MoveTurret() != 0 ) //!MoveTurret
{
g_Game.AlertMessage( at_console, "We Retire\n" );
if ( self.pev.sequence != TURRET_ANIM_RETIRE )
{
g_Game.AlertMessage( at_console, "Now\n" );
SetTurretAnim( TURRET_ANIM_RETIRE );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_retract.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
self.SUB_UseTargets( self, USE_OFF, 0 );
}
else if ( self.m_fSequenceFinished )
{
g_Game.AlertMessage( at_console, "later\n" );
m_iOn = false;
m_flLastSight = 0;
SetTurretAnim( TURRET_ANIM_NONE );
self.pev.maxs.z = m_iRetractHeight;
self.pev.mins.z = -m_iRetractHeight;
g_EntityFuncs.SetSize( self.pev, self.pev.mins, self.pev.maxs );
if ( m_iAutoStart )
{
SetThink( ThinkFunction( this.AutoSearchThink ) );
self.pev.nextthink = g_Engine.time + .1;
}
else
SetThink( ThinkFunction( self.SUB_DoNothing ) ); //self.SUB_DoNothing
}
}
else
{
g_Game.AlertMessage( at_console, "spin\n" );
SetTurretAnim( TURRET_ANIM_SPIN );
}
g_Game.AlertMessage( at_console, "Retire 2\n" );
}

void SetTurretAnim( TURRET_ANIM anim )
{
if (self.pev.sequence != anim)
{
switch( anim )
{
case TURRET_ANIM_FIRE:
if ( self.pev.sequence != TURRET_ANIM_FIRE && self.pev.sequence != TURRET_ANIM_SPIN )
{
self.pev.frame = 0;
}
break;
default:
self.pev.frame = 0;
break;
}

self.pev.sequence = anim;
self.ResetSequenceInfo( );

switch( anim )
{
case TURRET_ANIM_RETIRE:
self.pev.frame = 255;
self.pev.framerate = -1.0;
break;
case TURRET_ANIM_DIE:
self.pev.framerate = 1.0;
break;
}
//ALERT(at_console, "Turret anim #%d\n", anim);
}
}


//
// This search function will sit with the turret deployed and look for a new target.
// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will
// retact.
//
void SearchThink( )
{
// ensure rethink
SetTurretAnim( TURRET_ANIM_SPIN );
self.StudioFrameAdvance( );
self.pev.nextthink = g_Engine.time + 0.1;

//if ( m_flSpinUpTime == 0 && ( m_flMaxSpin is not null) )
// m_flSpinUpTime = g_Engine.time + m_flMaxSpin;

Ping();

// If we have a target and we're still healthy
if ( self.m_hEnemy.GetEntity() !is null )
{
if ( !self.m_hEnemy.GetEntity().IsAlive() )
self.m_hEnemy = null;// Dead enemy forces a search for new one
}


// Acquire Target
if ( self.m_hEnemy.GetEntity() is null )
{
self.Look( TURRET_RANGE );
self.m_hEnemy = BestVisibleEnemy();
}

// If we've found a target, spin up the barrel and start to attack
if ( self.m_hEnemy.GetEntity() !is null )
{
m_flLastSight = 0;

SetThink( ThinkFunction( this.ActiveThink ) );
}
else
{
// Are we out of time, do we need to retract?
if ( g_Engine.time > m_flLastSight )
{
//Before we retrace, make sure that we are spun down.
m_flLastSight = 0;

SetThink( ThinkFunction( this.Retire ) );
}

// generic hunt for new victims
m_vecGoalAngles.y = ( m_vecGoalAngles.y + 0.1 * m_fTurnRate );
if ( m_vecGoalAngles.y >= 360 )
m_vecGoalAngles.y -= 360;
MoveTurret();
}
}


//
// This think function will deploy the turret when something comes into range. This is for
// automatically activated turrets.
//
void AutoSearchThink( )
{
// ensure rethink
self.StudioFrameAdvance( );
self.pev.nextthink = g_Engine.time + 0.3;

// If we have a target and we're still healthy

if ( self.m_hEnemy.GetEntity() !is null )
{
if ( !self.m_hEnemy.GetEntity().IsAlive() )
self.m_hEnemy = null;// Dead enemy forces a search for new one
}

// Acquire Target

if ( self.m_hEnemy.GetEntity() is null )
{
self.Look( TURRET_RANGE );
self.m_hEnemy = BestVisibleEnemy();
}

if ( self.m_hEnemy.GetEntity() !is null )
{
SetThink( ThinkFunction( this.Deploy ) );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
}
}


void TurretDeath( void )
{
bool iActive = false;

self.StudioFrameAdvance( );
self.pev.nextthink = g_Engine.time + 0.1;

if ( self.pev.deadflag != DEAD_DEAD )
{
self.pev.deadflag = DEAD_DEAD;

float flRndSound = Math.RandomFloat( 0 , 1 );

if ( flRndSound <= 0.33 )
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_die.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
else if ( flRndSound <= 0.66 )
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_die2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
else
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_die3.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);

g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100);

if ( m_iOrientation == 0 )
m_vecGoalAngles.x = -15;
else
m_vecGoalAngles.x = -90;

SetTurretAnim( TURRET_ANIM_DIE );

//EyeOn( );
}

//EyeOff( );
/*
if ( pev.dmgtime + Math.RandomFloat( 0, 2 ) > g_Engine.time )
{
// lots of smoke
NetworkMessage smoke ( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY );
smoke.WriteByte( TE_SMOKE );
smoke.WriteCoord( Math.RandomFloat( pev.absmin.x, pev.absmax.x ) );
smoke.WriteCoord( Math.RandomFloat( pev.absmin.y, pev.absmax.y ) );
smoke.WriteCoord( pev.origin.z - m_iOrientation * 64 );
//smoke.WriteCoord( g_sModelIndexSmoke );
smoke.WriteByte( 25 ); // scale * 10
smoke.WriteByte( 10 - m_iOrientation * 5); // framerate
smoke.End();
}*/

if ( self.pev.dmgtime + Math.RandomFloat( 0, 5 ) > g_Engine.time )
{
Vector vecSrc = Vector( Math.RandomFloat( self.pev.absmin.x, self.pev.absmax.x ), Math.RandomFloat( self.pev.absmin.y, self.pev.absmax.y ), 0 );
if (m_iOrientation == 0)
vecSrc = vecSrc + Vector( 0, 0, Math.RandomFloat( self.pev.origin.z, self.pev.absmax.z ) );
else
vecSrc = vecSrc + Vector( 0, 0, Math.RandomFloat( self.pev.absmin.z, self.pev.origin.z ) );

g_Utility.Sparks( vecSrc );
}

if ( self.m_fSequenceFinished && ( MoveTurret() == 0 ) && self.pev.dmgtime + 5 < g_Engine.time )
{
self.pev.framerate = 0;
SetThink( null );
}
}



void TraceAttack( entvars_t@ pevAttacker, float flDamage, Vector vecDir, TraceResult ptr, int bitsDamageType)
{
if ( ptr.iHitgroup == 10 )
{
// hit armor
if ( self.pev.dmgtime != g_Engine.time || ( Math.RandomLong( 0,10 ) < 1 ) )
{
g_Utility.Ricochet( ptr.vecEndPos, Math.RandomFloat( 1, 2 ) );
self.pev.dmgtime = g_Engine.time;
}

flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
}

if ( self.pev.takedamage == 0 )
return;

//g_WeaponFuncs.AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType );
}

// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET

int TakeDamage(entvars_t@ pevInflictor, entvars_t@ pevAttacker, float flDamage, int bitsDamageType)
{
if ( self.pev.takedamage == 0 )
return 0;

if (!m_iOn)
flDamage /= 10.0;

self.pev.health -= flDamage;
if ( self.pev.health <= 0 )
{
self.pev.health = 0;
self.pev.takedamage = DAMAGE_NO;
self.pev.dmgtime = g_Engine.time;

//self.ClearBits ( pev.flags, FL_MONSTER ); // why are they set in the first place???

//SetUse( null );
SetThink( ThinkFunction( this.TurretDeath ) );
self.SUB_UseTargets( self, USE_ON, 0 ); // wake up others
self.pev.nextthink = g_Engine.time + 0.1;

return 0;
}

if ( self.pev.health <= 10 )
{
if ( m_iOn )//&& ( 1 || Math.RandomLong( 0, 0x7FFF ) > 800 ) )
{
m_fBeserk = true;
SetThink( ThinkFunction( this.SearchThink ) );
}
}

return 1;
}

int MoveTurret(void)
{
int state = 0;
// any x movement?

if ( m_vecCurAngles.x != m_vecGoalAngles.x )
{
float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;

m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir;

// if we started below the goal, and now we're past, peg to goal
if ( flDir == 1 )
{
if ( m_vecCurAngles.x > m_vecGoalAngles.x )
m_vecCurAngles.x = m_vecGoalAngles.x;
}
else
{
if ( m_vecCurAngles.x < m_vecGoalAngles.x )
m_vecCurAngles.x = m_vecGoalAngles.x;
}

if ( m_iOrientation == 0 )
self.SetBoneController( 1, -m_vecCurAngles.x );
else
self.SetBoneController( 1, m_vecCurAngles.x );
state = 1;
}

if ( m_vecCurAngles.y != m_vecGoalAngles.y )
{
float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
float flDist = ( m_vecGoalAngles.y - m_vecCurAngles.y ); //fabs

if ( flDist > 180 )
{
flDist = 360 - flDist;
flDir = -flDir;
}
if ( flDist > 30 )
{
if ( m_fTurnRate < m_iBaseTurnRate * 10 )
{
m_fTurnRate += m_iBaseTurnRate;
}
}
else if ( m_fTurnRate > 45 )
{
m_fTurnRate -= m_iBaseTurnRate;
}
else
{
m_fTurnRate += m_iBaseTurnRate;
}

m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;

if ( m_vecCurAngles.y < 0 )
m_vecCurAngles.y += 360;
else if ( m_vecCurAngles.y >= 360 )
m_vecCurAngles.y -= 360;

if ( flDist < ( 0.05 * m_iBaseTurnRate ) )
m_vecCurAngles.y = m_vecGoalAngles.y;

//ALERT(at_console, "%.2f . %.2f\n", m_vecCurAngles.y, y);
if ( m_iOrientation == 0 )
self.SetBoneController( 0, m_vecCurAngles.y - pev.angles.y );
else
self.SetBoneController( 0, pev.angles.y - 180 - m_vecCurAngles.y );
state = 1;
}

if ( state == 0 )
m_fTurnRate = m_iBaseTurnRate;

//ALERT(at_console, "(%.2f, %.2f).(%.2f, %.2f)\n", m_vecCurAngles.x,
// m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y);
return state;
}

//
// ID as a machine
//
int Classify ( void )
{
if ( m_iOn || m_iAutoStart )
{
return CLASS_MACHINE;
}
return CLASS_NONE;
}

CBaseEntity@ BestVisibleEnemy()
{
CBaseEntity@ pReturn = null;

while( ( @pReturn = g_EntityFuncs.FindEntityInSphere( pReturn, self.pev.origin, 16384, "player", "classname" ) ) !is null )
{
if( pReturn.IsAlive() )
return pReturn;
}
return pReturn;
}
}

string GetTurretRocketName()
{
return "monster_turret_rocket";
}

void RegisterTurretRocket()
{
g_CustomEntityFuncs.RegisterCustomEntity( "monster_turret_rocket", GetTurretRocketName() );
}

The code is a bit dirty, sorry.

Solokiller
27-05-2016, 01:39 PM
SetBits is identical to operator|:


self.pev.flags |= FL_MONSTER;


ClearBits is the same as:


pev.flags &= ~FL_MONSTER;


What this does is it performs a bitwise AND operation with all bits set to 1, except for the flag you're clearing.

For example, if you have the value 00000001, and you clear the first bit, it performs AND with 11111110, which results in the value becoming 00000000.

As to why it sets and clears FL_MONSTER: this flag tells the game whether an entity is a monster or not. Clearing it will cause code to ignore the entity when searching for monsters. Barnacles for example are not flagged as being a monster to avoid getting shot all the time.

SetUse has to be used in conjunction with UseFunction:


SetUse( UseFunction( this.TurretUse ) );


As explained here: https://github.com/SamVanheer/SC_Angelscript/wiki/Entity-Intermediate#setting-use-functions

AddMultiDamage uses CBaseEntity, so you have to use the custom entity's CBaseEntity instance here:


g_WeaponFuncs.AddMultiDamage( pevAttacker, self, flDamage, bitsDamageType );


lastly, the turret spins around because you converted an if statement incorrectly. In the SDK source code, this line tells the turret when it has stopped moving and can fully retire:


if (!MoveTurret())


You converted it to:


if ( MoveTurret() != 0 ) //!MoveTurret


!MoveTurret() can be written as MoveTurret() == 0. You've inverted the condition, so instead of retiring, it spins.

You seem to be having trouble understanding the difference between "this" and "self". "this" is the custom entity class, defined in Angelscript. It derives from ScriptBaseEntity or another base class. "self" is the CBaseEntity derived class defined in C++ code. "this" is used when you work with code that couldn't be made to work with "self" due to language limitations. In cases like Think, Use, Touch and Blocked functions it's necessary to allow you to set these functions, since you can't reference a method defined in a custom entity class from a handle to a CBaseEntity instance.

Maestro Fénix
28-05-2016, 02:41 PM
Whoops, the MoveTurret() one was a mistake that I overlooked. Thanks again.

Now, is basically complete except a single problem: If is at the ceiling, and the entity is placed into the ceiling brush, it will not rotate to aim the target, but it will rotate fine while seeking targets (Ping) or retiring. Moving one unit down fixes the problem, but this issue doesn't happen with the normal monster_turret.

I have been checking the code, and I believe the bug must be at ActiveThink(), as at the line "bool fEnemyVisible = self.m_hEnemy.GetEntity().FVisible( self, false );", is like it couldn't "see" the player (yet if you go in front on it, it will shoot, although it will not move neither their gun in the X axis).

I upload the code with a test map (one of the ceiling turrets are in the air, so it will work fine. The button toggles them): http://www.mediafire.com/download/l1vxc54e4ir62mh/turret.7z

Solokiller
28-05-2016, 03:19 PM
The SDK turret code uses FBoxVisible to calculate if the enemy is visible or not. You've replaced it with a call to FVisible. FBoxVisible will try to calculate if the target entity's bounding box is visible to the looker (the turret in this case). You can find the implementation for the function here: https://github.com/ValveSoftware/halflife/blob/master/dlls/h_ai.cpp#L48

This should solve your problems.

Maestro Fénix
30-05-2016, 03:27 PM
Is complete. However, I want to expand it by making possible selecting the relationship that you want to have it. I want to implement BestVisibleEnemy() as the one made by Nero only targets the player, while I want to target all the hostile entities set up by the relationship selected.



CBaseEntity@ BestVisibleEnemy()
{
CBaseEntity@ pReturn = null;
CBaseEntity@ pNextEnt = m_pLink;
int iBestRelationship = R_NO;
float iNearest = 8192;
float iDist;

while ( @pNextEnt != null )
{
if ( pNextEnt.IsAlive() )
{

if ( self.IRelationship( pNextEnt ) > iBestRelationship )
{
// this entity is disliked MORE than the entity that we
// currently think is the best visible enemy. No need to do
// a distance check, just get mad at this one for now.
iBestRelationship = self.IRelationship( pNextEnt );
iNearest = ( pNextEnt.pev.origin - pev.origin ).Length();
@pReturn = pNextEnt;
}
else if ( self.IRelationship( pNextEnt) == iBestRelationship )
{
// this entity is disliked just as much as the entity that
// we currently think is the best visible enemy, so we only
// get mad at it if it is closer.
iDist = ( pNextEnt.pev.origin - pev.origin ).Length();
if ( iDist <= iNearest )
{
iNearest = iDist;
iBestRelationship = self.IRelationship( pNextEnt );
@pReturn = pNextEnt;
}
}
}

@pNextEnt = pNextEnt.m_pLink;
}

return pReturn;
}

The problem here is m_pLink. According to monsters.cpp (https://github.com/ValveSoftware/halflife/blob/5d761709a31ce1e71488f2668321de05f791b405/dlls/monsters.cpp), is undefined/null. And while this works in normal HL, it doesn't in SC as is considered as null, so it never goes into the first while. I believe the problem is at the penultimate line, pNextEnt = pNextEnt.m_pLink;, as when trying m_pLink this way, the compiler will tell you there's an error since CBaseEntity doesn't have that.

Solokiller
30-05-2016, 03:35 PM
Those weren't exposed to Angelscript. I've created an issue for it: https://github.com/SamVanheer/SC_Angelscript/issues/36

Nero
30-05-2016, 08:40 PM
Yeah I know, I tried making it target all hostile(to the turret) entities, but gave up and just....*gag* *twitch* *cringe*hard-coded it

It was a specialized npc anyway ^^;

We needs them sexposures! ♥♡

Maestro Fénix
31-05-2016, 09:22 AM
Damm. Oh well, I guess I will have no choice but to patch it up by targetting manually the selected entity :/

Maestro Fénix
31-05-2016, 12:07 PM
weapon_wrench repairs any entity with CLASS_MACHINE, right? How I could apply it, but at the same time, CLASS_PLAYER_ALLY so is in the same team of the players (this would also attack other enemy players?). Or how it works?

w00tguy123
31-05-2016, 12:21 PM
This probably isn't the proper way to do it, but what I've done is temporarily disable ally status of the monster, then check the classificaiton, then enable the ally state again. I couldn't find any other method to get the monster's classification when it is set as an ally.

Some code you could use:

int getMonsterClassify(CBaseMonster@ mon)
{
if (mon.Classify() == CLASS_PLAYER_ALLY)
{
// PLAYER_ALLY masks the actual monster class, so remove that for a sec
mon.SetPlayerAlly(false);
int c = mon.Classify();
mon.SetPlayerAlly(true);
return c;
}
return mon.Classify();
}

So once you know it is a MACHINE you can check the ally status and repair or damage it.

Edit: Oops, this is a thread about custom monsters and I gave you an answer for custom weapons. Oh well.

Solokiller
31-05-2016, 12:34 PM
Machine repair behavior is implemented in each machine entity separately. In TakeDamage, it checks if damage is DMG_CLUB, and if the relationship between the attacker and the entity being damaged is friendly, repairs it.

The code that checks if it's being hit with a wrench is this:


bool CanRepairTurret(CBaseEntity *pAttacker)
{
if (pAttacker != NULL && pAttacker->IsPlayer())
{
CBasePlayer *pPlayer = (CBasePlayer *)pAttacker;
if (pPlayer != NULL)
{
CBasePlayerItem *pClientActiveItem = pPlayer->m_pClientActiveItem;
if (pClientActiveItem)
{
ItemInfo sItemInfo;
memset( &sItemInfo, 0, sizeof( sItemInfo ) );
pClientActiveItem->GetItemInfo( &sItemInfo );

// Check m_iDamageGiven in order to prevent repairables from being repaired by thrown crowbars. -Nev
if ( sItemInfo.iId == WEAPON_PIPEWRENCH && ((CWrench *) pClientActiveItem)->m_iDamageGiven > 0 )
return true; //Player is using a pipewrench
}
}
}
return false;
}

//In TakeDamage
const bool bIsHealing = ( pAttacker != nullptr ) && ( bitsDamageType & DMG_CLUB ) && CanRepairTurret( pAttacker ) && ( IRelationship( pAttacker ) <= R_NO );


It's hardcoded to the wrench, so don't count on custom weapons doing the same thing. If you want the wrench to repair a custom entity you've made, you'll need this.

Maestro Fénix
01-06-2016, 11:47 AM
Well, after porting that code to AS, not all of it works:

-memset is not present in AngelScript (or I didn't found the equivalent to it).
-For some unknown reasons, at pClientActiveItem.GetItemInfo( sItemInfo ), it yells at me that there's no appropiate opAssign found in ItemInfo. I can't use the "&" there. Without that, I can't check if the weapon used is the wrench.
-The bool does nothing regarless if is true or false.

At the end, I had to patch it by using TakeHealth:

TakeDamage code snippet:


CBaseEntity@ ptest = g_EntityFuncs.Instance( pevAttacker );

//Wrench repair check
if ( ( ptest !is null ) && ( bitsDamageType == DMG_CLUB ) && CanRepairTurret( ptest ) && ( self.IRelationship( ptest ) <= R_NO ) )
{
g_Game.AlertMessage( at_console, "we hope so\n" );
bIsHealing = true;
self.TakeHealth( 25, DMG_CLUB, 1500);
}

Entire code:



const int TURRET_SHOTS = 2;
//const int TURRET_RANGE = (100 * 12);
const Vector TURRET_SPREAD = Vector( 0, 0, 0 );
const int TURRET_TURNRATE = 30; //angles per 0.1 second
const int TURRET_MAXWAIT = 15; // seconds turret will stay active w/o a target
const float TURRET_MACHINE_VOLUME = 0.5;

const string TURRET_GLOW_SPRITE = "sprites/flare3.spr";
const string TURRET_SMOKE = "sprites/steam1.spr";

enum TURRET_ANIM
{
TURRET_ANIM_NONE = 0,
TURRET_ANIM_FIRE,
TURRET_ANIM_SPIN,
TURRET_ANIM_DEPLOY,
TURRET_ANIM_RETIRE,
TURRET_ANIM_DIE,
};

class monster_turret_rocket : ScriptBaseMonsterEntity
{
CSprite@ m_pEyeGlow;
int m_eyeBrightness;

int m_iDeployHeight;
int m_iRetractHeight;
int m_iMinPitch;

int m_iBaseTurnRate; // angles per second
float m_fTurnRate; // actual turn rate
int m_iOrientation; // 0 = floor, 1 = Ceiling
bool m_iOn;
bool m_fBeserk; // Sometimes this bitch will just freak out
bool m_iAutoStart; // true if the turret auto deploys when a target enters its range

Vector m_vecLastSight;
float m_flLastSight; // Last time we saw a target
float m_flMaxWait; // Max time to seach w/o a target
int m_iSearchSpeed; // Not Used!

int m_fRange;

// movement
float m_flStartYaw;
Vector m_vecCurAngles;
Vector m_vecGoalAngles;


float m_flPingTime; // Time until the next ping, used when searching

int g_sModelIndexSmoke; //Index for the smoke sprite

int m_iBodyGibs;

float m_fireLast;
float rocketcount;
float m_fireRate;

CBaseEntity@ rocketquided;
CBaseEntity@ pAttacker;

bool bIsHealing;

bool KeyValue( const string& in szKey, const string& in szValue )
{
if ( szKey == "maxsleep" )
{
m_flMaxWait = atof( szValue );
return true;
}
else if ( szKey == "orientation" )
{
m_iOrientation = atoi( szValue );
return true;

}
else if ( szKey == "searchspeed" )
{
m_iSearchSpeed = atoi( szValue );
return true;

}
else if ( szKey == "turnrate" )
{
m_iBaseTurnRate = atoi( szValue );
return true;
}
else if ( szKey == "fireRate" )
{
m_fireRate = atoi( szValue );
return true;
}
else if ( szKey == "attackrange" )
{
m_fRange = atoi( szValue );
return true;
}
else if ( ( szKey == "style" ) ||
( szKey == "height" ) ||
( szKey == "value1" ) ||
( szKey == "value2" ) ||
( szKey == "value3") )
return true;
else
return BaseClass.KeyValue( szKey, szValue );
}

void Spawn()
{
Precache( );

g_EntityFuncs.SetModel( self, "models/turret.mdl" );
self.pev.health = 1000;
self.m_HackedGunPos = Vector( 0, 0, 12.75 );
pev.view_ofs.z = 12.75;

self.pev.nextthink = g_Engine.time + 1;
self.pev.movetype = MOVETYPE_FLY;
self.pev.sequence = 0;
self.pev.frame = 0;
self.pev.solid = SOLID_SLIDEBOX;
self.pev.takedamage = DAMAGE_AIM;
self.m_bloodColor = DONT_BLEED;

g_EntityFuncs.DispatchKeyValue( self.edict(), "displayname", "Turret Rocket" );

self.pev.flags |= FL_MONSTER;

SetUse( UseFunction( this.TurretUse) );

if ( ( self.pev.spawnflags == 32 )
&& !( self.pev.spawnflags == 64 ) ) //SF_MONSTER_TURRET_AUTOACTIVATE == 32 SF_MONSTER_TURRET_STARTINACTIVE == 64
{
m_iAutoStart = true;
}

self.ResetSequenceInfo( );
self.SetBoneController( 0, 0 );
self.SetBoneController( 1, 0 );
self.m_flFieldOfView = VIEW_FIELD_FULL;

m_iRetractHeight = 16;
m_iDeployHeight = 32;
m_iMinPitch = -15;
g_EntityFuncs.SetSize( pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));

SetThink( ThinkFunction( this.Initialize ) );

@m_pEyeGlow = g_EntityFuncs.CreateSprite( TURRET_GLOW_SPRITE, self.pev.origin, false );
m_pEyeGlow.SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
m_pEyeGlow.SetAttachment( self.edict(), 2 );
m_eyeBrightness = 0;

//Just in case some fucks it over
if ( m_fireRate <= 0 )
{
m_fireRate = 1.0;
}

if ( m_fRange <= 0 )
{
m_fRange = 4000;
}

self.pev.nextthink = g_Engine.time + 0.3;
}

void Precache( )
{
g_SoundSystem.PrecacheSound( "turret/tu_fire1.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_ping.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_active2.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_die.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_die2.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_die3.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_retract.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_deploy.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_spinup.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_spindown.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_search.wav" );
g_SoundSystem.PrecacheSound( "turret/tu_alert.wav" );

g_Game.PrecacheModel( "models/turret.mdl" );
g_Game.PrecacheModel( TURRET_GLOW_SPRITE );
g_sModelIndexSmoke = g_Game.PrecacheModel(TURRET_SMOKE);// smoke

m_iBodyGibs = g_Game.PrecacheModel( "models/metalplategibs_green.mdl" );
}

void Initialize( void )
{
m_iOn = false;
m_fBeserk = false;

self.SetBoneController( 0, 0 );
self.SetBoneController( 1, 0 );

if ( m_iBaseTurnRate == 0 )
m_iBaseTurnRate = TURRET_TURNRATE;

if ( m_flMaxWait == 0 )
m_flMaxWait = TURRET_MAXWAIT;

m_flStartYaw = pev.angles.y;

if ( m_iOrientation == 1 )
{
self.pev.idealpitch = 180;
self.pev.angles.x = 180;
self.pev.view_ofs.z = -pev.view_ofs.z;
self.pev.effects |= EF_INVLIGHT;
self.pev.angles.y = self.pev.angles.y + 180;
if ( self.pev.angles.y > 360 )
self.pev.angles.y = self.pev.angles.y - 360;
}

m_vecGoalAngles.x = 0;

if ( m_iAutoStart )
{
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.AutoSearchThink ) );
self.pev.nextthink = g_Engine.time + .1;
}
else
SetThink( ThinkFunction( self.SUB_DoNothing ) );
}

void TurretUse( CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float value )
{
if ( !self.ShouldToggle( useType, m_iOn ) )
return;

if (m_iOn)
{
self.m_hEnemy = null;
self.pev.nextthink = g_Engine.time + 0.1;
m_iAutoStart = false;// switching off a turret disables autostart
SetThink( ThinkFunction( this.Retire ) );
}
else
{
self.pev.nextthink = g_Engine.time + 0.1; // turn on delay

// if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
if ( self.pev.spawnflags == 32 )
{
m_iAutoStart = true;
}
SetThink( ThinkFunction( this.Deploy ) );
}
}

void Ping( void )
{
// make the pinging noise every second while searching
if ( m_flPingTime == 0 )
m_flPingTime = g_Engine.time + 1;
else if ( m_flPingTime <= g_Engine.time )
{
m_flPingTime = g_Engine.time + 1;
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_ITEM, "turret/tu_ping.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
EyeOn( );
}
else if ( m_eyeBrightness > 0 )
{
EyeOff( );
}
}

void EyeOn()
{
if ( m_pEyeGlow !is null )
{
if ( m_eyeBrightness != 255 )
{
m_eyeBrightness = 255;
}
m_pEyeGlow.SetBrightness( m_eyeBrightness );
}
}

void EyeOff()
{
if ( m_pEyeGlow !is null )
{
if ( m_eyeBrightness > 0 )
{
m_eyeBrightness = Math.max( 0, m_eyeBrightness - 30 );
m_pEyeGlow.SetBrightness( m_eyeBrightness );
}
}
}

void ActiveThink( void )
{
bool fAttack = false;
Vector vecDirToEnemy;

self.pev.nextthink = g_Engine.time + 0.1;
self.StudioFrameAdvance( );

if ( ( !m_iOn ) || ( self.m_hEnemy.GetEntity() is null ) )
{
self.m_hEnemy = null;
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.SearchThink ) );
return;
}

// if it's dead, look for something new
if ( !self.m_hEnemy.GetEntity().IsAlive() )
{
if ( m_flLastSight <= 0.0 )
{
m_flLastSight = g_Engine.time + 0.5; // continue-shooting timeout
}
else
{
if ( g_Engine.time > m_flLastSight )
{
self.m_hEnemy = null;
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.SearchThink ) );
return;
}
}
}

Vector vecMid = self.pev.origin + self.pev.view_ofs;
Vector vecMidEnemy = self.m_hEnemy.GetEntity().BodyTarget( vecMid );

// Look for our current enemy
bool fEnemyVisible = FBoxVisible( self.pev, self.pev, vecMidEnemy );

vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy
float flDistToEnemy = vecDirToEnemy.Length();

Vector vec = Math.VecToAngles(vecMidEnemy - vecMid);

// Current enmey is not visible.
if ( !fEnemyVisible || ( flDistToEnemy > m_fRange ) )
{
if ( m_flLastSight <= 0.0 )
m_flLastSight = g_Engine.time + 0.5;
else
{
// Should we look for a new target?
if ( g_Engine.time > m_flLastSight )
{
self.m_hEnemy = null;
m_flLastSight = g_Engine.time + m_flMaxWait;
SetThink( ThinkFunction( this.SearchThink ) );
return;
}
}
fEnemyVisible = false;
}
else
{
m_vecLastSight = vecMidEnemy;
}

Math.MakeAimVectors( m_vecCurAngles );

Vector vecLOS = vecDirToEnemy;
vecLOS = vecLOS.Normalize();

// Is the Gun looking at the target
if ( DotProduct( vecLOS, g_Engine.v_forward ) <= 0.866 ) // 30 degree slop
fAttack = false;
else
fAttack = true;

// fire the gun
if ( fAttack || m_fBeserk )
{
Vector vecSrc, vecAng;
self.GetAttachment( 0, vecSrc, vecAng );
SetTurretAnim( TURRET_ANIM_FIRE );
Shoot( vecSrc, g_Engine.v_forward );

//Our rocket is in air? Then lets start guiding it :D
if ( rocketquided !is null )
{
rocketquided.pev.angles = Math.VecToAngles(vecDirToEnemy);
}
}
else
{
SetTurretAnim( TURRET_ANIM_SPIN );
}

//move the gun
if ( m_fBeserk )
{
if ( Math.RandomLong( 0, 9 ) == 0 )
{
m_vecGoalAngles.y = Math.RandomFloat( 0, 360 );
m_vecGoalAngles.x = Math.RandomFloat( 0, 90 ) - 90 * m_iOrientation;
TakeDamage( pev, pev, 1, DMG_GENERIC ); // don't beserk forever
return;
}
}
else if ( fEnemyVisible )
{
if ( vec.y > 360 )
vec.y -= 360;

if ( vec.y < 0 )
vec.y += 360;

if ( vec.x < -180 )
vec.x += 360;

if ( vec.x > 180 )
vec.x -= 360;

// now all numbers should be in [1...360]
// pin to turret limitations to [-90...15]

if ( m_iOrientation == 0 )
{
if ( vec.x > 90 )
vec.x = 90;
else if ( vec.x < m_iMinPitch )
vec.x = m_iMinPitch;
}
else
{
if ( vec.x < -90 )
vec.x = -90;
else if ( vec.x > -m_iMinPitch )
vec.x = -m_iMinPitch;
}

m_vecGoalAngles.y = vec.y;
m_vecGoalAngles.x = vec.x;

}

MoveTurret();
}


void Shoot( Vector vecSrc, Vector vecDirToEnemy )
{
/*
self.FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, m_fRange, BULLET_MONSTER_12MM, 1 );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6, 0, 120);
pev.effects = pev.effects | EF_MUZZLEFLASH;*/

if ( m_fireLast != 0 )
{
rocketcount = ( g_Engine.time - m_fireLast ) * m_fireRate;
if (rocketcount > 0)
{
@rocketquided = g_EntityFuncs.CreateRPGRocket(vecSrc, Math.VecToAngles(vecDirToEnemy), self.edict());
rocketquided.pev.velocity = rocketquided.pev.velocity.Normalize() * 300;
m_fireLast = g_Engine.time + (1.0 / m_fireRate);
}
}
else
{
m_fireLast = g_Engine.time;
}
}

void Deploy( void )
{
self.pev.nextthink = g_Engine.time + 0.1;
self.StudioFrameAdvance( );

if ( self.pev.sequence != TURRET_ANIM_DEPLOY )
{
m_iOn = true;
SetTurretAnim( TURRET_ANIM_DEPLOY );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
self.SUB_UseTargets( self, USE_ON, 0 );
}

if ( self.m_fSequenceFinished )
{
self.pev.maxs.z = m_iDeployHeight;
self.pev.mins.z = -m_iDeployHeight;
g_EntityFuncs.SetSize( self.pev, self.pev.mins, self.pev.maxs );

m_vecCurAngles.x = 0;

if (m_iOrientation == 1)
{
m_vecCurAngles.y = Math.AngleMod( pev.angles.y + 180 );
}
else
{
m_vecCurAngles.y = Math.AngleMod( pev.angles.y );
}

SetTurretAnim( TURRET_ANIM_SPIN );
self.pev.framerate = 0;
SetThink( ThinkFunction( this.SearchThink ) );
}

m_flLastSight = g_Engine.time + m_flMaxWait;
}

void Retire( void )
{
// make the turret level
m_vecGoalAngles.x = 0;
m_vecGoalAngles.y = m_flStartYaw;

self.pev.nextthink = g_Engine.time + 0.1;

self.StudioFrameAdvance( );

EyeOff( );

if ( MoveTurret() == 0 )
{
if ( self.pev.sequence != TURRET_ANIM_RETIRE )
{
SetTurretAnim( TURRET_ANIM_RETIRE );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_retract.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
self.SUB_UseTargets( self, USE_OFF, 0 );
}
else if ( self.m_fSequenceFinished )
{
m_iOn = false;
m_flLastSight = 0;
SetTurretAnim( TURRET_ANIM_NONE );
self.pev.maxs.z = m_iRetractHeight;
self.pev.mins.z = -m_iRetractHeight;
g_EntityFuncs.SetSize( self.pev, self.pev.mins, self.pev.maxs );
if ( m_iAutoStart )
{
SetThink( ThinkFunction( this.AutoSearchThink ) );
self.pev.nextthink = g_Engine.time + .1;
}
else
SetThink( ThinkFunction( self.SUB_DoNothing ) );
}
}
else
{
SetTurretAnim( TURRET_ANIM_SPIN );
}
}

void SetTurretAnim( TURRET_ANIM anim )
{
if (self.pev.sequence != anim)
{
switch( anim )
{
case TURRET_ANIM_FIRE:
if ( self.pev.sequence != TURRET_ANIM_FIRE && self.pev.sequence != TURRET_ANIM_SPIN )
{
self.pev.frame = 0;
}
break;
default:
self.pev.frame = 0;
break;
}

self.pev.sequence = anim;
self.ResetSequenceInfo( );

switch( anim )
{
case TURRET_ANIM_RETIRE:
self.pev.frame = 255;
self.pev.framerate = -1.0;
break;
case TURRET_ANIM_DIE:
self.pev.framerate = 1.0;
break;
}
}
}

//
// This search function will sit with the turret deployed and look for a new target.
// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will
// retact.
//
void SearchThink( )
{
// ensure rethink
SetTurretAnim( TURRET_ANIM_SPIN );
self.StudioFrameAdvance( );
self.pev.nextthink = g_Engine.time + 0.1;

Ping();

// If we have a target and we're still healthy
if ( self.m_hEnemy.GetEntity() !is null )
{
if ( !self.m_hEnemy.GetEntity().IsAlive() )
self.m_hEnemy = null;// Dead enemy forces a search for new one
}

// Acquire Target
if ( self.m_hEnemy.GetEntity() is null )
{
self.Look( m_fRange );
self.m_hEnemy = BestVisibleEnemy();
}

// If we've found a target, spin up the barrel and start to attack
if ( self.m_hEnemy.GetEntity() !is null )
{
m_flLastSight = 0;

SetThink( ThinkFunction( this.ActiveThink ) );
}
else
{
// Are we out of time, do we need to retract?
if ( g_Engine.time > m_flLastSight )
{
//Before we retrace, make sure that we are spun down.
m_flLastSight = 0;

SetThink( ThinkFunction( this.Retire ) );
}

// generic hunt for new victims
m_vecGoalAngles.y = ( m_vecGoalAngles.y + 0.1 * m_fTurnRate );
if ( m_vecGoalAngles.y >= 360 )
m_vecGoalAngles.y -= 360;
MoveTurret();
}
}

//
// This think function will deploy the turret when something comes into range. This is for
// automatically activated turrets.
//
void AutoSearchThink( )
{
// ensure rethink
self.StudioFrameAdvance( );
self.pev.nextthink = g_Engine.time + 0.3;

// If we have a target and we're still healthy

if ( self.m_hEnemy.GetEntity() !is null )
{
if ( !self.m_hEnemy.GetEntity().IsAlive() )
self.m_hEnemy = null;// Dead enemy forces a search for new one
}

// Acquire Target

if ( self.m_hEnemy.GetEntity() is null )
{
self.Look( m_fRange );
self.m_hEnemy = BestVisibleEnemy();
}

if ( self.m_hEnemy.GetEntity() !is null )
{
SetThink( ThinkFunction( this.Deploy ) );
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
}
}

void TurretDeath( void )
{
bool iActive = false;

self.StudioFrameAdvance( );
self.pev.nextthink = g_Engine.time + 0.1;

if ( self.pev.deadflag != DEAD_DEAD )
{
self.pev.deadflag = DEAD_DEAD;

float flRndSound = Math.RandomFloat( 0 , 1 );

if ( flRndSound <= 0.33 )
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_die.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
else if ( flRndSound <= 0.66 )
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_die2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);
else
g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, "turret/tu_die3.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120);

g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100);

if ( m_iOrientation == 0 )
m_vecGoalAngles.x = -15;
else
m_vecGoalAngles.x = -90;

SetTurretAnim( TURRET_ANIM_DIE );

EyeOn( );
}

EyeOff( );

if ( self.pev.dmgtime + Math.RandomFloat( 0, 2 ) > g_Engine.time )
{
// lots of smoke
NetworkMessage smoke( MSG_PVS, NetworkMessages::SVC_TEMPENTITY );
smoke.WriteByte( TE_SMOKE );
smoke.WriteCoord( Math.RandomFloat( self.pev.absmin.x, self.pev.absmax.x ) );
smoke.WriteCoord( Math.RandomFloat( self.pev.absmin.y, self.pev.absmax.y ) );
smoke.WriteCoord( self.pev.origin.z - m_iOrientation * 64 );
smoke.WriteShort( g_sModelIndexSmoke );
smoke.WriteByte( 25 ); // scale * 10
smoke.WriteByte( 10 - m_iOrientation * 5); // framerate
smoke.End();
}

if ( self.pev.dmgtime + Math.RandomFloat( 0, 5 ) > g_Engine.time )
{
Vector vecSrc = Vector( Math.RandomFloat( self.pev.absmin.x, self.pev.absmax.x ), Math.RandomFloat( self.pev.absmin.y, self.pev.absmax.y ), 0 );
if (m_iOrientation == 0)
vecSrc = vecSrc + Vector( 0, 0, Math.RandomFloat( self.pev.origin.z, self.pev.absmax.z ) );
else
vecSrc = vecSrc + Vector( 0, 0, Math.RandomFloat( self.pev.absmin.z, self.pev.origin.z ) );

g_Utility.Sparks( vecSrc );
}

if ( self.m_fSequenceFinished && ( MoveTurret() == 0 ) && self.pev.dmgtime + 5 < g_Engine.time )
{
//Explodes
g_EntityFuncs.CreateExplosion(self.pev.origin, self.pev.angles, self.edict(), 100, true);

Vector vecSpot = self.pev.origin + (self.pev.mins + self.pev.maxs) * 0.5;

uint8 shards = 10;
uint8 durationshard = 5;

//Gibs
NetworkMessage gibs( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, vecSpot );
gibs.WriteByte( TE_BREAKMODEL );

//Position
gibs.WriteCoord( vecSpot.x );
gibs.WriteCoord( vecSpot.y );
gibs.WriteCoord( vecSpot.z );

//Size
gibs.WriteCoord( self.pev.size.x );
gibs.WriteCoord( self.pev.size.y );
gibs.WriteCoord( self.pev.size.z );

//Velocity
gibs.WriteCoord( 0 );
gibs.WriteCoord( 0 );
gibs.WriteCoord( 200 );

//Randomization
gibs.WriteByte( 20 );

//Model
gibs.WriteShort( m_iBodyGibs );

//Number of gibs
gibs.WriteByte( shards );

//Duration
gibs.WriteByte( durationshard );

//Flags
gibs.WriteByte( BREAK_METAL + 16 );
gibs.End();

self.pev.framerate = 0;
g_EntityFuncs.Remove( self );
}
}

void TraceAttack( entvars_t@ pevAttacker, float flDamage, Vector vecDir, TraceResult ptr, int bitsDamageType)
{
if ( ptr.iHitgroup == 10 )
{
// hit armor
if ( self.pev.dmgtime != g_Engine.time || ( Math.RandomLong( 0,10 ) < 1 ) )
{
g_Utility.Ricochet( ptr.vecEndPos, Math.RandomFloat( 1, 2 ) );
self.pev.dmgtime = g_Engine.time;
}

flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
}

if ( self.pev.takedamage == 0 )
return;

g_WeaponFuncs.AddMultiDamage( pevAttacker, self, flDamage, bitsDamageType );
}

// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET
int TakeDamage(entvars_t@ pevInflictor, entvars_t@ pevAttacker, float flDamage, int bitsDamageType)
{
g_Game.AlertMessage( at_console, "This ever works?\n" );
if ( self.pev.takedamage == 0 )
return 0;

if (!m_iOn)
flDamage /= 10.0;

self.pev.health -= flDamage;
if ( self.pev.health <= 0 )
{
self.pev.health = 0;
self.pev.takedamage = DAMAGE_NO;
self.pev.dmgtime = g_Engine.time;

self.pev.flags &= ~FL_MONSTER; // why are they set in the first place???

SetUse( null );
SetThink( ThinkFunction( this.TurretDeath ) );
self.SUB_UseTargets( self, USE_ON, 0 ); // wake up others
self.pev.nextthink = g_Engine.time + 0.1;

return 0;
}

if ( self.pev.health <= 10 )
{
if ( m_iOn )//&& ( 1 || Math.RandomLong( 0, 0x7FFF ) > 800 ) )
{
m_fBeserk = true;
SetThink( ThinkFunction( this.SearchThink ) );
}
}

CBaseEntity@ ptest = g_EntityFuncs.Instance( pevAttacker );

//Wrench repair check
if ( ( ptest !is null ) && ( bitsDamageType == DMG_CLUB ) && CanRepairTurret( ptest ) && ( self.IRelationship( ptest ) <= R_NO ) )
{
g_Game.AlertMessage( at_console, "we hope so\n" );
bIsHealing = true;
self.TakeHealth( 25, DMG_CLUB, 1500);
}

//const bool bIsHealing = ( ( ptest !is null ) && ( bitsDamageType == DMG_CLUB ) && CanRepairTurret( ptest ) && ( self.IRelationship( ptest ) <= R_NO ) );

return 1;
}

int MoveTurret( void )
{
int state = 0;
// any x movement?

if ( m_vecCurAngles.x != m_vecGoalAngles.x )
{
float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;

m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir;

// if we started below the goal, and now we're past, peg to goal
if ( flDir == 1 )
{
if ( m_vecCurAngles.x > m_vecGoalAngles.x )
m_vecCurAngles.x = m_vecGoalAngles.x;
}
else
{
if ( m_vecCurAngles.x < m_vecGoalAngles.x )
m_vecCurAngles.x = m_vecGoalAngles.x;
}

if ( m_iOrientation == 0 )
self.SetBoneController( 1, -m_vecCurAngles.x );
else
self.SetBoneController( 1, m_vecCurAngles.x );
state = 1;
}

if ( m_vecCurAngles.y != m_vecGoalAngles.y )
{
float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
float flDist = ( m_vecGoalAngles.y - m_vecCurAngles.y ); //fabs

if ( flDist > 180 )
{
flDist = 360 - flDist;
flDir = -flDir;
}
if ( flDist > 30 )
{
if ( m_fTurnRate < m_iBaseTurnRate * 10 )
{
m_fTurnRate += m_iBaseTurnRate;
}
}
else if ( m_fTurnRate > 45 )
{
m_fTurnRate -= m_iBaseTurnRate;
}
else
{
m_fTurnRate += m_iBaseTurnRate;
}

m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;

if ( m_vecCurAngles.y < 0 )
m_vecCurAngles.y += 360;
else if ( m_vecCurAngles.y >= 360 )
m_vecCurAngles.y -= 360;

if ( flDist < ( 0.05 * m_iBaseTurnRate ) )
m_vecCurAngles.y = m_vecGoalAngles.y;

if ( m_iOrientation == 0 )
self.SetBoneController( 0, m_vecCurAngles.y - pev.angles.y );
else
self.SetBoneController( 0, pev.angles.y - 180 - m_vecCurAngles.y );
state = 1;
}

if ( state == 0 )
m_fTurnRate = m_iBaseTurnRate;

return state;
}

//
// ID as a machine
//
int Classify ( void )
{
if ( m_iOn || m_iAutoStart )
{
return CLASS_PLAYER_ALLY;
}
return CLASS_NONE;
}

//Implements BestVisibleEnemy()
CBaseEntity@ BestVisibleEnemy()
{
CBaseEntity@ pReturn = null;

//Since m_pLink is not exposed yet, we have no choice but to force to seek X entity as enemy around us :/
while( ( @pReturn = g_EntityFuncs.FindEntityInSphere( pReturn, self.pev.origin, m_fRange, "monster_apache", "classname" ) ) !is null )
{
if( pReturn.IsAlive() )
return pReturn;

}
return pReturn;
}

//Implements FBoxVisible()
bool FBoxVisible ( entvars_t@ pevLooker, entvars_t@ pevTarget, Vector vecTargetOrigin )
{
// don't look through water
if ((pevLooker.waterlevel != 3 && pevTarget.waterlevel == 3)
|| (pevLooker.waterlevel == 3 && pevTarget.waterlevel == 0))
return false;

TraceResult tr;
Vector vecLookerOrigin = pevLooker.origin + pevLooker.view_ofs;//look through the monster's 'eyes'
for (int i = 0; i < 5; i++)
{
Vector vecTarget = pevTarget.origin;
vecTarget.x += Math.RandomFloat( pevTarget.mins.x, pevTarget.maxs.x );
vecTarget.y += Math.RandomFloat( pevTarget.mins.y, pevTarget.maxs.y );
vecTarget.z += Math.RandomFloat( pevTarget.mins.z, pevTarget.maxs.z );

g_Utility.TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, self.edict()/*pentIgnore*/, tr);

if (tr.flFraction == 1.0)
{
vecTargetOrigin = vecTarget;
return true;// line of sight is valid.
}
}
return false;// Line of sight is not established
}

//Allows to be repairable with the wrench
bool CanRepairTurret( CBaseEntity @pAttacker )
{
g_Game.AlertMessage( at_console, "Checking\n" );
if ( pAttacker !is null && pAttacker.IsPlayer() )
{
g_Game.AlertMessage( at_console, "In1\n" );
CBasePlayer @pPlayer = cast<CBasePlayer @>( pAttacker );
if (pPlayer !is null)
{
g_Game.AlertMessage( at_console, "In2\n" );
CBasePlayerItem @pClientActiveItem = pPlayer.m_pActiveItem; //m_pClientActiveItem
if ( pClientActiveItem !is null )
{
g_Game.AlertMessage( at_console, "In3\n" );
ItemInfo sItemInfo;
//memset( sItemInfo, 0, sizeof( sItemInfo ) );
//pClientActiveItem.GetItemInfo( sItemInfo );

// Check m_iDamageGiven in order to prevent repairables from being repaired by thrown crowbars. -Nev
//if ( sItemInfo.iId == WEAPON_PIPEWRENCH )//&& ( cast<CWrench @>( pClientActiveItem ) ).m_iDamageGiven > 0 )
return true; //Player is using a pipewrench
}
}
}
return false;
}

}

string GetTurretRocketName()
{
return "monster_turret_rocket";
}

void RegisterTurretRocket()
{
g_CustomEntityFuncs.RegisterCustomEntity( "monster_turret_rocket", GetTurretRocketName() );
}



In the end it works, but since you don't check the type of melee weapon, the crowbar can be used to heal (and the secondary attack of the wrench heals too).


I also wonder if I could perfect the homing rockets that the turret fires. They are similar to the RPG as they follow the target a bit, although after looking a normal monster_human_grunt using the RPG, it seems it can be improved.
I hope it doesn't revolve about making a projectile from zero so you can do their own thinking part.

Solokiller
01-06-2016, 12:14 PM
memset is something that is too dangerous to expose to Angelscript. The ItemInfo constructor should set everything to 0 for you.
ItemInfo will need an opAssign method added to it. Issue here: https://github.com/SamVanheer/SC_Angelscript/issues/37

The RPG rocket is designed to follow laser spots only. If the spot's owner matches the rocket's owner, it will follow that one. If multiple spots exist, the last found spot that the rocket can see is used. You're probably better off implementing your own version.