517 lines
19 KiB
C
517 lines
19 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 2009 Karl Kuglin
|
|
|
|
This file is part of the Open Arena source code.
|
|
Parts of this file utilize code originally written for Tremulous by
|
|
Tony J. White.
|
|
Use of that code is governed by GPL version 2 and any later versions.
|
|
|
|
Open Arena source code 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.
|
|
|
|
Open Arena source code 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 Open Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// NOTE: This code is by no means complete.
|
|
|
|
#include "g_local.h"
|
|
|
|
killspree_t *killSprees[ MAX_KSPREE ];
|
|
deathspree_t *deathSprees[ MAX_DSPREE ];
|
|
multikill_t *multiKills[ MAX_MULTIKILLS ];
|
|
|
|
/*
|
|
=================
|
|
G_ReadAltKillSettings
|
|
Since this is cvar dependent, it has to be placed in G_InitGame after cvars are registered.
|
|
=================
|
|
*/
|
|
qboolean G_ReadAltKillSettings( gentity_t *ent, int skiparg )
|
|
{
|
|
//Let's Initialize some Spree structs/objects
|
|
killspree_t *k = NULL;
|
|
deathspree_t *d = NULL;
|
|
multikill_t *m = NULL;
|
|
//spree counters
|
|
int ksc = 0, dsc = 0, mc = 0;
|
|
int spreeDivisor;
|
|
|
|
//Give us an int to use in "for" loops
|
|
int i = 0;
|
|
|
|
//File Stuff
|
|
fileHandle_t file;
|
|
int length;
|
|
qboolean kspree_read;
|
|
qboolean dspree_read;
|
|
qboolean mkill_read;
|
|
char *cnf, *cnf2;
|
|
char *t;
|
|
|
|
//Let's clear out any existing killing sprees/death sprees. YAYY BG_FREE!!!!!
|
|
for( i = 0; i < MAX_KSPREE && killSprees[ i ]; i++ ) {
|
|
BG_Free( killSprees[ i ] );
|
|
killSprees[ i ] = NULL;
|
|
}
|
|
|
|
for( i = 0; i < MAX_KSPREE && deathSprees[ i ]; i++ ) {
|
|
BG_Free( deathSprees[ i ] );
|
|
deathSprees[ i ] = NULL;
|
|
}
|
|
|
|
for( i = 0; i < MAX_MULTIKILLS && multiKills[ i ]; i++ ) {
|
|
BG_Free( multiKills[ i ] );
|
|
multiKills[ i ] = NULL;
|
|
}
|
|
|
|
// If the config file is not defined...forget reading/loading
|
|
if( !g_sprees.string[0] ) {
|
|
//Let's disable multikills to keep stock excellent sound
|
|
if( g_multiKill.integer == 1 )
|
|
{
|
|
trap_Cvar_Set( "g_multiKill", "0" );
|
|
}
|
|
return qfalse;
|
|
}
|
|
/*
|
|
only set spreeDivisor to cvar g_spreeDiv if g_spreeDiv is >= "2".
|
|
0 will cause problems and having 1 is just a fool who wants to hear something
|
|
on every kill.
|
|
*/
|
|
if( g_spreeDiv.integer >= 2 ) {
|
|
level.spreeDivisor = g_spreeDiv.integer;
|
|
spreeDivisor = level.spreeDivisor;
|
|
} else {
|
|
level.spreeDivisor = 5;
|
|
// We don't want to change the value, keep reminding the server operator.
|
|
//g_spreeDiv.integer = 5;
|
|
spreeDivisor = 5;
|
|
G_Printf( "Error: cvar g_spreeDiv must not be set to 0 or 1, reverting to default settings!\n" );
|
|
G_Printf( "Error: Set g_spreeDiv higher than 1 if 5 is not desired!\n" );
|
|
}
|
|
|
|
length = trap_FS_FOpenFile( g_sprees.string, &file, FS_READ );
|
|
|
|
//If the file can't be accessed/opened.
|
|
if( length < 0 ) {
|
|
G_Printf( "Could not open configuration file for Sprees and Multikills %s\n", g_sprees.string );
|
|
trap_Cvar_Set( "g_multiKill", "0" );
|
|
return qfalse;
|
|
}
|
|
//Allocate some memory.
|
|
cnf = BG_Alloc( length + 1 );
|
|
cnf2 = cnf;
|
|
|
|
//Load the whole file up.
|
|
trap_FS_Read( cnf, length, file );
|
|
*( cnf + length ) = '\0';
|
|
trap_FS_FCloseFile( file );
|
|
|
|
kspree_read = dspree_read = mkill_read = qfalse;
|
|
|
|
//Let's start parsing em.
|
|
COM_BeginParseSession( g_sprees.string );
|
|
while( 1 )
|
|
{
|
|
t = COM_Parse( &cnf );
|
|
if( !*t )
|
|
break;
|
|
if( !Q_stricmp( t, "[kspree]" ) ) {
|
|
|
|
if( ksc >= MAX_KSPREE )
|
|
return qfalse;
|
|
k = BG_Alloc( sizeof( killspree_t ) );
|
|
killSprees[ ksc++ ] = k;
|
|
kspree_read = qtrue;
|
|
dspree_read = qfalse;
|
|
mkill_read = qfalse;
|
|
|
|
} else if ( !Q_stricmp( t, "[dspree]" ) ) {
|
|
|
|
if( dsc >= MAX_DSPREE )
|
|
return qfalse;
|
|
d = BG_Alloc( sizeof( deathspree_t ) );
|
|
deathSprees[ dsc++ ] = d;
|
|
dspree_read = qtrue;
|
|
kspree_read = qfalse;
|
|
mkill_read = qfalse;
|
|
} else if ( !Q_stricmp( t, "[mkill]" ) ) {
|
|
|
|
if( mc >= MAX_MULTIKILLS )
|
|
return qfalse;
|
|
m = BG_Alloc( sizeof( multikill_t ) );
|
|
multiKills[ mc++ ] = m;
|
|
mkill_read = qtrue;
|
|
kspree_read = qfalse;
|
|
dspree_read = qfalse;
|
|
//Parse a killing spree
|
|
} else if ( kspree_read ) {
|
|
if( !Q_stricmp( t, "level" ) ) {
|
|
readFile_int( &cnf, &k->spreeLevel );
|
|
//Let's take the spreeLevel and multiply it by the spreeDivisor to give us our count
|
|
k->streakCount = ( ( k->spreeLevel ) * ( spreeDivisor ) );
|
|
} else if ( !Q_stricmp( t, "message" ) ) {
|
|
readFile_string( &cnf, k->spreeMsg, sizeof( k->spreeMsg ) );
|
|
} else if ( !Q_stricmp( t, "printpos" ) ) {
|
|
readFile_int( &cnf, &k->position );
|
|
} else if ( !Q_stricmp( t, "sound" ) ) {
|
|
readFile_string( &cnf, k->sound2Play, sizeof( k->sound2Play ) );
|
|
} else {
|
|
COM_ParseError( "Killing Spree unrecognized token \"%s\"", t );
|
|
}
|
|
} else if ( dspree_read ) {
|
|
if( !Q_stricmp( t, "level" ) ) {
|
|
readFile_int( &cnf, &d->spreeLevel );
|
|
//Let's take the spreeLevel and multiply it by the spreeDivisor to give us our count
|
|
d->streakCount = ( ( d->spreeLevel ) * ( spreeDivisor ) );
|
|
} else if ( !Q_stricmp( t, "message" ) ) {
|
|
readFile_string( &cnf, d->spreeMsg, sizeof( d->spreeMsg ) );
|
|
} else if ( !Q_stricmp( t, "printpos" ) ) {
|
|
readFile_int( &cnf, &d->position );
|
|
} else if ( !Q_stricmp( t, "sound" ) ) {
|
|
readFile_string( &cnf, d->sound2Play, sizeof( d->sound2Play ) );
|
|
} else {
|
|
COM_ParseError( "Death Spree unrecognized token \"%s\"", t );
|
|
}
|
|
} else if ( mkill_read ) {
|
|
if ( !Q_stricmp( t, "kills" ) ) {
|
|
readFile_int( &cnf, &m->kills );
|
|
} else if ( !Q_stricmp( t, "message" ) ) {
|
|
readFile_string( &cnf, m->killMsg, sizeof( m->killMsg ) );
|
|
} else if ( !Q_stricmp( t, "sound" ) ) {
|
|
readFile_string( &cnf, m->sound2Play, sizeof( m->sound2Play ) );
|
|
} else {
|
|
COM_ParseError( "Multikill unrecognized token \"%s\"", t );
|
|
}
|
|
} else {
|
|
COM_ParseError( "unexpected token \"%s\"", t );
|
|
}
|
|
}
|
|
//Let's "free" some memory now.
|
|
BG_Free( cnf2 );
|
|
G_Printf("Sprees/Kills: loaded %d killing sprees, %d death sprees, and %d multikills.\n", ksc, dsc, mc );
|
|
//Mark the Upper Bounds of the Arrays (Since they start numbering at 0, We subtract 1 )
|
|
level.kSpreeUBound = ( ksc - 1 );
|
|
level.dSpreeUBound = ( dsc - 1 );
|
|
if( mc > 0 ) {
|
|
level.mKillUBound = ( mc - 1 );
|
|
} else {
|
|
level.mKillUBound = -1;
|
|
//KK-OAX We don't have any kills defined, revert to stock.
|
|
//FIXME: Make sure this change shows up in the console...
|
|
if( g_multiKill.integer == 1 ) {
|
|
trap_Cvar_Set( "g_multiKill", "0" );
|
|
}
|
|
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static char *fillPlaceHolder( char *stringToSearch, char *placeHolder, char *replaceWith )
|
|
{
|
|
static char output[ MAX_SAY_TEXT ];
|
|
char *p;
|
|
|
|
if( !( p = strstr( stringToSearch, placeHolder ) ) )
|
|
return stringToSearch;
|
|
|
|
strncpy( output, stringToSearch, p - stringToSearch );
|
|
output[ p - stringToSearch ] = '\0';
|
|
|
|
Q_snprintf( output + ( p - stringToSearch ), output - stringToSearch, "%s%s", replaceWith, p + strlen( placeHolder ) );
|
|
|
|
return output;
|
|
}
|
|
|
|
//This concatenate's the message to broadcast to the clients.
|
|
static char *CreateMessage( gentity_t *ent, char *message, char *spreeNumber )
|
|
{
|
|
static char output[ MAX_SAY_TEXT ] = { "" };
|
|
|
|
char name[ MAX_NAME_LENGTH ];
|
|
//Do some sanity checks
|
|
if( !ent ) {
|
|
return output;
|
|
} else if ( !*message ) {
|
|
return output;
|
|
} else if ( !spreeNumber ) {
|
|
return output;
|
|
}
|
|
//Get the player name.
|
|
Q_strncpyz( name, ent->client->pers.netname, sizeof( name ) );
|
|
//Do Our Replacements
|
|
Q_strncpyz( output, fillPlaceHolder( message, "[n]", name ), sizeof( output ) );
|
|
Q_strncpyz( output, fillPlaceHolder( output, "[k]", spreeNumber ), sizeof( output ) );
|
|
return output;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
G_RunStreakLogic
|
|
KK-OAX This is called from player_die.
|
|
It does all the adding resetting of kill/death streaks
|
|
to be compared against the kill/death spree levels.
|
|
================
|
|
*/
|
|
void G_RunStreakLogic( gentity_t *attacker, gentity_t *victim )
|
|
{
|
|
//We only want to sanity check for the victim at this point.
|
|
if( !victim || !victim->client )
|
|
return;
|
|
|
|
//We will reset the victim's killstreak counter
|
|
victim->client->pers.killstreak = 0;
|
|
//Add one to the death streak counter
|
|
victim->client->pers.deathstreak++;
|
|
|
|
//Let's check for a deathspree for the victim
|
|
G_CheckForSpree( victim, victim->client->pers.deathstreak, qfalse );
|
|
|
|
//Move on to the attacker
|
|
//Make sure they are a client and that the attacker and victim are not the same client
|
|
//We don't want suicide to count towards a killstreak...
|
|
if( ( attacker )
|
|
&& ( attacker->client )
|
|
&& ( attacker != victim ) ) {
|
|
|
|
//Check the gametype--If FFA enabled, everybody's on the same team.
|
|
if( g_gametype.integer >= GT_TEAM && g_ffa_gt!= 1 ) {
|
|
//If they are on the same team we don't want to count it towards a killing spree.
|
|
if( OnSameTeam( victim, attacker ) ) {
|
|
return;
|
|
}
|
|
}
|
|
//Add to the killstreak, reset the deathstreak
|
|
attacker->client->pers.deathstreak = 0;
|
|
attacker->client->pers.killstreak++;
|
|
|
|
//Let's check for a killingspree for the attacker
|
|
G_CheckForSpree( attacker, attacker->client->pers.killstreak, qtrue );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//If the streak / spree divisor is a whole number, we have a spree
|
|
static qboolean TestSpreeWhole( int streak2Test ) {
|
|
float float2Test;
|
|
float spreeFDiv;
|
|
float resultf;
|
|
int spreeDiv;
|
|
int result;
|
|
|
|
float2Test = streak2Test;
|
|
spreeFDiv = level.spreeDivisor;
|
|
spreeDiv = level.spreeDivisor;
|
|
result = ( streak2Test / spreeDiv );
|
|
resultf = ( float2Test / spreeFDiv );
|
|
|
|
if( result == resultf ) {
|
|
return qtrue;
|
|
} else {
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
G_CheckForSpree
|
|
==================
|
|
*/
|
|
void G_CheckForSpree( gentity_t *ent, int streak2Test, qboolean checkKillSpree )
|
|
{
|
|
int i;
|
|
char *returnedString;
|
|
//If somebody want's to award killing sprees above 99 kills he/she can mod this his/herself!!! :)
|
|
char streakcount[ 3 ];
|
|
char *sound;
|
|
int position;
|
|
int soundIndex;
|
|
|
|
qboolean isSpree = qfalse;
|
|
int divisionHolder;
|
|
|
|
//Probably Not Needed, but to protect Server Ops from Crashing their Stuff MidMatch
|
|
if( level.spreeDivisor < 1 ) {
|
|
return;
|
|
}
|
|
divisionHolder = ( streak2Test / level.spreeDivisor );
|
|
//if it's a deathspree
|
|
if( !checkKillSpree ) {
|
|
//Is the streak higher than the largest level defined?
|
|
if( divisionHolder > level.dSpreeUBound ) {
|
|
//Let's make sure it's a whole number to mimic the other sprees
|
|
isSpree = TestSpreeWhole( streak2Test );
|
|
if( !isSpree ) {
|
|
return;
|
|
}
|
|
//We've made it this far...now do the largest spree defined.
|
|
Q_snprintf( streakcount, sizeof( streakcount ), "%i", streak2Test );
|
|
//Check if deathSprees is NULL (actual problem!)
|
|
if(!deathSprees[ level.dSpreeUBound ])
|
|
return;
|
|
returnedString = CreateMessage( ent, deathSprees[ level.dSpreeUBound ]->spreeMsg, streakcount );
|
|
position = deathSprees[ level.dSpreeUBound ]->position;
|
|
sound = deathSprees[ level.dSpreeUBound ]->sound2Play;
|
|
soundIndex = G_SoundIndex( sound );
|
|
//Play the Sound
|
|
G_GlobalSound( soundIndex );
|
|
//Print the Message
|
|
if( position == CENTER_PRINT ) {
|
|
AP( va("cp \"%s\"", returnedString ) );
|
|
} else {
|
|
AP( va("chat \"%s\"", returnedString ) );
|
|
}
|
|
} else {
|
|
for( i = 0; deathSprees[ i ]; i++ ) {
|
|
//If the deathSpree is equal to the streak to test
|
|
if( deathSprees[ i ]->streakCount == streak2Test ) {
|
|
//Using Q_snprintf to change the int into a char for replacement.
|
|
Q_snprintf( streakcount, sizeof( streakcount ), "%i", deathSprees[ i ]->streakCount );
|
|
//Let's grab the message to show, fill up the placeholders and concatenate it.
|
|
returnedString = CreateMessage ( ent, deathSprees[ i ]->spreeMsg, streakcount );
|
|
//Grab the Print Position ( 1 for Center Printing, 2 for Chat )
|
|
position = deathSprees[ i ]->position;
|
|
//Grab the Sound
|
|
sound = deathSprees[ i ]->sound2Play;
|
|
//Index the Sound
|
|
soundIndex = G_SoundIndex( sound );
|
|
//Play the Sound
|
|
G_GlobalSound( soundIndex );
|
|
//Print the Message
|
|
if( position == CENTER_PRINT ) {
|
|
AP( va("cp \"%s\"", returnedString ) );
|
|
} else {
|
|
AP( va("chat \"%s\"", returnedString ) );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else /*if( checkKillSpree )*/ {
|
|
//Is the streak higher than the largest level defined?
|
|
if( divisionHolder > level.kSpreeUBound ) {
|
|
//Let's make sure it's a whole number to mimic the other sprees
|
|
isSpree = TestSpreeWhole( streak2Test );
|
|
if( !isSpree ) {
|
|
return;
|
|
}
|
|
//We've made it this far...now do the largest spree defined.
|
|
Q_snprintf( streakcount, sizeof( streakcount ), "%i", streak2Test );
|
|
//Check if killSprees is NULL (actual problem!)
|
|
if(!killSprees[ level.kSpreeUBound ])
|
|
return;
|
|
returnedString = CreateMessage( ent, killSprees[ level.kSpreeUBound ]->spreeMsg, streakcount );
|
|
position = killSprees[ level.kSpreeUBound ]->position;
|
|
sound = killSprees[ level.kSpreeUBound ]->sound2Play;
|
|
soundIndex = G_SoundIndex( sound );
|
|
soundIndex = G_SoundIndex( sound );
|
|
//G_GlobalSound( soundIndex );
|
|
G_Sound(ent,0,soundIndex);
|
|
/* Doesn't do anything at the moment. cp does not work while kill message is displayed
|
|
* if( position == CENTER_PRINT ) {
|
|
//Only Center print for player doing the killing spree
|
|
CP( va("cp \"%s\"", returnedString ) );
|
|
}*/
|
|
AP( va("chat \"%s\"", returnedString ) );
|
|
} else {
|
|
for( i = 0; killSprees[ i ]; i++ ) {
|
|
if( killSprees[ i ]->streakCount == streak2Test ) {
|
|
Q_snprintf( streakcount, sizeof( streakcount ), "%i", killSprees[ i ]->streakCount );
|
|
returnedString = CreateMessage ( ent, killSprees[ i ]->spreeMsg, streakcount );
|
|
position = killSprees[ i ]->position;
|
|
sound = killSprees[ i ]->sound2Play;
|
|
soundIndex = G_SoundIndex( sound );
|
|
soundIndex = G_SoundIndex( sound );
|
|
//G_GlobalSound( soundIndex );
|
|
G_Sound(ent,0,soundIndex);
|
|
/*if( position == CENTER_PRINT ) {
|
|
//Only Center print for player doing the killing spree
|
|
CP( va("cp \"%s\"", returnedString ) );
|
|
}*/
|
|
AP( va("chat \"%s\"", returnedString ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} /*else {
|
|
G_Printf("Killing Spree Error in G_CheckForSpree\n");
|
|
return;
|
|
}*/
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
G_checkForMultiKill
|
|
===============
|
|
*/
|
|
void G_checkForMultiKill( gentity_t *ent ) {
|
|
|
|
int i;
|
|
char *returnedString;
|
|
char *sound;
|
|
int soundIndex;
|
|
int multiKillCount;
|
|
char multiKillString[ 2 ];
|
|
gclient_t *client;
|
|
int clientNum;
|
|
|
|
client = ent->client;
|
|
clientNum = client - level.clients;
|
|
|
|
//Let's grab the multikill count for the player first
|
|
multiKillCount = ent->client->pers.multiKillCount;
|
|
|
|
if( multiKillCount > multiKills[ level.mKillUBound ]->kills ) {
|
|
Q_snprintf( multiKillString, sizeof( multiKillString ), "%i", multiKillCount );
|
|
if(!multiKills[ level.mKillUBound ])
|
|
return; //If null
|
|
returnedString = CreateMessage ( ent, multiKills[ level.mKillUBound ]->killMsg, multiKillString );
|
|
sound = multiKills[ level.mKillUBound ]->sound2Play;
|
|
soundIndex = G_SoundIndex( sound );
|
|
G_Sound(ent, 0, soundIndex );
|
|
AP( va("chat \"%s\"", returnedString ) );
|
|
return;
|
|
} else {
|
|
for( i = 0; multiKills[ i ]; i++ ) {
|
|
//If the multikill count is equal to a killLevel let's do this.
|
|
|
|
if( multiKills[ i ]->kills == multiKillCount ) {
|
|
Q_snprintf( multiKillString, sizeof( multiKillString ), "%i", multiKills[ i ]->kills );
|
|
//Build the Message
|
|
returnedString = CreateMessage ( ent, multiKills[ i ]->killMsg, multiKillString );
|
|
//Grab the sound
|
|
sound = multiKills[ i ]->sound2Play;
|
|
//Index the sound
|
|
soundIndex = G_SoundIndex( sound );
|
|
//Play the sound
|
|
G_Sound(ent, 0, soundIndex );
|
|
/* Print the String
|
|
Since we don't want to clutter screens (the player is already going to get the excellent icon)
|
|
we won't give them an option to centerprint.
|
|
*/
|
|
AP( va("chat \"%s\"", returnedString ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|