http://fbe.am/uoS
Don't worry, that's all I was hoping you would do.Dr. Shadowborg wrote: Keep in mind I'm only going to impliment the bot fixes, nothing more.
Don't worry, that's all I was hoping you would do.Dr. Shadowborg wrote: Keep in mind I'm only going to impliment the bot fixes, nothing more.
Of course! You can modify whatever you like with the Painkeep code to make it more compatible with the Frikbots. After all, this version of Painkeep should be geared toward bots anyway.Dr. Shadowborg wrote: Question: Would it be okay with you if I fixed the obvious bugs / flaws / inconsistencies within painkeep? Alternately I could just make two or three versions, one with the original buggy painkeep code, one with bugfixes (but leaving everything as is), and one with my personal preferences, enhancements and bugfixes?
In bot_ai.qc, within bot_angle_set, just add this to the top of the else if (self.b_skill < 1) block:Lightning Hunter wrote:Hey Shadowborg, I just realized the bots are all messed up on skill 1 (and possibly other skills). Throughout this entire thread, I've been testing them on skill 3 only. In skill 1, they seem to act like all the other bots have the Ring of shadows, and behave strangely when aiming. They also proceed to walk in to walls when other bots are present.
Code: Select all
if (self.b_angle_x < -180)
self.b_angle_x = self.b_angle_x + 360;
Code: Select all
/***********************************************
* *
* FrikBot General AI *
* "The I'd rather be playing Quake AI" *
* *
***********************************************/
/*
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.
*/
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
target_onstack
checks to see if an entity is on the bot's stack
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
float(entity scot) target_onstack =
{
if (scot == world)
return FALSE;
else if (self.target1 == scot)
return 1;
else if (self.target2 == scot)
return 2;
else if (self.target3 == scot)
return 3;
else if (self.target4 == scot)
return 4;
else
return FALSE;
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
target_add
adds a new entity to the stack, since it's a
LIFO stack, this will be the bot's new target1
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
void(entity ent) target_add =
{
if (ent == world)
return;
if (target_onstack(ent))
return;
self.target4 = self.target3;
self.target3 = self.target2;
self.target2 = self.target1;
self.target1 = ent;
self.search_time = time + 5;
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
target_drop
Removes an entity from the bot's target stack.
The stack will empty everything up to the object
So if you have target2 item_health, target1
waypoint, and you drop the health, the waypoint
is gone too.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
void(entity ent) target_drop =
{
local float tg;
tg = target_onstack(ent);
if (tg == 1)
{
self.target1 = self.target2;
self.target2 = self.target3;
self.target3 = self.target4;
self.target4 = world;
}
else if (tg == 2)
{
self.target1 = self.target3;
self.target2 = self.target4;
self.target3 = self.target4 = world;
}
else if (tg == 3)
{
self.target1 = self.target4;
self.target2 = self.target3 = self.target4 = world;
}
else if (tg == 4)
self.target1 = self.target2 = self.target3 = self.target4 = world;
self.search_time = time + 5;
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bot_lost
Bot has lost its target.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
void(entity targ, float success) bot_lost =
{
if (!targ)
return;
target_drop(targ);
if (targ.classname == "waypoint")
targ.b_sound = targ.b_sound - (targ.b_sound & ClientBitFlag(self.b_clientno));
// find a new route
if (!success)
{
self.target1 = self.target2 = self.target3 = self.target4 = world;
self.last_way = FindWayPoint(self.current_way);
ClearMyRoute();
self.b_aiflags = 0;
}
else
{
if (targ.classname == "item_artifact_invisibility")
if (self.items & 524288)
bot_start_topic(3);
if (targ.flags & FL_ITEM)
{
if (targ.model == string_null)
targ._last = world;
else
targ._last = self;
}
}
if (targ.classname != "player")
targ.search_time = time + 5;
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bot_check_lost
decide if my most immediate target should be
removed.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
void(entity targ) bot_check_lost =
{
local vector dist;
dist = realorigin(targ) - self.origin;
dist_z = 0;
if (targ == world)
return;
// waypoints and items are lost if you get close enough to them
else if (targ.flags & FL_ITEM)
{
if (vlen(targ.origin - self.origin) < 32)
bot_lost(targ, TRUE);
else if (targ.model == string_null)
bot_lost(targ, TRUE);
}
else if (targ.classname == "waypoint")
{
if (!(self.b_aiflags & (AI_SNIPER | AI_AMBUSH)))
{
if (self.b_aiflags & AI_RIDE_TRAIN)
{
if (vlen(targ.origin - self.origin) < 48)
bot_lost(targ, TRUE);
}
else if (self.b_aiflags & AI_PRECISION)
{
if (vlen(targ.origin - self.origin) < 24)
bot_lost(targ, TRUE);
}
else if (vlen(targ.origin - self.origin) < 32)
bot_lost(targ, TRUE);
}
}
else if (targ.classname == "temp_waypoint")
{
if (vlen(targ.origin - self.origin) < 32)
bot_lost(targ, TRUE);
}
else if (targ.classname == "player")
{
if (targ.health <= 0)
bot_lost(targ, TRUE);
else if ((coop) || (teamplay && targ.team == self.team))
{
if (targ.target1.classname == "player")
{
if (!targ.target1.ishuman)
bot_lost(targ, TRUE);
}
else if (targ.teleport_time > time)
{
// try not to telefrag teammates
self.keys = self.keys & 960;
}
else if (vlen(targ.origin - self.origin) < 128)
{
if (vlen(targ.origin - self.origin) < 48)
frik_walkmove(self.origin - targ.origin);
else
{
self.keys = self.keys & 960;
bot_start_topic(4);
}
self.search_time = time + 5; // never time out
}
else if (!fisible(targ))
bot_lost(targ, FALSE);
}
else if (waypoint_mode > WM_LOADED)
{
if (vlen(targ.origin - self.origin) < 128)
{
bot_lost(targ, TRUE);
}
}
}
// buttons are lost of their frame changes
else if (targ.classname == "func_button")
{
if (targ.frame)
{
bot_lost(targ, TRUE);
if (self.enemy == targ)
self.enemy = world;
//if (self.target1)
// bot_get_path(self.target1, TRUE);
}
}
// trigger_multiple style triggers are lost if their thinktime changes
else if ((targ.movetype == MOVETYPE_NONE) && (targ.solid == SOLID_TRIGGER))
{
if (targ.nextthink >= time)
{
bot_lost(targ, TRUE);
//if (self.target1)
// bot_get_path(self.target1, TRUE);
}
}
// DRS: Teleporter target fix
if(targ.classname == "trigger_teleport" && (self.bot_teleport_dest.classname == "info_teleport_destination" && vlen(self.bot_teleport_dest.origin - self.origin) < 32))
{
self.bot_teleport_dest = world;
bot_lost(targ, TRUE);
}
// lose any target way above the bot's head
// FIXME: if the bot can fly in your mod..
if ((targ.origin_z - self.origin_z) > 64)
{
dist = targ.origin - self.origin;
dist_z = 0;
if (vlen(dist) < 32)
if (self.flags & FL_ONGROUND)
if(!frik_recognize_plat(FALSE))
bot_lost(targ, FALSE);
}
else if (targ.classname == "train")
{
if (frik_recognize_plat(FALSE))
bot_lost(targ, TRUE);
}
// targets are lost if the bot's search time has expired
if (time > self.search_time)
bot_lost(targ, FALSE);
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bot_handle_ai
This is a 0.10 addition. Handles any action
based b_aiflags.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
void() bot_handle_ai =
{
local entity newt;
local vector v;
// handle ai flags -- note, not all aiflags are handled
// here, just those that perform some sort of action
// wait is used by the ai to stop the bot until his search time expires / or route changes
if (self.b_aiflags & AI_WAIT)
self.keys = self.keys & 960;
if (self.b_aiflags & AI_DOORFLAG) // was on a door when spawned
{
b_temp3 = self;
self = self.last_way;
if (!frik_recognize_plat(FALSE)) // if there is nothing there now
{
newt = FindThing("door"); // this is likely the door responsible (crossfingers)
self = b_temp3;
if (self.b_aiflags & AI_DOOR_NO_OPEN)
{
if (newt.nextthink)
self.keys = self.keys & 960; // wait until it closes
else
{
bot_lost(self.last_way, FALSE);
}
}
else
{
if (newt.targetname)
{
newt = find(world, target, newt.targetname);
if (newt.health > 0)
{
self.enemy = newt;
bot_weapon_switch(1);
}
else
{
// target_drop(self.last_way);
target_add(newt);
// bot_get_path(newt, TRUE);
}
}
self.b_aiflags = self.b_aiflags - AI_DOORFLAG;
}
}
else
self = b_temp3;
}
if (self.b_aiflags & AI_JUMP)
{
if (self.flags & FL_ONGROUND)
{
bot_jump();
self.b_aiflags = self.b_aiflags - AI_JUMP;
}
}
else if (self.b_aiflags & AI_SUPER_JUMP)
{
if (self.weapon != 32)
self.impulse = 7;
else if (self.flags & FL_ONGROUND)
{
self.b_aiflags = self.b_aiflags - AI_SUPER_JUMP;
if (bot_can_rj(self))
{
bot_jump();
self.v_angle_x = self.b_angle_x = 80;
self.button0 = TRUE;
}
else
bot_lost(self.target1, FALSE);
}
}
if (self.b_aiflags & AI_SURFACE)
{
if (self.waterlevel > 2)
{
self.keys = KEY_MOVEUP;
self.button2 = TRUE; // swim!
}
else
self.b_aiflags = self.b_aiflags - AI_SURFACE;
}
if (self.b_aiflags & AI_RIDE_TRAIN)
{
// simple, but effective
// this can probably be used for a lot of different
// things, not just trains (door elevators come to mind)
b_temp3 = self;
self = self.last_way;
if (!frik_recognize_plat(FALSE)) // if there is nothing there now
{
self = b_temp3;
self.keys = self.keys & 960;
}
else
{
self = b_temp3;
if (frik_recognize_plat(FALSE))
{
v = realorigin(trace_ent) + trace_ent.origin - self.origin;
v_z = 0;
if (vlen(v) < 24)
self.keys = self.keys & 960;
else
{
self.b_aiflags = self.b_aiflags | AI_PRECISION;
self.keys = frik_KeysForDir(v);
}
}
}
}
if (self.b_aiflags & AI_PLAT_BOTTOM)
{
newt = FindThing("plat");
if (newt.state != 1)
{
v = self.origin - realorigin(newt);
v_z = 0;
if (vlen(v) > 96)
self.keys = self.keys & 960;
else
frik_walkmove(v);
}
else
self.b_aiflags = self.b_aiflags - AI_PLAT_BOTTOM;
}
if (self.b_aiflags & AI_DIRECTIONAL)
{
if ((normalize(self.last_way.origin - self.origin) * self.b_dir) > 0.4)
{
self.b_aiflags = self.b_aiflags - AI_DIRECTIONAL;
bot_lost(self.target1, TRUE);
}
}
if (self.b_aiflags & AI_SNIPER)
{
self.b_aiflags = (self.b_aiflags | AI_WAIT | AI_PRECISION) - AI_SNIPER;
// FIXME: Add a switch to wep command
// FIXME: increase delay?
}
if (self.b_aiflags & AI_AMBUSH)
{
self.b_aiflags = (self.b_aiflags | AI_WAIT) - AI_AMBUSH;
// FIXME: Add a switch to wep command
// FIXME: increase delay?
}
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bot_path
Bot will follow a route generated by the
begin_route set of functions in bot_way.qc.
This code, while it works pretty well, can get
confused
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
void() bot_path =
{
local entity jj, tele;
bot_check_lost(self.target1);
if (!self.target1)
{
self.keys=0;
return;
}
if (target_onstack(self.last_way))
return; // old waypoint still being hunted
jj = FindRoute(self.last_way);
if (!jj)
{
// this is an ugly hack
if (self.target1.current_way != self.last_way)
{
if (self.target1.classname != "temp_waypoint")
if (self.target1.classname != "player")
bot_lost(self.target1, FALSE);
}
return;
}
// update the bot's special ai features
// Readahed types are AI conditions to perform while heading to a waypoint
// point types are AI flags that should be executed once reaching a waypoint
self.b_aiflags = (jj.b_aiflags & AI_READAHEAD_TYPES) | (self.last_way.b_aiflags & AI_POINT_TYPES);
target_add(jj);
if (self.last_way)
{
if (CheckLinked(self.last_way, jj) == 2) // waypoints are telelinked
{
tele = FindThing("trigger_teleport"); // this is probbly the teleport responsible
target_add(tele);
}
traceline(self.last_way.origin, jj.origin, FALSE, self); // check for blockage
if (trace_fraction != 1)
{
if (trace_ent.classname == "door" && !(self.b_aiflags & AI_DOOR_NO_OPEN)) // a door blocks the way
{
// linked doors fix
if (trace_ent.owner)
trace_ent = trace_ent.owner;
if ((trace_ent.health > 0) && (self.enemy == world))
{
self.enemy = trace_ent;
bot_weapon_switch(1);
self.b_aiflags = self.b_aiflags | AI_BLIND; // nick knack paddy hack
}
else if (trace_ent.targetname)
{
tele = find(world, target, trace_ent.targetname);
if (tele.health > 0)
{
self.enemy = tele;
bot_weapon_switch(1);
}
else
{
// target_drop(jj);
target_add(tele);
// bot_get_path(tele, TRUE);
self.b_aiflags = self.b_aiflags | AI_BLIND; // give a bot a bone
return;
}
}
}
else if (trace_ent.classname == "func_wall")
{
// give up
bot_lost(self.target1, FALSE);
return;
}
}
}
// this is used for AI_DRIECTIONAL
self.b_dir = normalize(jj.origin - self.last_way.origin);
self.last_way = jj;
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Bot Priority Look. What a stupid name. This is where
the bot finds things it wants to kill/grab.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
// priority scale
// 0 - 10 virtually ignore
// 10 - 30 normal item range
// 30 - 50 bot will consider this a target worth changing course for
// 50 - 90 bot will hunt these as vital items
// *!* Make sure you add code to bot_check_lost to remove the target *!*
float(entity thing) priority_for_thing =
{
local float thisp;
thisp = 0;
// This is the most executed function in the bot. Careful what you do here.
if ((thing.flags & FL_ITEM) && thing.model != string_null && thing.search_time < time)
{
// ugly hack
if (thing._last != self)
thisp = 10*(random()) + 20;
//~ ai fix start
/*
if (thing.classname == "item_artifact_super_damage")
thisp = 65;
else if (thing.classname == "item_artifact_invulnerability")
thisp = 65;
else if (thing.classname == "item_health")
{
if (thing.spawnflags & 2)
thisp = 55;
if (self.health < 50)
thisp = thisp + 115;
}
else if (thing.model == "progs/armor.mdl")
{
if (self.armorvalue < 200)
{
if (thing.skin == 2)
thisp = 60;
else if (self.armorvalue < 100)
thisp = thisp + 25;
}
}
else if (thing.classname == "weapon_supershotgun")
{
if (!(self.items & 2)) // IT_SUPER_SHOTGUN
thisp = 35;
}
else if (thing.classname == "weapon_nailgun")
{
if (!(self.items & 4)) // IT_NAILGUN
thisp = 30;
}
else if (thing.classname == "weapon_supernailgun")
{
if (!(self.items & 8)) // IT_SUPER_NAILGUN
thisp = 40;
}
else if (thing.classname == "weapon_grenadelauncher")
{
if (!(self.items & 16)) // IT_GRENADE_LAUNCHER
thisp = 45;
}
else if (thing.classname == "weapon_rocketlauncher")
{
if (!(self.items & 32)) // IT_ROCKET_LAUNCHER
thisp = 60;
}
else if (thing.classname == "weapon_lightning")
{
if (!(self.items & 64)) // IT_LIGHTNING
thisp = 50;
}
*/
if (thing.classname == "item_artifact_super_damage")
thisp = 19*(random()) + 40;
else if (thing.classname == "item_artifact_invulnerability")
thisp = 19*(random()) + 40;
else if (thing.classname == "item_artifact_invisibility")
thisp = 19*(random()) + 40;
else if (thing.classname == "item_health")
{
if (self.health <= 50)
thisp = 50;
if (self.health > 50)
thisp = 35;
if (self.health > 80)
thisp = 20;
if (self.health > 95)
thisp = 0;
if (thing.spawnflags & 2)
thisp = 13*(random()) + 37;
}
else if (thing.classname == "item_armor1")
{
if (self.armorvalue <= 99)
thisp = 13*(random()) + 26;
if (self.armorvalue > 99)
thisp = 0;
}
else if (thing.classname == "item_armor2")
{
if (self.armorvalue <= 149)
thisp = 13*(random()) + 35;
if (self.armorvalue > 149)
thisp = 0;
}
else if (thing.classname == "item_armorInv")
{
if (self.armorvalue <= 199)
thisp = 10*(random()) + 45;
if (self.armorvalue > 199)
thisp = 0;
}
else if (thing.classname == "weapon_rocketlauncher")
{
if (!(self.items & 32)) // IT_ROCKET_LAUNCHER
thisp = 5*(random()) + 45;
}
else if (thing.classname == "weapon_lightning")
{
if (!(self.items & 64)) // IT_LIGHTNING
thisp = 13*(random()) + 37;
}
else if (thing.classname == "weapon_grenadelauncher")
{
if (!(self.items & 16)) // IT_GRENADE_LAUNCHER
thisp = 10*(random()) + 30;
}
else if (thing.classname == "weapon_supernailgun")
{
if (!(self.items & 8)) // IT_SUPER_NAILGUN
thisp = 8*(random()) + 25;
}
else if (thing.classname == "weapon_nailgun")
{
if (!(self.items & 4)) // IT_NAILGUN
thisp = 8*(random()) + 15;
}
else if (thing.classname == "weapon_supershotgun")
{
if (!(self.items & 2)) // IT_SUPER_SHOTGUN
thisp = 8*(random()) + 20;
}
else if (thing.model == "progs/backpack.mdl")
thisp = 18*(random()) + 20;
else if (thing.classname == "item_artifact_envirosuit")
thisp = 15;
}
//~ ai fix end
else if ((thing.flags & FL_MONSTER) && thing.health > 0)
thisp = 45;
else if (thing.classname == "player")
{
if (thing.health > 0)
{
if (thing == self)
return 0;
else
{
if (thing.items & IT_INVISIBILITY) //FIXME
thisp = 2;
else if (coop)
{
thisp = 200;
if (thing.target1.classname == "player")
if (!thing.target1.ishuman)
return 0;
}
else if (teamplay && thing.team == self.team)
{
thisp = 100;
if (thing.target1.classname == "player")
return 0;
}
else thisp = 30;
}
}
}
else if (thing.classname == "waypoint")
{
if (thing.b_aiflags & AI_SNIPER)
thisp = 30;
else if (thing.b_aiflags & AI_AMBUSH)
thisp = 30;
}
if (pointcontents(thing.origin) < -3)
return 0;
if (thisp)
{
if (thing.current_way)
{
// check to see if it's unreachable
if (thing.current_way.items == -1)
return 0;
else
thisp = thisp + (13000 - thing.current_way.items) * 0.05;
}
}
return thisp;
};
void(float scope) bot_look_for_crap =
{
local entity foe, best;
local float thatp, bestp, dist;
if (scope == 1)
foe = findradius(self.origin, 13000);
else
foe = findradius(self.origin, 500);
bestp = 1;
while(foe)
{
thatp = priority_for_thing(foe);
if (thatp)
if (!scope)
if (!sisible(foe))
thatp = 0;
if (thatp > bestp)
{
bestp = thatp;
best = foe;
dist = vlen(self.origin - foe.origin);
}
foe = foe.chain;
}
if (best == world)
return;
if (!target_onstack(best))
{
target_add(best);
if (scope)
{
bot_get_path(best, FALSE);
self.b_aiflags = self.b_aiflags | AI_WAIT;
}
}
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bot_angle_set
Sets the bots look keys & b_angle to point at
the target - used for fighting and just
generally making the bot look good.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
float() crandom;
void() bot_angle_set =
{
local float h;
local vector view, voffset;
local vector offset, spot1, oldview;
local float offset_amt, dist1, dist2;
if (self.enemy)
{
if (self.enemy.items & 524288)
if (random() > 0.01)
return;
if (self.missile_speed == 0)
self.missile_speed = 10000;
if (self.enemy.solid == SOLID_BSP)
{
view = (((self.enemy.absmin + self.enemy.absmax) * 0.5)); // - self.origin
self.dontshoot = FALSE;
view = normalize(view - (self.origin + self.view_ofs));
}
else
{
view = self.enemy.origin;
// find lead point on target
// then traceline to it, if blocked before full dist,
// set targetpoint to stopped origin
// then trace again to check if there's a clear path to
// target, if not, just target enemy.origin.
// also, check to see if trace endpoint is within 140 qu
// of the bot, if so, aim at the enemy.origin, because
// we are looking at a wall, and that could hurt if it's a rocketlauncher
h = vlen(view - self.origin) / self.missile_speed; // need to get the speed of projectile
if (self.enemy.flags & FL_ONGROUND)
view = self.enemy.velocity * h; // Aim straight at enemy, not at his feet, that will be handled later
else
view = (self.enemy.velocity - (sv_gravity * '0 0 1') * h) * h; // hes in the air, lead accordingly
view = self.enemy.origin + view; // set our leadpoint
traceline(self.enemy.origin, view, FALSE, self); // trace from enemy.origin to lead aimpoint
view = trace_endpos; // set view origin to lead aimpoint
traceline(self.origin + self.view_ofs, view, FALSE, self); // do a trace to view from self.
spot1 = trace_endpos; // get trace_endpos
dist1 = vlen(spot1 - (self.origin + self.view_ofs)); // range from self to trace
dist2 = vlen(view - (self.origin + self.view_ofs)); // range from self to aimpoint
if((dist1 < 140) && (dist1 != dist2)) // too close, shoot at enemy.origin
view = self.enemy.origin;
else if(vlen(view - spot1) > 32 && trace_ent.takedamage == DAMAGE_NO)
view = self.enemy.origin; // something that can't be damaged in the way, just shoot at enemy.origin.
dist2 = vlen(view - (self.origin+self.view_ofs));
// DRS: Tweaked Supa Aimcode (wazat inspired) below
// DRS: FIXME: Change this so that it's not so consistent...
if(self.weapon != IT_GRENADE_LAUNCHER) // Grenade Launcher should not jitter
{
if(self.b_skill == 0)
offset_amt = 55; //110
else if (self.b_skill == 1)
offset_amt = 45; //80
else if (self.b_skill == 2)
offset_amt = 36; //60
else if (self.b_skill == 3)
offset_amt = 15; //0
offset_x = crandom() * offset_amt;
offset_y = crandom() * offset_amt;
offset_z = crandom() * offset_amt;
}
if(self.weapon == IT_GRENADE_LAUNCHER) // using grenades? Aim properly then!
{
local float realrange, flitime;
local vector grndir;
grndir = normalize(view - (self.origin + self.view_ofs));//normalize(self.enemy.origin - self.origin);
realrange = dist2*mathlib_cos(grndir_x);
flitime = realrange / self.missile_speed;
view = view + '0 0 -20'; // aim at feet
grndir = normalize(view - (self.origin + self.view_ofs));//normalize(self.enemy.origin - self.origin);
grndir = grndir * 600;
grndir_z = grndir_z + (sv_gravity*flitime) * 0.3;
view = normalize(grndir);
self.dontshoot = FALSE;
}
if(self.weapon == IT_ROCKET_LAUNCHER && (self.enemy.flags & FL_ONGROUND)) // Rocketlauncher? Aim at feet if they're on the ground!
{
oldview = view;
view = view + '0 0 -20'; // aim at feet
traceline(self.origin + self.view_ofs, view, FALSE, self);
if(vlen(view - trace_endpos) > 32)
view = oldview;
}
// DRS: Do a viability test on whether we actually should shoot or not
if(self.enemy.solid != SOLID_BSP && self.weapon != IT_GRENADE_LAUNCHER)
{
traceline(self.origin+self.view_ofs, view, TRUE, self);
if(vlen(trace_endpos - (self.origin+self.view_ofs)) < (dist2 - 32))
self.dontshoot = TRUE;
else if(vlen(trace_endpos - (self.origin+self.view_ofs)) > (dist2 + 32))
self.dontshoot = TRUE;
else self.dontshoot = FALSE;
}
if(self.weapon != IT_GRENADE_LAUNCHER)
view = normalize((view + offset) - (self.origin + self.view_ofs));
}
view = vectoangles(view);
view_x = view_x * -1;
self.b_angle = view;
}
else if (self.target1)
{
view = realorigin(self.target1);
if (self.target1.flags & FL_ITEM)
view = view + '0 0 48';
view = view - (self.origin + self.view_ofs);
view = vectoangles(view);
view_x = view_x * -1;
self.b_angle = view;
self.dontshoot = TRUE;
}
else
{
self.b_angle_x = 0;
self.dontshoot = TRUE;
}
// HACK HACK HACK HACK
// The bot falls off ledges a lot because of "turning around"
// so let the bot use instant turn around when not hunting a player
if (self.b_skill == 3)
{
if (self.b_angle_x < -180)
self.b_angle_x = self.b_angle_x + 360;
if (self.b_angle_x > 180)
self.b_angle_x = self.b_angle_x - 360;
self.keys = self.keys & 63;
self.v_angle = self.b_angle;
}
else if ((self.enemy == world || self.enemy.movetype == MOVETYPE_PUSH) && self.target1.classname != "player")
{
if (self.b_angle_x < -180)
self.b_angle_x = self.b_angle_x + 360;
if (self.b_angle_x > 180)
self.b_angle_x = self.b_angle_x - 360;
self.keys = self.keys & 63;
self.v_angle = self.b_angle;
}
else if (self.b_skill < 1) // skill 2 handled in bot_phys
{
if (self.b_angle_x < -180)
self.b_angle_x = self.b_angle_x + 360;
if (self.b_angle_x > 180)
self.b_angle_x = self.b_angle_x - 360;
self.keys = self.keys & 63;
if (angcomp(self.b_angle_y, self.v_angle_y) > 10)
self.keys = self.keys | KEY_LOOKLEFT;
else if (angcomp(self.b_angle_y, self.v_angle_y) < -10)
self.keys = self.keys | KEY_LOOKRIGHT;
if (angcomp(self.b_angle_x, self.v_angle_x) < -10)
self.keys = self.keys | KEY_LOOKUP;
else if (angcomp(self.b_angle_x, self.v_angle_x) > 10)
self.keys = self.keys | KEY_LOOKDOWN;
}
};
/*
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
BotAI
This is the main ai loop. Though called every
frame, the ai_time limits it's actual updating
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
*/
float stagger_think;
void() BotAI =
{
// am I dead? Fire randomly until I respawn
// health < 1 is used because fractional healths show up as 0 on normal player
// status bars, and the mod probably already compensated for that
if (self.health < 1)
{
self.button0 = floor(random() * 2);
self.button2 = 0;
self.keys = 0;
self.b_aiflags = 0;
ClearMyRoute();
self.target1 = self.target2 = self.target3 = self.target4 = self.enemy = world;
self.last_way = world;
return;
}
// stagger the bot's AI out so they all don't think at the same time, causing game
// 'spikes'
if (self.b_skill < 2)
{
if (self.ai_time > time)
return;
self.ai_time = time + 0.05;
if (bot_count > 0)
{
if ((time - stagger_think) < (0.1 / bot_count))
self.ai_time = self.ai_time + 0.1 / (2 * bot_count);
}
else
return;
}
if (self.view_ofs == '0 0 0')
bot_start_topic(7);
stagger_think = time;
// shut the bot's buttons off, various functions will turn them on by AI end
self.button2 = 0;
self.button0 = 0;
// target1 is like goalentity in normal Quake monster AI.
// it's the bot's most immediate target
if (route_table == self)
{
if (busy_waypoints <= 0)
{
if (waypoint_mode < WM_EDITOR)
bot_look_for_crap(TRUE);
}
self.b_aiflags = 0;
self.keys = 0;
}
else if (self.target1)
{
frik_movetogoal();
bot_path();
}
else
{
if (waypoint_mode < WM_EDITOR)
{
if(self.route_failed)
{
if (waypoint_mode == WM_DYNAMIC)
frik_bot_roam();
else
self.keys = 0;
self.route_failed = 0;
}
else if(!begin_route())
{
bot_look_for_crap(FALSE);
self.keys = 0;
}
}
else
{
self.b_aiflags = AI_WAIT;
self.keys = 0;
}
}
// bot_angle_set points the bot at it's goal (self.enemy or target1)
bot_angle_set();
// fight my enemy. Enemy is probably a field QC coders will most likely use a lot
// for their own needs, since it's unused on a normal player
// FIXME
if (self.enemy)
bot_fight_style();
else if (random() < 0.2)
if (random() < 0.2)
bot_weapon_switch(-1);
bot_dodge_stuff();
// checks to see if bot needs to start going up for air
if (self.waterlevel > 2)
{
if (time > (self.air_finished - 2))
{
traceline (self.origin, self.origin + '0 0 6800', TRUE, self);
if (trace_inopen)
{
self.keys = KEY_MOVEUP;
self.button2 = TRUE; // swim!
return; // skip ai flags for now - this is life or death
}
}
}
// b_aiflags handling
if (self.b_aiflags)
bot_handle_ai();
else
bot_chat(); // don't want chat to screw him up if he's rjing or something
};
Wazat's aim offset code still works though, right? If the bots seem too challenging, I can always just further modify the aim offset. After all, the most challenging aspect of bots in fps games is their aim. Reduce their aim, and they become much easier. I'll do some testing with their aim once this current bug is resolved!I think they're pretty deadly on any skill now. Guess I did too good of a job on their aimcode.
It's all good! I'm glad you are doing this, because I'm getting the urge to waypoint more painkeep maps. I may just do a whole separate release for the painkeep mod, rather than bundling it all together with my other waypoints. Speaking of which, I meant to ask you if you wanted to release all your Frikbot fixes on your own (as a stand-alone release), or if you wanted me to bundle the fixes with my waypoint pack. If you choose the latter, I would of course mention your name multiple times in the readme file and all the web sites that I release the waypoint pack. I have already been documenting all the changes you have made in a readme, so I can mention it in the release. Readme files for my projects always end up being large, because I believe in always mentioning all parties involved who helped with my projects.Sorry this painkeep adaptation is taking so long, but there are quite a few bugs within painkeeps code (I know this is a very old mod, and team evolve did their best at the time, but I'm amazed that people were actually able to play this over the net )
Wazat's aimcode still works, I've just been eating too many rockets to the face during playtesting. (and suffering ego damage from being last in place 20 - 8 frags) Don't worry about it.Lightning Hunter wrote: Wazat's aim offset code still works though, right? If the bots seem too challenging, I can always just further modify the aim offset. After all, the most challenging aspect of bots in fps games is their aim. Reduce their aim, and they become much easier. I'll do some testing with their aim once this current bug is resolved!
Might actually be better to do painkeeps waypoint package separate, I dunno. In either case, the bugfixed / enhanced version of painkeep will definitely be a step up from the original.Lightning Hunter wrote: It's all good! I'm glad you are doing this, because I'm getting the urge to waypoint more painkeep maps. I may just do a whole separate release for the painkeep mod, rather than bundling it all together with my other waypoints. Speaking of which, I meant to ask you if you wanted to release all your Frikbot fixes on your own (as a stand-alone release), or if you wanted me to bundle the fixes with my waypoint pack. If you choose the latter, I would of course mention your name multiple times in the readme file and all the web sites that I release the waypoint pack. I have already been documenting all the changes you have made in a readme, so I can mention it in the release. Readme files for my projects always end up being large, because I believe in always mentioning all parties involved who helped with my projects.
That code did in fact work. I have no clue what was wrong with my code. Maybe it was something else other than bot_ai. I have made so many changes in my code from your posts that I could have messed up anywhere along the way. Does the developmental code you just uploaded contain all the fixes until now? I think I may scrap all of my modified QC files and just start clean using the one you just uploaded. I did notice that your upload contains a mathlib.qc that mine does NOT contain. What kind of side effects could that have created?Dr. Shadowborg wrote:Your code appears to be correct, and I even tested it on the developmental build. You recompiled your code right?
Try this copy of the developmental build if you're still having problems:
http://fbe.am/uuk
That one works fine over here.
Haha. I know what you mean. I will definitely be testing the bots more thoroughly before a release to see if the skills need more balancing with their aim. I was actually thinking of making a request if it's possible to add maybe 2 more skill levels, but I didn't want to bother you with such a request with so many of my other requests lately. I have no clue how difficult it would be to make the skills go up to 5 instead of 3. If there were two more skills added, they could simply be identical to the skill 3 code in every way, except have different aim offsets using Wazat's code. So something like this:Wazat's aimcode still works, I've just been eating too many rockets to the face during playtesting. (and suffering ego damage from being last in place 20 - 8 frags) Don't worry about it.
Code: Select all
// DRS: Tweaked Supa Aimcode (wazat inspired) below
// DRS: FIXME: Change this so that it's not so consistent...
if(self.weapon != IT_GRENADE_LAUNCHER) // Grenade Launcher should not jitter
{
if(self.b_skill == 0)
offset_amt = 75;
else if (self.b_skill == 1)
offset_amt = 60;
else if (self.b_skill == 2)
offset_amt = 45;
else if (self.b_skill == 3)
offset_amt = 30;
else if (self.b_skill == 4)
offset_amt = 15;
else if (self.b_skill == 5)
offset_amt = 0;
offset_x = crandom() * offset_amt;
offset_y = crandom() * offset_amt;
offset_z = crandom() * offset_amt;
}
I'm looking forward to it! And I think I will make a separate release for the Painkeep mod, if not just to keep the filesize down.Might actually be better to do painkeeps waypoint package separate, I dunno. In either case, the bugfixed / enhanced version of painkeep will definitely be a step up from the original.
Sounds good. In that case, I will make sure I give you full credit for everything you have done when my next waypoint pack is complete.I'm going to release a fixed up Frikbot at some point built into an enhanced / fixed version of GPL v101qc, but it will be a bit off into the future, so feel free to release the current fixes with your pack.
Yes, all fixes until now (including the skill 1 and lower) have been included.Lightning Hunter wrote: ...Does the developmental code you just uploaded contain all the fixes until now? I think I may scrap all of my modified QC files and just start clean using the one you just uploaded. I did notice that your upload contains a mathlib.qc that mine does NOT contain. What kind of side effects could that have created?
I see no reason why that code wouldn't work, beyond some small tweaks being required to support the higher skill levels. (predominately within the skill == 3 related stuff needing to be set to skill >= 3 instead)Lightning Hunter wrote: Haha. I know what you mean. I will definitely be testing the bots more thoroughly before a release to see if the skills need more balancing with their aim. I was actually thinking of making a request if it's possible to add maybe 2 more skill levels, but I didn't want to bother you with such a request with so many of my other requests lately. I have no clue how difficult it would be to make the skills go up to 5 instead of 3. If there were two more skills added, they could simply be identical to the skill 3 code in every way, except have different aim offsets using Wazat's code. So something like this:That would give players more options if they were either getting their rear kicked by the bots, or found the bots too easy. The bots could be as difficult as being god-like with their aim, or miss almost every time (and everything in-between).Code: Select all
// DRS: Tweaked Supa Aimcode (wazat inspired) below // DRS: FIXME: Change this so that it's not so consistent... if(self.weapon != IT_GRENADE_LAUNCHER) // Grenade Launcher should not jitter { if(self.b_skill == 0) offset_amt = 75; else if (self.b_skill == 1) offset_amt = 60; else if (self.b_skill == 2) offset_amt = 45; else if (self.b_skill == 3) offset_amt = 30; else if (self.b_skill == 4) offset_amt = 15; else if (self.b_skill == 5) offset_amt = 0; offset_x = crandom() * offset_amt; offset_y = crandom() * offset_amt; offset_z = crandom() * offset_amt; }
Sounds good, be sure to send them my way when they're done so I can include them in the later GPL v101qc+FBX package.Lightning Hunter wrote: Edit: By the way, I am currently doing the waypoints for dm1-6 from scratch. I figured now that I have waypointed 730 custom made maps, it is time to apply all my knowledge and experience of waypointing to the official deathmatch maps. I am being extra picky with these waypoints, and will make sure they are pretty much flawless. I will then apply them to the .qc files instead of using waypoint files.
So the developmental code you sent me has faster nails and such? I was going to use it as my new permanent source code for the Frikbot waypoint pack, but I guess some things would have to be reverted back to the default before I do that. Has anything else besides the nails and lifetime messages changed? If you have modified too much of the code, then maybe I should use my old code and just fix it up.Dr. Shadowborg wrote: The only other things different with the developmental would be faster SNG nails (for lead code testing), and player lifetime messages when players / bots die. Neither of them would cause the problems you mentioned.
Will do. I completed DM1 and DM2 so far. DM2 was quite a bit easier than I thought it would be to waypoint (many of the custom maps I waypointed had more difficult buttons and traps). The bots will activate all traps, obtain all items, use all the different paths and shortcuts, etc.. They will even obtain the red armor and mega health without extending the bridge (although they will sometimes use the bridge too, just for some randomization). The most difficult part actually ended up being the train that goes to the grenade launcher and mega health. It took about 15 minutes to set that thing up properly, but the bots use it well now.Sounds good, be sure to send them my way when they're done so I can include them in the later GPL v101qc+FBX package.
That's all I remember touching, and both are easily removed.Lightning Hunter wrote: So the developmental code you sent me has faster nails and such? I was going to use it as my new permanent source code for the Frikbot waypoint pack, but I guess some things would have to be reverted back to the default before I do that. Has anything else besides the nails and lifetime messages changed? If you have modified too much of the code, then maybe I should use my old code and just fix it up.
Code: Select all
bprint(targ.netname);
bprint(" died after ");
bprint(ftos(time - targ.deathtime));
bprint(" seconds\n");
The code has been applied to the developmental code. I tried it on ztnbase again, and they are definitely changing their weapon properly for buttons. Maybe he was shooting at another bot that you couldn't see?Lightning Hunter wrote: Edit: I just now saw a bot fire a grenade launcher at a button. I thought that was fixed? I guess you did not apply that fix to the developmental code?
Found the problem, it can be fixed in one of two different ways:Lightning Hunter wrote: Maybe I should use the code I have been using throughout this thead, and just find the problem that is happening with skill 1. I have uploaded it for you here Shadowborg. Maybe you can find the issue by comparing it to your code?
http://fbe.am/uuA
Code: Select all
else if (self.b_skill < 1) // skill 2 handled in bot_phys
Code: Select all
else if (self.b_skill < 2) // skill 2 handled in bot_phys
Code: Select all
if (self.b_skill < 2) // use mouse emulation
Code: Select all
if (self.b_skill < 1) // use mouse emulation
That's possible. The bot was standing against a door that opens using a shootable button, and was firing grenades in the general direction of the button (the bot must have fired 10 grenades or so in that direction). I thought he was aiming at the button, but maybe I missed something else.The code has been applied to the developmental code. I tried it on ztnbase again, and they are definitely changing their weapon properly for buttons. Maybe he was shooting at another bot that you couldn't see?
So I'm guessing "bad keyboarder" controls makes the bots play worse than mouse emulation? If so, then skill 0 should be set that way, but skill 1 should use mouse emulation. I guess skill 2 should use mouse emulation as well, although I'm not sure what it currently uses.Found the problem, it can be fixed in one of two different ways:
Method #1: In bot_ai.qc, within bot_angle_set, change thisto this:Code: Select all
else if (self.b_skill < 1) // skill 2 handled in bot_phys
This method makes skills 0-1 use bad keyboarder controls.Code: Select all
else if (self.b_skill < 2) // skill 2 handled in bot_phys
Method #2:
In bot_phys.qc, within CL_KeyMove and change this:to this:Code: Select all
if (self.b_skill < 2) // use mouse emulation
This method will allow skill 1-2 to use mouse emulation. (but not skill 0) Skill 3 pretty much just uses instalook, so it doesn't really matter.Code: Select all
if (self.b_skill < 1) // use mouse emulation
Original FBX behavior was "Bad Keyboarder" for skills 0-1 (skill 0 disables some AI flags), Mouse Emulation for skill 2, and insta-look for skill 3. Developmental code was set for skill 0 = bad keyboarder (with some disabled AI flags), skill 1-2 mouse emulation, skill 3 instalook. (Method #2) Personally, I would just remove the ai flag limiters for skill 0 (retaining "bad keyboarder"), and allow skill 1 to use mouse emulation.Lightning Hunter wrote: So I'm guessing "bad keyboarder" controls makes the bots play worse than mouse emulation? If so, then skill 0 should be set that way, but skill 1 should use mouse emulation. I guess skill 2 should use mouse emulation as well, although I'm not sure what it currently uses.
Which method(s) are you using in your developmental code? I'm not even sure how the default was set.
Agreed.Lightning Hunter wrote: Edit: I'm not sure if this topic has anything to with an issue I am noticing with the skill levels or not, but I do think that the bots should navigate the same way in skill 1 as they do in skill 2. I noticed that in DM2 that I just waypointed, skill 2 and 3 bots will succeed in bypassing the extending bridge and jump across the lava gap to the red armor and mega health 100% of the time, unless another bot interrupts them mid-jump. However, in Skill 1, the bots only make the jump maybe 10% of the time. This is not good, because I'm guessing a lot of people will play the bots on skill 1. I think the only difference between skill 1 and 2 should be with the aim. Skill 1 bots currently look pretty sucky with their navigation. I think the current skill 1 navigation should be what skill 0 looks like.
Quite helpful, this issue would have driven me up the wall a month or so ago, now it's this easy to fix:Lightning Hunter wrote: I have uploaded my dm2 waypoints in case you want to take a look for yourself, Shadowborg. If you want to see the difference in navigation between skills, simply spawn a bot and order it to jump across the lava gap to the red armor/mega health using the bot management menu (I have it bound to the home key). Skill 2/3 bots should make it just fine, while skill 1 bots will probably fall right in to the lava.
http://fbe.am/uv6
Code: Select all
if (self.b_skill < 2)
Code: Select all
if (self.b_skill < 1)
I had no clue it would be almost as simple as adding the code in the quote. Doesn't more code have to be added for the game to recognize a skill 4 and 5? What else would need to be done? I don't know how many times skill 3 is mentioned.Dr. Shadowborg wrote:I see no reason why that code wouldn't work, beyond some small tweaks being required to support the higher skill levels. (predominately within the skill == 3 related stuff needing to be set to skill >= 3 instead)Lightning Hunter wrote: I was actually thinking of making a request if it's possible to add maybe 2 more skill levels, but I didn't want to bother you with such a request with so many of my other requests lately. I have no clue how difficult it would be to make the skills go up to 5 instead of 3. If there were two more skills added, they could simply be identical to the skill 3 code in every way, except have different aim offsets using Wazat's code. So something like this:That would give players more options if they were either getting their rear kicked by the bots, or found the bots too easy. The bots could be as difficult as being god-like with their aim, or miss almost every time (and everything in-between).Code: Select all
// DRS: Tweaked Supa Aimcode (wazat inspired) below // DRS: FIXME: Change this so that it's not so consistent... if(self.weapon != IT_GRENADE_LAUNCHER) // Grenade Launcher should not jitter { if(self.b_skill == 0) offset_amt = 75; else if (self.b_skill == 1) offset_amt = 60; else if (self.b_skill == 2) offset_amt = 45; else if (self.b_skill == 3) offset_amt = 30; else if (self.b_skill == 4) offset_amt = 15; else if (self.b_skill == 5) offset_amt = 0; offset_x = crandom() * offset_amt; offset_y = crandom() * offset_amt; offset_z = crandom() * offset_amt; }
Probably only existed to help differentiate skill 1 from the other skills as a sort of simulated "reflex" latency to help them screw up more. (e.g. make them more human. Even I occasionally screw up on that jump, which is why I usually jump from the handrail instead.)Lightning Hunter wrote: I went ahead and set skill 1 to use mouse emulation, and changed the bit of code you mentioned. Indeed, it did fix the navigation issues! How funny that all this time, one line of code could have made waypointing easier for me. It wasn't until the last hundred or so waypointed maps that I started testing the bots on skill 3. I always tested them using skill 1, and it drove me nuts how the bots were not consistent (randomly failing jumps after tweaking the waypoints for hours). If I had realized that they could navigate better, it might have saved me hundreds of hours of headaches.
I just investigated this, while theres nothing stopping you from supporting skills higher than 3 in the code, there appears to be some engine hardcoding that locks skills higher than 3 back down to 3 when switching maps. (at least there is in winquake, other engines may have removed this limitation)Lightning Hunter wrote: By the way, I meant to ask you about this:
Dr. Shadowborg wrote:Lightning Hunter wrote: I was actually thinking of making a request if it's possible to add maybe 2 more skill levels, but I didn't want to bother you with such a request with so many of my other requests lately. I have no clue how difficult it would be to make the skills go up to 5 instead of 3. If there were two more skills added, they could simply be identical to the skill 3 code in every way, except have different aim offsets using Wazat's code. So something like this:That would give players more options if they were either getting their rear kicked by the bots, or found the bots too easy. The bots could be as difficult as being god-like with their aim, or miss almost every time (and everything in-between).Code: Select all
// DRS: Tweaked Supa Aimcode (wazat inspired) below // DRS: FIXME: Change this so that it's not so consistent... if(self.weapon != IT_GRENADE_LAUNCHER) // Grenade Launcher should not jitter { if(self.b_skill == 0) offset_amt = 75; else if (self.b_skill == 1) offset_amt = 60; else if (self.b_skill == 2) offset_amt = 45; else if (self.b_skill == 3) offset_amt = 30; else if (self.b_skill == 4) offset_amt = 15; else if (self.b_skill == 5) offset_amt = 0; offset_x = crandom() * offset_amt; offset_y = crandom() * offset_amt; offset_z = crandom() * offset_amt; }