/* 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 * 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 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 ); //gsh this was the original code Sound_StartSound( NULL, 1, CHAN_VOICE, Sound_RegisterSound( "sfx/002.wav" ), 1, ATTN_NORM, 0 ); //gsh changed to this... the original code wasn't the correct sound file 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 && currentMap.episode >= 6 && currentMap.episode < 9 )//added the episode check... gsh) { 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 && currentMap.episode >= 6 && currentMap.episode < 9)//added the episode check... gsh) { 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 && currentMap.episode >= 6 && currentMap.episode < 9)//added the episode check... gsh) { 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 ); } } }