593 lines
16 KiB
C++
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 );
|
|
}
|