Files
illusion-arena/code/game/g_killspree.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;
}
}
}
}