sin-2015/checkpoints.cpp
1999-04-22 00:00:00 +00:00

593 lines
16 KiB
C++

/*
================================================================
CHECKPOINTS
================================================================
Copyright (C) 1998 by 2015, Inc.
All rights reserved.
This source is may not be distributed and/or modified without
expressly written permission by 2015, Inc.
*/
#include "checkpoints.h"
#include "actor.h"
// defines for ammo maxes
#define ROCKET_MAX 100
#define BULLET_MAX 250
#define MINE_MAX 10
// hoverbike health
#define HOVER_MAX_HEALTH 250
// checkpoint spawnflags
#define ANYORDER 1
#define RANDREWARD 2
#define TURBOFILL 4
#define ALWAYSREWARD 8
/*****************************************************************************/
/*SINED func_checkpoint (0 .5 .8) ? ANYORDER RANDREWARD TURBOFILL ALWAYSREWARD
Each time a player passes through a checkpoint, he recieves a number of
frag points, as well as some hoverbike ammo. (rockets, bullets, or mines, randomly)
Each of these have default values, but may all be changed. For ammo, enter a value
of -1 for the player to never recieve that kind of ammo. Ammo amounts recieved are
doubled for going through a goal line.
ANYORDER is only settable on the goal line. When set, it allows players to hit the
checkpoints in the level in any order. They must still hit them all
RANDREWARD specifies that the checkpoint will give out only one type of ammo (choosen randomly)
when touched. Otherwise, a player will recieve some of each type of ammo.
TURBOFILL makes this checkpoint refill a hoverbike's turbo meter when passed.
ALWAYSREWARD always gives a player the ammo and health reward even if he's already hit it.
"id" is the id number of the checkpoint. Set to 0 for the goal line and 1 or greater for checkpoints.
This needs to be unique to each checkpoint. Max allowed value is 255.
"previous_id" is the id number of the checkpoint that comes before this one.
This does not need to be set if the ANYORDER flag is used.
"points" is the number of frag points recieved for passing this checkpoint.
Default is 0 for checkpoints and 1 for goal lines.
"fastestpoints" is the number of points rewarded for making the fastest lap. Default is 1.
"rockets" is the amount of rocket ammo that the player may recieve. Default is 10.
"bullets" is the amount of bullet ammo that the player may recieve. Default is 25.
"mines" is the number of mines that the player may recieve. Default is 3.
"bikehealth" is the amount of health a hoverbike may recieve. Default is -1(don't give any).
"riderhealth" is the amount of health a hoverbike may recieve. Default is -1(don't give any).
"disabletime" sets how long after someone successfully hits this checkpoint that it will be disabled.
No one can successfully hit the checkpoint while it's disabled. Default is 0, don't disable.
If ALWAYSREWARD is set, default is 5.
"target" is activated when the checkpoint is hit successfully.
"thread" is the script thread that is called when the checkpoint is hit successfully.
/*****************************************************************************/
CLASS_DECLARATION( Trigger, CheckPoint, "func_checkpoint" );
ResponseDef CheckPoint::Responses[] =
{
{&EV_Trigger_Effect, (Response)CheckPoint::CPTouch},
{&EV_Touch, (Response)CheckPoint::TriggerStuff},
{&EV_Killed, (Response)CheckPoint::TriggerStuff},
{&EV_Activate, (Response)CheckPoint::TriggerStuff},
{NULL, NULL}
};
CheckPoint::CheckPoint()
{
rockets = G_GetIntArg("rockets", 10);
bullets = G_GetIntArg("bullets", 25);
mines = G_GetIntArg("mines", 3);
bikehealth = G_GetIntArg("bikehealth", -1);
riderhealth = G_GetIntArg("riderhealth", -1);
if(spawnflags & ALWAYSREWARD)
disabletime = G_GetFloatArg("disabletime", 5);
else
disabletime = G_GetFloatArg("disabletime", 0);
id = G_GetIntArg("id", 0);
if(id < 0)
id = 0;
else if(id > 255)
id = 255;
if(id == 0) // it's a goal line
{
Player *player;
int i;
points = G_GetIntArg("points", 1);
id = -1; // goal lines are 0 to the LD's, but -1 to the code
fastestpoints = G_GetIntArg("fastestpoints", 1);
// double the ammo rewards
rockets *= 2;
bullets *= 2;
mines *= 2;
// init checkpoint system stuff
level.fastest_lap = 5999.9; // init fastest lap to 99:59.9
// this is used by client to display the fastest lap time
gi.configstring(CS_CHECKPOINTS, "99:59.9");
// set any order boolean if needed
if(spawnflags & ANYORDER)
level.cp_anyorder = true;
// make sure that CP stuff is properly setup
// for players spawned before this
for(i = 1; i <= maxclients->value; i++)
{
if(!g_edicts[i].inuse || !g_edicts[i].entity)
{
continue;
}
player = (Player *)g_edicts[i].entity;
player->last_cp_id = -1;
player->last_goal_time = level.time;
}
}
else
{
points = G_GetIntArg("points", 0);
// add it to the level's checkpoint total
level.cp_num++;
}
previous_id = G_GetIntArg("previous_id", 0);
if(previous_id == 0)
previous_id = -1;
respondto = TRIGGER_PLAYERS | TRIGGER_HOVERBIKES;
// get alias and cache the checkpoint sounds
G_LoadAndExecScript(level.cp_sounds_script.c_str());
}
void CheckPoint::CPTouch(Event *ev)
{
Entity *ent;
Player *other;
qboolean successfullhit, givereward;
float awardmult;
successfullhit = true;
awardmult = 1;
ent = ev->GetEntity(1);
assert(ent);
if(!ent->isSubclassOf(Player))
return;
other = (Player *)ent;
if(other->checkpoint_debounce > level.time)
{
if((other->last_cp_id == id) ||
((other->last_cp_id == 0) && (id != -1)))
{
return;
}
}
// completely different touching checks done for anyorder levels
if(level.cp_anyorder)
{
other->last_cp_id = id;
if(id == -1)
{
// check if player has touched all the checkpoints
if(other->CPTouchedAll())
{
float laptime;
// check if player got the fastest lap time
laptime = level.time - other->last_goal_time;
if(other->last_goal_time > 0 && laptime < level.fastest_lap)
{
int flm;
float fls;
char opstring[10];
other->RandomGlobalSound("fastestlap_sound", 1.0, CHAN_VOICE);
level.fastest_lap = laptime;
other->client->resp.score += fastestpoints;
// update config string with new fastest lap time
flm = floor(level.fastest_lap/60);
fls = level.fastest_lap - (float)flm*60.0;
opstring[0] = 0;
sprintf(opstring, "%2i:%04.1f", flm, fls);
gi.configstring(CS_CHECKPOINTS, opstring);
}
else // just play regular passing sound
{
other->RandomGlobalSound("gl_sound", 1.0, CHAN_VOICE);
}
other->last_goal_time = level.time;
other->client->resp.last_lap_time = laptime;
other->client->resp.score += points;
other->CPListClear();
}
else
{
if(other->checkpoint_debounce < level.time)
other->RandomGlobalSound("bad_cp_sound", 1.0, CHAN_VOICE);
other->checkpoint_debounce = level.time + 5.0;
successfullhit = false;
}
}
else // touched a regular checkpoint
{
if(other->CPTouched(id))
{
//already touched this checkpoint this lap
other->checkpoint_debounce = level.time + 5.0;
successfullhit = false;
}
else
{
other->RandomGlobalSound("cp_sound", 1.0, CHAN_VOICE);
// add this checkpoint to the player's list
other->CPListAdd(id);
other->client->resp.score += points;
}
}
other->checkpoint_debounce = level.time + 5.0;
}
else
{
// check for player start his first lap
if(other->last_cp_id == 0)
{
// start up the player if passing through the goal line
if(id == -1)
{
other->RandomGlobalSound("startlap_sound", 1.0, CHAN_VOICE);
other->last_cp_id = -1;
other->last_goal_time = level.time;
}
else
{
if(other->checkpoint_debounce < level.time)
other->RandomGlobalSound("bad_cp_sound", 1.0, CHAN_VOICE);
}
other->checkpoint_debounce = level.time + 1.0;
successfullhit = false;
}
// make sure player touched correct previous checkpoint
else if(other->last_cp_id != previous_id)
{
if(other->checkpoint_debounce < level.time)
other->RandomGlobalSound("bad_cp_sound", 1.0, CHAN_VOICE);
other->checkpoint_debounce = level.time + 1.0;
successfullhit = false;
}
if(successfullhit)
{
other->client->resp.score += points;
if(id == -1)
{
float laptime;
// check if player got the fastest lap time
laptime = level.time - other->last_goal_time;
if(other->last_goal_time > 0 && laptime < level.fastest_lap)
{
int flm;
float fls;
char opstring[10];
other->RandomGlobalSound("fastestlap_sound", 1.0, CHAN_VOICE);
level.fastest_lap = laptime;
other->client->resp.score += fastestpoints;
// update config string with new fastest lap time
flm = floor(level.fastest_lap/60);
fls = level.fastest_lap - (float)flm*60.0;
opstring[0] = 0;
sprintf(opstring, "%2i:%04.1f", flm, fls);
gi.configstring(CS_CHECKPOINTS, opstring);
}
else // just play regular passing sound
{
other->RandomGlobalSound("gl_sound", 1.0, CHAN_VOICE);
}
other->last_goal_time = level.time;
other->client->resp.last_lap_time = laptime;
other->CPListClear(); // reset CP counter
}
else
{
other->RandomGlobalSound("cp_sound", 1.0, CHAN_VOICE);
other->CPListAdd(id); // decrement the CP counter
}
other->last_cp_id = id;
other->client->resp.score += points;
other->checkpoint_debounce = level.time + 5.0;
}
}
// check reward disabler
if(disabletime && offtimmer > level.time)
givereward = false;
else
givereward = true;
// check for doing a sound correction
if(!successfullhit && (spawnflags & ALWAYSREWARD) && givereward)
{
other->RandomGlobalSound("cp_sound", 1.0, CHAN_VOICE);
}
// cut reward in half if not a successfull CP hit
if(!successfullhit)
awardmult = 0.5;
// give hoverbike ammo reward
if (
givereward &&
(successfullhit || (spawnflags & ALWAYSREWARD)) &&
(
rockets >= 0 ||
bullets >= 0 ||
mines >= 0 ||
bikehealth >= 0 ||
riderhealth >= 0 ||
(spawnflags & TURBOFILL)
)
)
{
if(other->GetHoverbike())
{
if(spawnflags & RANDREWARD)
{
float tmpflt;
while(1) // keep going till we give the player a reward
{
tmpflt = G_Random();
if(tmpflt < 0.1666)
{
if(rockets >=0)
{
other->GetHoverbike()->rockets += rockets*awardmult;
if(other->GetHoverbike()->rockets > ROCKET_MAX)
other->GetHoverbike()->rockets = ROCKET_MAX;
break;
}
}
else if(tmpflt < 0.3333)
{
if(bullets >=0)
{
other->GetHoverbike()->bullets += bullets*awardmult;
if(other->GetHoverbike()->bullets > BULLET_MAX)
other->GetHoverbike()->bullets = BULLET_MAX;
break;
}
}
else if(tmpflt < 0.4999)
{
if(mines >=0)
{
other->GetHoverbike()->mines += mines*awardmult;
if(other->GetHoverbike()->mines > MINE_MAX)
other->GetHoverbike()->mines = MINE_MAX;
break;
}
}
else if(tmpflt < 0.6666)
{
if(bikehealth >= 0)
{
other->GetHoverbike()->health += bikehealth*awardmult;
if(other->GetHoverbike()->health > HOVER_MAX_HEALTH)
other->GetHoverbike()->health = HOVER_MAX_HEALTH;
break;
}
}
else if(tmpflt < 0.8333)
{
if(riderhealth > 0)
{
Event *e;
float newhealth;
if(other->health < 100)
{
newhealth = other->health + riderhealth*awardmult;
if(newhealth > 100)
newhealth = 100;
e = new Event(EV_Sentient_GiveHealth);
e->AddFloat(newhealth);
other->ProcessEvent(e);;
}
break;
}
}
else
{
if(spawnflags & TURBOFILL)
{
other->GetHoverbike()->turbo = 100*awardmult;
}
}
}
}
else
{
if(rockets > 0)
{
other->GetHoverbike()->rockets += rockets*awardmult;
if(other->GetHoverbike()->rockets > ROCKET_MAX)
other->GetHoverbike()->rockets = ROCKET_MAX;
}
if(bullets > 0)
{
other->GetHoverbike()->bullets += bullets*awardmult;
if(other->GetHoverbike()->bullets > BULLET_MAX)
other->GetHoverbike()->bullets = BULLET_MAX;
}
if(mines > 0)
{
other->GetHoverbike()->mines += mines*awardmult;
if(other->GetHoverbike()->mines > MINE_MAX)
other->GetHoverbike()->mines = MINE_MAX;
}
if(bikehealth > 0)
{
other->GetHoverbike()->health += bikehealth*awardmult;
if(other->GetHoverbike()->health > HOVER_MAX_HEALTH)
other->GetHoverbike()->health = HOVER_MAX_HEALTH;
}
if(riderhealth > 0)
{
Event *e;
float newhealth;
if(other->health < 100)
{
newhealth = other->health + riderhealth*awardmult;
if(newhealth > 100)
newhealth = 100;
e = new Event(EV_Sentient_GiveHealth);
e->AddFloat(newhealth);
other->ProcessEvent(e);;
}
}
if(spawnflags & TURBOFILL)
{
other->GetHoverbike()->turbo = 100*awardmult;
}
}
}
else if(riderhealth > 0)
{
Event *e;
float newhealth;
if(other->health < 100)
{
newhealth = other->health + riderhealth*awardmult;
if(newhealth > 100)
newhealth = 100;
e = new Event(EV_Sentient_GiveHealth);
e->AddFloat(newhealth);
other->ProcessEvent(e);;
}
}
}
// check if we have to disable this checkpoint for a while
// nover disable the goal line though.
if(disabletime && id != -1)
{
offtimmer = level.time + disabletime;
}
}
void CheckPoint::TriggerStuff (Event *ev)
{
Entity *other;
Event *event;
qboolean respond;
// Don't bother with testing anything if we can't trigger yet
if ( ( level.time < trigger_time ) || ( trigger_time == -1 ) )
{
return;
}
health = max_health;
if ( health && ( ( int )*ev != ( int )EV_Killed ) && ( ( int )*ev != ( int )EV_Activate ) )
{
// if health is set, we only respond to killed and activate messages
return;
}
other = ev->GetEntity( 1 );
assert( other != this );
if((respondto & TRIGGER_PLAYERS) && other->isClient())
{
// player has a hoverbike, but this trigger doesn't respond to them
if(!(respondto & TRIGGER_HOVERBIKES) && ((Player *)other)->GetHoverbike())
respond = 0;
else
respond = 1;
}
else
{
respond = ( ( ( respondto & TRIGGER_MONSTERS ) && other->isSubclassOf( Actor ) ) ||
( ( respondto & TRIGGER_PROJECTILES ) && other->isSubclassOf( Projectile ) ) );
}
// Always respond to activate messages from the world since they're probably from
// the "trigger" command
if ( !respond && !( ( other == world ) && ( ( int )*ev == ( int )EV_Activate ) ) )
{
return;
}
if ( key.length() )
{
if ( !other->isSubclassOf( Sentient ) || !( ( (Sentient *)other )->HasItem( key.c_str() ) ) )
{
Item *item;
ClassDef *cls;
str dialog;
cls = getClass( key.c_str() );
if ( !cls )
{
gi.dprintf( "No item named '%s'\n", key.c_str() );
return;
}
item = ( Item * )cls->newInstance();
item->CancelEventsOfType( EV_Item_DropToFloor );
item->CancelEventsOfType( EV_Remove );
item->ProcessPendingEvents();
dialog = item->GetDialogNeeded();
if ( dialog.length() > 1 )
{
if ( !ExecuteThread( dialog ) )
{
warning( "TriggerStuff", "Null game script" );
}
}
else
{
gi.centerprintf ( other->edict, "jcx yv 20 string \"You need this item:\" jcx yv -20 icon %d", item->GetIconIndex() );
}
delete item;
return;
}
}
trigger_time = level.time + wait;
event = new Event( EV_Trigger_Effect );
event->AddEntity( other );
PostEvent( event, delay );
}