mirror of
https://git.code.sf.net/p/quake/game-source
synced 2024-11-14 16:31:34 +00:00
631 lines
16 KiB
C++
631 lines
16 KiB
C++
/***********************************************
|
|
* *
|
|
* FrikBot Physics *
|
|
* The near-perfect emulation of *
|
|
* Client movement *
|
|
* *
|
|
* Special Thanks to: Asdf, Frog *
|
|
* Alan "Strider" Kivlin *
|
|
* *
|
|
* *
|
|
***********************************************/
|
|
|
|
/*
|
|
|
|
This program is in the Public Domain. My crack legal
|
|
team would like to add:
|
|
|
|
RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
|
|
AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
|
|
ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
|
|
FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
|
|
NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
|
|
GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
|
|
EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
|
|
SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES.
|
|
|
|
You accept this software on the condition that you
|
|
indemnify and hold harmless Ryan "FrikaC" Smith from
|
|
any and all liability or damages to third parties,
|
|
including attorney fees, court costs, and other
|
|
related costs and expenses, arising out of your use
|
|
of this software irrespective of the cause of said
|
|
liability.
|
|
|
|
The export from the United States or the subsequent
|
|
reexport of this software is subject to compliance
|
|
with United States export control and munitions
|
|
control restrictions. You agree that in the event you
|
|
seek to export this software, you assume full
|
|
responsibility for obtaining all necessary export
|
|
licenses and approvals and for assuring compliance
|
|
with applicable reexport restrictions.
|
|
|
|
Any reproduction of this software must contain
|
|
this notice in its entirety.
|
|
|
|
*/
|
|
|
|
#include "libfrikbot.h"
|
|
|
|
/*
|
|
=========================================
|
|
|
|
Stuff mimicking cl_input.c code
|
|
|
|
=========================================
|
|
*/
|
|
float(float key) CL_KeyState =
|
|
{
|
|
return ((self.keys & key) > 0) ? 1.0 : 0.0;
|
|
};
|
|
|
|
void() CL_KeyMove = // CL_BaseMove + CL_AdjustAngles
|
|
{
|
|
local float anglespeed;
|
|
local vector view;
|
|
if (self.keys != self.oldkeys) {
|
|
self.movevect = '0 0 0';
|
|
self.movevect_y = self.movevect_y + (350 * CL_KeyState(KEY_MOVERIGHT));
|
|
// 350 is the default cl_sidespeed
|
|
self.movevect_y = self.movevect_y - (350 * CL_KeyState(KEY_MOVELEFT));
|
|
// 350 is the default cl_sidespeed
|
|
self.movevect_x = self.movevect_x + (200 * CL_KeyState(KEY_MOVEFORWARD));
|
|
// 200 is the default cl_forwardspeed
|
|
self.movevect_x = self.movevect_x - (200 * CL_KeyState(KEY_MOVEBACK));
|
|
// 200 is the default cl_backspeed
|
|
self.movevect_z = self.movevect_z + (200 * CL_KeyState(KEY_MOVEUP));
|
|
// 200 is the default cl_upspeed
|
|
self.movevect_z = self.movevect_z - (200 * CL_KeyState(KEY_MOVEDOWN));
|
|
// 200 is the default cl_upspeed
|
|
if (!(self.b_aiflags & AI_PRECISION))
|
|
self.movevect = self.movevect * 2;
|
|
// 2 is the default cl_movespeedkey & bot always has +speed
|
|
}
|
|
self.oldkeys = self.keys;
|
|
|
|
if (self.b_skill != 2) {
|
|
// use mouse emulation
|
|
anglespeed = 1.5 * real_frametime;
|
|
// 1.5 is the default cl_anglespeedkey & bot always has +speed
|
|
self.v_angle_y = self.v_angle_y + anglespeed * CL_KeyState(KEY_LOOKLEFT) * 140;
|
|
// 140 is default cl_yawspeed
|
|
self.v_angle_y = self.v_angle_y - anglespeed * CL_KeyState(KEY_LOOKRIGHT) * 140;
|
|
// 140 is default cl_yawspeed
|
|
self.v_angle_x = self.v_angle_x - anglespeed * CL_KeyState(KEY_LOOKUP) * 150;
|
|
// 150 is default cl_pitchspeed
|
|
self.v_angle_x = self.v_angle_x + anglespeed * CL_KeyState(KEY_LOOKDOWN) * 150;
|
|
// 150 is default cl_pitchspeed
|
|
} else {
|
|
view_x = angcomp(self.b_angle_x, self.v_angle_x);
|
|
view_y = angcomp(self.b_angle_y, self.v_angle_y);
|
|
view_z = 0;
|
|
if (vlen(view) > 30) {
|
|
self.mouse_emu = self.mouse_emu + (view * 30);
|
|
if (vlen(self.mouse_emu) > 180)
|
|
self.mouse_emu = normalize(self.mouse_emu) * 180;
|
|
} else
|
|
self.mouse_emu = view * (1 / real_frametime);
|
|
self.v_angle = self.v_angle + self.mouse_emu * real_frametime;
|
|
|
|
|
|
}
|
|
if (self.v_angle_x > 80)
|
|
self.v_angle_x = 80;
|
|
else if (self.v_angle_x < -70)
|
|
self.v_angle_x = -70;
|
|
|
|
if (self.v_angle_z > 50)
|
|
self.v_angle_z = 50;
|
|
else if (self.v_angle_z < -50)
|
|
self.v_angle_z = -50;
|
|
self.v_angle_y = frik_anglemod(self.v_angle_y);
|
|
|
|
};
|
|
|
|
/*
|
|
=========================================
|
|
|
|
Stuff mimicking sv_user.c
|
|
|
|
=========================================
|
|
*/
|
|
void() SV_UserFriction =
|
|
{
|
|
local vector vel, start, stop;
|
|
local float sped, friction, newspeed;
|
|
|
|
vel = self.velocity;
|
|
vel_z =0;
|
|
sped = vlen(vel);
|
|
vel = self.velocity;
|
|
|
|
if (!sped)
|
|
return;
|
|
|
|
// if the leading edge is over a dropoff, increase friction
|
|
|
|
start_x = stop_x = self.origin_x + vel_x / (sped * 16);
|
|
start_y = stop_y = self.origin_y + vel_y / (sped * 16);
|
|
start_z = self.origin_z + self.mins_z;
|
|
stop_z = start_z - 34;
|
|
|
|
traceline(start, stop, TRUE, self);
|
|
|
|
if (trace_fraction == 1)
|
|
friction = sv_friction * 2; // 2 is default edgefriction, removed for QW compatability
|
|
else
|
|
friction = sv_friction;
|
|
if (sped < sv_stopspeed)
|
|
newspeed = sped - real_frametime * sv_stopspeed * friction;
|
|
else
|
|
newspeed = sped - real_frametime * sped * friction;
|
|
|
|
if (newspeed < 0)
|
|
newspeed = 0;
|
|
newspeed = newspeed / sped;
|
|
|
|
self.velocity_y = vel_y * newspeed;
|
|
self.velocity_x = vel_x * newspeed;
|
|
};
|
|
void() SV_WaterJump =
|
|
{
|
|
if (time > self.teleport_time || !self.waterlevel) {
|
|
self.flags = self.flags - (self.flags & FL_WATERJUMP);
|
|
self.teleport_time = 0;
|
|
}
|
|
self.velocity_x = self.movedir_x;
|
|
self.velocity_y = self.movedir_y;
|
|
};
|
|
|
|
void() DropPunchAngle =
|
|
{
|
|
local float len;
|
|
len = vlen(self.punchangle);
|
|
self.punchangle = normalize(self.punchangle);
|
|
len = len - 10 * real_frametime;
|
|
if (len < 0)
|
|
len = 0;
|
|
self.punchangle = self.punchangle * len;
|
|
};
|
|
|
|
|
|
void(vector wishvel) SV_AirAccelerate =
|
|
{
|
|
local float addspeed, wishspd, accelspeed, currentspeed;
|
|
|
|
wishspd = vlen(wishvel);
|
|
wishvel = normalize(wishvel);
|
|
if (wishspd > 30)
|
|
wishspd = 30;
|
|
currentspeed = self.velocity * wishvel;
|
|
addspeed = wishspd - currentspeed;
|
|
if (addspeed <= 0)
|
|
return;
|
|
accelspeed = 10 * sv_accelerate * wishspd * real_frametime;
|
|
if (accelspeed > addspeed)
|
|
accelspeed = addspeed;
|
|
|
|
self.velocity = self.velocity + accelspeed * wishvel;
|
|
};
|
|
|
|
void(vector wishvel) SV_Accelerate =
|
|
{
|
|
local float addspeed, wishspd, accelspeed, currentspeed;
|
|
|
|
wishspd = vlen(wishvel);
|
|
wishvel = normalize(wishvel);
|
|
|
|
currentspeed = self.velocity * wishvel;
|
|
addspeed = wishspd - currentspeed;
|
|
if (addspeed <= 0)
|
|
return;
|
|
accelspeed = sv_accelerate * wishspd * real_frametime;
|
|
if (accelspeed > addspeed)
|
|
accelspeed = addspeed;
|
|
|
|
self.velocity = self.velocity + accelspeed * wishvel;
|
|
};
|
|
void() SV_WaterMove =
|
|
{
|
|
local vector wishvel;
|
|
local float wishspeed, addspeed, cspeed, newspeed;
|
|
makevectors(self.v_angle);
|
|
wishvel = v_right * self.movevect_y + v_forward * self.movevect_x;
|
|
|
|
if (self.movevect == '0 0 0')
|
|
wishvel_z = wishvel_z - 60;
|
|
else
|
|
wishvel_z = wishvel_z + self.movevect_z;
|
|
wishspeed = vlen(wishvel);
|
|
|
|
if (wishspeed > sv_maxspeed) {
|
|
wishvel = (sv_maxspeed / wishspeed) * wishvel;
|
|
wishspeed = sv_maxspeed;
|
|
}
|
|
wishspeed = wishspeed * 0.7;
|
|
cspeed = vlen(self.velocity);
|
|
if (cspeed) {
|
|
newspeed = cspeed - (real_frametime * cspeed * sv_friction);
|
|
if (newspeed < 0)
|
|
newspeed = 0;
|
|
self.velocity = self.velocity * (newspeed / cspeed);
|
|
|
|
} else
|
|
newspeed = 0;
|
|
|
|
if (!wishspeed)
|
|
return;
|
|
addspeed = wishspeed - newspeed;
|
|
if (addspeed <= 0)
|
|
return;
|
|
wishvel = normalize(wishvel);
|
|
cspeed = sv_accelerate * wishspeed * real_frametime;
|
|
if (cspeed > addspeed)
|
|
cspeed = addspeed;
|
|
self.velocity = self.velocity + cspeed * wishvel;
|
|
};
|
|
void() SV_AirMove =
|
|
{
|
|
local vector wishvel, vangle;
|
|
|
|
vangle = self.v_angle;
|
|
vangle_x = vangle_z = 0;
|
|
makevectors(vangle);
|
|
if (time < self.teleport_time && (self.movevect_x < 0))
|
|
self.movevect_x = 0;
|
|
wishvel = v_right * self.movevect_y + v_forward * self.movevect_x;
|
|
|
|
|
|
if (self.movetype != MOVETYPE_WALK)
|
|
wishvel_z = self.movevect_z;
|
|
else
|
|
wishvel_z = 0;
|
|
if (vlen(wishvel) > sv_maxspeed)
|
|
wishvel = normalize(wishvel) * sv_maxspeed;
|
|
if (self.movetype == MOVETYPE_NOCLIP)
|
|
self.velocity = wishvel;
|
|
else if (self.flags & FL_ONGROUND) {
|
|
SV_UserFriction();
|
|
SV_Accelerate(wishvel);
|
|
} else
|
|
SV_AirAccelerate (wishvel);
|
|
};
|
|
|
|
void() SV_ClientThink =
|
|
{
|
|
local vector vangle;
|
|
|
|
if (self.movetype == MOVETYPE_NONE)
|
|
return;
|
|
DropPunchAngle();
|
|
if (self.health <= 0)
|
|
return;
|
|
self.v_angle_z = 0; // V_CalcRoll removed, sucks
|
|
self.angles_z = self.v_angle_z * 4;
|
|
vangle = self.v_angle + self.punchangle;
|
|
if (!self.fixangle) {
|
|
self.angles_x = (vangle_x / -3);
|
|
self.angles_y = vangle_y;
|
|
} else {
|
|
self.v_angle = self.angles;
|
|
self.fixangle = 0;
|
|
}
|
|
if (self.flags & FL_WATERJUMP) {
|
|
SV_WaterJump();
|
|
return;
|
|
}
|
|
if ((self.waterlevel >= 2) && (self.movetype != MOVETYPE_NOCLIP)) {
|
|
SV_WaterMove();
|
|
return;
|
|
}
|
|
SV_AirMove();
|
|
|
|
};
|
|
/*
|
|
=========================================
|
|
|
|
Stuff mimicking sv_phys.c
|
|
|
|
=========================================
|
|
*/
|
|
|
|
float() SV_RunThink =
|
|
{
|
|
local float thinktime, bkuptime;
|
|
thinktime = self.nextthink;
|
|
bkuptime = time;
|
|
if (thinktime <= 0 || thinktime > (time + real_frametime))
|
|
return TRUE;
|
|
if (thinktime < time)
|
|
thinktime = time;
|
|
self.nextthink = 0;
|
|
time = thinktime;
|
|
other = NIL;
|
|
makevectors(self.v_angle); // hack
|
|
self.think();
|
|
time = bkuptime;
|
|
return TRUE;
|
|
};
|
|
|
|
void(float scale) SV_AddGravity =
|
|
{
|
|
self.velocity_z = self.velocity_z - (scale * sv_gravity * real_frametime);
|
|
};
|
|
|
|
float() SV_CheckWater =
|
|
{
|
|
local vector point;
|
|
local float cont;
|
|
|
|
point_x = self.origin_x;
|
|
point_y = self.origin_y;
|
|
self.waterlevel = 0;
|
|
self.watertype = CONTENT_EMPTY;
|
|
point_z = self.origin_z + self.mins_z + 1;
|
|
cont = pointcontents(point);
|
|
if (cont <= CONTENT_WATER) {
|
|
self.watertype = cont;
|
|
self.waterlevel = 1;
|
|
point_z = self.origin_z + (self.mins_z + self.maxs_z) * 0.5;
|
|
cont = pointcontents(point);
|
|
if (cont <= CONTENT_WATER) {
|
|
self.waterlevel = 2;
|
|
point_z = self.origin_z + self.view_ofs_z;
|
|
cont = pointcontents(point);
|
|
if (cont <= CONTENT_WATER)
|
|
self.waterlevel = 3;
|
|
}
|
|
}
|
|
return (self.waterlevel > 1) ? 1.0 : 0.0;
|
|
|
|
};
|
|
void() RemoveThud = // well sometimes
|
|
{
|
|
local entity oself;
|
|
if (other == NIL) {
|
|
if (self.flags & FL_ONGROUND) {
|
|
self.flags = self.flags - FL_ONGROUND;
|
|
}
|
|
} else {
|
|
if (other.solid == SOLID_BSP && (self.flags & FL_ONGROUND)) {
|
|
// RM: Does this break anything?
|
|
// If not, then some more thuds have been removed.
|
|
self.flags = self.flags - FL_ONGROUND;
|
|
}
|
|
if (other == self.owner)
|
|
return;
|
|
if (self.owner.solid == SOLID_NOT)
|
|
return;
|
|
oself = other;
|
|
other = self.owner;
|
|
self = oself;
|
|
if (self.solid == SOLID_BSP)
|
|
if (self.touch)
|
|
self.touch();
|
|
}
|
|
|
|
};
|
|
void() SV_CheckOnGround =
|
|
{
|
|
local vector org, v;
|
|
org = self.origin;
|
|
local float currentflags;
|
|
currentflags = self.flags;
|
|
self.flags = self.flags | FL_ONGROUND | FL_PARTIALGROUND;
|
|
walkmove(0,0); // perform C touch function
|
|
self.flags = currentflags | FL_ONGROUND;
|
|
if ((org_x != self.origin_x) || (org_y != self.origin_y))
|
|
org = self.origin;
|
|
else
|
|
self.origin = org;
|
|
v = org;
|
|
v_z = self.maxs_z + org_z + 1;
|
|
traceline (org, v, TRUE, self);
|
|
if ((self.waterlevel == 3) && (self.movetype == MOVETYPE_WALK))
|
|
self.flags = self.flags - FL_ONGROUND;
|
|
else if ((trace_plane_normal_z <= 0.7) && (trace_fraction != 1))
|
|
self.flags = self.flags - FL_ONGROUND;
|
|
else if (!droptofloor ())
|
|
self.flags = self.flags - FL_ONGROUND;
|
|
else if (org_z - self.origin_z < 2)
|
|
self.flags = self.flags | FL_ONGROUND;
|
|
else
|
|
self.flags = self.flags - FL_ONGROUND;
|
|
setorigin(self, org);
|
|
};
|
|
// Thanks to Alan Kivlin for this function
|
|
// modified heavily by me
|
|
float(vector dir) botCheckForStep =
|
|
{
|
|
local vector currentorigin, v;
|
|
local float currentflags, yaw, stepdistance, movedistance;
|
|
currentorigin = self.origin;
|
|
currentflags = self.flags;
|
|
self.flags = FL_ONGROUND | FL_PARTIALGROUND;
|
|
dir = normalize(dir);
|
|
dir_z = 0;
|
|
yaw = vectoyaw(dir);
|
|
if(walkmove(yaw, 3)) {
|
|
if(droptofloor ()) {
|
|
stepdistance = self.origin_z - currentorigin_z;
|
|
v = self.origin - currentorigin;
|
|
v_z = 0;
|
|
movedistance = vlen(v);
|
|
if((stepdistance > 0 && stepdistance <= 16) && movedistance != 0) {
|
|
self.flags = currentflags | FL_PARTIALGROUND;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
self.flags = currentflags;
|
|
setorigin(self, currentorigin);
|
|
return 0;
|
|
};
|
|
// this is merely here to fix a problem with e3m5
|
|
void(vector dir) BruteForceStep =
|
|
{
|
|
local vector currentorigin;
|
|
local float currentflags, i = 0, len;
|
|
|
|
currentorigin = self.origin;
|
|
currentflags = self.flags;
|
|
len = vlen(dir);
|
|
if (len > 16)
|
|
dir = normalize(dir) * 16;
|
|
|
|
setorigin(self, currentorigin + dir);
|
|
|
|
while(i < 18 && !walkmove(0, 0)) {
|
|
self.origin_z = currentorigin_z + i;
|
|
i = i + 2;
|
|
}
|
|
self.flags = currentflags;
|
|
if (i >=18)
|
|
setorigin(self, currentorigin);
|
|
};
|
|
|
|
void() PostPhysics =
|
|
{
|
|
local vector obstr, org;
|
|
local float back, dst,cflags;
|
|
|
|
self = self.owner;
|
|
|
|
self.velocity = self.velocity - self.phys_obj.dest1 + self.phys_obj.velocity;
|
|
if (self.phys_obj.dest2 == self.origin) {
|
|
setorigin(self, self.phys_obj.origin);
|
|
// might've been moved during other person's physics
|
|
// (teleporters / plats)
|
|
|
|
if (self.movetype == MOVETYPE_WALK) {
|
|
|
|
if (self.phys_obj.dest1_x || self.phys_obj.dest1_y) {
|
|
if ((self.flags & FL_ONGROUND) || (self.waterlevel <= 2)) {
|
|
obstr = self.phys_obj.movedir - self.origin;
|
|
obstr_z = 0;
|
|
if (vlen(obstr) > 0.1) {
|
|
dst = vlen(obstr);
|
|
back = vectoyaw(obstr);
|
|
cflags = self.flags;
|
|
self.flags = self.flags | FL_PARTIALGROUND;
|
|
if(walkmove(back, dst)) {
|
|
self.flags = cflags;
|
|
self.phys_obj.dest1_z = 0;
|
|
self.velocity = self.velocity + self.phys_obj.dest1 - self.phys_obj.velocity;
|
|
} else {
|
|
if (dst > 1)
|
|
frik_obstructed(obstr, FALSE);
|
|
|
|
org = self.origin;
|
|
self.flags = cflags;
|
|
obstr = self.phys_obj.dest1;
|
|
obstr_x = 0;
|
|
if (!botCheckForStep(obstr)) {
|
|
obstr = self.phys_obj.dest1;
|
|
obstr_y = 0;
|
|
if (!botCheckForStep(obstr)) {
|
|
// if no steps were found, bot is really obstucted
|
|
BruteForceStep(self.phys_obj.dest1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SV_CheckOnGround();
|
|
|
|
PlayerPostThink();
|
|
BotAI();
|
|
self.dmg_take = self.dmg_save = 0;
|
|
|
|
};
|
|
// Avoid calling BotAI and the physics at the same time
|
|
// Can trip the runaway loop counter
|
|
|
|
void() SV_FlyMove =
|
|
{
|
|
// This is nothing like the Quake function.
|
|
|
|
if (self.phys_obj == NIL) {
|
|
self.phys_obj = find(NIL,classname,"phys_obj");
|
|
while (self.phys_obj.owner != self) {
|
|
self.phys_obj = find(self.phys_obj,classname,"phys_obj");
|
|
if (self.phys_obj == NIL) {
|
|
error("No physics entity spawned!\nMake sure BotInit was called\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
setmodel (self.phys_obj, NIL);
|
|
self.phys_obj.movetype = MOVETYPE_STEP;
|
|
|
|
self.phys_obj.solid = SOLID_TRIGGER;
|
|
self.phys_obj.touch = RemoveThud;
|
|
setsize(self.phys_obj, self.mins, self.maxs);
|
|
self.phys_obj.dest2 = self.phys_obj.origin = self.origin;
|
|
self.phys_obj.watertype = 0;
|
|
self.phys_obj.movedir = self.origin + real_frametime * self.velocity;
|
|
self.phys_obj.dest1 = self.phys_obj.velocity = self.velocity;
|
|
self.phys_obj.velocity_z = self.phys_obj.velocity_z + sv_gravity * real_frametime;
|
|
self.phys_obj.flags = 0;
|
|
self.phys_obj.think = PostPhysics;
|
|
self.phys_obj.nextthink = time;
|
|
};
|
|
|
|
|
|
void() SV_Physics_Toss =
|
|
{
|
|
if (!SV_RunThink())
|
|
return;
|
|
if (self.flags & FL_ONGROUND) {
|
|
self.velocity = '0 0 0';
|
|
BotAI();
|
|
return;
|
|
}
|
|
if (self.movetype != MOVETYPE_FLY)
|
|
SV_AddGravity(1);
|
|
self.angles = self.angles + real_frametime * self.avelocity;
|
|
SV_FlyMove();
|
|
|
|
};
|
|
void() SV_Physics_Client =
|
|
{
|
|
|
|
PlayerPreThink();
|
|
|
|
if (self.movetype == MOVETYPE_NONE) {
|
|
if (!SV_RunThink())
|
|
return;
|
|
PlayerPostThink();
|
|
BotAI();
|
|
|
|
} else if ((self.movetype == MOVETYPE_WALK) || (self.movetype == MOVETYPE_STEP)) {
|
|
if (!SV_RunThink())
|
|
return;
|
|
if (!(SV_CheckWater()) && (!(self.flags & FL_WATERJUMP)))
|
|
SV_AddGravity(1);
|
|
SV_FlyMove();
|
|
} else if ((self.movetype == MOVETYPE_TOSS) || (self.movetype == MOVETYPE_BOUNCE)) {
|
|
SV_Physics_Toss();
|
|
} else if (self.movetype == MOVETYPE_FLY) {
|
|
if (!SV_RunThink())
|
|
return;
|
|
SV_FlyMove();
|
|
} else if (self.movetype == MOVETYPE_NOCLIP) {
|
|
if (!SV_RunThink())
|
|
return;
|
|
self.origin = self.origin + real_frametime * self.velocity;
|
|
|
|
PlayerPostThink();
|
|
BotAI();
|
|
} else
|
|
error ("SV_Physics_Client: Bad Movetype (BOT)");
|
|
|
|
};
|
|
|
|
|