void SUB_NullThink(void) { }
-void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
void() SUB_CalcMoveDone;
void() SUB_CalcAngleMoveDone;
//void() SUB_UseTargets;
self.think1 ();
}
+.float platmovetype_turn;
void SUB_CalcMove_controller_think (void)
{
entity oldself;
vector delta;
vector delta2;
vector veloc;
+ vector angloc;
vector nextpos;
delta = self.destvec;
delta2 = self.destvec2;
traveltime = self.animstate_endtime - self.animstate_starttime;
phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
- if(self.platmovetype != 1)
- {
- phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
- phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
- phasepos = phasepos + 1; // correct range to [0, 2]
- phasepos = phasepos / 2; // correct range to [0, 1]
- }
+ phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
// derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
- if(nexttick < self.animstate_endtime) {
+ if(self.owner.platmovetype_turn)
+ {
+ vector destangle;
+ destangle = delta + 2 * delta2 * phasepos;
+ destangle = vectoangles(destangle);
+ destangle_x = -destangle_x; // flip up / down orientation
+
+ // take the shortest distance for the angles
+ self.owner.angles_x -= 360 * floor((self.owner.angles_x - destangle_x) / 360 + 0.5);
+ self.owner.angles_y -= 360 * floor((self.owner.angles_y - destangle_y) / 360 + 0.5);
+ self.owner.angles_z -= 360 * floor((self.owner.angles_z - destangle_z) / 360 + 0.5);
+ angloc = destangle - self.owner.angles;
+ angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
+ }
+ if(nexttick < self.animstate_endtime)
veloc = nextpos - self.owner.origin;
- veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
- } else {
+ else
veloc = self.finaldest - self.owner.origin;
- veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
- }
+ veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
+
self.owner.velocity = veloc;
+ self.owner.avelocity = angloc;
self.nextthink = nexttick;
} else {
// derivative: delta + 2 * delta2 (e.g. for angle positioning)
controller.destvec = 2 * control; // control point
controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
+ // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
}
void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
controller.destvec2 = '0 0 0';
}
-void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
+float TSPEED_TIME = -1;
+float TSPEED_LINEAR = 0;
+float TSPEED_START = 1;
+float TSPEED_END = 2;
+// TODO average too?
+
+void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
{
float traveltime;
entity controller;
self.finaldest = tdest;
self.think = SUB_CalcMoveDone;
- if(tspeed > 0) // positive: start speed
- traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
- else // negative: end speed
- traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
+ switch(tspeedtype)
+ {
+ default:
+ case TSPEED_START:
+ traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
+ break;
+ case TSPEED_END:
+ traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
+ break;
+ case TSPEED_LINEAR:
+ traveltime = vlen(tdest - self.origin) / tspeed;
+ break;
+ case TSPEED_TIME:
+ traveltime = tspeed;
+ break;
+ }
if (traveltime < 0.1) // useless anim
{
controller.classname = "SUB_CalcMove_controller";
controller.owner = self;
controller.platmovetype = self.platmovetype;
+ controller.platmovetype_start = self.platmovetype_start;
+ controller.platmovetype_end = self.platmovetype_end;
SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
controller.animstate_starttime = time;
self = self.owner;
}
-void SUB_CalcMove (vector tdest, float tspeed, void() func)
+void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
{
vector delta;
float traveltime;
}
delta = tdest - self.origin;
- traveltime = vlen (delta) / tspeed;
+
+ switch(tspeedtype)
+ {
+ default:
+ case TSPEED_START:
+ case TSPEED_END:
+ case TSPEED_LINEAR:
+ traveltime = vlen (delta) / tspeed;
+ break;
+ case TSPEED_TIME:
+ traveltime = tspeed;
+ break;
+ }
// Very short animations don't really show off the effect
// of controlled animation, so let's just use linear movement.
// Alternatively entities can choose to specify non-controlled movement.
// The only currently implemented alternative movement is linear (value 1)
- if (traveltime < 0.15 || self.platmovetype == 1)
+ if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
{
self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
self.nextthink = self.ltime + traveltime;
}
// now just run like a bezier curve...
- SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
+ SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
}
-void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
+void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
{
entity oldself;
oldself = self;
self = ent;
- SUB_CalcMove (tdest, tspeed, func);
+ SUB_CalcMove (tdest, tspeedtype, tspeed, func);
self = oldself;
}
}
// FIXME: I fixed this function only for rotation around the main axes
-void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
+void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
{
vector delta;
float traveltime;
self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
delta = destangle - self.angles;
- traveltime = vlen (delta) / tspeed;
+
+ switch(tspeedtype)
+ {
+ default:
+ case TSPEED_START:
+ case TSPEED_END:
+ case TSPEED_LINEAR:
+ traveltime = vlen (delta) / tspeed;
+ break;
+ case TSPEED_TIME:
+ traveltime = tspeed;
+ break;
+ }
self.think1 = func;
self.finalangle = destangle;
self.nextthink = self.ltime + traveltime;
}
-void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
+void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
{
entity oldself;
oldself = self;
self = ent;
- SUB_CalcAngleMove (destangle, tspeed, func);
+ SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
self = oldself;
}
{
sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
self.state = 3;
- SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
+ SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
}
void plat_go_up()
{
sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
self.state = 4;
- SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
+ SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
}
void plat_center_touch()
}
}
-void spawnfunc_path_corner() { }
+.float platmovetype_start_default, platmovetype_end_default;
+float set_platmovetype(entity e, string s)
+{
+ // sets platmovetype_start and platmovetype_end based on a string consisting of two values
+
+ float n;
+ n = tokenize_console(s);
+ if(n > 0)
+ e.platmovetype_start = stof(argv(0));
+ else
+ e.platmovetype_start = 0;
+
+ if(n > 1)
+ e.platmovetype_end = stof(argv(1));
+ else
+ e.platmovetype_end = e.platmovetype_start;
+
+ if(n > 2)
+ if(argv(2) == "force")
+ return TRUE; // no checking, return immediately
+
+ if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
+ {
+ objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void spawnfunc_path_corner()
+{
+ // setup values for overriding train movement
+ // if a second value does not exist, both start and end speeds are the single value specified
+ if(!set_platmovetype(self, self.platmovetype))
+ return;
+}
void spawnfunc_func_plat()
{
if (self.sounds == 0)
plat_spawn_inside_trigger (); // the "start moving" trigger
}
-
+.float train_wait_turning;
void() train_next;
void train_wait()
{
+ entity oldself;
+ oldself = self;
+ self = self.enemy;
+ SUB_UseTargets();
+ self = oldself;
+ self.enemy = world;
+
+ // if turning is enabled, the train will turn toward the next point while waiting
+ if(self.platmovetype_turn && !self.train_wait_turning)
+ {
+ entity targ, cp;
+ vector ang;
+ targ = find(world, targetname, self.target);
+ if((self.spawnflags & 1) && targ.curvetarget)
+ cp = find(world, targetname, targ.curvetarget);
+ else
+ cp = world;
+
+ if(cp) // bezier curves movement
+ ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
+ else // linear movement
+ ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
+ ang = vectoangles(ang);
+ ang_x = -ang_x; // flip up / down orientation
+
+ if(self.wait > 0) // slow turning
+ SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
+ else // instant turning
+ SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
+ self.train_wait_turning = TRUE;
+ return;
+ }
+
if(self.noise != "")
stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
- if(self.wait < 0)
+ if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
{
+ self.train_wait_turning = FALSE;
train_next();
}
else
self.think = train_next;
self.nextthink = self.ltime + self.wait;
}
-
- entity oldself;
- oldself = self;
- self = self.enemy;
- SUB_UseTargets();
- self = oldself;
- self.enemy = world;
}
void train_next()
{
- entity targ;
+ entity targ, cp;
+ vector cp_org;
+
targ = find(world, targetname, self.target);
- self.enemy = targ;
self.target = targ.target;
+ if (self.spawnflags & 1)
+ {
+ if(targ.curvetarget)
+ {
+ cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
+ cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
+ }
+ else
+ cp = world; // no cp
+ }
if (self.target == "")
objerror("train_next: no next target");
self.wait = targ.wait;
if (!self.wait)
self.wait = 0.1;
+ if(targ.platmovetype)
+ {
+ // this path_corner contains a movetype overrider, apply it
+ self.platmovetype_start = targ.platmovetype_start;
+ self.platmovetype_end = targ.platmovetype_end;
+ }
+ else
+ {
+ // this path_corner doesn't contain a movetype overrider, use the train's defaults
+ self.platmovetype_start = self.platmovetype_start_default;
+ self.platmovetype_end = self.platmovetype_end_default;
+ }
+
if (targ.speed)
- SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
+ {
+ if (cp)
+ SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
+ else
+ SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
+ }
else
- SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
+ {
+ if (cp)
+ SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
+ else
+ SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
+ }
if(self.noise != "")
sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
self.target = targ.target;
if (self.target == "")
objerror("func_train_find: no next target");
- setorigin(self, targ.origin - self.mins);
+ setorigin(self, targ.origin - self.view_ofs);
self.nextthink = self.ltime + 1;
self.think = train_next;
}
if (!self.speed)
self.speed = 100;
+ if (self.spawnflags & 2)
+ {
+ self.platmovetype_turn = TRUE;
+ self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
+ }
+ else
+ self.view_ofs = self.mins;
+
if not(InitMovingBrushTrigger())
return;
self.effects |= EF_LOWPRECISION;
self.dmgtime = 0.25;
self.dmgtime2 = time;
+ if(!set_platmovetype(self, self.platmovetype))
+ return;
+ self.platmovetype_start_default = self.platmovetype_start;
+ self.platmovetype_end_default = self.platmovetype_end;
+
// TODO make a reset function for this one
}
void button_return()
{
self.state = STATE_DOWN;
- SUB_CalcMove (self.pos1, self.speed, button_done);
+ SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
self.frame = 0; // use normal textures
if (self.health)
self.takedamage = DAMAGE_YES; // can be shot again
sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
self.state = STATE_UP;
- SUB_CalcMove (self.pos2, self.speed, button_wait);
+ SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
}
void button_reset()
}
self.state = STATE_DOWN;
- SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
+ SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
}
void door_go_up()
if (self.noise2 != "")
sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
self.state = STATE_UP;
- SUB_CalcMove (self.pos2, self.speed, door_hit_top);
+ SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
string oldmessage;
oldmessage = self.message;
}
self.state = STATE_DOWN;
- SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
+ SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
}
void door_rotating_go_up()
if (self.noise2 != "")
sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
self.state = STATE_UP;
- SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
+ SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
string oldmessage;
oldmessage = self.message;
self.dest1 = self.origin + v_right * (self.t_width * temp);
self.dest2 = self.dest1 + v_forward * self.t_length;
- SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
+ SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
if (self.noise2 != "")
sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
}
{
if (self.noise2 != "")
sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
+ SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
}
// Wait here until time to go back...
{
if (self.noise2 != "")
sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
+ SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
}
// Wait 1 second...
{
if (self.noise2 != "")
sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
+ SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
}
void fd_secret_done()