wolf3d-ios/wolf3d/newCode/wolf/wolf_ai_com.c

1315 lines
27 KiB
C

/*
Copyright (C) 2004 Michael Liebscher
Copyright (C) 2000-2002 by DarkOne the Hacker
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* wolf_ai_com.c: Wolfenstein3-D actor manager.
*
* Author: Michael Liebscher <johnnycanuck@users.sourceforge.net>
* Date: 2004
*
* Acknowledgement:
* This code was derived from NewWolf, and was originally
* written by DarkOne the Hacker.
*
*/
#include "../wolfiphone.h"
#define RUNSPEED 6000
/*
-----------------------------------------------------------------------------
Function: AI_ChangeDir() -Entity is going to move in a new direction.
Parameters:
Returns: 1 if direction is OK, otherwise 0.
Notes:
Called, when actor finished previous moving & located in the 'center' of
the tile. Entity will try walking in direction.
-----------------------------------------------------------------------------
*/
PRIVATE int AI_ChangeDir( entity_t *self, dir8type new_dir, LevelData_t *lvl )
{
int oldx, oldy, newx, newy; // all it tiles
int n;
oldx = POS2TILE( self->x );
oldy = POS2TILE( self->y );
assert( new_dir >= 0 && new_dir <= 8 );
newx = oldx + dx8dir[ new_dir ];
newy = oldy + dy8dir[ new_dir ];
if( new_dir & 0x01 ) // same as %2 (diagonal dir)
{
if( lvl->tilemap[ newx ][ oldy ] & SOLID_TILE ||
lvl->tilemap[ oldx ][ newy ] & SOLID_TILE ||
lvl->tilemap[ newx ][ newy ] & SOLID_TILE )
{
return 0;
}
for( n = 0 ; n < NumGuards ; ++n )
{
if( Guards[ n ].state >= st_die1 )
continue;
if( Guards[ n ].tilex == newx && Guards[ n ].tiley == newy )
return 0; // another guard in path
if( Guards[ n ].tilex == oldx && Guards[ n ].tiley == newy )
return 0; // another guard in path
if( Guards[ n ].tilex == newx && Guards[ n ].tiley == oldy )
return 0; // another guard in path
}
}
else // linear dir (E, N, W, S)
{
if( lvl->tilemap[ newx ][ newy ] & SOLID_TILE )
{
return 0;
}
if( lvl->tilemap[ newx ][ newy ] & DOOR_TILE )
{
if( self->type == en_fake || self->type == en_dog) // they can't open doors
{
if( lvl->Doors.DoorMap[ newx ][ newy ].action != dr_open ) // path is blocked by a closed opened door
{
return 0;
}
}
else
{
self->waitfordoorx = newx;
self->waitfordoory = newy;
goto moveok;
}
}
for( n = 0 ; n < NumGuards ; ++n )
{
if( Guards[ n ].state >= st_die1 )
{
continue;
}
if( Guards[ n ].tilex == newx && Guards[ n ].tiley == newy )
{
return 0; // another guard in path
}
}
}
moveok:
self->tilex = newx;
self->tiley = newy;
lvl->tilemap[ oldx ][ oldy ] &= ~ACTOR_TILE; // update map status
lvl->tilemap[ newx ][ newy ] |= ACTOR_TILE;
if( lvl->areas[ newx ][ newy ] > 0 )
{ // ambush tiles don't have valid area numbers (-3), so don't change the area if walking over them
self->areanumber = lvl->areas[ newx ][ newy ];
assert( self->areanumber >= 0 && self->areanumber < NUMAREAS );
}
self->distance = TILEGLOBAL;
self->dir = new_dir;
return 1;
}
/*
-----------------------------------------------------------------------------
Function: AI_Path() -Entity is going to turn on a way point.
Parameters:
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void AI_Path( entity_t *self )
{
if( r_world->tilemap[ self->x >> TILESHIFT ][ self->y >> TILESHIFT ] & WAYPOINT_TILE )
{
long tileinfo = r_world->tilemap[self->x>>TILESHIFT][self->y>>TILESHIFT];
if(tileinfo&TILE_IS_E_TURN)
self->dir=dir8_east;
else if(tileinfo&TILE_IS_NE_TURN)
self->dir=dir8_northeast;
else if(tileinfo&TILE_IS_N_TURN)
self->dir=dir8_north;
else if(tileinfo&TILE_IS_NW_TURN)
self->dir=dir8_northwest;
else if(tileinfo&TILE_IS_W_TURN)
self->dir=dir8_west;
else if(tileinfo&TILE_IS_SW_TURN)
self->dir=dir8_southwest;
else if(tileinfo&TILE_IS_S_TURN)
self->dir=dir8_south;
else if(tileinfo&TILE_IS_SE_TURN)
self->dir=dir8_southeast;
}
if( ! AI_ChangeDir( self, self->dir, r_world ))
{
self->dir=dir8_nodir;
}
}
/*
-----------------------------------------------------------------------------
Function: AI_Dodge() -Attempts to choose and initiate a movement for entity
that sends it towards the player while dodging.
Parameters:
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void AI_Dodge( entity_t *self )
{
int deltax, deltay, i;
dir8type dirtry[ 5 ], turnaround, tdir;
if( self->flags & FL_FIRSTATTACK )
{
// turning around is only ok the very first time after noticing the player
turnaround = dir8_nodir;
self->flags &= ~FL_FIRSTATTACK;
}
else
{
turnaround = opposite8[ self->dir ];
}
deltax = POS2TILE( Player.position.origin[ 0 ] ) - POS2TILE( self->x );
deltay = POS2TILE( Player.position.origin[ 1 ] ) - POS2TILE( self->y );
//
// arange 5 direction choices in order of preference
// the four cardinal directions plus the diagonal straight towards
// the player
//
if( deltax > 0 )
{
dirtry[ 1 ] = dir8_east;
dirtry[ 3 ] = dir8_west;
}
else
{
dirtry[ 1 ] = dir8_west;
dirtry[ 3 ] = dir8_east;
}
if( deltay > 0 )
{
dirtry[ 2 ] = dir8_north;
dirtry[ 4 ] = dir8_south;
}
else
{
dirtry[ 2 ] = dir8_south;
dirtry[ 4 ] = dir8_north;
}
// randomize a bit for dodging
if( ABS( deltax ) > ABS( deltay ) )
{
tdir = dirtry[1]; dirtry[1]=dirtry[2]; dirtry[2]=tdir; // => swap dirtry[1] & dirtry[2]
tdir = dirtry[3]; dirtry[3]=dirtry[4]; dirtry[4]=tdir; // => swap dirtry[3] & dirtry[4]
}
if( US_RndT() < 128 )
{
tdir=dirtry[1]; dirtry[1]=dirtry[2]; dirtry[2]=tdir;
tdir=dirtry[3]; dirtry[3]=dirtry[4]; dirtry[4]=tdir;
}
dirtry[ 0 ] = diagonal[ dirtry[ 1 ] ][ dirtry[ 2 ] ];
// try the directions util one works
for( i = 0 ; i < 5 ; ++i )
{
if( dirtry[ i ] == dir8_nodir || dirtry[ i ] == turnaround )
{
continue;
}
if( AI_ChangeDir( self, dirtry[ i ], r_world ) )
{
return;
}
}
// turn around only as a last resort
if( turnaround != dir8_nodir )
{
if( AI_ChangeDir( self, turnaround, r_world ) )
{
return;
}
}
self->dir = dir8_nodir;
}
/*
-----------------------------------------------------------------------------
Function: AI_Chase() -As AI_Dodge, but doesn't try to dodge.
Parameters:
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void AI_Chase( entity_t *self )
{
int deltax, deltay;
dir8type d[2];
dir8type tdir, olddir, turnaround;
olddir = self->dir;
turnaround = opposite8[ olddir ];
d[ 0 ] = d[ 1 ] = dir8_nodir;
deltax = POS2TILE( Player.position.origin[ 0 ] ) - POS2TILE( self->x );
deltay = POS2TILE( Player.position.origin[ 1 ] ) - POS2TILE( self->y );
if( deltax > 0 )
{
d[ 0 ] = dir8_east;
}
else if( deltax < 0 )
{
d[ 0 ] = dir8_west;
}
if( deltay > 0 )
{
d[ 1 ] = dir8_north;
}
else if( deltay < 0 )
{
d[ 1 ] = dir8_south;
}
if( ABS( deltay ) > ABS( deltax ) )
{
tdir = d[ 0 ];
d[ 0 ] = d[ 1 ];
d[ 1 ] = tdir;
} // swap d[0] & d[1]
if( d[ 0 ] == turnaround )
{
d[ 0 ] = dir8_nodir;
}
if( d[ 1 ] == turnaround )
{
d[ 1 ] = dir8_nodir;
}
if( d[ 0 ] != dir8_nodir )
{
if( AI_ChangeDir( self, d[ 0 ], r_world ) )
{
return;
}
}
if( d[ 1 ] != dir8_nodir )
{
if( AI_ChangeDir( self, d[ 1 ], r_world ) )
{
return;
}
}
// there is no direct path to the player, so pick another direction
if( olddir != dir8_nodir )
{
if( AI_ChangeDir( self, olddir, r_world ) )
{
return;
}
}
if(US_RndT()>128) // randomly determine direction of search
{
for( tdir = dir8_east; tdir <= dir8_south; tdir += 2 ) // * Revision
{
if( tdir != turnaround )
{
if( AI_ChangeDir(self, tdir, r_world) )
{
return;
}
}
}
}
else
{
for( tdir = dir8_south; (int)tdir >= dir8_east; tdir -= 2 ) // * Revision (JDC fix for unsigned enums)
{
if( tdir != turnaround )
{
if( AI_ChangeDir( self, tdir, r_world ) )
{
return;
}
}
}
}
if( turnaround != dir8_nodir )
{
if( AI_ChangeDir( self, turnaround, r_world ) )
{
return;
}
}
self->dir = dir8_nodir; // can't move
}
/*
-----------------------------------------------------------------------------
Function: AI_Retreat() -Run Away from player.
Parameters:
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void AI_Retreat( entity_t *self )
{
int deltax, deltay;
dir8type d[2], tdir;
deltax = POS2TILE( Player.position.origin[ 0 ] ) - POS2TILE( self->x );
deltay = POS2TILE( Player.position.origin[ 1 ] ) - POS2TILE( self->y );
d[ 0 ] = deltax < 0 ? dir8_east : dir8_west;
d[ 1 ] = deltay < 0 ? dir8_north : dir8_south;
if( ABS( deltay ) > ABS( deltax ) )
{
tdir = d[ 0 ];
d[ 0 ] = d[ 1 ];
d[ 1 ] = tdir;
} // swap d[0] & d[1]
if( AI_ChangeDir( self, d[ 0 ], r_world) )
{
return;
}
if( AI_ChangeDir( self, d[ 1 ], r_world) )
{
return;
}
// there is no direct path to the player, so pick another direction
if( US_RndT() > 128 ) // randomly determine direction of search
{
for(tdir = dir8_east; tdir <= dir8_south; tdir += 2 ) // * Revision
{
if( AI_ChangeDir(self, tdir, r_world) )
{
return;
}
}
}
else
{
for( tdir = dir8_south; (int)tdir >= dir8_east; tdir -= 2 ) // * Revision (JDC fix for unsigned enums)
{
if( AI_ChangeDir(self, tdir, r_world) )
{
return;
}
}
}
self->dir=dir8_nodir; // can't move
}
/*
-----------------------------------------------------------------------------
Function: AI_CheckSight() -Checks a straight line between player and
current object.
Parameters: buf -[out] Storage location for data.
offset -[in] Number of bytes from beginning of file.
length -[in] Maximum number of items to be read.
Returns: true if the player has been spoted, otherwise false.
Notes:
If the sight is ok, check alertness and angle to see if they notice.
-----------------------------------------------------------------------------
*/
PRIVATE _boolean AI_CheckSight( entity_t *self )
{
#define MINSIGHT 0x18000
int deltax, deltay;
// don't bother tracing a line if the area isn't connected to the player's
if( ! (self->flags & FL_AMBUSH) )
{
if( ! areabyplayer[ self->areanumber ] )
{
return false;
}
}
// if the player is real close, sight is automatic
deltax = Player.position.origin[ 0 ] - self->x;
deltay = Player.position.origin[ 1 ] - self->y;
if( ABS( deltax ) < MINSIGHT && ABS( deltay ) < MINSIGHT )
{
return true;
}
// see if they are looking in the right direction
switch( self->dir )
{
case dir8_north:
if( deltay < 0 )
return false;
break;
case dir8_east:
if( deltax < 0 )
return false;
break;
case dir8_south:
if( deltay > 0 )
return false;
break;
case dir8_west:
if( deltax > 0 )
return false;
break;
}
// trace a line to check for blocking tiles (corners)
return Level_CheckLine( self->x, self->y, Player.position.origin[0], Player.position.origin[1], r_world );
}
/*
-----------------------------------------------------------------------------
Function: AI_FindTarget() -Called by entities that ARE NOT chasing the player.
Parameters:
Returns:
If the player is detected (by sight, noise, or proximity), the entity
is put into its combat frame and true is returned.
Notes:
Incorporates a random reaction delay.
-----------------------------------------------------------------------------
*/
PRIVATE _boolean AI_FindTarget( entity_t *self )
{
if( self->temp2 ) // count down reaction time
{
self->temp2 -= tics;
if( self->temp2 > 0 )
{
return false;
}
self->temp2 = 0; // time to react
}
else
{
// check if we can/want to see/hear player
if( Player.flags & FL_NOTARGET )
{
return false; // notarget cheat
}
assert( self->areanumber >= 0 && self->areanumber < NUMAREAS );
if( ! (self->flags & FL_AMBUSH) && ! areabyplayer[ self->areanumber ] )
{
return false;
}
if( ! AI_CheckSight( self ) ) // Player is visible - normal behavior
{
if( self->flags & FL_AMBUSH || ! Player.madenoise )
{
return false;
}
}
self->flags &= ~FL_AMBUSH;
// if we are here we see/hear player!!!
switch( self->type )
{
case en_guard:
self->temp2 = 1 + US_RndT() / 4;
break;
case en_officer:
self->temp2 = 2;
break;
case en_mutant:
self->temp2 = 1 + US_RndT() / 6;
break;
case en_ss:
self->temp2 = 1 + US_RndT() / 6;
break;
case en_dog:
self->temp2 = 1 + US_RndT() / 8;
break;
case en_boss:
case en_schabbs:
case en_fake:
case en_mecha:
case en_hitler:
case en_gretel:
case en_gift:
case en_fat:
case en_spectre:
case en_angel:
case en_trans:
case en_uber:
case en_will:
case en_death:
self->temp2 = 1;
break;
}
return false; // we are amazed & waiting to understand what to do!
}
A_FirstSighting( self );
return true;
}
/*
-----------------------------------------------------------------------------
Function: T_Move() -Moves object for distance in global units,
in ob->dir direction.
Parameters:
Returns:
If the player is detected (by sight, noise, or proximity), the entity
is put into its combat frame and true is returned.
Notes:
ob->x = adjusted for new position
ob->y
Actors are not allowed to move inside the player.
Does NOT check to see if the move is tile map valid.
-----------------------------------------------------------------------------
*/
PRIVATE void T_Move( entity_t *self, long dist )
{
if( self->dir == dir8_nodir || ! dist )
{
return;
}
self->x += dist * dx8dir[ self->dir ];
self->y += dist * dy8dir[ self->dir ];
// check to make sure it's not on top of player
if( ABS( self->x - Player.position.origin[ 0 ] ) <= MINACTORDIST )
if( ABS( self->y - Player.position.origin[ 1 ] ) <= MINACTORDIST )
{
if(self->type==en_blinky||
self->type==en_clyde ||
self->type==en_pinky ||
self->type==en_inky ||
self->type==en_spectre) PL_Damage(&Player, self, 2); // ghosts hurt player!
//
// back up
//
self->x -= dist * dx8dir[ self->dir ];
self->y -= dist * dy8dir[ self->dir ];
return;
}
self->distance -= dist;
if( self->distance < 0 )
{
self->distance = 0;
}
}
/*
-----------------------------------------------------------------------------
Function: T_Advance() -Move object forward.
Parameters:
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void T_Advance( entity_t *self, think_t think )
{
long move;
if( ! think )
{
Com_DPrintf( "Warning: Advance without <think> proc\n" );
return;
}
move = self->speed * tics;
while( move > 0)
{
// waiting for a door to open
if( self->waitfordoorx )
{
doors_t *door = &r_world->Doors.DoorMap[ self->waitfordoorx ][ self->waitfordoory ];
Door_OpenDoor( door );
if( door->action != dr_open )
{
return; // not opened yet...
}
self->waitfordoorx = self->waitfordoory = 0; // go ahead, the door is now open
}
if( move < self->distance )
{
T_Move( self, move );
break;
}
// fix position to account for round off during moving
self->x = TILE2POS( self->tilex );
self->y = TILE2POS( self->tiley );
move -= self->distance;
// think: Where to go now?
think( self );
self->angle = dir8angle[ self->dir ];
if( self->dir == dir8_nodir )
{
return; // all movement is blocked
}
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Stand( entity_t *self )
{
AI_FindTarget( self );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Path( entity_t *self )
{
if( AI_FindTarget( self ) )
{
return;
}
if( ! self->speed )
{
return; // if patroling with a speed of 0
}
if( self->dir == dir8_nodir )
{
AI_Path( self );
if( self->dir == dir8_nodir )
{
return; // all movement is blocked
}
}
T_Advance(self, AI_Path);
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Ghosts( entity_t *self )
{
if( self->dir == dir8_nodir )
{
AI_Chase( self );
if( self->dir == dir8_nodir )
{
return; // object is blocked in
}
self->angle = dir8angle[ self->dir ];
}
T_Advance( self, AI_Chase );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Chase( entity_t *self )
{
int dx,dy,dist,chance;
char dodge;
// if (gamestate.victoryflag) return;
dodge = 0;
if( Level_CheckLine( self->x, self->y, Player.position.origin[0], Player.position.origin[1], r_world ) ) // got a shot at player?
{
dx = ABS( POS2TILE( self->x ) - POS2TILE( Player.position.origin[ 0 ] ) );
dy = ABS( POS2TILE( self->y ) - POS2TILE( Player.position.origin[ 1 ] ) );
dist = max_of_2(dx, dy);
if( ! dist || (dist == 1 && self->distance < 16) )
{
chance = 300;
}
else
{
chance = (tics << 4) / dist;//100/dist;
}
if( US_RndT() < chance )
{ // go into attack frame
A_StateChange(self, st_shoot1);
return;
}
dodge = 1;
}
if( self->dir == dir8_nodir )
{
if( dodge )
{
AI_Dodge( self );
}
else
{
AI_Chase( self );
}
if( self->dir == dir8_nodir )
{
return; // object is blocked in
}
self->angle = dir8angle[ self->dir ];
}
T_Advance( self, dodge ? AI_Dodge : AI_Chase );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Bite( entity_t *self )
{
long dx, dy;
Sound_StartSound( NULL, 1, CHAN_VOICE, Sound_RegisterSound( "lsfx/076.wav" ), 1, ATTN_NORM, 0 );
dx = ABS( Player.position.origin[ 0 ] - self->x ) - TILEGLOBAL;
if( dx <= MINACTORDIST )
{
dy = ABS( Player.position.origin[ 1 ] - self->y ) - TILEGLOBAL;
if( dy <= MINACTORDIST )
{
if(US_RndT()<180)
{
PL_Damage(&Player, self, US_RndT()>>4);
return;
}
}
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_DogChase( entity_t *self )
{
long dx, dy;
if( self->dir == dir8_nodir )
{
AI_Dodge( self );
self->angle = dir8angle[ self->dir ];
if( self->dir == dir8_nodir )
{
return; // object is blocked in
}
}
//
// check for bite range
//
dx = ABS( Player.position.origin[ 0 ] - self->x ) - TILEGLOBAL / 2;
if(dx <= MINACTORDIST)
{
dy = ABS( Player.position.origin[ 1 ] - self->y ) - TILEGLOBAL / 2;
if( dy <= MINACTORDIST )
{
A_StateChange( self, st_shoot1 );
return; // bite player!
}
}
T_Advance( self, AI_Dodge );
}
/*
-----------------------------------------------------------------------------
Function: T_BossChase
Parameters: Nothing.
Returns: Nothing.
Notes:
They retreat if too close to player.
-----------------------------------------------------------------------------
*/
PUBLIC void T_BossChase( entity_t *self )
{
int dx, dy, dist;
W8 dodge;
dodge = 0;
dx = ABS( self->tilex - POS2TILE( Player.position.origin[ 0 ] ) );
dy = ABS( self->tiley - POS2TILE( Player.position.origin[ 1 ] ) );
dist = max_of_2( dx, dy );
if( Level_CheckLine( self->x, self->y, Player.position.origin[0], Player.position.origin[1], r_world ) ) // got a shot at player?
{
if( US_RndT() < tics << 3 )
{ // go into attack frame
A_StateChange( self, st_shoot1 );
return;
}
dodge = 1;
}
if( self->dir == dir8_nodir )
{
if(dodge)
{
AI_Dodge(self);
}
else
{
AI_Chase(self);
}
if( self->dir == dir8_nodir )
{
return; // object is blocked in
}
}
T_Advance( self, dist < 4 ? AI_Retreat : (dodge ? AI_Dodge : AI_Chase));
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Fake( entity_t *self )
{
if( Level_CheckLine( self->x, self->y, Player.position.origin[0], Player.position.origin[1], r_world ) ) // got a shot at player?
{
if( US_RndT() < tics << 1 )
{ // go into attack frame
A_StateChange( self, st_shoot1 );
return;
}
}
if( self->dir == dir8_nodir )
{
AI_Dodge( self );
if( self->dir == dir8_nodir )
{
return; // object is blocked in
}
}
T_Advance( self, AI_Dodge );
}
/*
-----------------------------------------------------------------------------
Function: T_Shoot -Try to damage the player.
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Shoot( entity_t *self )
{
int dx, dy, dist;
int hitchance, damage;
if( ! areabyplayer[ self->areanumber ] )
{
return;
}
if( ! Level_CheckLine( self->x, self->y, Player.position.origin[0], Player.position.origin[1], r_world ) )
{
return; // player is behind a wall
}
dx = ABS( POS2TILE( self->x ) - POS2TILE( Player.position.origin[ 0 ] ) );
dy = ABS( POS2TILE( self->y ) - POS2TILE( Player.position.origin[ 1 ] ) );
dist = max_of_2( dx, dy );
if( self->type == en_ss || self->type == en_boss )
{
dist = dist * 2 / 3; // ss are better shots
}
if( Player.speed >= RUNSPEED )
{
hitchance = 160;
}
else
{
hitchance = 256;
}
// if guard is visible by player
// player can see to dodge
// (if CheckLine both player & enemy see each other)
// So left only check if guard is in player's fov: FIXME: not fixed fov!
if( angle_diff( TransformPoint( self->x, self->y, Player.position.origin[0], Player.position.origin[1] ), FINE2DEG( Player.position.angle ) ) < (M_PI/3) )
{
hitchance -= dist * 16;
}
else
{
hitchance -= dist * 8;
}
// see if the shot was a hit
if( US_RndT() < hitchance )
{
if( dist < 2 )
{
damage = US_RndT() >> 2;
}
else if( dist < 4 )
{
damage = US_RndT() >> 3;
}
else
{
damage = US_RndT() >> 4;
}
PL_Damage( &Player, self, damage );
}
switch( self->type )
{
case en_ss:
if( g_version->value == SPEAROFDESTINY )
{
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "sfx/020.wav" ), 1, ATTN_NORM, 0 );
}
else
{
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "sfx/024.wav" ), 1, ATTN_NORM, 0 );
}
break;
case en_gift:
case en_fat:
case en_mecha:
case en_hitler:
case en_boss:
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "sfx/022.wav" ), 1, ATTN_NORM, 0 );
break;
default:
if( g_version->value == SPEAROFDESTINY )
{
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "sfx/038.wav" ), 1, ATTN_NORM, 0 );
}
else
{
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "sfx/049.wav" ), 1, ATTN_NORM, 0 );
}
break;
}
}
/*
-----------------------------------------------------------------------------
Function: T_UShoot -[UberMutant]
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_UShoot( entity_t *self )
{
int dx, dy, dist;
T_Shoot( self );
dx = ABS( self->tilex - POS2TILE( Player.position.origin[ 0 ] ) );
dy = ABS( self->tiley - POS2TILE( Player.position.origin[ 1 ] ) );
dist = max_of_2( dx, dy );
if( dist <= 1 )
{
PL_Damage( &Player, self, 10 );
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void T_Launch( entity_t *self )
{
entity_t *proj;
float iangle;
iangle = TransformPoint( self->x, self->y, Player.position.origin[ 0 ], Player.position.origin[ 1 ] ) + M_PI;
if( iangle > 2 * M_PI )
{
iangle -= 2 * M_PI;
}
if( self->type == en_death )
{// death knight launches 2 rockets with 4 degree shift each.
T_Shoot( self );
if( self->state == st_shoot2 )
{
iangle = normalize_angle( iangle - DEG2RAD( 4 ) );
}
else
{
iangle = normalize_angle( iangle + DEG2RAD( 4 ) );
}
}
proj = GetNewActor();
if( proj == NULL )
{
return;
}
proj->x = self->x;
proj->y = self->y;
proj->tilex = self->tilex;
proj->tiley = self->tiley;
proj->state = st_stand;
proj->ticcount = 1;
proj->dir = dir8_nodir;
proj->angle = RAD2FINE( iangle );
proj->speed = 0x2000;
proj->flags = FL_NONMARK; // FL_NEVERMARK;
proj->sprite = Sprite_GetNewSprite();
switch( self->type )
{
case en_death:
proj->type = en_hrocket;
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "lsfx/078.wav" ), 1, ATTN_NORM, 0 );
break;
case en_angel:
proj->type = en_spark;
proj->state = st_path1;
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "lsfx/069.wav" ), 1, ATTN_NORM, 0 );
break;
case en_fake:
proj->type = en_fire;
proj->state = st_path1;
proj->flags = FL_NEVERMARK;
proj->speed = 0x1200;
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "lsfx/069.wav" ), 1, ATTN_NORM, 0 );
break;
case en_schabbs:
proj->type = en_needle;
proj->state = st_path1;
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "lsfx/008.wav" ), 1, ATTN_NORM, 0 );
break;
default:
proj->type = en_rocket;
if( g_version->value == SPEAROFDESTINY )
{
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "lsfx/008.wav" ), 1, ATTN_NORM, 0 );
}
else
{
Sound_StartSound( NULL, 1, CHAN_WEAPON, Sound_RegisterSound( "lsfx/085.wav" ), 1, ATTN_NORM, 0 );
}
}
}