#include "g_local.h"
#define PLAT_LOW_TRIGGER 1
#define STATE_TOP 0
#define STATE_BOTTOM 1
#define STATE_UP 2
#define STATE_DOWN 3
#define DOOR_START_OPEN 1
#define DOOR_REVERSE 2
#define DOOR_CRUSHER 4
#define DOOR_NOMONSTER 8
#define DOOR_TOGGLE 32
#define DOOR_X_AXIS 64
#define DOOR_Y_AXIS 128
void Move_Done(edict_t *ent) {
VectorClear(ent->velocity);
ent->moveinfo.endfunc(ent);
}
void Move_Final(edict_t *ent) {
if(ent->moveinfo.remaining_distance == 0) {
Move_Done(ent);
return;
}
VectorScale(ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
ent->think = Move_Done;
ent->nextthink = level.time + FRAMETIME;
}
void Move_Begin(edict_t *ent) {
float frames;
if((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) {
Move_Final(ent);
return;
}
VectorScale(ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
ent->nextthink = level.time + (frames * FRAMETIME);
ent->think = Move_Final;
}
void Think_AccelMove(edict_t *ent);
void Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *)) {
VectorClear(ent->velocity);
VectorSubtract(dest, ent->s.origin, ent->moveinfo.dir);
ent->moveinfo.remaining_distance = VectorNormalize(ent->moveinfo.dir);
ent->moveinfo.endfunc = func;
if(ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) {
if(level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) {
Move_Begin(ent);
} else {
ent->nextthink = level.time + FRAMETIME;
ent->think = Move_Begin;
}
} else {
ent->moveinfo.current_speed = 0;
ent->think = Think_AccelMove;
ent->nextthink = level.time + FRAMETIME;
}
}
void AngleMove_Done(edict_t *ent) {
VectorClear(ent->avelocity);
ent->moveinfo.endfunc(ent);
}
void AngleMove_Final(edict_t *ent) {
vec3_t move;
if(ent->moveinfo.state == STATE_UP)
VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, move);
else
VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, move);
if(VectorCompare(move, vec3_origin)) {
AngleMove_Done(ent);
return;
}
VectorScale(move, 1.0 / FRAMETIME, ent->avelocity);
ent->think = AngleMove_Done;
ent->nextthink = level.time + FRAMETIME;
}
void AngleMove_Begin(edict_t *ent) {
vec3_t destdelta;
float len;
float traveltime;
float frames;
if(ent->moveinfo.state == STATE_UP)
VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, destdelta);
else
VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, destdelta);
len = VectorLength(destdelta);
traveltime = len / ent->moveinfo.speed;
if(traveltime < FRAMETIME) {
AngleMove_Final(ent);
return;
}
frames = floor(traveltime / FRAMETIME);
VectorScale(destdelta, 1.0 / traveltime, ent->avelocity);
ent->nextthink = level.time + frames * FRAMETIME;
ent->think = AngleMove_Final;
}
void AngleMove_Calc(edict_t *ent, void (*func)(edict_t *)) {
VectorClear(ent->avelocity);
ent->moveinfo.endfunc = func;
if(level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) {
AngleMove_Begin(ent);
} else {
ent->nextthink = level.time + FRAMETIME;
ent->think = AngleMove_Begin;
}
}
#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2)
void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) {
float accel_dist;
float decel_dist;
moveinfo->move_speed = moveinfo->speed;
if(moveinfo->remaining_distance < moveinfo->accel) {
moveinfo->current_speed = moveinfo->remaining_distance;
return;
}
accel_dist = AccelerationDistance(moveinfo->speed, moveinfo->accel);
decel_dist = AccelerationDistance(moveinfo->speed, moveinfo->decel);
if((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) {
float f;
f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel);
}
moveinfo->decel_distance = decel_dist;
};
void plat_Accelerate(moveinfo_t *moveinfo) {
if(moveinfo->remaining_distance <= moveinfo->decel_distance) {
if(moveinfo->remaining_distance < moveinfo->decel_distance) {
if(moveinfo->next_speed) {
moveinfo->current_speed = moveinfo->next_speed;
moveinfo->next_speed = 0;
return;
}
if(moveinfo->current_speed > moveinfo->decel)
moveinfo->current_speed -= moveinfo->decel;
}
return;
}
if(moveinfo->current_speed == moveinfo->move_speed)
if((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) {
float p1_distance;
float p2_distance;
float distance;
p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
distance = p1_distance + p2_distance;
moveinfo->current_speed = moveinfo->move_speed;
moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
return;
}
if(moveinfo->current_speed < moveinfo->speed) {
float old_speed;
float p1_distance;
float p1_speed;
float p2_distance;
float distance;
old_speed = moveinfo->current_speed;
moveinfo->current_speed += moveinfo->accel;
if(moveinfo->current_speed > moveinfo->speed)
moveinfo->current_speed = moveinfo->speed;
if((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
return;
p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
distance = p1_distance + p2_distance;
moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
return;
}
return;
};
void Think_AccelMove(edict_t *ent) {
ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
if(ent->moveinfo.current_speed == 0) plat_CalcAcceleratedMove(&ent->moveinfo);
plat_Accelerate(&ent->moveinfo);
if(ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) {
Move_Final(ent);
return;
}
VectorScale(ent->moveinfo.dir, ent->moveinfo.current_speed * 10, ent->velocity);
ent->nextthink = level.time + FRAMETIME;
ent->think = Think_AccelMove;
}
void plat_go_down(edict_t *ent);
void plat_hit_top(edict_t *ent) {
if(!(ent->flags & FL_TEAMSLAVE)) {
if(ent->moveinfo.sound_end)
gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
ent->s.sound = 0;
}
ent->moveinfo.state = STATE_TOP;
ent->think = plat_go_down;
ent->nextthink = level.time + 3;
}
void plat_hit_bottom(edict_t *ent) {
if(!(ent->flags & FL_TEAMSLAVE)) {
if(ent->moveinfo.sound_end)
gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
ent->s.sound = 0;
}
ent->moveinfo.state = STATE_BOTTOM;
}
void plat_go_down(edict_t *ent) {
if(!(ent->flags & FL_TEAMSLAVE)) {
if(ent->moveinfo.sound_start)
gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
ent->s.sound = ent->moveinfo.sound_middle;
}
ent->moveinfo.state = STATE_DOWN;
Move_Calc(ent, ent->moveinfo.end_origin, plat_hit_bottom);
}
void plat_go_up(edict_t *ent) {
if(!(ent->flags & FL_TEAMSLAVE)) {
if(ent->moveinfo.sound_start)
gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
ent->s.sound = ent->moveinfo.sound_middle;
}
ent->moveinfo.state = STATE_UP;
Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top);
}
void plat_blocked(edict_t *self, edict_t *other) {
if(!(other->svflags & SVF_MONSTER) && (!other->client)) {
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
if(other)
BecomeExplosion1(other);
return;
}
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
if(self->moveinfo.state == STATE_UP)
plat_go_down(self);
else if(self->moveinfo.state == STATE_DOWN)
plat_go_up(self);
}
void Use_Plat(edict_t *ent, edict_t *other, edict_t *activator) {
if(ent->think)
return; plat_go_down(ent);
}
void Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) {
if(!other->client)
return;
if(ent_read_health(other)->value <= 0)
return;
ent = ent->enemy; if(ent->moveinfo.state == STATE_BOTTOM)
plat_go_up(ent);
else if(ent->moveinfo.state == STATE_TOP)
ent->nextthink = level.time + 1; }
void plat_spawn_inside_trigger(edict_t *ent) {
edict_t *trigger;
vec3_t tmin, tmax;
trigger = G_Spawn(ent->s.cmodel_index);
trigger->touch = Touch_Plat_Center;
trigger->movetype = MOVETYPE_NONE;
trigger->solid = SOLID_TRIGGER;
trigger->enemy = ent;
tmin[0] = ent->mins[0] + 25;
tmin[1] = ent->mins[1] + 25;
tmin[2] = ent->mins[2];
tmax[0] = ent->maxs[0] - 25;
tmax[1] = ent->maxs[1] - 25;
tmax[2] = ent->maxs[2] + 8;
tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
if(ent->spawnflags & PLAT_LOW_TRIGGER)
tmax[2] = tmin[2] + 8;
if(tmax[0] - tmin[0] <= 0) {
tmin[0] = (ent->mins[0] + ent->maxs[0]) * 0.5;
tmax[0] = tmin[0] + 1;
}
if(tmax[1] - tmin[1] <= 0) {
tmin[1] = (ent->mins[1] + ent->maxs[1]) * 0.5;
tmax[1] = tmin[1] + 1;
}
VectorCopy(tmin, trigger->mins);
VectorCopy(tmax, trigger->maxs);
gi.linkentity(trigger);
}
void SP_func_plat(edict_t *ent) {
VectorClear(ent->s.angles);
ent->solid = SOLID_BSP;
ent->movetype = MOVETYPE_PUSH;
gi.setmodel(ent, ent->model);
ent->blocked = plat_blocked;
if(!ent->speed)
ent->speed = 20;
else
ent->speed *= 0.1;
if(!ent->accel)
ent->accel = 5;
else
ent->accel *= 0.1;
if(!ent->decel)
ent->decel = 5;
else
ent->decel *= 0.1;
if(!ent->dmg)
ent->dmg = 2;
if(!st.lip)
st.lip = 8;
VectorCopy(ent->s.origin, ent->pos1);
VectorCopy(ent->s.origin, ent->pos2);
if(st.height)
ent->pos2[2] -= st.height;
else
ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
ent->use = Use_Plat;
plat_spawn_inside_trigger(ent);
if(ent->targetname) {
ent->moveinfo.state = STATE_UP;
} else {
VectorCopy(ent->pos2, ent->s.origin);
gi.linkentity(ent);
ent->moveinfo.state = STATE_BOTTOM;
}
ent->moveinfo.speed = ent->speed;
ent->moveinfo.accel = ent->accel;
ent->moveinfo.decel = ent->decel;
ent->moveinfo.wait = ent->wait;
VectorCopy(ent->pos1, ent->moveinfo.start_origin);
VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
VectorCopy(ent->pos2, ent->moveinfo.end_origin);
VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
ent->moveinfo.sound_start = gi.soundindex("plats/pt1_strt.wav");
ent->moveinfo.sound_middle = gi.soundindex("plats/pt1_mid.wav");
ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav");
}
void rotating_blocked(edict_t *self, edict_t *other) {
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
}
void rotating_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) {
if(self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
}
void rotating_use(edict_t *self, edict_t *other, edict_t *activator) {
if(!VectorCompare(self->avelocity, vec3_origin)) {
self->s.sound = 0;
VectorClear(self->avelocity);
self->touch = NULL;
} else {
self->s.sound = self->moveinfo.sound_middle;
VectorScale(self->movedir, self->speed, self->avelocity);
if(self->spawnflags & 16)
self->touch = rotating_touch;
}
}
void SP_func_rotating(edict_t *ent) {
ent->solid = SOLID_BSP;
if(ent->spawnflags & 32)
ent->movetype = MOVETYPE_STOP;
else
ent->movetype = MOVETYPE_PUSH;
VectorClear(ent->movedir);
if(ent->spawnflags & 4)
ent->movedir[2] = 1.0;
else if(ent->spawnflags & 8)
ent->movedir[0] = 1.0;
else ent->movedir[1] = 1.0;
if(ent->spawnflags & 2)
VectorNegate(ent->movedir, ent->movedir);
if(!ent->speed)
ent->speed = 100;
if(!ent->dmg)
ent->dmg = 2;
ent->use = rotating_use;
if(ent->dmg)
ent->blocked = rotating_blocked;
if(ent->spawnflags & 1)
ent->use(ent, NULL, NULL);
if(ent->spawnflags & 64)
ent->s.effects |= EF_ANIM_ALL;
if(ent->spawnflags & 128)
ent->s.effects |= EF_ANIM_ALLFAST;
gi.setmodel(ent, ent->model);
gi.linkentity(ent);
}
void button_done(edict_t *self) {
self->moveinfo.state = STATE_BOTTOM;
self->s.effects &= ~EF_ANIM23;
self->s.effects |= EF_ANIM01;
}
void button_return(edict_t *self) {
self->moveinfo.state = STATE_DOWN;
Move_Calc(self, self->moveinfo.start_origin, button_done);
self->s.frame = 0;
if(ent_read_health(self)->value)
self->takedamage = DAMAGE_YES;
}
void button_wait(edict_t *self) {
self->moveinfo.state = STATE_TOP;
self->s.effects &= ~EF_ANIM01;
self->s.effects |= EF_ANIM23;
G_UseTargets(self, self->activator);
self->s.frame = 1;
if(self->moveinfo.wait >= 0) {
self->nextthink = level.time + self->moveinfo.wait;
self->think = button_return;
}
}
void button_fire(edict_t *self) {
if(self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
return;
self->moveinfo.state = STATE_UP;
if(self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
Move_Calc(self, self->moveinfo.end_origin, button_wait);
}
void button_use(edict_t *self, edict_t *other, edict_t *activator) {
self->activator = activator;
button_fire(self);
}
void button_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) {
if(!other->client)
return;
if(ent_read_health(other)->value <= 0)
return;
self->activator = other;
button_fire(self);
}
void button_killed(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) {
self->activator = attacker;
dv_set(ent_write_health(self), cv_get(ent_read_max_health(self)));
self->takedamage = DAMAGE_NO;
button_fire(self);
}
void SP_func_button(edict_t *ent) {
vec3_t abs_movedir;
float dist;
G_SetMovedir(ent->s.angles, ent->movedir);
ent->movetype = MOVETYPE_STOP;
ent->solid = SOLID_BSP;
gi.setmodel(ent, ent->model);
if(ent->sounds != 1)
ent->moveinfo.sound_start = gi.soundindex("switches/butn2.wav");
if(!ent->speed)
ent->speed = 40;
if(!ent->accel)
ent->accel = ent->speed;
if(!ent->decel)
ent->decel = ent->speed;
if(!ent->wait)
ent->wait = 3;
if(!st.lip)
st.lip = 4;
VectorCopy(ent->s.origin, ent->pos1);
abs_movedir[0] = fabs(ent->movedir[0]);
abs_movedir[1] = fabs(ent->movedir[1]);
abs_movedir[2] = fabs(ent->movedir[2]);
dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
VectorMA(ent->pos1, dist, ent->movedir, ent->pos2);
ent->use = button_use;
ent->s.effects |= EF_ANIM01;
if(ent_read_health(ent)->value) {
cv_initialize(ent_write_max_health(ent), ent_read_health(ent)->value);
ent->die = button_killed;
ent->takedamage = DAMAGE_YES;
} else if(!ent->targetname)
ent->touch = button_touch;
ent->moveinfo.state = STATE_BOTTOM;
ent->moveinfo.speed = ent->speed;
ent->moveinfo.accel = ent->accel;
ent->moveinfo.decel = ent->decel;
ent->moveinfo.wait = ent->wait;
VectorCopy(ent->pos1, ent->moveinfo.start_origin);
VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
VectorCopy(ent->pos2, ent->moveinfo.end_origin);
VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
gi.linkentity(ent);
}
void door_use_areaportals(edict_t *self, bool open) {
edict_t *t = NULL;
if(!self->target)
return;
while((t = G_Find(self->s.cmodel_index, t, FOFS(targetname), self->target))) {
if(Q_stricmp(t->classname, "func_areaportal") == 0) {
gi.SetAreaPortalState(self->s.cmodel_index, t->style, open);
}
}
}
void door_go_down(edict_t *self);
void door_hit_top(edict_t *self) {
if(!(self->flags & FL_TEAMSLAVE)) {
if(self->moveinfo.sound_end)
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
self->s.sound = 0;
}
self->moveinfo.state = STATE_TOP;
if(self->spawnflags & DOOR_TOGGLE)
return;
if(self->moveinfo.wait >= 0) {
self->think = door_go_down;
self->nextthink = level.time + self->moveinfo.wait;
}
}
void door_hit_bottom(edict_t *self) {
if(!(self->flags & FL_TEAMSLAVE)) {
if(self->moveinfo.sound_end)
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
self->s.sound = 0;
}
self->moveinfo.state = STATE_BOTTOM;
door_use_areaportals(self, false);
}
void door_go_down(edict_t *self) {
if(!(self->flags & FL_TEAMSLAVE)) {
if(self->moveinfo.sound_start)
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
self->s.sound = self->moveinfo.sound_middle;
}
if(cv_get(ent_read_max_health(self))) {
self->takedamage = DAMAGE_YES;
dv_set(ent_write_health(self), cv_get(ent_read_max_health(self)));
}
self->moveinfo.state = STATE_DOWN;
if(strcmp(self->classname, "func_door") == 0)
Move_Calc(self, self->moveinfo.start_origin, door_hit_bottom);
else if(strcmp(self->classname, "func_door_rotating") == 0)
AngleMove_Calc(self, door_hit_bottom);
}
void door_go_up(edict_t *self, edict_t *activator) {
if(self->moveinfo.state == STATE_UP)
return;
if(self->moveinfo.state == STATE_TOP) { if(self->moveinfo.wait >= 0)
self->nextthink = level.time + self->moveinfo.wait;
return;
}
if(!(self->flags & FL_TEAMSLAVE)) {
if(self->moveinfo.sound_start)
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
self->s.sound = self->moveinfo.sound_middle;
}
self->moveinfo.state = STATE_UP;
if(strcmp(self->classname, "func_door") == 0)
Move_Calc(self, self->moveinfo.end_origin, door_hit_top);
else if(strcmp(self->classname, "func_door_rotating") == 0)
AngleMove_Calc(self, door_hit_top);
G_UseTargets(self, activator);
door_use_areaportals(self, true);
}
void door_use(edict_t *self, edict_t *other, edict_t *activator) {
edict_t *ent;
if(self->flags & FL_TEAMSLAVE)
return;
if(self->spawnflags & DOOR_TOGGLE) {
if(self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) {
for(ent = self; ent; ent = ent->teamchain) {
ent->message = NULL;
ent->touch = NULL;
door_go_down(ent);
}
return;
}
}
for(ent = self; ent; ent = ent->teamchain) {
ent->message = NULL;
ent->touch = NULL;
door_go_up(ent, activator);
}
};
void Touch_DoorTrigger(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) {
if(ent_read_health(other) <= 0)
return;
if(!(other->svflags & SVF_MONSTER) && (!other->client))
return;
if((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
return;
if(level.time < self->touch_debounce_time)
return;
self->touch_debounce_time = level.time + 1.0;
door_use(self->owner, other, other);
}
void Think_CalcMoveSpeed(edict_t *self) {
edict_t *ent;
float min;
float time;
float newspeed;
float ratio;
float dist;
if(self->flags & FL_TEAMSLAVE)
return;
min = fabs(self->moveinfo.distance);
for(ent = self->teamchain; ent; ent = ent->teamchain) {
dist = fabs(ent->moveinfo.distance);
if(dist < min)
min = dist;
}
time = min / self->moveinfo.speed;
for(ent = self; ent; ent = ent->teamchain) {
newspeed = fabs(ent->moveinfo.distance) / time;
ratio = newspeed / ent->moveinfo.speed;
if(ent->moveinfo.accel == ent->moveinfo.speed)
ent->moveinfo.accel = newspeed;
else
ent->moveinfo.accel *= ratio;
if(ent->moveinfo.decel == ent->moveinfo.speed)
ent->moveinfo.decel = newspeed;
else
ent->moveinfo.decel *= ratio;
ent->moveinfo.speed = newspeed;
}
}
void Think_SpawnDoorTrigger(edict_t *ent) {
edict_t *other;
vec3_t mins, maxs;
if(ent->flags & FL_TEAMSLAVE)
return;
VectorCopy(ent->absmin, mins);
VectorCopy(ent->absmax, maxs);
for(other = ent->teamchain; other; other = other->teamchain) {
AddPointToBounds(other->absmin, mins, maxs);
AddPointToBounds(other->absmax, mins, maxs);
}
mins[0] -= 60;
mins[1] -= 60;
maxs[0] += 60;
maxs[1] += 60;
other = G_Spawn(ent->s.cmodel_index);
VectorCopy(mins, other->mins);
VectorCopy(maxs, other->maxs);
other->owner = ent;
other->solid = SOLID_TRIGGER;
other->movetype = MOVETYPE_NONE;
other->touch = Touch_DoorTrigger;
gi.linkentity(other);
if(ent->spawnflags & DOOR_START_OPEN)
door_use_areaportals(ent, true);
Think_CalcMoveSpeed(ent);
}
void door_blocked(edict_t *self, edict_t *other) {
edict_t *ent;
if(!(other->svflags & SVF_MONSTER) && (!other->client)) {
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
if(other)
BecomeExplosion1(other);
return;
}
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
if(self->spawnflags & DOOR_CRUSHER)
return;
if(self->moveinfo.wait >= 0) {
if(self->moveinfo.state == STATE_DOWN) {
for(ent = self->teammaster; ent; ent = ent->teamchain)
door_go_up(ent, ent->activator);
} else {
for(ent = self->teammaster; ent; ent = ent->teamchain)
door_go_down(ent);
}
}
}
void door_killed(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) {
edict_t *ent;
for(ent = self->teammaster; ent; ent = ent->teamchain) {
dv_set(ent_write_health(ent), cv_get(ent_read_max_health(ent)));
ent->takedamage = DAMAGE_NO;
}
door_use(self->teammaster, attacker, attacker);
}
void door_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) {
if(!other->client)
return;
if(level.time < self->touch_debounce_time)
return;
self->touch_debounce_time = level.time + 5.0;
gi.centerprintf(other, "%s", self->message);
gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
}
void SP_func_door(edict_t *ent) {
vec3_t abs_movedir;
if(ent->sounds != 1) {
ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
}
G_SetMovedir(ent->s.angles, ent->movedir);
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_BSP;
gi.setmodel(ent, ent->model);
ent->blocked = door_blocked;
ent->use = door_use;
if(!ent->speed)
ent->speed = 100;
if(deathmatch->value)
ent->speed *= 2;
if(!ent->accel)
ent->accel = ent->speed;
if(!ent->decel)
ent->decel = ent->speed;
if(!ent->wait)
ent->wait = 3;
if(!st.lip)
st.lip = 8;
if(!ent->dmg)
ent->dmg = 2;
VectorCopy(ent->s.origin, ent->pos1);
abs_movedir[0] = fabs(ent->movedir[0]);
abs_movedir[1] = fabs(ent->movedir[1]);
abs_movedir[2] = fabs(ent->movedir[2]);
ent->moveinfo.distance =
abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
VectorMA(ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
if(ent->spawnflags & DOOR_START_OPEN) {
VectorCopy(ent->pos2, ent->s.origin);
VectorCopy(ent->pos1, ent->pos2);
VectorCopy(ent->s.origin, ent->pos1);
}
ent->moveinfo.state = STATE_BOTTOM;
if(ent_read_health(ent)->value) {
ent->takedamage = DAMAGE_YES;
ent->die = door_killed;
cv_initialize(ent_write_max_health(ent), ent_read_health(ent)->value);
} else if(ent->targetname && ent->message) {
gi.soundindex("misc/talk.wav");
ent->touch = door_touch;
}
ent->moveinfo.speed = ent->speed;
ent->moveinfo.accel = ent->accel;
ent->moveinfo.decel = ent->decel;
ent->moveinfo.wait = ent->wait;
VectorCopy(ent->pos1, ent->moveinfo.start_origin);
VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
VectorCopy(ent->pos2, ent->moveinfo.end_origin);
VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
if(ent->spawnflags & 16)
ent->s.effects |= EF_ANIM_ALL;
if(ent->spawnflags & 64)
ent->s.effects |= EF_ANIM_ALLFAST;
if(!ent->team)
ent->teammaster = ent;
gi.linkentity(ent);
ent->nextthink = level.time + FRAMETIME;
if(ent_read_health(ent)->value || ent->targetname)
ent->think = Think_CalcMoveSpeed;
else
ent->think = Think_SpawnDoorTrigger;
}
void SP_func_door_rotating(edict_t *ent) {
VectorClear(ent->s.angles);
VectorClear(ent->movedir);
if(ent->spawnflags & DOOR_X_AXIS)
ent->movedir[2] = 1.0;
else if(ent->spawnflags & DOOR_Y_AXIS)
ent->movedir[0] = 1.0;
else ent->movedir[1] = 1.0;
if(ent->spawnflags & DOOR_REVERSE)
VectorNegate(ent->movedir, ent->movedir);
if(!st.distance) {
gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
st.distance = 90;
}
VectorCopy(ent->s.angles, ent->pos1);
VectorMA(ent->s.angles, st.distance, ent->movedir, ent->pos2);
ent->moveinfo.distance = st.distance;
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_BSP;
gi.setmodel(ent, ent->model);
ent->blocked = door_blocked;
ent->use = door_use;
if(!ent->speed)
ent->speed = 100;
if(!ent->accel)
ent->accel = ent->speed;
if(!ent->decel)
ent->decel = ent->speed;
if(!ent->wait)
ent->wait = 3;
if(!ent->dmg)
ent->dmg = 2;
if(ent->sounds != 1) {
ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
}
if(ent->spawnflags & DOOR_START_OPEN) {
VectorCopy(ent->pos2, ent->s.angles);
VectorCopy(ent->pos1, ent->pos2);
VectorCopy(ent->s.angles, ent->pos1);
VectorNegate(ent->movedir, ent->movedir);
}
if(ent_read_health(ent)->value) {
ent->takedamage = DAMAGE_YES;
ent->die = door_killed;
cv_initialize(ent_write_max_health(ent), ent_read_health(ent)->value);
}
if(ent->targetname && ent->message) {
gi.soundindex("misc/talk.wav");
ent->touch = door_touch;
}
ent->moveinfo.state = STATE_BOTTOM;
ent->moveinfo.speed = ent->speed;
ent->moveinfo.accel = ent->accel;
ent->moveinfo.decel = ent->decel;
ent->moveinfo.wait = ent->wait;
VectorCopy(ent->s.origin, ent->moveinfo.start_origin);
VectorCopy(ent->pos1, ent->moveinfo.start_angles);
VectorCopy(ent->s.origin, ent->moveinfo.end_origin);
VectorCopy(ent->pos2, ent->moveinfo.end_angles);
if(ent->spawnflags & 16)
ent->s.effects |= EF_ANIM_ALL;
if(!ent->team)
ent->teammaster = ent;
gi.linkentity(ent);
ent->nextthink = level.time + FRAMETIME;
if(ent_read_health(ent)->value || ent->targetname)
ent->think = Think_CalcMoveSpeed;
else
ent->think = Think_SpawnDoorTrigger;
}
void SP_func_water(edict_t *self) {
vec3_t abs_movedir;
G_SetMovedir(self->s.angles, self->movedir);
self->movetype = MOVETYPE_PUSH;
self->solid = SOLID_BSP;
gi.setmodel(self, self->model);
switch(self->sounds) {
default:
break;
case 1: self->moveinfo.sound_start = gi.soundindex("world/mov_watr.wav");
self->moveinfo.sound_end = gi.soundindex("world/stp_watr.wav");
break;
case 2: self->moveinfo.sound_start = gi.soundindex("world/mov_watr.wav");
self->moveinfo.sound_end = gi.soundindex("world/stp_watr.wav");
break;
}
VectorCopy(self->s.origin, self->pos1);
abs_movedir[0] = fabs(self->movedir[0]);
abs_movedir[1] = fabs(self->movedir[1]);
abs_movedir[2] = fabs(self->movedir[2]);
self->moveinfo.distance =
abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
VectorMA(self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
if(self->spawnflags & DOOR_START_OPEN) {
VectorCopy(self->pos2, self->s.origin);
VectorCopy(self->pos1, self->pos2);
VectorCopy(self->s.origin, self->pos1);
}
VectorCopy(self->pos1, self->moveinfo.start_origin);
VectorCopy(self->s.angles, self->moveinfo.start_angles);
VectorCopy(self->pos2, self->moveinfo.end_origin);
VectorCopy(self->s.angles, self->moveinfo.end_angles);
self->moveinfo.state = STATE_BOTTOM;
if(!self->speed)
self->speed = 25;
self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
if(!self->wait)
self->wait = -1;
self->moveinfo.wait = self->wait;
self->use = door_use;
if(self->wait == -1)
self->spawnflags |= DOOR_TOGGLE;
self->classname = "func_door";
gi.linkentity(self);
}
#define TRAIN_START_ON 1
#define TRAIN_TOGGLE 2
#define TRAIN_BLOCK_STOPS 4
void train_next(edict_t *self);
void train_blocked(edict_t *self, edict_t *other) {
if(!(other->svflags & SVF_MONSTER) && (!other->client)) {
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
if(other)
BecomeExplosion1(other);
return;
}
if(level.time < self->touch_debounce_time)
return;
if(!self->dmg)
return;
self->touch_debounce_time = level.time + 0.5;
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
}
void train_wait(edict_t *self) {
if(self->target_ent->pathtarget) {
char *savetarget;
edict_t *ent;
ent = self->target_ent;
savetarget = ent->target;
ent->target = ent->pathtarget;
G_UseTargets(ent, self->activator);
ent->target = savetarget;
if(!self->inuse)
return;
}
if(self->moveinfo.wait) {
if(self->moveinfo.wait > 0) {
self->nextthink = level.time + self->moveinfo.wait;
self->think = train_next;
} else if(self->spawnflags & TRAIN_TOGGLE) {
train_next(self);
self->spawnflags &= ~TRAIN_START_ON;
VectorClear(self->velocity);
self->nextthink = 0;
}
if(!(self->flags & FL_TEAMSLAVE)) {
if(self->moveinfo.sound_end)
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
self->s.sound = 0;
}
} else {
train_next(self);
}
}
void train_next(edict_t *self) {
edict_t *ent;
vec3_t dest;
bool first;
first = true;
again:
if(!self->target) {
return;
}
ent = G_PickTarget(self->s.cmodel_index, self->target);
if(!ent) {
gi.dprintf("train_next: bad target %s\n", self->target);
return;
}
self->target = ent->target;
if(ent->spawnflags & 1) {
if(!first) {
gi.dprintf("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
return;
}
first = false;
VectorSubtract(ent->s.origin, self->mins, self->s.origin);
VectorCopy(self->s.origin, self->s.old_origin);
self->s.event = EV_OTHER_TELEPORT;
gi.linkentity(self);
goto again;
}
self->moveinfo.wait = ent->wait;
self->target_ent = ent;
if(!(self->flags & FL_TEAMSLAVE)) {
if(self->moveinfo.sound_start)
gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
self->s.sound = self->moveinfo.sound_middle;
}
VectorSubtract(ent->s.origin, self->mins, dest);
self->moveinfo.state = STATE_TOP;
VectorCopy(self->s.origin, self->moveinfo.start_origin);
VectorCopy(dest, self->moveinfo.end_origin);
Move_Calc(self, dest, train_wait);
self->spawnflags |= TRAIN_START_ON;
}
void train_resume(edict_t *self) {
edict_t *ent;
vec3_t dest;
ent = self->target_ent;
VectorSubtract(ent->s.origin, self->mins, dest);
self->moveinfo.state = STATE_TOP;
VectorCopy(self->s.origin, self->moveinfo.start_origin);
VectorCopy(dest, self->moveinfo.end_origin);
Move_Calc(self, dest, train_wait);
self->spawnflags |= TRAIN_START_ON;
}
void func_train_find(edict_t *self) {
edict_t *ent;
if(!self->target) {
gi.dprintf("train_find: no target\n");
return;
}
ent = G_PickTarget(self->s.cmodel_index, self->target);
if(!ent) {
gi.dprintf("train_find: target %s not found\n", self->target);
return;
}
self->target = ent->target;
VectorSubtract(ent->s.origin, self->mins, self->s.origin);
gi.linkentity(self);
if(!self->targetname)
self->spawnflags |= TRAIN_START_ON;
if(self->spawnflags & TRAIN_START_ON) {
self->nextthink = level.time + FRAMETIME;
self->think = train_next;
self->activator = self;
}
}
void train_use(edict_t *self, edict_t *other, edict_t *activator) {
self->activator = activator;
if(self->spawnflags & TRAIN_START_ON) {
if(!(self->spawnflags & TRAIN_TOGGLE))
return;
self->spawnflags &= ~TRAIN_START_ON;
VectorClear(self->velocity);
self->nextthink = 0;
} else {
if(self->target_ent)
train_resume(self);
else
train_next(self);
}
}
void SP_func_train(edict_t *self) {
self->movetype = MOVETYPE_PUSH;
VectorClear(self->s.angles);
self->blocked = train_blocked;
if(self->spawnflags & TRAIN_BLOCK_STOPS)
self->dmg = 0;
else {
if(!self->dmg)
self->dmg = 100;
}
self->solid = SOLID_BSP;
gi.setmodel(self, self->model);
if(st.noise)
self->moveinfo.sound_middle = gi.soundindex(st.noise);
if(!self->speed)
self->speed = 100;
self->moveinfo.speed = self->speed;
self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
self->use = train_use;
gi.linkentity(self);
if(self->target) {
self->nextthink = level.time + FRAMETIME;
self->think = func_train_find;
} else {
gi.dprintf("func_train without a target at %s\n", vtos(self->absmin));
}
}
void trigger_elevator_use(edict_t *self, edict_t *other, edict_t *activator) {
edict_t *target;
if(self->movetarget->nextthink) {
return;
}
if(!other->pathtarget) {
gi.dprintf("elevator used with no pathtarget\n");
return;
}
target = G_PickTarget(self->s.cmodel_index, other->pathtarget);
if(!target) {
gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
return;
}
self->movetarget->target_ent = target;
train_resume(self->movetarget);
}
void trigger_elevator_init(edict_t *self) {
if(!self->target) {
gi.dprintf("trigger_elevator has no target\n");
return;
}
self->movetarget = G_PickTarget(self->s.cmodel_index, self->target);
if(!self->movetarget) {
gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
return;
}
if(strcmp(self->movetarget->classname, "func_train") != 0) {
gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
return;
}
self->use = trigger_elevator_use;
self->svflags = SVF_NOCLIENT;
}
void SP_trigger_elevator(edict_t *self) {
self->think = trigger_elevator_init;
self->nextthink = level.time + FRAMETIME;
}
void func_timer_think(edict_t *self) {
G_UseTargets(self, self->activator);
self->nextthink = level.time + self->wait + crandom() * self->random;
}
void func_timer_use(edict_t *self, edict_t *other, edict_t *activator) {
self->activator = activator;
if(self->nextthink) {
self->nextthink = 0;
return;
}
if(self->delay)
self->nextthink = level.time + self->delay;
else
func_timer_think(self);
}
void SP_func_timer(edict_t *self) {
if(!self->wait)
self->wait = 1.0;
self->use = func_timer_use;
self->think = func_timer_think;
if(self->random >= self->wait) {
self->random = self->wait - FRAMETIME;
gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
}
if(self->spawnflags & 1) {
self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
self->activator = self;
}
self->svflags = SVF_NOCLIENT;
}
void func_conveyor_use(edict_t *self, edict_t *other, edict_t *activator) {
if(self->spawnflags & 1) {
self->speed = 0;
self->spawnflags &= ~1;
} else {
self->speed = self->count;
self->spawnflags |= 1;
}
if(!(self->spawnflags & 2))
self->count = 0;
}
void SP_func_conveyor(edict_t *self) {
if(!self->speed)
self->speed = 100;
if(!(self->spawnflags & 1)) {
self->count = self->speed;
self->speed = 0;
}
self->use = func_conveyor_use;
gi.setmodel(self, self->model);
self->solid = SOLID_BSP;
gi.linkentity(self);
}
#define SECRET_ALWAYS_SHOOT 1
#define SECRET_1ST_LEFT 2
#define SECRET_1ST_DOWN 4
void door_secret_move1(edict_t *self);
void door_secret_move2(edict_t *self);
void door_secret_move3(edict_t *self);
void door_secret_move4(edict_t *self);
void door_secret_move5(edict_t *self);
void door_secret_move6(edict_t *self);
void door_secret_done(edict_t *self);
void door_secret_use(edict_t *self, edict_t *other, edict_t *activator) {
if(!VectorCompare(self->s.origin, vec3_origin))
return;
Move_Calc(self, self->pos1, door_secret_move1);
door_use_areaportals(self, true);
}
void door_secret_move1(edict_t *self) {
self->nextthink = level.time + 1.0;
self->think = door_secret_move2;
}
void door_secret_move2(edict_t *self) { Move_Calc(self, self->pos2, door_secret_move3); }
void door_secret_move3(edict_t *self) {
if(self->wait == -1)
return;
self->nextthink = level.time + self->wait;
self->think = door_secret_move4;
}
void door_secret_move4(edict_t *self) { Move_Calc(self, self->pos1, door_secret_move5); }
void door_secret_move5(edict_t *self) {
self->nextthink = level.time + 1.0;
self->think = door_secret_move6;
}
void door_secret_move6(edict_t *self) { Move_Calc(self, vec3_origin, door_secret_done); }
void door_secret_done(edict_t *self) {
if(!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) {
dv_set(ent_write_health(self), 0);
self->takedamage = DAMAGE_YES;
}
door_use_areaportals(self, false);
}
void door_secret_blocked(edict_t *self, edict_t *other) {
if(!(other->svflags & SVF_MONSTER) && (!other->client)) {
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
if(other)
BecomeExplosion1(other);
return;
}
if(level.time < self->touch_debounce_time)
return;
self->touch_debounce_time = level.time + 0.5;
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
}
void door_secret_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) {
self->takedamage = DAMAGE_NO;
door_secret_use(self, attacker, attacker);
}
void SP_func_door_secret(edict_t *ent) {
vec3_t forward, right, up;
float side;
float width;
float length;
ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_BSP;
gi.setmodel(ent, ent->model);
ent->blocked = door_secret_blocked;
ent->use = door_secret_use;
if(!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) {
dv_set(ent_write_health(ent), 0);
ent->takedamage = DAMAGE_YES;
ent->die = door_secret_die;
}
if(!ent->dmg)
ent->dmg = 2;
if(!ent->wait)
ent->wait = 5;
ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = 50;
AngleVectors(ent->s.angles, forward, right, up);
VectorClear(ent->s.angles);
side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
if(ent->spawnflags & SECRET_1ST_DOWN)
width = fabs(DotProduct(up, ent->size));
else
width = fabs(DotProduct(right, ent->size));
length = fabs(DotProduct(forward, ent->size));
if(ent->spawnflags & SECRET_1ST_DOWN)
VectorMA(ent->s.origin, -1 * width, up, ent->pos1);
else
VectorMA(ent->s.origin, side * width, right, ent->pos1);
VectorMA(ent->pos1, length, forward, ent->pos2);
if(ent_read_health(ent)->value) {
ent->takedamage = DAMAGE_YES;
ent->die = door_killed;
cv_initialize(ent_write_max_health(ent), ent_read_health(ent)->value);
} else if(ent->targetname && ent->message) {
gi.soundindex("misc/talk.wav");
ent->touch = door_touch;
}
ent->classname = "func_door";
gi.linkentity(ent);
}
void use_killbox(edict_t *self, edict_t *other, edict_t *activator) { KillBox(self); }
void SP_func_killbox(edict_t *ent) {
gi.setmodel(ent, ent->model);
ent->use = use_killbox;
ent->svflags = SVF_NOCLIENT;
}