1998-11-24 00:00:00 +00:00
|
|
|
//g_rope.c
|
|
|
|
//
|
|
|
|
// In game, animating rope
|
|
|
|
|
|
|
|
#include "g_local.h"
|
|
|
|
#include "vector.h"
|
|
|
|
#include "random.h"
|
|
|
|
#include "timing.h" // for time based sway
|
1999-03-18 00:00:00 +00:00
|
|
|
#include "p_main.h"
|
|
|
|
#include "p_anims.h"
|
1998-11-24 00:00:00 +00:00
|
|
|
#include "fx.h"
|
1999-03-18 00:00:00 +00:00
|
|
|
#include "p_anim_branch.h"
|
1998-11-24 00:00:00 +00:00
|
|
|
#include "utilities.h"
|
|
|
|
#include "p_animactor.h"
|
|
|
|
#include "m_chicken_anim.h"
|
|
|
|
#include "cl_strings.h"
|
|
|
|
|
|
|
|
#define CHICKEN_KNOCKBACK 1
|
|
|
|
|
|
|
|
#define ROPE_SEGMENT_LENGTH 16
|
|
|
|
#define ROPE_MAX_SEGMENTS 256
|
|
|
|
|
|
|
|
#define ROPEFLAG_VINE 1
|
|
|
|
#define ROPEFLAG_CHAIN 2
|
|
|
|
#define ROPEFLAG_TENDRIL 4
|
|
|
|
#define ROPEFLAG_HANGING_CHICKEN 8
|
|
|
|
|
|
|
|
//Correspondes with the model index on the client side DIRECTLY, ie DON'T CHANGE!
|
|
|
|
enum {
|
|
|
|
RM_ROPE = 0,
|
|
|
|
RM_CHAIN,
|
|
|
|
RM_VINE,
|
|
|
|
RM_TENDRIL
|
|
|
|
};
|
|
|
|
|
|
|
|
/*QUAKED obj_rope (.3 .2 .8) ? VINE CHAIN TENDRIL HANGING_CHICKEN
|
|
|
|
|
|
|
|
A rope to climb or swing (default to rope model)
|
|
|
|
|
|
|
|
Place the top of the entity at the top of the rope, and drag
|
|
|
|
the bottom of the box to the end of the rope
|
|
|
|
|
|
|
|
THE ENTITY MUST HAVE AN ORIGIN BRUSH
|
|
|
|
|
|
|
|
------------ FIELDS -------------
|
|
|
|
ROPE (default)
|
|
|
|
VINE - use a vine model
|
|
|
|
CHAIN - use a chain model
|
|
|
|
TENDRIL - use a tendril model
|
|
|
|
------------- KEYS --------------
|
|
|
|
-----------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
//============================================
|
|
|
|
|
|
|
|
//SIR NATE
|
|
|
|
|
|
|
|
//============================================
|
|
|
|
|
|
|
|
#define NATE_HEALTH 10000
|
|
|
|
|
|
|
|
enum//instructions
|
|
|
|
{
|
|
|
|
NATE_INSTRUCTION_SWIPE = 0,
|
|
|
|
NATE_INSTRUCTION_SPIN,
|
|
|
|
NATE_INSTRUCTION_JUMP_KICK,
|
|
|
|
NATE_INSTRUCTION_FIREBALL,
|
|
|
|
NUM_INSTRUCTIONS
|
|
|
|
};
|
|
|
|
|
|
|
|
enum//sayings
|
|
|
|
{
|
|
|
|
NATE_SAYING_GREETING = 0,
|
|
|
|
NATE_SAYING_INTRO,
|
|
|
|
NATE_SAYING_FAILURE,
|
|
|
|
NATE_SAYING_SUCCESS,
|
|
|
|
NATE_SAYING_FINISHED,
|
|
|
|
NATE_SAYING_HITME_AGAIN1,//5
|
|
|
|
NATE_SAYING_HITME_AGAIN2,
|
|
|
|
NATE_SAYING_HITME_AGAIN3,
|
|
|
|
NATE_SAYING_HITME_AGAIN4,
|
|
|
|
NATE_SAYING_HITME_AGAIN5,
|
|
|
|
NATE_SAYING_HITME_AGAIN6,
|
|
|
|
NATE_SAYING_HITME_AGAIN7,
|
|
|
|
NATE_SAYING_HITME_AGAIN8,
|
|
|
|
NATE_SAYING_HITME_AGAIN9,
|
|
|
|
NATE_SAYING_HITME_AGAIN10,
|
|
|
|
NATE_SAYING_END_LEVEL,
|
|
|
|
NUM_SAYINGS
|
|
|
|
};
|
|
|
|
|
|
|
|
qboolean UsedRightAttack(int instruction, edict_t *attacker, edict_t *projectile)
|
|
|
|
{
|
|
|
|
int sequence;
|
|
|
|
|
|
|
|
if (attacker->client)
|
|
|
|
{
|
|
|
|
if (attacker->client->playerinfo.upperseq == ASEQ_NONE)
|
|
|
|
sequence = attacker->client->playerinfo.lowerseq;
|
|
|
|
else
|
|
|
|
sequence = attacker->client->playerinfo.upperseq;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(instruction)
|
|
|
|
{
|
|
|
|
case NATE_INSTRUCTION_SWIPE:
|
|
|
|
if(sequence == ASEQ_WSWORD_STD1||
|
|
|
|
sequence == ASEQ_WSWORD_STD2||
|
|
|
|
sequence == ASEQ_WSWORD_STEP2||
|
|
|
|
sequence == ASEQ_WSWORD_STEP)
|
|
|
|
return (true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NATE_INSTRUCTION_SPIN:
|
|
|
|
if(sequence == ASEQ_WSWORD_SPIN || sequence == ASEQ_WSWORD_SPIN2)
|
|
|
|
return (true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NATE_INSTRUCTION_JUMP_KICK:
|
|
|
|
if(sequence == ASEQ_POLEVAULT2)
|
|
|
|
return (true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NATE_INSTRUCTION_FIREBALL:
|
|
|
|
if(!stricmp(projectile->classname, "Spell_FlyingFist"))
|
|
|
|
return (true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
|
|
|
|
int sir_nate_of_the_embarassingly_shortshanks_pain (edict_t *self, edict_t *attacker, float kick, int damage)
|
|
|
|
{
|
|
|
|
short msg;
|
|
|
|
|
|
|
|
self->health = NATE_HEALTH;
|
|
|
|
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
|
|
|
|
if(self->enemy->use)//still waiting to be triggered
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if(!attacker->client)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if(self->count == NUM_INSTRUCTIONS)
|
|
|
|
{
|
|
|
|
gi.gamemsg_centerprintf(attacker, GM_SIR_NATE_END);
|
|
|
|
self->count++;
|
|
|
|
}
|
|
|
|
else if(self->count > NUM_INSTRUCTIONS)
|
|
|
|
{
|
|
|
|
msg = (short)(irand(NATE_SAYING_HITME_AGAIN1, NATE_SAYING_HITME_AGAIN10) - 5 + GM_SIR_NATE_HIT_AGAIN0);
|
|
|
|
gi.msgvar_centerprintf(attacker, msg, damage);
|
|
|
|
}
|
|
|
|
else if(UsedRightAttack(self->count, attacker, self->activator))
|
|
|
|
{
|
|
|
|
if(self->damage_debounce_time>level.time)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
self->count++;
|
|
|
|
if(self->count == NUM_INSTRUCTIONS)
|
|
|
|
{
|
|
|
|
gi.gamemsg_centerprintf(attacker, GM_SIR_NATE_FINISH);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gi.msgdual_centerprintf(attacker, GM_SIR_NATE_SUCCESS, (short)(self->count + GM_SIR_NATE_INSTRUCTIONS0));
|
|
|
|
self->damage_debounce_time = level.time + 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gi.msgdual_centerprintf(attacker, GM_SIR_NATE_FAILURE, (short)(self->count + GM_SIR_NATE_INSTRUCTIONS0));
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sir_nate_of_the_embarassingly_shortshanks_use(edict_t *self, edict_t *other, edict_t *activator)
|
|
|
|
{
|
|
|
|
|
|
|
|
if(!activator->client)
|
|
|
|
return;
|
|
|
|
|
|
|
|
gi.gamemsg_centerprintf(activator, (short)(self->dmg_radius+GM_SIR_NATE_GREETING));
|
|
|
|
|
|
|
|
self->dmg_radius++;
|
|
|
|
|
|
|
|
if(self->dmg_radius == 2)//last opening message
|
|
|
|
self->use = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void rope_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
|
|
{
|
|
|
|
sir_nate_of_the_embarassingly_shortshanks_use(self, other, activator);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rope_think(edict_t *self)
|
|
|
|
{//FIXME!!!! Do a trace down rope to make sure the ropse does not clip through stuff!
|
|
|
|
trace_t trace;
|
|
|
|
vec3_t rope_top;
|
|
|
|
|
|
|
|
if (!self->enemy)
|
|
|
|
return;
|
|
|
|
|
|
|
|
//See if the player has chosen this rope as the one to grab
|
|
|
|
if (self->enemy->targetEnt == self)
|
|
|
|
{
|
|
|
|
//If he's already grabbed it...
|
|
|
|
if ( (self->enemy->client->playerinfo.flags & PLAYER_FLAG_ONROPE) && (!(self->enemy->client->playerinfo.flags & PLAYER_FLAG_RELEASEROPE)) )
|
|
|
|
{
|
|
|
|
if (!self->count)
|
|
|
|
{
|
|
|
|
self->count = 1;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, rope_top);
|
|
|
|
rope_top[2] += self->maxs[2];
|
|
|
|
|
|
|
|
//Create the new rope that's attached to the player
|
|
|
|
self->rope_grab->s.effects |= EF_ALTCLIENTFX;
|
|
|
|
|
|
|
|
gi.CreateEffect(&self->enemy->s,
|
|
|
|
FX_ROPE, CEF_BROADCAST | CEF_OWNERS_ORIGIN | CEF_FLAG6,
|
|
|
|
rope_top,
|
|
|
|
"ssbvvv",
|
|
|
|
self->rope_grab->s.number, //ID for the grab entity
|
|
|
|
self->rope_end->s.number, //ID for the end entity
|
|
|
|
self->bloodType, //Model type
|
|
|
|
rope_top, //Top of the rope
|
|
|
|
self->rope_grab->s.origin, //Grab's current origin (???)
|
|
|
|
self->rope_end->s.origin); //End's current origin (???)
|
|
|
|
}
|
|
|
|
|
|
|
|
gi.trace(self->rope_grab->s.origin, vec3_origin, vec3_origin, self->s.origin, self->enemy, MASK_SOLID,&trace);
|
|
|
|
|
|
|
|
if ( (trace.fraction == 1) && (!trace.startsolid && !trace.allsolid) )
|
|
|
|
gi.trace(self->enemy->s.origin, self->enemy->mins, self->enemy->maxs, self->rope_grab->s.origin, self->enemy, MASK_PLAYERSOLID,&trace);
|
|
|
|
|
|
|
|
//If the rope's movement is clear, move the player and the rope
|
|
|
|
if ( (trace.fraction == 1) && (!trace.startsolid && !trace.allsolid) )
|
|
|
|
{
|
|
|
|
VectorCopy(self->rope_grab->s.origin, self->enemy->s.origin);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Otherwise stop the player and the rope from entering a solid brush
|
|
|
|
VectorScale(self->rope_grab->velocity, -0.5, self->rope_grab->velocity);
|
|
|
|
VectorCopy(self->enemy->s.origin, self->rope_grab->s.origin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->count = 0;
|
|
|
|
self->rope_grab->s.effects &= ~EF_ALTCLIENTFX;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//This grabber is invalid, clear it
|
|
|
|
self->enemy = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
rope_end_think2
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void rope_end_think2( edict_t *self )
|
|
|
|
{
|
|
|
|
edict_t *grab = self->rope_end;
|
|
|
|
trace_t trace;
|
|
|
|
vec3_t rope_end, rope_top, end_rest, end_vel, end_vec, end_dest;
|
|
|
|
float grab_len, mag, end_len;
|
|
|
|
|
|
|
|
vec3_t end_pos;
|
|
|
|
|
|
|
|
VectorCopy(grab->velocity, end_pos);
|
|
|
|
mag = VectorNormalize(end_pos);
|
|
|
|
VectorMA(grab->s.origin, (mag*FRAMETIME), end_pos, end_pos);
|
|
|
|
|
|
|
|
if(!CHICKEN_KNOCKBACK)
|
|
|
|
{//otherwise, done in hanging_chicken_think
|
|
|
|
gi.trace(grab->s.origin, self->targetEnt->mins, self->targetEnt->maxs, end_pos, self->targetEnt, MASK_MONSTERSOLID,&trace);
|
|
|
|
|
|
|
|
if ((trace.fraction < 1 || trace.startsolid || trace.allsolid) && trace.ent != self)
|
|
|
|
{
|
|
|
|
if ( (trace.ent) && (stricmp(trace.ent->classname, "worldspawn")) )
|
|
|
|
{
|
|
|
|
VectorScale(grab->velocity, -0.5, grab->velocity);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
VectorScale(grab->velocity, -0.5, grab->velocity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Setup the top of the rope entity (the rope's attach point)
|
|
|
|
VectorCopy(self->s.origin, rope_top);
|
|
|
|
|
|
|
|
//Find the length of the end segment
|
|
|
|
grab_len = Q_fabs(self->maxs[2]+self->mins[2]);
|
|
|
|
|
|
|
|
//Find the vector to the rope's point of rest
|
|
|
|
VectorCopy(rope_top, end_rest);
|
|
|
|
end_rest[2] -= grab_len;
|
|
|
|
|
|
|
|
//Find the vector towards the middle, and that distance (disregarding height)
|
|
|
|
VectorSubtract(end_rest, grab->s.origin, end_vec);
|
|
|
|
VectorNormalize(end_vec);
|
|
|
|
end_len = vhlen(end_rest, grab->s.origin);
|
|
|
|
|
|
|
|
//Subtract away from the rope's velocity based on that distance
|
|
|
|
VectorScale(end_vec, -end_len*0.75, end_vec);
|
|
|
|
VectorSubtract(grab->velocity, end_vec, grab->velocity);
|
|
|
|
VectorScale(grab->velocity, 0.99, grab->velocity);
|
|
|
|
|
|
|
|
//Move the rope based on the new velocity
|
|
|
|
VectorCopy(grab->velocity, end_vel);
|
|
|
|
|
|
|
|
mag = VectorNormalize(end_vel);
|
|
|
|
VectorMA(grab->s.origin, FRAMETIME * mag, end_vel, end_dest);
|
|
|
|
|
|
|
|
//Find the angle between the top of the rope and the bottom
|
|
|
|
VectorSubtract(end_dest, rope_top, end_vel);
|
|
|
|
VectorNormalize(end_vel);
|
|
|
|
|
|
|
|
//Move the length of the rope in that direction from the top
|
|
|
|
VectorMA(rope_top, grab_len, end_vel, rope_end);
|
|
|
|
|
|
|
|
//You're done
|
|
|
|
VectorCopy(rope_end, grab->s.origin);
|
|
|
|
|
|
|
|
self->nextthink = level.time + 0.1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
rope_end_think
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void rope_end_think( edict_t *self )
|
|
|
|
{
|
|
|
|
edict_t *grab = self->rope_end;
|
|
|
|
vec3_t rope_end, rope_top, end_rest, end_vel, end_vec, end_dest;
|
|
|
|
float grab_len, mag, end_len;
|
|
|
|
|
|
|
|
//Setup the top of the rope entity (the rope's attach point)
|
|
|
|
VectorCopy(self->rope_grab->s.origin, rope_top);
|
|
|
|
|
|
|
|
//Find the length of the end segment
|
|
|
|
grab_len = Q_fabs(self->maxs[2]+self->mins[2]) - self->rope_grab->viewheight;
|
|
|
|
|
|
|
|
//Find the vector to the rope's point of rest
|
|
|
|
VectorCopy(rope_top, end_rest);
|
|
|
|
end_rest[2] -= grab_len;
|
|
|
|
|
|
|
|
//Find the vector towards the middle, and that distance (disregarding height)
|
|
|
|
VectorSubtract(end_rest, grab->s.origin, end_vec);
|
|
|
|
VectorNormalize(end_vec);
|
|
|
|
end_len = vhlen(end_rest, grab->s.origin);
|
|
|
|
|
|
|
|
//Subtract away from the rope's velocity based on that distance
|
|
|
|
VectorScale(end_vec, -end_len, end_vec);
|
|
|
|
VectorSubtract(grab->velocity, end_vec, grab->velocity);
|
|
|
|
VectorScale(grab->velocity, 0.95, grab->velocity);
|
|
|
|
|
|
|
|
//Move the rope based on the new velocity
|
|
|
|
VectorCopy(grab->velocity, end_vel);
|
|
|
|
|
|
|
|
mag = VectorNormalize(end_vel);
|
|
|
|
VectorMA(grab->s.origin, FRAMETIME * mag, end_vel, end_dest);
|
|
|
|
|
|
|
|
//Find the angle between the top of the rope and the bottom
|
|
|
|
VectorSubtract(end_dest, rope_top, end_vel);
|
|
|
|
VectorNormalize(end_vel);
|
|
|
|
|
|
|
|
//Move the length of the rope in that direction from the top
|
|
|
|
VectorMA(rope_top, grab_len, end_vel, rope_end);
|
|
|
|
|
|
|
|
//You're done
|
|
|
|
VectorCopy(rope_end, grab->s.origin);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
rope_sway
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void rope_sway(edict_t *self)
|
|
|
|
{
|
|
|
|
//edict_t *end = self->slave;
|
|
|
|
edict_t *grab = self->rope_grab;
|
|
|
|
vec3_t rope_end, rope_top, grab_end;
|
|
|
|
vec3_t v_rope, v_grab, v_dest, rope_rest, v_dir;
|
|
|
|
float rope_len, grab_len, dist, mag;
|
|
|
|
|
|
|
|
if ( ((Q_fabs(grab->velocity[0])) < 0.13) && ((Q_fabs(grab->velocity[1])) < 0.13) )
|
|
|
|
{
|
|
|
|
//The rope isn't moving enough to run all the math, so just make it sway a little
|
|
|
|
VectorCopy(self->s.origin, rope_rest);
|
|
|
|
rope_rest[2] += self->mins[2];
|
|
|
|
|
|
|
|
rope_rest[0] += (sin((float) level.time * 2)) * 1.25;
|
|
|
|
rope_rest[1] += (cos((float) level.time * 2)) * 1.75;
|
|
|
|
|
|
|
|
VectorCopy(rope_rest, self->pos1);
|
|
|
|
VectorCopy(rope_rest, self->slave->s.origin);
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, rope_top);
|
|
|
|
rope_top[2] += self->maxs[2];
|
|
|
|
|
|
|
|
VectorSubtract(rope_rest, rope_top, v_rope);
|
|
|
|
VectorNormalize(v_rope);
|
|
|
|
VectorMA(rope_top, grab->viewheight, v_rope, grab_end);
|
|
|
|
|
|
|
|
VectorCopy(grab_end, grab->s.origin);
|
|
|
|
|
|
|
|
rope_think(self);
|
|
|
|
|
|
|
|
self->think = rope_sway;
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Setup the top of the rope entity (the rope's attach point)
|
|
|
|
VectorCopy(self->s.origin, rope_top);
|
|
|
|
rope_top[2] += self->maxs[2];
|
|
|
|
|
|
|
|
//Find the length of the grab
|
|
|
|
VectorSubtract(rope_top, grab->s.origin, v_grab);
|
|
|
|
grab_len = VectorLength(v_grab);
|
|
|
|
|
|
|
|
//Find the vector to the rope's point of rest
|
|
|
|
VectorCopy(self->s.origin, rope_rest);
|
|
|
|
rope_rest[2] -= grab_len;
|
|
|
|
|
|
|
|
//Find the length of the rope
|
|
|
|
VectorSubtract(rope_top, rope_rest, v_rope);
|
|
|
|
rope_len = VectorLength(v_rope);
|
|
|
|
|
|
|
|
//Find the vector towards the middle, and that distance (disregarding height)
|
|
|
|
VectorSubtract(rope_rest, grab->s.origin, v_rope);
|
|
|
|
VectorNormalize(v_rope);
|
|
|
|
dist = vhlen(rope_rest, grab->s.origin);
|
|
|
|
|
|
|
|
//NOTENOTE: There's a fine line between a real pendulum motion here that comes to rest,
|
|
|
|
// and a chaotic one that builds too much and runs amuck.. so don't monkey with
|
|
|
|
// the values in here... ok? --jweier
|
|
|
|
|
|
|
|
//Subtract away from the rope's velocity based on that distance
|
|
|
|
VectorScale(v_rope, -dist, v_rope);
|
|
|
|
VectorSubtract(grab->velocity, v_rope, grab->velocity);
|
|
|
|
VectorScale(grab->velocity, 0.95, grab->velocity);
|
|
|
|
|
|
|
|
//Move the rope based on the new velocity
|
|
|
|
VectorCopy(grab->velocity, v_dir);
|
|
|
|
|
|
|
|
mag = VectorNormalize(v_dir);
|
|
|
|
VectorMA(grab->s.origin, FRAMETIME * mag, v_dir, v_dest);
|
|
|
|
|
|
|
|
//Find the angle between the top of the rope and the bottom
|
|
|
|
VectorSubtract(v_dest, rope_top, v_rope);
|
|
|
|
VectorNormalize(v_rope);
|
|
|
|
|
|
|
|
//Move the length of the rope in that direction from the top
|
|
|
|
VectorMA(rope_top, rope_len, v_rope, rope_end);
|
|
|
|
|
|
|
|
VectorSubtract(v_dest, rope_top, v_rope);
|
|
|
|
VectorNormalize(v_rope);
|
|
|
|
VectorMA(rope_top, grab->viewheight, v_rope, grab_end);
|
|
|
|
|
|
|
|
//You're done
|
|
|
|
VectorCopy(grab_end, grab->s.origin);
|
|
|
|
|
|
|
|
//VectorCopy(rope_end, grab->s.origin);
|
|
|
|
VectorCopy(grab_end, self->pos1);
|
|
|
|
|
|
|
|
//Make the end of the rope come to the end
|
|
|
|
rope_end_think(self);
|
|
|
|
|
|
|
|
rope_think(self);
|
|
|
|
|
|
|
|
self->think = rope_sway;
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
}
|
|
|
|
|
|
|
|
void rope_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
|
|
{
|
|
|
|
if ( !stricmp(other->classname, "player") )
|
|
|
|
{
|
|
|
|
//If the player is already on a rope, forget him as a valid grabber
|
|
|
|
if ( (other->targetEnt) && (other->client->playerinfo.flags & PLAYER_FLAG_ONROPE) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
//If we've got a player on the rope, and this guy isn't it, then don't let him grab
|
|
|
|
if (self->enemy && other != self->enemy)
|
|
|
|
return;
|
|
|
|
|
|
|
|
self->enemy = other;
|
|
|
|
self->viewheight = other->s.origin[2];
|
|
|
|
other->targetEnt = self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void grab_think(edict_t *self)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void end_think(edict_t *self)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int hanging_chicken_pain(edict_t *self, edict_t *other, float kick, int damage)
|
|
|
|
{
|
|
|
|
self->health = 10000;
|
|
|
|
VectorCopy(self->targetEnt->s.origin, self->s.origin);
|
|
|
|
self->velocity[2] = 0;
|
|
|
|
|
|
|
|
VectorCopy(self->velocity, self->targetEnt->velocity);
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
VectorClear(self->knockbackvel);
|
|
|
|
|
|
|
|
self->svflags &= ~SVF_ONFIRE;
|
|
|
|
|
|
|
|
sir_nate_of_the_embarassingly_shortshanks_pain(self, other, kick, damage);
|
|
|
|
|
|
|
|
if (damage < 100)
|
|
|
|
gi.CreateEffect(&self->s, FX_CHICKEN_EXPLODE, CEF_OWNERS_ORIGIN|CEF_FLAG6, NULL, "" );
|
|
|
|
else
|
|
|
|
gi.CreateEffect(&self->s, FX_CHICKEN_EXPLODE, CEF_OWNERS_ORIGIN, NULL, "" );
|
|
|
|
|
|
|
|
if (irand(0,1))
|
|
|
|
gi.sound (self, CHAN_AUTO, gi.soundindex("monsters/chicken/pain1.wav"), 1, ATTN_NORM, 0);
|
|
|
|
else
|
|
|
|
gi.sound (self, CHAN_AUTO, gi.soundindex("monsters/chicken/pain2.wav"), 1, ATTN_NORM, 0);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void hanging_chicken_think(edict_t *self)
|
|
|
|
{
|
|
|
|
vec3_t vec, angles;
|
|
|
|
float d_ang, knockbacktime;
|
|
|
|
int i, mag;
|
|
|
|
qboolean knockback = true;
|
|
|
|
trace_t trace;
|
|
|
|
|
|
|
|
VectorCopy(self->targetEnt->velocity, vec);
|
|
|
|
vec[2] = 0;
|
|
|
|
mag = VectorLength(vec);
|
|
|
|
|
|
|
|
if (mag > 100)
|
|
|
|
{
|
|
|
|
self->dmg++;
|
|
|
|
self->dmg = self->dmg & ((FRAME_cluck18-FRAME_cluck14)-1);
|
|
|
|
self->s.frame = FRAME_cluck14 + self->dmg;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (++self->dmg > (FRAME_wait6-FRAME_wait1))
|
|
|
|
self->dmg = 0;
|
|
|
|
|
|
|
|
self->s.frame = FRAME_wait1 + self->dmg;
|
|
|
|
}
|
|
|
|
|
|
|
|
//knockback
|
|
|
|
if(CHICKEN_KNOCKBACK)
|
|
|
|
{
|
|
|
|
gi.trace(self->s.origin, self->mins, self->maxs, self->targetEnt->s.origin, self, self->clipmask,&trace);
|
|
|
|
if(trace.ent)
|
|
|
|
{
|
|
|
|
if(movable(trace.ent))
|
|
|
|
{
|
|
|
|
vec3_t kvel;
|
|
|
|
float mass, force, upvel;
|
|
|
|
|
|
|
|
VectorSubtract(self->targetEnt->s.origin, self->s.origin, vec);
|
|
|
|
|
|
|
|
force = VectorNormalize(vec);
|
|
|
|
mass = VectorLength(trace.ent->size) * 3;
|
|
|
|
|
|
|
|
force = 600.0 * force / mass;
|
|
|
|
|
|
|
|
// Players are not as affected by velocities when they are on the ground, so increase what players experience.
|
|
|
|
if (trace.ent->client && trace.ent->groundentity)
|
|
|
|
force *= 4.0;
|
|
|
|
else if (trace.ent->client) // && !(targ->groundentity)
|
|
|
|
force *= 0.25; // Too much knockback
|
|
|
|
|
|
|
|
if (force > 512) // Cap this speed so it doesn't get insane
|
|
|
|
force=512;
|
|
|
|
VectorScale (vec, force, kvel);
|
|
|
|
|
|
|
|
if (trace.ent->client) // Don't force players up quite so much as monsters.
|
|
|
|
upvel=30;
|
|
|
|
else
|
|
|
|
upvel=120;
|
|
|
|
// Now if the player isn't being forced DOWN very far, let's force them UP a bit.
|
|
|
|
if ((vec[2] > -0.5 || trace.ent->groundentity) && kvel[2] < upvel && force > 30)
|
|
|
|
{ // Don't knock UP the player more than we do across...
|
|
|
|
if (force < upvel)
|
|
|
|
kvel[2] = force;
|
|
|
|
else
|
|
|
|
kvel[2] = upvel;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorAdd (trace.ent->velocity, kvel, trace.ent->velocity);
|
|
|
|
|
|
|
|
if (trace.ent->client) // If player, then set the player flag that will affect this.
|
|
|
|
{
|
|
|
|
trace.ent->client->playerinfo.flags |= PLAYER_FLAG_USE_ENT_POS;
|
|
|
|
// The knockbacktime indicates how long this knockback affects the player.
|
|
|
|
if (force>200 && trace.ent->health>0 && trace.ent->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN && infront(trace.ent, self))
|
|
|
|
{
|
|
|
|
if(self->evade_debounce_time<level.time)
|
|
|
|
{
|
|
|
|
gi.sound(self, CHAN_BODY, gi.soundindex("monsters/pssithra/land.wav") ,1, ATTN_NORM , 0);
|
|
|
|
self->evade_debounce_time = level.time + 3.0;
|
|
|
|
}
|
1999-03-18 00:00:00 +00:00
|
|
|
P_PlayerAnimSetLowerSeq(&trace.ent->client->playerinfo,ASEQ_KNOCKDOWN);
|
|
|
|
P_PlayerAnimSetUpperSeq(&trace.ent->client->playerinfo,ASEQ_NONE);
|
|
|
|
P_TurnOffPlayerEffects(&trace.ent->client->playerinfo);
|
1998-11-24 00:00:00 +00:00
|
|
|
VectorMA (trace.ent->velocity, 3, kvel, trace.ent->velocity);
|
|
|
|
knockbacktime = level.time + 3.0;
|
|
|
|
}
|
|
|
|
else if(force > 500)
|
|
|
|
knockbacktime = level.time + 1.25;
|
|
|
|
else
|
|
|
|
knockbacktime = level.time + (force/400.0);
|
|
|
|
|
|
|
|
if (knockbacktime > trace.ent->client->playerinfo.knockbacktime)
|
|
|
|
trace.ent->client->playerinfo.knockbacktime = knockbacktime;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(force>100)
|
|
|
|
{
|
|
|
|
VectorMA(trace.endpos, -force/5, vec, self->targetEnt->s.origin);
|
|
|
|
VectorScale(self->enemy->rope_end->velocity, -0.5 * force/400 , self->enemy->rope_end->velocity);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
VectorScale(self->enemy->rope_end->velocity, -0.5, self->enemy->rope_end->velocity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
VectorCopy(self->targetEnt->s.origin, self->s.origin);
|
|
|
|
VectorSubtract(self->targetEnt->owner->s.origin, self->s.origin, vec);
|
|
|
|
VectorNormalize(vec);
|
|
|
|
vectoangles(vec, angles);
|
|
|
|
|
|
|
|
//interpolate the angles
|
|
|
|
for (i=0;i<2;i++)
|
|
|
|
{
|
|
|
|
d_ang = (angles[i]-self->s.angles[i]);
|
|
|
|
|
|
|
|
if (i==PITCH)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (d_ang == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (d_ang > 0)
|
|
|
|
{
|
|
|
|
if (Q_fabs(d_ang) > 8)
|
|
|
|
self->s.angles[i] -= 8;
|
|
|
|
else
|
|
|
|
self->s.angles[i] = angles[i];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Q_fabs(d_ang) > 8)
|
|
|
|
self->s.angles[i] += 8;
|
|
|
|
else
|
|
|
|
self->s.angles[i] = angles[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!irand(0,100))
|
|
|
|
{
|
|
|
|
if (irand(0,1))
|
|
|
|
gi.sound (self, CHAN_AUTO, gi.soundindex("monsters/chicken/cluck1.wav"), 1, ATTN_NORM, 0);
|
|
|
|
else
|
|
|
|
gi.sound (self, CHAN_AUTO, gi.soundindex("monsters/chicken/cluck2.wav"), 1, ATTN_NORM, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
|
|
|
|
gi.linkentity(self);
|
|
|
|
self->nextthink = level.time + 0.1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void spawn_hanging_chicken(edict_t *self)
|
|
|
|
{
|
|
|
|
edict_t *end_ent;
|
|
|
|
edict_t *chicken;
|
|
|
|
vec3_t rope_end;
|
|
|
|
short end_id;
|
|
|
|
byte model_type;
|
|
|
|
|
|
|
|
gi.setmodel(self, self->model);
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
|
|
|
|
|
|
//We only need the vertical size from the designer
|
|
|
|
self->maxs[0] = 32;
|
|
|
|
self->maxs[1] = 32;
|
|
|
|
|
|
|
|
self->mins[0] = -32;
|
|
|
|
self->mins[1] = -32;
|
|
|
|
|
|
|
|
self->movetype = PHYSICSTYPE_NONE;
|
|
|
|
self->solid = SOLID_NOT;
|
|
|
|
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
|
|
|
|
end_ent = G_Spawn();
|
|
|
|
end_ent->movetype = PHYSICSTYPE_NONE;
|
|
|
|
end_ent->solid = SOLID_NOT;
|
|
|
|
end_ent->svflags |= SVF_ALWAYS_SEND;
|
|
|
|
end_ent->owner = self;
|
|
|
|
end_id = end_ent->s.number;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, end_ent->s.origin);
|
|
|
|
end_ent->s.origin[2] += self->mins[2];
|
|
|
|
|
|
|
|
VectorClear(end_ent->velocity);
|
|
|
|
|
|
|
|
gi.linkentity(end_ent);
|
|
|
|
|
|
|
|
//gi.setmodel(end_ent, "models/objects/barrel/normal/tris.fm");
|
|
|
|
|
|
|
|
self->rope_end = end_ent;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, rope_end);
|
|
|
|
rope_end[2] += self->mins[2];
|
|
|
|
|
|
|
|
model_type = RM_ROPE;
|
|
|
|
|
|
|
|
self->bloodType = end_ent->bloodType = model_type;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, rope_end);
|
|
|
|
rope_end[2] += self->mins[2];
|
|
|
|
|
|
|
|
gi.CreatePersistantEffect(&self->s, FX_ROPE, CEF_BROADCAST, self->s.origin, "ssbvvv", end_id, end_id, model_type, end_ent->s.origin, end_ent->s.origin, end_ent->s.origin );
|
|
|
|
|
|
|
|
self->think = rope_end_think2;
|
|
|
|
self->nextthink = level.time + 0.1;
|
|
|
|
|
|
|
|
gi.linkentity(self);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
//Now spawn the poor chicken
|
|
|
|
|
|
|
|
chicken = G_Spawn();
|
|
|
|
|
|
|
|
gi.setmodel(chicken, "models/monsters/chicken2/tris.fm");
|
|
|
|
chicken->enemy = self;
|
|
|
|
|
|
|
|
//Hanging by feet
|
|
|
|
chicken->s.angles[PITCH] += 180;
|
|
|
|
chicken->pain = hanging_chicken_pain;
|
|
|
|
chicken->classID = 0;
|
|
|
|
chicken->client = NULL;
|
|
|
|
|
|
|
|
chicken->health = 10000;
|
|
|
|
VectorSet(chicken->mins, -16, -16, -8);
|
|
|
|
VectorSet(chicken->maxs, 16, 16, 16);
|
|
|
|
|
|
|
|
chicken->movetype = PHYSICSTYPE_STEP;
|
|
|
|
chicken->gravity = 0;
|
|
|
|
chicken->solid = SOLID_BBOX;
|
|
|
|
chicken->takedamage = DAMAGE_YES;
|
|
|
|
chicken->clipmask = MASK_MONSTERSOLID;
|
|
|
|
chicken->svflags = SVF_DO_NO_IMPACT_DMG | SVF_ALLOW_AUTO_TARGET;
|
|
|
|
chicken->s.effects = EF_CAMERA_NO_CLIP;
|
|
|
|
|
|
|
|
VectorCopy(self->rope_end->s.origin, chicken->s.origin);
|
|
|
|
|
|
|
|
chicken->targetEnt = self->rope_end;
|
|
|
|
|
|
|
|
chicken->s.scale = 1;
|
|
|
|
chicken->classname = "NATE";
|
|
|
|
chicken->think = hanging_chicken_think;
|
|
|
|
chicken->nextthink = level.time + 0.1;
|
|
|
|
chicken->materialtype = MAT_FLESH;
|
|
|
|
|
|
|
|
self->targetEnt = chicken;
|
|
|
|
|
|
|
|
gi.linkentity(chicken);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SP_obj_rope(edict_t *self)
|
|
|
|
{
|
|
|
|
edict_t *grab_ent, *end_ent;
|
|
|
|
vec3_t rope_end;
|
|
|
|
short grab_id, end_id;
|
|
|
|
byte model_type;
|
|
|
|
|
|
|
|
if (self->spawnflags & ROPEFLAG_HANGING_CHICKEN)
|
|
|
|
{
|
|
|
|
if(self->targetname)
|
|
|
|
self->use = rope_use;
|
|
|
|
else
|
|
|
|
gi.dprintf("Chicken on a Rope with no targetname...\n");
|
|
|
|
spawn_hanging_chicken(self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
gi.setmodel(self, self->model);
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
|
|
|
|
|
|
//We only need the vertical size from the designer
|
|
|
|
self->maxs[0] = 32;
|
|
|
|
self->maxs[1] = 32;
|
|
|
|
|
|
|
|
self->mins[0] = -32;
|
|
|
|
self->mins[1] = -32;
|
|
|
|
|
|
|
|
self->movetype = PHYSICSTYPE_NONE;
|
|
|
|
self->solid = SOLID_TRIGGER;
|
|
|
|
self->touch = rope_touch;
|
|
|
|
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
|
|
|
|
end_ent = G_Spawn();
|
|
|
|
end_ent->movetype = PHYSICSTYPE_NONE;
|
|
|
|
end_ent->solid = SOLID_NOT;
|
|
|
|
end_ent->svflags |= SVF_ALWAYS_SEND;
|
|
|
|
end_id = end_ent->s.number;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, end_ent->s.origin);
|
|
|
|
end_ent->s.origin[2] += self->mins[2];
|
|
|
|
|
|
|
|
VectorClear(end_ent->velocity);
|
|
|
|
|
|
|
|
gi.linkentity(end_ent);
|
|
|
|
|
|
|
|
//gi.setmodel(end_ent, "models/objects/barrel/normal/tris.fm");
|
|
|
|
|
|
|
|
self->rope_end = end_ent;
|
|
|
|
|
|
|
|
grab_ent = G_Spawn();
|
|
|
|
grab_ent->movetype = PHYSICSTYPE_NONE;
|
|
|
|
grab_ent->solid = SOLID_NOT;
|
|
|
|
grab_ent->svflags |= SVF_ALWAYS_SEND;
|
|
|
|
grab_id = grab_ent->s.number;
|
|
|
|
VectorClear(grab_ent->velocity);
|
|
|
|
|
|
|
|
gi.linkentity(grab_ent);
|
|
|
|
|
|
|
|
//gi.setmodel(grab_ent, "models/objects/barrel/normal/tris.fm");
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, grab_ent->s.origin);
|
|
|
|
grab_ent->s.origin[2] += self->maxs[2] + 4;
|
|
|
|
|
|
|
|
self->rope_grab = grab_ent;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, rope_end);
|
|
|
|
rope_end[2] += self->mins[2];
|
|
|
|
|
|
|
|
if (self->spawnflags & ROPEFLAG_CHAIN)
|
|
|
|
{
|
|
|
|
model_type = RM_CHAIN;
|
|
|
|
}
|
|
|
|
else if (self->spawnflags & ROPEFLAG_VINE)
|
|
|
|
{
|
|
|
|
model_type = RM_VINE;
|
|
|
|
}
|
|
|
|
else if (self->spawnflags & ROPEFLAG_TENDRIL)
|
|
|
|
{
|
|
|
|
model_type = RM_TENDRIL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
model_type = RM_ROPE;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->bloodType = grab_ent->bloodType = end_ent->bloodType = model_type;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, rope_end);
|
|
|
|
rope_end[2] += self->mins[2];
|
|
|
|
|
|
|
|
rope_sway(self);
|
|
|
|
|
|
|
|
gi.CreatePersistantEffect(&self->s, FX_ROPE, CEF_BROADCAST, self->s.origin, "ssbvvv", grab_id, end_id, model_type, self->s.origin, grab_ent->s.origin, end_ent->s.origin );
|
|
|
|
|
|
|
|
self->think = rope_sway;
|
|
|
|
self->nextthink = level.time + 1;
|
|
|
|
|
|
|
|
gi.linkentity(self);
|
|
|
|
}
|