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

1024 lines
21 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_player.c: Wolfenstein3-D player management.
*
* Author: Michael Liebscher <johnnycanuck@users.sourceforge.net>
* Date: 2004
*
* Acknowledgement:
* Portion of this code was derived from NewWolf, and was originally
* written by DarkOne the Hacker.
*
*/
#include "../wolfiphone.h"
player_t Player; // player struct (pos, health etc...)
#define PLAYERSIZE MINDIST // player radius
struct atkinf
{
char tics, attack, frame; // attack is 1 for gun, 2 for knife
} attackinfo[ 4 ][ 14 ] = // 4 guns, 14 frames max for every gun!
{
{ {6,0,1},{6,2,2},{6,0,3},{6,-1,4} },
{ {6,0,1},{6,1,2},{6,0,3},{6,-1,4} },
{ {6,0,1},{6,1,2},{6,3,3},{6,-1,4} },
{ {6,0,1},{6,1,2},{6,4,3},{6,-1,4} },
};
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns: true if player can change weapons, otherwise false.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE _boolean PL_ChangeWeapon( player_t *self, int weapon )
{
unsigned itemflag;
itemflag = ITEM_WEAPON_1 << weapon;
if( self->ammo[ AMMO_BULLETS ] == 0 && weapon != WEAPON_KNIFE )
{
Com_Printf("Not enough ammo.\n");
return false;
}
if( ! (self->items & itemflag) )
{
Com_Printf( "No weapon.\n" );
return false;
}
self->weapon =
self->pendingweapon = weapon;
self->attackframe =
self->attackcount =
self->weaponframe = 0;
return true;
}
/*
-----------------------------------------------------------------------------
Function: Called if player pressed USE button
Parameters:
Returns: returns true if player used something
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE _boolean PL_Use( player_t *self, LevelData_t *lvl )
{
int x, y, dir;
dir = Get4dir( FINE2RAD( self->position.angle ) );
x = self->tilex + dx4dir[ dir ];
y = self->tiley + dy4dir[ dir ];
if( lvl->tilemap[ x ][ y ] & DOOR_TILE )
{
return Door_TryUse( &lvl->Doors.DoorMap[ x ][ y ], Player.items );
}
if( lvl->tilemap[ x ][ y ] & SECRET_TILE )
{
return PushWall_Push( x, y, dir );
}
if( lvl->tilemap[ x ][ y ] & ELEVATOR_TILE )
{
int newtex;
switch( dir )
{
case dir4_east:
case dir4_west:
newtex = lvl->wall_tex_x[ x ][ y ] += 2;
break;
case dir4_north:
case dir4_south:
return false; // don't allow to press elevator rails
}
if( lvl->tilemap[ self->tilex ][ self->tiley ] & SECRETLEVEL_TILE )
{
self->playstate = ex_secretlevel;
}
else
{
self->playstate = ex_complete;
}
Sound_StartSound( NULL, 0, CHAN_BODY, Sound_RegisterSound( "lsfx/040.wav" ), 1, ATTN_NORM, 0 );
iphoneStartIntermission( 0 );
return true;
}
//Sound_StartSound( NULL, 0, CHAN_BODY, Sound_RegisterSound( "lsfx/020.wav" ), 1, ATTN_NORM, 0 );
return false;
}
#define STOPSPEED 0x0D00
#define FRICTION 0.25f
#define MAXMOVE (MINDIST*2-1)
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns: returns true if move ok
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE _boolean PL_TryMove( player_t *self, LevelData_t *lvl )
{
int xl, yl, xh, yh, x, y;
int d, n;
xl = POS2TILE( Player.position.origin[ 0 ] - PLAYERSIZE );
yl = POS2TILE( Player.position.origin[ 1 ] - PLAYERSIZE );
xh = POS2TILE( Player.position.origin[ 0 ] + PLAYERSIZE );
yh = POS2TILE( Player.position.origin[ 1 ] + PLAYERSIZE );
// Cheching for solid walls:
for( y = yl ; y <= yh ; ++y )
for( x = xl ; x <= xh ; ++x )
{
if( lvl->tilemap[ x ][ y ] & SOLID_TILE )
return 0;
if( lvl->tilemap[ x ][ y ] & DOOR_TILE &&
Door_Opened( &lvl->Doors, x, y) != DOOR_FULLOPEN ) {
// iphone hack to allow player to move halfway into door tiles
// if the player bounds doesn't cross the middle of the tile, let the move continue
if ( abs( Player.position.origin[0] - TILE2POS( x ) ) <= 0x9000
&& abs( Player.position.origin[1] - TILE2POS( y ) ) <= 0x9000 ) {
return 0;
}
}
}
// check for actors
for( n = 0 ; n < NumGuards ; ++n )
{
if( Guards[ n ].state >= st_die1 )
continue;
d = self->position.origin[ 0 ] - Guards[ n ].x;
if( d < -MINACTORDIST || d > MINACTORDIST )
continue;
d = self->position.origin[ 1 ] - Guards[ n ].y;
if( d < -MINACTORDIST || d > MINACTORDIST)
continue;
return false;
}
return true;
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void PL_ClipMove( player_t *self, int xmove, int ymove )
{
int basex, basey;
basex = self->position.origin[ 0 ];
basey = self->position.origin[ 1 ];
self->position.origin[ 0 ] += xmove;
self->position.origin[ 1 ] += ymove;
if( PL_TryMove( self, r_world ) )
{
return; // we moved as we wanted
}
//Sound_StartSound( NULL, 0, CHAN_BODY, Sound_RegisterSound( "lsfx/000.wav" ), 1, ATTN_NORM, 0 );
if( xmove ) // don't bother if we don't move x!
{
self->position.origin[ 0 ] = basex + xmove;
self->position.origin[ 1 ] = basey;
if( PL_TryMove( self, r_world ) )
{
return; // May be we'll move only X direction?
}
}
if( ymove ) // don't bother if we don't move y!
{
self->position.origin[ 0 ] = basex;
self->position.origin[ 1 ] = basey + ymove;
if( PL_TryMove( self, r_world ) )
{
return; // May be we'll move only Y direction?
}
}
// movement blocked; we must stay on one place... :(
self->position.origin[ 0 ] = basex;
self->position.origin[ 1 ] = basey;
}
/*
-----------------------------------------------------------------------------
Function: Changes player's angle and position
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void PL_ControlMovement( player_t *self, LevelData_t *lvl )
{
int angle, speed;
// rotation
angle = self->position.angle;
// if(cmd->forwardmove || cmd->sidemove)
self->movx = self->movy = 0; // clear accumulated movement
if( Player.cmd.forwardmove )
{
speed = tics * Player.cmd.forwardmove;
self->movx+=(int)(speed * CosTable[ angle ] );
self->movy+=(int)(speed * SinTable[ angle ] );
}
if( Player.cmd.sidemove )
{
speed = tics * Player.cmd.sidemove;
self->movx += (int)( speed * SinTable[ angle ] );
self->movy -= (int)( speed * CosTable[ angle ] );
}
if( ! self->movx && ! self->movy )
return;
#ifdef SPEAR
funnyticount = 0; // ZERO FUNNY COUNTER IF MOVED! // FIXME!
#endif
self->speed = self->movx + self->movy;
// bound movement
if( self->movx > MAXMOVE )
self->movx = MAXMOVE;
else if( self->movx < -MAXMOVE )
self->movx = -MAXMOVE;
if( self->movy > MAXMOVE )
self->movy = MAXMOVE;
else if( self->movy < -MAXMOVE )
self->movy = -MAXMOVE;
// move player and clip movement to walls (check for no-clip mode here)
PL_ClipMove( self, self->movx, self->movy );
self->tilex = POS2TILE( self->position.origin[ 0 ] );
self->tiley = POS2TILE( self->position.origin[ 1 ] );
// pick up items easier -- any tile you touch, instead of
// just the midpoint tile
{
int x, y;
for ( x = -1 ; x <= 1 ; x+= 2 ) {
int tilex = POS2TILE( self->position.origin[0] + x * PLAYERSIZE );
for ( y = -1 ; y <= 1 ; y+= 2 ) {
int tiley = POS2TILE( self->position.origin[1] + y * PLAYERSIZE );
Powerup_PickUp( tilex, tiley );
}
}
}
// Powerup_PickUp( self->tilex, self->tiley );
// Checking for area change, ambush tiles and doors will have negative values
if( lvl->areas[ self->tilex ][ self->tiley ] >= 0 &&
lvl->areas[ self->tilex ][ self->tiley ] != Player.areanumber )
{
Player.areanumber = lvl->areas[ self->tilex ][ self->tiley ];
assert( Player.areanumber >= 0 && Player.areanumber < NUMAREAS );
Areas_ConnectAreas( Player.areanumber );
}
if( lvl->tilemap[ self->tilex ][ self->tiley ] & EXIT_TILE )
{
iphoneStartIntermission( 0 );
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void PL_PlayerAttack( player_t *self, _boolean re_attack )
{
struct atkinf *cur;
self->attackcount -= tics;
while( self->attackcount <= 0 )
{
cur = &attackinfo[ self->weapon ][ self->attackframe ];
switch( cur->attack )
{
case -1:
self->flags &= ~PL_FLAG_ATTCK;
if( ! self->ammo[ AMMO_BULLETS ] )
{
self->weapon = WEAPON_KNIFE;
}
else if( self->weapon != self->pendingweapon )
{
self->weapon = self->pendingweapon;
}
self->attackframe = self->weaponframe = 0;
return;
case 4:
if( ! self->ammo[ AMMO_BULLETS ] )
{
break;
}
if( re_attack )
{
self->attackframe -= 2;
}
case 1:
if( ! self->ammo[ AMMO_BULLETS ] ) // can only happen with chain gun
{
self->attackframe++;
break;
}
fire_lead( self );
self->ammo[ AMMO_BULLETS ]--;
break;
case 2:
fire_hit( self );
break;
case 3:
if(self->ammo[AMMO_BULLETS] && re_attack)
self->attackframe-=2;
break;
}
self->attackcount += cur->tics;
self->attackframe++;
self->weaponframe = attackinfo[ self->weapon ][ self->attackframe ].frame;
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_Process( player_t *self, LevelData_t *lvl )
{
int n;
self->madenoise = false;
PL_ControlMovement( self, lvl );
if( self->flags & PL_FLAG_ATTCK )
{
PL_PlayerAttack( self, Player.cmd.buttons & BUTTON_ATTACK );
}
else
{
if( Player.cmd.buttons & BUTTON_USE )
{
if(!(self->flags & PL_FLAG_REUSE) && PL_Use( self, lvl ) )
{
self->flags|=PL_FLAG_REUSE;
}
}
else
{
self->flags &= ~PL_FLAG_REUSE;
}
if( Player.cmd.buttons & BUTTON_ATTACK )
{
self->flags |= PL_FLAG_ATTCK;
self->attackframe = 0;
self->attackcount = attackinfo[ self->weapon ][ 0 ].tics;
self->weaponframe = attackinfo[ self->weapon ][ 0 ].frame;
}
}
// process impulses
switch( Player.cmd.impulse )
{
case 0:
break; // no impulse
case 1:
case 2:
case 3:
case 4:
PL_ChangeWeapon( self, Player.cmd.impulse - 1 );
break;
case 10: // next weapon /like in Quake/ FIXME: weapprev, weapnext
self->pendingweapon=self->weapon;
for( n = 0 ; n < 4; ++n )
{
if( ++self->weapon > WEAPON_CHAIN )
{
self->weapon = WEAPON_KNIFE;
}
if( PL_ChangeWeapon( self, self->weapon ) )
{
break;
}
}
self->weapon = self->pendingweapon;
break;
default:
Com_Printf( "Unknown Impulse: %d\n", Player.cmd.impulse );
break;
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_Reset(void)
{
memset( &Player, 0, sizeof( Player ) );
Player.playstate = ex_notingame;
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_Spawn( placeonplane_t location, LevelData_t *lvl )
{
Player.position = location;
Player.tilex = POS2TILE( location.origin[ 0 ] );
Player.tiley = POS2TILE( location.origin[ 1 ] );
Player.areanumber = lvl->areas[ Player.tilex ][ Player.tiley ];
assert( Player.areanumber >= 0 && Player.areanumber < NUMAREAS );
if( Player.areanumber < 0 )
{
Player.areanumber = 36;
}
Areas_ConnectAreas( Player.areanumber );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void Cmd_Give_f( void )
{
PL_GiveHealth( &Player, 999, 0 );
PL_GiveAmmo( &Player, AMMO_BULLETS, 99 );
PL_GiveWeapon( &Player, WEAPON_AUTO );
PL_GiveWeapon( &Player, WEAPON_CHAIN );
PL_GiveKey( &Player, KEY_GOLD );
PL_GiveKey( &Player, KEY_SILVER );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void Cmd_God_f( void )
{
Player.flags ^= FL_GODMODE;
Com_Printf( "God mode %s\n", Player.flags & FL_GODMODE ? "ON":"OFF" );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PRIVATE void PL_notarget_f( void )
{
Player.flags ^= FL_NOTARGET;
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_Init(void)
{
PL_Reset();
PL_NewGame( &Player );
Cmd_AddCommand( "god", Cmd_God_f );
Cmd_AddCommand( "notarget", PL_notarget_f );
Cmd_AddCommand( "give", Cmd_Give_f );
}
// ------------------------- * environment interraction * -------------------------
#define EXTRAPOINTS 40000 // points for an extra life
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
#ifdef IPHONE
void vibrateDevice();
#else
void vibrateDevice() {}
#endif
PUBLIC void PL_Damage( player_t *self, entity_t *attacker, int points )
{
if( self->playstate == ex_dead )
{
return;
}
self->LastAttacker = attacker;
if( skill->value == gd_baby )
{
points >>= 2;
}
// vibe the phone
vibrateDevice();
// note the direction of the last hit for the directional blends
{
int dx = attacker->x - self->position.origin[0];
int dy = attacker->y - self->position.origin[1];
// probably won't ever have damage from self, but check anyway
if ( dx != 0 || dy != 0 ) {
float angle = atan2f( dy, dx );
float playerAngle = self->position.angle * 360.0f / (float)ANG_360;
float deltaAngle;
angle = angle * 180.0f / M_PI;
if ( angle < 0 ) {
angle = 360 + angle;
}
deltaAngle = angle - playerAngle;
if ( deltaAngle > 180 ) {
deltaAngle = deltaAngle - 360;
}
if ( deltaAngle < -180 ) {
deltaAngle = 360 + deltaAngle;
}
// Com_Printf( "damage: player angle: %4.0f shotAngle: %4.0f deltaAngle:%4.0f\n", playerAngle, angle, deltaAngle );
if ( deltaAngle > 40 ) {
iphoneSetAttackDirection( 1 );
} else if ( deltaAngle < -40 ) {
iphoneSetAttackDirection( -1 );
}
}
}
// do everything else but subtract health in god mode, to ease
// testing of damage feedback
if( !(self->flags & FL_GODMODE) )
{
self->health -= points;
}
if( self->health <= 0 )
{
// dead
self->health = 0;
self->playstate = ex_dead;
Sound_StartSound( NULL, 0, CHAN_BODY, Sound_RegisterSound( "lsfx/009.wav" ), 1, ATTN_NORM, 0 );
}
// red screen flash
iphoneStartDamageFlash( points );
// stop the happy grin face if shot before it times out
Player.face_gotgun = false;
// make BJ's eyes bulge on huge hits
if( points > 30 && Player.health != 0 )
{
Player.face_ouch = true;
Player.facecount = 0;
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns: returns true if player needs this health.
Notes:
gives player some HP
max can be:
0 - natural player's health limit (100 or 150 with augment)
>0 - indicates the limit
-----------------------------------------------------------------------------
*/
PUBLIC _boolean PL_GiveHealth( player_t *self, int points, int max )
{
if( max == 0 )
{
max = (self->items & ITEM_AUGMENT) ? 150 : 100;
}
if( self->health >= max )
{
return false; // doesn't need this health
}
self->health += points;
if( self->health > max )
{
self->health = max;
}
Player.face_gotgun = false;
return true; // took it
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns: returns true if player needs this ammo
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC _boolean PL_GiveAmmo( player_t *self, int type, int ammo )
{
int max_ammo[ AMMO_TYPES ] = { 99 };
int max;
max = max_ammo[ type ];
if( self->items & ITEM_BACKPACK )
{
max *= 2;
}
if( self->ammo[ type ] >= max )
{
return false; // don't need
}
if( ! self->ammo[ type ] && ! self->attackframe ) // knife was out
{
self->weapon = self->pendingweapon;
}
self->ammo[ type ] += ammo;
if( self->ammo[ type ] > max )
{
self->ammo[ type ] = max;
}
return true;
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_GiveWeapon( player_t *self, int weapon )
{
unsigned itemflag;
PL_GiveAmmo( self, AMMO_BULLETS, 6 ); // give some ammo with a weapon
itemflag = ITEM_WEAPON_1 << weapon;
if( self->items & itemflag )
{
return; // player owns this weapon
}
else
{
self->items |= itemflag;
if ( self->weapon < weapon ) { // don't switch if already using better weapon
self->weapon = self->pendingweapon = weapon;
}
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_GiveLife( player_t *self )
{
if( self->lives < 9 )
{
self->lives++;
}
Sound_StartSound( NULL, 0, CHAN_ITEM, Sound_RegisterSound( "lsfx/044.wav" ), 1, ATTN_NORM, 0 );
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_GivePoints( player_t *self, W32 points )
{
self->score += points;
while( self->score >= self->next_extra )
{
self->next_extra += EXTRAPOINTS;
PL_GiveLife( self );
}
}
/*
-----------------------------------------------------------------------------
Function:
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_GiveKey( player_t *self, int key )
{
self->items |= ITEM_KEY_1 << key;
}
/*
-----------------------------------------------------------------------------
Function: Set up player for the new game
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_NewGame( player_t *self )
{
memset( self, 0, sizeof( player_t ) );
self->health = 100;
self->ammo[ AMMO_BULLETS ] = 16; // JDC: changed for iphone 8;
self->lives = 3;
self->weapon = self->pendingweapon = WEAPON_PISTOL;
self->items = ITEM_WEAPON_1 | ITEM_WEAPON_2;
self->next_extra = EXTRAPOINTS;
}
/*
-----------------------------------------------------------------------------
Function: Set up player for level transition
Parameters:
Returns:
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void PL_NextLevel( player_t *self )
{
self->old_score = self->score;
self->attackcount = self->attackframe = self->weaponframe = 0;
self->flags = 0;
self->items &= ~(ITEM_KEY_1 | ITEM_KEY_2 | ITEM_KEY_3 | ITEM_KEY_4);
}
/*
-----------------------------------------------------------------------------
Function:
Parameters: self -[in] Player to respawn in game world.
Returns: returns false if no lives left
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC _boolean PL_Reborn( player_t *self )
{
#if 0 // removed game over from iphone version
if( --self->lives < 1 )
{
return false;
}
#endif
self->health = 100;
self->ammo[ AMMO_BULLETS ] = 16; // JDC: changed for iphone 8;
self->score = self->old_score;
self->attackcount = 0;
self->attackframe = 0;
self->weaponframe = 0;
self->flags = 0;
self->weapon = self->pendingweapon = WEAPON_PISTOL;
self->items = ITEM_WEAPON_1 | ITEM_WEAPON_2;
self->playstate = ex_playing;
return true;
}