680 lines
16 KiB
C
680 lines
16 KiB
C
|
|
// g_teamplay.c - teamplay oriented code
|
|
|
|
#include "g_local.h"
|
|
|
|
// current teamplay mode (set by "level.style")
|
|
teamplay_mode_t teamplay_mode;
|
|
|
|
#define CASH_ROLL 10
|
|
#define CASH_BAG 25
|
|
|
|
#define MAX_CASH_ITEMS 10 // never spawn more than this many cash items at once
|
|
int num_cash_items;
|
|
|
|
char *team_names[] = {
|
|
"(spectator)",
|
|
"Dragons",
|
|
"Nikki's Boyz",
|
|
NULL
|
|
};
|
|
|
|
|
|
int team_cash[3]; // cash per team, 0 is neutral so just ignore
|
|
|
|
float last_safe_withdrawal[3];
|
|
float last_safe_deposit[3];
|
|
|
|
//=====================================================================
|
|
// Entity spawn functions
|
|
|
|
// ....................................................................
|
|
// Cash Spawning during GRAB DA LOOT
|
|
|
|
void cash_touch( edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
float speed;
|
|
|
|
if (surf && plane && plane->normal[2] > 0.5)
|
|
{ // let it rest here
|
|
|
|
if ((speed = VectorLength( self->velocity )) > 10)
|
|
{
|
|
self->s.angles[ROLL] = 0;
|
|
self->s.angles[PITCH] = 0;
|
|
self->avelocity[PITCH] = 0;
|
|
self->avelocity[ROLL] = 0;
|
|
self->avelocity[YAW] *= 0.5;
|
|
|
|
// randomize bounce
|
|
VectorAdd( self->velocity, tv( crandom()*speed*0.3, crandom()*speed*0.3, random()*speed*0.15 ), self->velocity );
|
|
}
|
|
else
|
|
{
|
|
VectorClear( self->velocity );
|
|
VectorClear( self->avelocity );
|
|
self->s.angles[PITCH] = 0;
|
|
self->s.angles[ROLL] = 0;
|
|
|
|
self->movetype = MOVETYPE_NONE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (other->client)
|
|
{
|
|
if (other->client->pers.currentcash < MAX_CASH_PLAYER)
|
|
{ // they can hold the cash
|
|
|
|
if ((self->currentcash == CASH_BAG) || (self->movetype != MOVETYPE_NONE) || (other->client->ps.pmove.pm_flags & PMF_DUCKED))
|
|
{ // they can pick it up
|
|
|
|
Touch_Item( self, other, plane, surf );
|
|
|
|
num_cash_items--;
|
|
|
|
G_FreeEdict( self );
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void cash_kill( edict_t *self )
|
|
{
|
|
num_cash_items--;
|
|
G_FreeEdict( self );
|
|
}
|
|
|
|
void cashroll_animate( edict_t *self )
|
|
{
|
|
// reduce XY velocity (air friction)
|
|
self->velocity[0] *= 0.9;
|
|
self->velocity[1] *= 0.9;
|
|
|
|
if (level.time > (self->timestamp))
|
|
{
|
|
cash_kill( self );
|
|
return;
|
|
}
|
|
|
|
if (self->movetype != MOVETYPE_NONE)
|
|
{
|
|
|
|
if (VectorDistance( self->s.origin, self->pos1 ) < 1)
|
|
self->count++;
|
|
else
|
|
self->count = 0;
|
|
|
|
VectorCopy( self->s.origin, self->pos1 );
|
|
|
|
if (self->count > 2) // rested for 2 frames
|
|
{
|
|
VectorClear( self->velocity );
|
|
VectorClear( self->avelocity );
|
|
self->s.angles[PITCH] = 0;
|
|
self->s.angles[ROLL] = 0;
|
|
|
|
self->movetype = MOVETYPE_NONE;
|
|
}
|
|
}
|
|
|
|
self->nextthink = level.time + 0.1;
|
|
}
|
|
|
|
void cashspawn_think( edict_t *self )
|
|
{
|
|
edict_t *cash;
|
|
|
|
if (num_cash_items > MAX_CASH_ITEMS)
|
|
{
|
|
self->nextthink = level.time + self->delay;
|
|
return;
|
|
}
|
|
|
|
// spawn some money
|
|
cash = G_Spawn();
|
|
|
|
VectorCopy( self->s.origin, cash->s.origin );
|
|
cash->movetype = MOVETYPE_BOUNCE;
|
|
cash->solid = SOLID_TRIGGER;
|
|
|
|
AngleVectors( self->s.angles, cash->velocity, NULL, NULL );
|
|
VectorScale( cash->velocity, self->speed, cash->velocity );
|
|
|
|
// randomize the velocity a bit
|
|
VectorAdd( cash->velocity, tv( crandom()*self->speed*0.3, crandom()*self->speed*0.3, crandom()*self->speed*0.15 ), cash->velocity );
|
|
|
|
cash->s.renderfx2 |= RF2_NOSHADOW;
|
|
|
|
// FIXME: doh this doesn't work, need to spawn actual item's, so the HUD is updated automatically when picking up
|
|
|
|
if (!strcmp(self->type, "cashroll"))
|
|
{ // small dollar notes
|
|
cash->s.modelindex = gi.modelindex( "models/pu_icon/cash/tris.md2" );
|
|
cash->gravity = 0.1 + random()*0.5;
|
|
|
|
cash->think = cashroll_animate;
|
|
cash->nextthink = level.time + 0.1;
|
|
cash->s.angles[PITCH] = 10;
|
|
VectorSet( cash->avelocity, 0, 10000 * cash->gravity, 0 );
|
|
|
|
VectorSet( cash->mins, -4, -4, -15 );
|
|
VectorSet( cash->maxs, 4, 4, -13 );
|
|
|
|
cash->item = FindItem("Cash");
|
|
|
|
cash->currentcash = CASH_ROLL;
|
|
cash->touch = cash_touch;
|
|
|
|
cash->timestamp = level.time + 60;
|
|
|
|
cash->think = cashroll_animate;
|
|
cash->nextthink = level.time + 0.1;
|
|
}
|
|
else
|
|
{
|
|
cash->s.modelindex = gi.modelindex( "models/pu_icon/money/money_sm.md2" );
|
|
cash->gravity = 1.0;
|
|
|
|
VectorSet( cash->mins, -12, -12, -15 );
|
|
VectorSet( cash->maxs, 12, 12, 10 );
|
|
|
|
cash->item = FindItem("Small Cash Bag");
|
|
|
|
cash->currentcash = CASH_BAG;
|
|
cash->touch = cash_touch;
|
|
|
|
cash->think = cash_kill;
|
|
cash->nextthink = level.time + 60;
|
|
}
|
|
|
|
num_cash_items++;
|
|
|
|
self->nextthink = level.time + self->delay;
|
|
|
|
}
|
|
|
|
/*QUAKED dm_cashspawn (0.5 0 1) (-16 -16 -16) (16 16 16)
|
|
Spawn location for cash during "Grab da Loot" games
|
|
|
|
angle - direction to project cash upon spawning
|
|
speed - speed of projection
|
|
type - "cashroll" or "cashbag" (more money, longer delay)
|
|
*/
|
|
void SP_dm_cashspawn( edict_t *self )
|
|
{
|
|
if (!teamplay->value || ((int)teamplay->value != TM_AUTO && (int)teamplay->value != TM_GRABDALOOT))
|
|
return;
|
|
|
|
// set the game to "Grab da Loot"
|
|
teamplay_mode = TM_GRABDALOOT;
|
|
num_cash_items = 0;
|
|
|
|
if (!strcmp(self->type, "cashroll"))
|
|
{
|
|
self->delay = (float)g_cashspawndelay->value;
|
|
}
|
|
else // bag, so longer delay
|
|
{
|
|
self->delay = (float)g_cashspawndelay->value * (CASH_BAG / CASH_ROLL);
|
|
}
|
|
|
|
if (!self->speed)
|
|
self->speed = 10;
|
|
|
|
self->think = cashspawn_think;
|
|
self->nextthink = level.time + self->delay;
|
|
}
|
|
|
|
// ....................................................................
|
|
// Safe Bag, used for Grab Da Loot and teamplay Cash-Match
|
|
|
|
void safebag_touch( edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
static float last_touch_time;
|
|
static edict_t *last_touch_ent;
|
|
static int last_touch_count = 0;
|
|
|
|
if (!other->client)
|
|
return;
|
|
|
|
if ((level.time < last_touch_time) || (last_touch_time && (last_touch_time < (level.time - 2.0))) || (last_touch_ent && (last_touch_ent != other)))
|
|
{ // reset
|
|
last_touch_time = 0;
|
|
last_touch_ent = NULL;
|
|
last_touch_count = 0;
|
|
}
|
|
else if (last_touch_time > (level.time - 0.1))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
last_touch_count++;
|
|
last_touch_time = level.time;
|
|
|
|
if (last_touch_count > (int)(50.0 * (1.0 + (0.5*(other->client->pers.team == self->style)))))
|
|
{
|
|
// let them go away on their own terms
|
|
T_Damage( other, other, other, vec3_origin, other->s.origin, vec3_origin, 9999, 0, 0, MOD_SAFECAMPER );
|
|
last_touch_count = 0;
|
|
}
|
|
}
|
|
|
|
last_touch_ent = other;
|
|
|
|
if (self->timestamp > (level.time - 1.0))
|
|
return;
|
|
|
|
self->timestamp = level.time;
|
|
|
|
// depositing, or withdrawing?
|
|
|
|
if (other->client->pers.team == self->style)
|
|
{ // deposit
|
|
|
|
if (other->client->pers.currentcash > 0 || other->client->pers.bagcash > 0)
|
|
{
|
|
int precash, amount;
|
|
|
|
precash = team_cash[self->style];
|
|
|
|
team_cash[self->style] += other->client->pers.currentcash;
|
|
team_cash[self->style] += other->client->pers.bagcash;
|
|
|
|
other->client->resp.deposited += other->client->pers.currentcash;
|
|
other->client->resp.deposited += other->client->pers.bagcash;
|
|
|
|
other->client->pers.currentcash = 0;
|
|
other->client->pers.bagcash = 0;
|
|
|
|
gi.sound(other, CHAN_ITEM, gi.soundindex("world/pickups/cash.wav"), 1, 3, 0);
|
|
|
|
// make a droping sound
|
|
gi.positioned_sound(self->s.origin, self, CHAN_ITEM, gi.soundindex("world/doors/dr1_end.wav"), 1, 1, 0);
|
|
|
|
// let everyone know how much was deposited
|
|
amount = team_cash[self->style] - precash;
|
|
gi.bprintf( PRINT_MEDIUM, "%s deposited $%i\n", other->client->pers.netname, amount );
|
|
|
|
last_safe_deposit[self->style] = level.time;
|
|
}
|
|
|
|
}
|
|
else if (team_cash[self->style] > 0)
|
|
{ // withdrawal
|
|
|
|
if (other->client->pers.bagcash < MAX_BAGCASH_PLAYER)
|
|
{
|
|
int precash, amount;
|
|
|
|
precash = team_cash[self->style];
|
|
|
|
team_cash[self->style] -= (MAX_BAGCASH_PLAYER - other->client->pers.bagcash);
|
|
other->client->pers.bagcash += (MAX_BAGCASH_PLAYER - other->client->pers.bagcash);
|
|
|
|
if (team_cash[self->style] < 0)
|
|
{ // don't take more than they have
|
|
other->client->pers.bagcash += team_cash[self->style];
|
|
team_cash[self->style] = 0;
|
|
}
|
|
|
|
gi.sound(other, CHAN_ITEM, gi.soundindex("world/pickups/cash.wav"), 1, 3, 0);
|
|
|
|
// alarm!
|
|
gi.positioned_sound(self->s.origin, self, CHAN_ITEM, gi.soundindex("misc/cashmatch_alarm.wav"), 1, 1, 0);
|
|
|
|
// let everyone know how much was stolen
|
|
amount = precash - team_cash[self->style];
|
|
gi.bprintf( PRINT_MEDIUM, "%s stole $%i from %s's safe!\n", other->client->pers.netname, amount, team_names[self->style] );
|
|
|
|
last_safe_withdrawal[self->style] = level.time;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Safebag think, if a friendly guy has been standing near us for more than a few seconds, make them vulnerable to friendly fire
|
|
void safebag_think(edict_t *self)
|
|
{
|
|
int i;
|
|
edict_t *trav;
|
|
qboolean noenemies = true;
|
|
#define SAFE_CLOSE_DIST 128
|
|
#define MAX_TIMEATSAFE 8.0
|
|
|
|
// first, check if we have any unwanted enemies around, if so, don't count
|
|
for (i=0; i<maxclients->value; i++)
|
|
{
|
|
trav = &g_edicts[i+1];
|
|
|
|
if (!trav->inuse || !trav->client)
|
|
continue;
|
|
|
|
if (trav->health <= 0)
|
|
continue;
|
|
|
|
if (!trav->client->pers.team || (trav->client->pers.team == self->style))
|
|
continue;
|
|
|
|
if (VectorDistance( self->s.origin, trav->s.origin ) > 512)
|
|
continue;
|
|
|
|
if (!gi.inPVS( self->s.origin, trav->s.origin ))
|
|
continue;
|
|
|
|
noenemies = false;
|
|
}
|
|
|
|
for (i=0; i<maxclients->value; i++)
|
|
{
|
|
trav = &g_edicts[i+1];
|
|
|
|
if (!trav->inuse || !trav->client)
|
|
continue;
|
|
|
|
if (trav->health <= 0)
|
|
continue;
|
|
|
|
if (!trav->client->pers.team || (trav->client->pers.team != self->style))
|
|
continue;
|
|
|
|
if (noenemies)
|
|
{
|
|
|
|
if (VectorDistance( self->s.origin, trav->s.origin ) > SAFE_CLOSE_DIST)
|
|
{
|
|
trav->client->pers.timeatsafe -= 0.2;
|
|
|
|
if (trav->client->pers.timeatsafe < 0)
|
|
trav->client->pers.timeatsafe = 0;
|
|
}
|
|
else
|
|
{
|
|
trav->client->pers.timeatsafe += 0.2;
|
|
|
|
if (trav->client->pers.timeatsafe > MAX_TIMEATSAFE)
|
|
trav->client->pers.timeatsafe = MAX_TIMEATSAFE;
|
|
}
|
|
|
|
if (trav->client->pers.timeatsafe >= MAX_TIMEATSAFE)
|
|
trav->client->pers.friendly_vulnerable = true;
|
|
else
|
|
trav->client->pers.friendly_vulnerable = false;
|
|
|
|
}
|
|
else // turn off vulnerability, there is an enemy in range
|
|
{
|
|
trav->client->pers.friendly_vulnerable = false;
|
|
}
|
|
}
|
|
|
|
self->nextthink = level.time + 0.2;
|
|
}
|
|
|
|
/*QUAKED dm_safebag (0.5 0 1) (-12 -12 -16) (12 12 12)
|
|
Bag that holds the money in the safe.
|
|
|
|
style - team that this bag belongs to (1 or 2)
|
|
*/
|
|
void SP_dm_safebag( edict_t *self )
|
|
{
|
|
if (!teamplay->value)
|
|
{
|
|
G_FreeEdict( self );
|
|
return;
|
|
}
|
|
|
|
if (self->style < 1 || self->style > 2)
|
|
{
|
|
gi.dprintf( "dm_safebag has invalid \"style\" at %s, should be 1 or 2.\n", vtos(self->s.origin));
|
|
G_FreeEdict( self );
|
|
return;
|
|
}
|
|
|
|
self->s.modelindex = gi.modelindex("models/pu_icon/money/money_lg.md2");
|
|
VectorSet( self->mins, -12, -12, -16 );
|
|
VectorSet( self->maxs, 12, 12, 12 );
|
|
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->solid = SOLID_TRIGGER;
|
|
|
|
gi.linkentity( self );
|
|
|
|
self->touch = safebag_touch;
|
|
self->currentcash = 0; // start with no cash
|
|
|
|
self->think = safebag_think;
|
|
self->nextthink = level.time + 2;
|
|
}
|
|
|
|
|
|
/*QUAKED dm_props_banner (.5 0 1) (-4 -4 -4) (4 4 4)
|
|
Temp banner for teamplay
|
|
|
|
style = team (1 / 2)
|
|
scale = scale the size up/down (2 = double size)
|
|
|
|
model="models\props\temp\triangle\small.md2"
|
|
*/
|
|
void SP_dm_props_banner (edict_t *self)
|
|
{
|
|
// vec3_t end, bestnorm, bestend;
|
|
// float bestdist;
|
|
// int x,y;
|
|
// trace_t tr;
|
|
|
|
if (!deathmatch->value || !teamplay->value)
|
|
{ // remove
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
if (!self->style)
|
|
{
|
|
gi.dprintf( "%s has invalid style (should be 1 or 2) at %s\n", self->classname, vtos(self->s.origin) );
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
/*
|
|
// trace a line back, to get the wall, then go out
|
|
{
|
|
bestdist = 9999;
|
|
|
|
for (x=-256; x<300; x+= 256)
|
|
{
|
|
VectorCopy( self->s.origin, end );
|
|
end[0] = self->s.origin[0] + x;
|
|
tr = gi.trace( self->s.origin, NULL, NULL, end, NULL, MASK_SOLID );
|
|
if (tr.fraction < bestdist)
|
|
{
|
|
VectorCopy( tr.plane.normal, bestnorm );
|
|
VectorCopy( tr.endpos, bestend );
|
|
bestdist = tr.fraction;
|
|
}
|
|
}
|
|
for (y=-256; y<300; y+= 256)
|
|
{
|
|
VectorCopy( self->s.origin, end );
|
|
end[1] = self->s.origin[1] + y;
|
|
tr = gi.trace( self->s.origin, NULL, NULL, end, NULL, MASK_SOLID );
|
|
if (tr.fraction < bestdist)
|
|
{
|
|
VectorCopy( tr.plane.normal, bestnorm );
|
|
VectorCopy( tr.endpos, bestend );
|
|
bestdist = tr.fraction;
|
|
}
|
|
}
|
|
|
|
vectoangles( bestnorm, self->s.angles );
|
|
|
|
VectorMA( bestend, 40 * self->cast_info.scale, bestnorm, self->s.origin );
|
|
}
|
|
|
|
*/
|
|
// Ridah, 1-jun-99, use flag models for now
|
|
#if 1
|
|
{
|
|
void think_flag (edict_t *self);
|
|
|
|
// self->solid = SOLID_BBOX;
|
|
self->movetype = MOVETYPE_NONE;
|
|
|
|
if (self->style == 2)
|
|
{
|
|
self->model = "models/props/flag/flag1.md2";
|
|
}
|
|
else
|
|
{
|
|
self->model = "models/props/flag/flag3.md2";
|
|
}
|
|
|
|
self->s.modelindex = gi.modelindex (self->model);
|
|
|
|
self->s.renderfx2 |= RF2_NOSHADOW;
|
|
self->s.renderfx |= RF_MINLIGHT;
|
|
|
|
if (!self->cast_info.scale)
|
|
self->cast_info.scale = 1;
|
|
|
|
self->s.scale = (self->cast_info.scale - 1);
|
|
|
|
// VectorMA( bestend, 40 * self->cast_info.scale, bestnorm, self->s.origin );
|
|
|
|
self->cast_info.scale *= 0.3;
|
|
|
|
gi.linkentity (self);
|
|
|
|
self->s.effects |= EF_ANIM_ALLFAST_NEW;
|
|
self->s.renderfx2 |= RF2_MODULUS_FRAME;
|
|
self->s.renderfx2 |= RDF_NOLERP;
|
|
|
|
// Disabled, doesn't animate much, and uses bandwidth
|
|
// self->nextthink = level.time + FRAMETIME *2;
|
|
// self->think = think_flag;
|
|
}
|
|
|
|
#else // TRIANGULAR ROTATING ICONS
|
|
|
|
self->solid = SOLID_NOT;
|
|
self->movetype = MOVETYPE_NONE;
|
|
|
|
self->s.skinnum = self->style - 1;
|
|
|
|
self->s.renderfx2 |= RF2_NOSHADOW;
|
|
self->s.renderfx |= RF_MINLIGHT;
|
|
|
|
if (!self->cast_info.scale)
|
|
self->cast_info.scale = 1;
|
|
|
|
self->s.scale = self->cast_info.scale - 1;
|
|
|
|
self->s.modelindex = gi.modelindex ("models/props/temp/triangle/small.md2");
|
|
|
|
gi.linkentity (self);
|
|
|
|
{
|
|
edict_t *arm;
|
|
|
|
arm = G_Spawn();
|
|
arm->solid = self->solid;
|
|
arm->movetype = self->movetype;
|
|
arm->s.renderfx2 |= RF2_NOSHADOW;
|
|
arm->s.scale = self->s.scale;
|
|
|
|
VectorCopy( self->s.origin, arm->s.origin );
|
|
VectorCopy( self->s.angles, arm->s.angles );
|
|
|
|
arm->s.modelindex = gi.modelindex ("models/props/temp/triangle/arm.md2");
|
|
gi.linkentity (arm);
|
|
}
|
|
|
|
VectorCopy( self->s.angles, self->last_step_pos );
|
|
VectorClear( self->move_angles );
|
|
#endif
|
|
}
|
|
|
|
// ....................................................................
|
|
|
|
void Teamplay_ValidateSkin( edict_t *self )
|
|
{
|
|
// TODO: we need color coded skins, for now, just use any skin
|
|
}
|
|
|
|
extern void ClientUserinfoChanged (edict_t *ent, char *userinfo);
|
|
|
|
qboolean Teamplay_ValidateJoinTeam( edict_t *self, int teamindex )
|
|
{
|
|
// NOTE: this is called by each player on level change, as well as when a player issues a "join XXX" command
|
|
|
|
// TODO: player limit per team? cvar?
|
|
|
|
|
|
// setup client stuff
|
|
self->movetype = MOVETYPE_WALK;
|
|
self->solid = SOLID_BBOX;
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
|
|
Teamplay_ValidateSkin( self );
|
|
|
|
// InitClientPersistant (self->client);
|
|
|
|
self->client->pers.team = teamindex;
|
|
|
|
/*
|
|
// Validate skins
|
|
{
|
|
char *str[256];
|
|
|
|
strcpy( str, self->client->pers.userinfo );
|
|
ClientUserinfoChanged ( self, str );
|
|
}
|
|
*/
|
|
|
|
self->health = 0; // so we're not counted in spawn point checking
|
|
self->client->resp.enterframe = level.framenum;
|
|
InitClientResp( self->client );
|
|
PutClientInServer( self ); // find a new spawn point
|
|
gi.bprintf( PRINT_HIGH, "%s joined %s\n", self->client->pers.netname, team_names[teamindex] );
|
|
|
|
return true;
|
|
}
|
|
|
|
void Teamplay_AutoJoinTeam( edict_t *self )
|
|
{
|
|
int team_count[2];
|
|
int i;
|
|
|
|
// count number of players on each team, assign the team with least players
|
|
|
|
team_count[0] = 0;
|
|
team_count[1] = 0;
|
|
|
|
for (i=1; i<maxclients->value; i++)
|
|
{
|
|
if (g_edicts[i].client && g_edicts[i].client->pers.team)
|
|
team_count[g_edicts[i].client->pers.team - 1]++;
|
|
}
|
|
|
|
if (team_count[0] > team_count[1])
|
|
self->client->pers.team = 2;
|
|
else
|
|
self->client->pers.team = 1;
|
|
|
|
Teamplay_ValidateJoinTeam( self, self->client->pers.team );
|
|
}
|
|
|
|
|
|
void Teamplay_InitTeamplay (void)
|
|
{
|
|
num_cash_items = 0;
|
|
memset( team_cash, 0, sizeof(int) * 3 );
|
|
memset( last_safe_withdrawal, 0, sizeof(float) * 3 );
|
|
memset( last_safe_deposit, 0, sizeof(float) * 3 );
|
|
|
|
last_safe_deposit[0] = last_safe_deposit[1] = 0;
|
|
last_safe_withdrawal[0] = last_safe_withdrawal[1] = 0;
|
|
}
|