diff --git a/fbxa/bot.cfg b/fbxa/bot.cfg new file mode 100644 index 0000000..cb17b85 --- /dev/null +++ b/fbxa/bot.cfg @@ -0,0 +1,3 @@ +alias addbot "impulse 100" +alias removebot "impulse 102" +alias botcam "impulse 103" diff --git a/fbxa/bot.qc b/fbxa/bot.qc new file mode 100644 index 0000000..5fe5fec --- /dev/null +++ b/fbxa/bot.qc @@ -0,0 +1,1245 @@ + +/* +====================================== +FrikBot X (Version 0.10.1) +====================================== + +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. + +====================================== +These installation instructions only apply to Normal Quake (as does this +entire file). For QuakeWorld, please refer to bot_qw.qc + +-------------------------------------- +To install on a new mod, do all this: +-------------------------------------- +Place all included bot*.qc files in the subdirectory "frikbot" +in your source folder, then... + +* Add the following lines to progs.src right after the defs.qc line +frikbot/bot.qc +frikbot/bot_way.qc +frikbot/bot_fight.qc +frikbot/bot_ai.qc +frikbot/bot_misc.qc +frikbot/bot_phys.qc +frikbot/bot_move.qc +frikbot/bot_ed.qc + +-------------------------------------- +* Comment out the following functions in defs.qc +sound, stuffcmd, sprint, aim, centerprint, setspawnparms +WriteByte, WriteChar, WriteShort, WriteLong, WriteCoord +WriteAngle, WriteString, WriteEntity +-------------------------------------- +* Add this to worldspawn() in world.qc, right at the very top, before InitBodyQue(); +BotInit(); // FrikBot +-------------------------------------- +* add this line to StartFrame() in world.qc, at the very top +BotFrame(); // FrikBot +-------------------------------------- +* Add these two lines to PlayerPreThink in client.qc at the very top +if (BotPreFrame()) // FrikBot + return; +-------------------------------------- +* Add this line to PlayerPostThink in client.qc at the very top +if (BotPostFrame()) // FrikBot + return; +-------------------------------------- +* Add the following line to the very top of Client Connect in client.qc +ClientInRankings(); // FrikBot +-------------------------------------- +* Add these lines to the very top of ClientDisconnect in client.qc +ClientDisconnected(); // FrikBot +-------------------------------------- +*/ + +void() bot_map_load = +{ + // place your qc loaded waypoints here + + if (mapname == "dm1") + map_dm1(); + else if (mapname == "dm2") + map_dm2(); + else if (mapname == "dm3") + map_dm3(); + else if (mapname == "dm4") + map_dm4(); + else if (mapname == "dm5") + map_dm5(); + else if (mapname == "dm6") + map_dm6(); +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Variables and shtuff + +bot.qc has become pretty much a header file +for all variable in the bot... + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +// ----- entity fields --- +.float wallhug, keys, oldkeys, ishuman; +.float b_frags, b_clientno, b_shirt, b_pants; +.float priority, ai_time, b_sound, missile_speed; +.float portal_time, b_skill, switch_wallhug; +.float b_aiflags, b_num, b_chattime; +.float b_menu, b_menu_time, b_menu_value; +.float route_failed, dyn_flags, dyn_time; +.float dyn_plat; +.entity temp_way, last_way, phys_obj; +.entity target1, target2, target3, target4; +.entity _next, _last; +.entity current_way; +.vector b_angle, b_dest, mouse_emu, obs_dir; +.vector movevect, b_dir; +.vector dyn_dest; + +// --------defines----- +float SVC_UPDATENAME = 13; +float SVC_UPDATEFRAGS = 14; +float SVC_UPDATECOLORS = 17; + +// used for the physics & movement AI +float KEY_MOVEUP = 1; +float KEY_MOVEDOWN = 2; +float KEY_MOVELEFT = 4; +float KEY_MOVERIGHT = 8; +float KEY_MOVEFORWARD = 16; +float KEY_MOVEBACK = 32; +float KEY_LOOKUP = 64; +float KEY_LOOKDOWN = 128; +float KEY_LOOKLEFT = 256; +float KEY_LOOKRIGHT = 512; + +// these are aiflags for waypoints +// some overlap to the bot +float AI_TELELINK_1 = 1; // link type +float AI_TELELINK_2 = 2; // link type +float AI_TELELINK_3 = 4; // link type +float AI_TELELINK_4 = 8; // link type +float AI_DOORFLAG = 16; // read ahead +float AI_PRECISION = 32; // read ahead + point +float AI_SURFACE = 64; // point +float AI_BLIND = 128; // read ahead + point +float AI_JUMP = 256; // point + ignore +float AI_DIRECTIONAL = 512; // read ahead + ignore +float AI_PLAT_BOTTOM = 1024; // read ahead +float AI_RIDE_TRAIN = 2048; // read ahead +float AI_SUPER_JUMP = 4096; // point + ignore + route test +float AI_SNIPER = 8192; // point type +float AI_AMBUSH = 16384; // point type +float AI_DOOR_NO_OPEN = 32768; // read ahead +float AI_DIFFICULT = 65536; // route test +float AI_TRACE_TEST = 131072; // route test + +// these are flags for bots/players (dynamic/editor flags) +float AI_OBSTRUCTED = 1; +float AI_HOLD_SELECT = 2; +float AI_ROUTE_FAILED = 2; +float AI_WAIT = 4; +float AI_DANGER = 8; + + +// addition masks +float AI_POINT_TYPES = 29152; +float AI_READAHEAD_TYPES = 36528; +float AI_IGNORE_TYPES = 4864; + +float WM_UNINIT = 0; +float WM_DYNAMIC = 1; +float WM_LOADING = 2; +float WM_LOADED = 3; +float WM_EDITOR = 4; +float WM_EDITOR_DYNAMIC = 5; +float WM_EDITOR_DYNLINK = 6; + + +float OPT_SAVEBOTS = 1; +float OPT_NOCHAT = 2; + +// -------globals----- +float active_clients; +float max_clients, real_frametime; +float bot_count, b_options; +float waypoint_mode, dump_mode; +float waypoints, direct_route; +float sv_friction, sv_gravity; +float sv_accelerate, sv_maxspeed, sv_stopspeed; +entity fixer; +entity route_table; +entity b_temp1, b_temp2, b_temp3; +entity player_head, phys_head, way_head; +float busy_waypoints; +float saved_bots, saved_skills1, saved_skills2, current_bots; + +// -------ProtoTypes------ +// external +void() ClientConnect; +void() ClientDisconnect; +void() SetNewParms; + +// rankings +float(float clientno) ClientBitFlag; +float() ClientNextAvailable; +void(float whichteam, float whatbot, float whatskill) BotConnect; +void(entity bot) BotDisconnect; +void(float clientno) BotInvalidClientNo; +void(entity who) UpdateClient; + +// waypointing +void() DynamicWaypoint; +entity(vector org) make_waypoint; +void() ClearAllWays; +void() FixWaypoints; +float() begin_route; +void(entity this, float direct) bot_get_path; +void() WaypointThink; +entity(entity start) FindWayPoint; + +// physics & movement +float(entity e) bot_can_rj; +void() bot_jump; +void() frik_bot_roam; +float(vector weird) frik_walkmove; +void() frik_movetogoal; +void() frik_obstacles; +float(float flag) frik_recognize_plat; +float(vector sdir) frik_KeysForDir; +void(vector whichway, float danger) frik_obstructed; +void() SV_Physics_Client; +void() SV_ClientThink; +void() CL_KeyMove; + +// ai & misc +string() PickARandomName; +float(entity targ) fov; +float(float y1, float y2) angcomp; +float(entity targ1, entity targ2) wisible; +float(entity targ) sisible; +float(entity targ) fisible; +vector(entity ent) realorigin; +void(entity ent) target_drop; +void(entity ent) target_add; +void() KickABot; +void() BotImpulses; +void(entity targ, float success) bot_lost; +string(float r) BotName; +float(float v) frik_anglemod; +void() bot_chat; +void(float tpic) bot_start_topic; + + +// editor stuffs + +void() bot_way_edit; +void() bot_menu_display; + + +// ----------Commands--------- +void(entity e, float chan, string samp, float vol, float atten) frik_sound = #8; +void(entity client, string s) frik_stuffcmd = #21; +void(entity client, string s) frik_sprint = #24; +vector(entity e, float sped) frik_aim = #44; +void(entity client, string s) frik_centerprint = #73; +void(entity e) frik_setspawnparms = #78; +void(float to, float f) frik_WriteByte = #52; +void(float to, float f) frik_WriteChar = #53; +void(float to, float f) frik_WriteShort = #54; +void(float to, float f) frik_WriteLong = #55; +void(float to, float f) frik_WriteCoord = #56; +void(float to, float f) frik_WriteAngle = #57; +void(float to, string s) frik_WriteString = #58; +void(float to, entity s) frik_WriteEntity = #59; + +void(entity client, string s1, string s2, string s3, string s4, string s5, string s6, string s7) +frik_big_centerprint = #73; + +//---------------------------------------------------------------------------- + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Function redclarations. These allow function +designed to work for clients (sprint, so forth) +to mainly not complain when working with a bot + +Although these shouldn't be needed anymore, +as the bots truly are clients now, if you don't +stop the SZ_ buffer from filling up by disabling +direct messages to the bots, it crashes quake :-( + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +void(entity client, string s) stuffcmd = +{ + if (client.ishuman == 1) + frik_stuffcmd(client, s); + b_temp1 = player_head; + + while(b_temp1) + { + if (b_temp1.classname == "botcam") + { + if ((b_temp1.enemy == client) && b_temp1.ishuman) + frik_stuffcmd(b_temp1, s); + } + b_temp1 = b_temp1._next; + } +}; + +void(entity e) setspawnparms = +{ + if (e.ishuman == 1) + frik_setspawnparms(e); + else + { + b_temp1 = player_head; + while(b_temp1) + { + if (b_temp1.ishuman) + { + frik_setspawnparms(b_temp1); + return; + } + b_temp1 = b_temp1._next; + } + SetNewParms(); + } +}; +void(entity client, string s) sprint = +{ + if (client.ishuman == 1) + frik_sprint(client, s); + b_temp1 = player_head; + + while(b_temp1) + { + if (b_temp1.classname == "botcam") + { + if ((b_temp1.enemy == client) && b_temp1.ishuman) + frik_sprint(b_temp1, s); + } + b_temp1 = b_temp1._next; + } + +}; +void(entity client, string s) centerprint = +{ + if (client.ishuman == 1) + frik_centerprint(client, s); + b_temp1 = player_head; + + while(b_temp1) + { + if (b_temp1.classname == "botcam") + { + if ((b_temp1.enemy == client) && b_temp1.ishuman) + frik_centerprint(b_temp1, s); + } + b_temp1 = b_temp1._next; + } +}; + +vector(entity e, float sped) aim = +{ + e.missile_speed = sped; + return frik_aim(e, sped); +}; + +void(entity e, float chan, string samp, float vol, float atten) sound = +{ + + frik_sound(e, chan, samp, vol, atten); + if (samp == "items/inv3.wav") + return; + else if (e.classname == "player") + e.b_sound = time + 1; + else if (other.classname == "player") + other.b_sound = time + 1; + +}; +void(float to, float f) WriteByte = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteByte(to, f); +}; +void(float to, float f) WriteChar = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteChar(to, f); +}; +void(float to, float f) WriteShort = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteShort(to, f); +}; +void(float to, float f) WriteLong = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteLong(to, f); +}; +void(float to, float f) WriteCoord = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteCoord(to, f); +}; +void(float to, float f) WriteAngle = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteAngle(to, f); +}; +void(float to, string s) WriteString = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteString(to, s); +}; +void(float to, entity s) WriteEntity = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteEntity(to, s); +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Bot Cam, see what the bot sees (or any other player) + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +float() botcam = +{ + if (self.classname != "botcam") + return FALSE; + setorigin(self, self.enemy.origin); + self.items = self.enemy.items; + self.weapon = self.enemy.weapon; + self.weaponmodel = self.enemy.weaponmodel; + self.currentammo = self.enemy.currentammo; + self.weaponframe = self.enemy.weaponframe; + self.ammo_shells = self.enemy.ammo_shells; + self.ammo_nails = self.enemy.ammo_nails; + self.ammo_rockets= self.enemy.ammo_rockets; + self.ammo_cells = self.enemy.ammo_cells; + self.view_ofs = self.enemy.view_ofs; + self.health = self.enemy.health; + self.armorvalue = self.enemy.armorvalue; + self.dmg_take = self.enemy.dmg_take; + self.dmg_save = self.enemy.dmg_save; + self.dmg_inflictor = self.enemy.dmg_inflictor; + self.punchangle = self.enemy.punchangle; + self.deadflag = self.enemy.deadflag; + msg_entity = self; + WriteByte (MSG_ONE,5); + WriteEntity (MSG_ONE, self.enemy); + WriteByte (MSG_ONE, 10); + WriteAngle (MSG_ONE,self.enemy.v_angle_x); + WriteAngle (MSG_ONE,self.enemy.v_angle_y); + WriteAngle (MSG_ONE,self.enemy.v_angle_z); + self.modelindex = 0; + + self.impulse = 0; + return TRUE; + +}; + +void() botcam_u = +{ + + // sloppy cycling code + if (self.classname != "botcam") + { + self.enemy = player_head; + } + else + { + do + self.enemy = self.enemy._next; + while (self.enemy.classname == "botcam"); + } + if (self.enemy == self) + { + do + self.enemy = self.enemy._next; + while (self.enemy.classname == "botcam"); + } + + self.classname = "botcam"; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + self.takedamage = DAMAGE_NO; + + + if (!self.enemy) + { + sprint(self, "No one left to track!\n"); + msg_entity = self; + WriteByte (MSG_ONE,5); + WriteEntity (MSG_ONE, self); + PutClientInServer(); + return; + } + if (!self.enemy.ishuman) + { + self.enemy.dmg_take = 0; + self.enemy.dmg_save = 0; + } + sprint(self, "Now tracking "); + sprint(self, self.enemy.netname); + sprint(self, "\n"); +}; + + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Stuff mentioned up top +it just links the bot into the mod + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() ClientFixRankings = +{ + local float cno; + if (self.switch_wallhug > time) + return; + self.switch_wallhug = 0; + + b_temp2 = nextent(world); + cno = 0; + + while (cno < max_clients) + { + if ((!b_temp2.ishuman) && (active_clients & ClientBitFlag(cno))) + UpdateClient(b_temp2); + cno = cno + 1; + b_temp2 = nextent(b_temp2); + } +}; + +void() ClientInRankings = +{ + local float cno; + if (player_head) + player_head._last = self; + + self._next = player_head; + self._last = world; + player_head = self; + + if (!self.phys_obj) + { + b_temp2 = phys_head; + while (b_temp2 != world && b_temp2.owner != self) + b_temp2 = b_temp2._next; + self.phys_obj = b_temp2; + } + + if (self.ishuman == 2) + { + self.ishuman = FALSE; + return; + } + cno = self.colormap - 1; + BotInvalidClientNo (cno); + active_clients = active_clients | ClientBitFlag(cno); + + self.b_clientno = cno; + self.ishuman = TRUE; + self.switch_wallhug = time + 1; +}; + + +void() ClientDisconnected = +{ + if (player_head == self) + player_head = self._next; + if (self._next) + self._next._last = self._last; + if (self._last) + self._last._next = self._next; + + active_clients = active_clients - active_clients & ClientBitFlag(self.b_clientno); +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotPreFrame & BotPostFrame, used to make the +bot easier to install + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +float () BotPreFrame = +{ + if (self.b_clientno == -1) + return TRUE; + if (self.ishuman) + { + if (self.switch_wallhug) + ClientFixRankings(); + if (self.classname == "botcam") + return TRUE; + } + if (self.b_frags != self.frags) + { + + if (self.b_frags > self.frags) + { + if (pointcontents(self.origin) == CONTENT_LAVA) + bot_start_topic(10); + else + bot_start_topic(9); + } + else + bot_start_topic(2); + self.b_frags = self.frags; + } + DynamicWaypoint(); + return FALSE; +}; +float () BotPostFrame = +{ + if (self.b_clientno == -1) + return TRUE; + if (self.ishuman) + { + + if (waypoint_mode > WM_LOADED) + bot_menu_display(); + + BotImpulses(); + + if (botcam()) + return TRUE; + } + return FALSE; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Bot Chat code + +The rest of this code is in bot_misc.qc + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +void(string h) BotSay = // simulate talking by composing a 'chat' message +{ + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 1); + WriteString(MSG_ALL, self.netname); + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 2); + WriteString(MSG_ALL, h); +}; +void() BotSayInit = +{ + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 1); + WriteString(MSG_ALL, self.netname); +}; +void(string h) BotSay2 = +{ + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 2); + WriteString(MSG_ALL, h); +}; +void(string h) BotSayTeam = +{ + local entity t; + if (!teamplay) + return; + t = player_head; + while(t) + { + if (t.team == self.team) + { + msg_entity = t; + WriteByte(MSG_ONE, 8); + WriteByte(MSG_ONE, 1); + WriteByte(MSG_ONE, 40); + WriteString(MSG_ONE, self.netname); + WriteByte(MSG_ONE, 8); + WriteByte(MSG_ONE, 2); + WriteByte(MSG_ONE, 41); + WriteString(MSG_ONE, h); + } + t = t._next; + } +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotInit + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +void() BotInit = +{ + local entity ent, fisent; + local float numents; + + // spawn entities for the physics + ent = nextent(world); + max_clients = 0; + + while(ent != world) + { + max_clients = max_clients + 1; + ent = nextent(ent); + } + if (max_clients > 16) + max_clients = 16; + + ent = nextent(world); + fisent = world; + while (numents < max_clients) + { + + phys_head = spawn(); + if (fisent) + fisent._next = phys_head; + phys_head._last = fisent; + fisent = phys_head; + ent.phys_obj = phys_head; + phys_head.classname = "phys_obj"; + phys_head.owner = ent; + numents = numents + 1; + ent = nextent(ent); + } + precache_model("progs/s_light.spr"); + precache_model("progs/s_bubble.spr"); + // the bots return! + b_options = cvar("saved1"); + if (coop || (b_options & OPT_SAVEBOTS)) + { + saved_bots = cvar("scratch1"); + saved_skills1 = cvar("scratch2"); + saved_skills2 = cvar("scratch3"); + } + cvar_set ("saved4", "0"); + if (max_clients > 1) + { + localcmd("exec maps/"); + localcmd(mapname); + localcmd(".way\n"); + waypoint_mode = WM_DYNAMIC; + bot_map_load(); + } + else + waypoint_mode = WM_LOADED; + +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Rankings 'utilities'. Written by Alan Kivlin, +this code just fools clients by sending precisely +the same network messages as when a real player +signs on to the server. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +void(entity who) UpdateClient = +{ + WriteByte (MSG_ALL, SVC_UPDATENAME); + WriteByte (MSG_ALL, who.b_clientno); + WriteString (MSG_ALL, who.netname); + WriteByte (MSG_ALL, SVC_UPDATECOLORS); + WriteByte (MSG_ALL, who.b_clientno); + WriteByte (MSG_ALL, who.b_shirt * 16 + who.b_pants); + WriteByte (MSG_ALL, SVC_UPDATEFRAGS); + WriteByte (MSG_ALL, who.b_clientno); + WriteShort (MSG_ALL, who.frags); +}; + +float(float clientno) ClientBitFlag = +{ + // bigger, but faster + if (clientno == 0) + return 1; + else if (clientno == 1) + return 2; + else if (clientno == 2) + return 4; + else if (clientno == 3) + return 8; + else if (clientno == 4) + return 16; + else if (clientno == 5) + return 32; + else if (clientno == 6) + return 64; + else if (clientno == 7) + return 128; + else if (clientno == 8) + return 256; + else if (clientno == 9) + return 512; + else if (clientno == 10) + return 1024; + else if (clientno == 11) + return 2048; + else if (clientno == 12) + return 4096; + else if (clientno == 13) + return 8192; + else if (clientno == 14) + return 16384; + else if (clientno == 15) + return 32768; + return 0; +}; + +float() ClientNextAvailable = +{ + local float clientno; + + clientno = max_clients; + while(clientno > 0) + { + clientno = clientno - 1; + + if(!(active_clients & ClientBitFlag(clientno))) + return clientno; + } + + return -1; +}; + + +void(entity e1, entity e2, float flag) DeveloperLightning = +{ + // used to show waypoint links for debugging + WriteByte (MSG_BROADCAST, 23); + if (flag) + WriteByte (MSG_BROADCAST, 6); + else + WriteByte (MSG_BROADCAST, 13); + WriteEntity (MSG_BROADCAST, e2); + WriteCoord (MSG_BROADCAST, e1.origin_x); + WriteCoord (MSG_BROADCAST, e1.origin_y); + WriteCoord (MSG_BROADCAST, e1.origin_z); + WriteCoord (MSG_BROADCAST, e2.origin_x); + WriteCoord (MSG_BROADCAST, e2.origin_y); + WriteCoord (MSG_BROADCAST, e2.origin_z); +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Find Another Color + +Team finding code + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +float(float tcolor) FindAnotherColor = +{ + local float bestbet, scolor, pcount, bestp; + bestbet = -1; + bestp = 16; + while(scolor < 14) + { + if (scolor != tcolor) + { + b_temp2 = player_head; + pcount = 0; + while(b_temp2 != world) + { + if (b_temp2.team == scolor + 1) + pcount = pcount + 1; + b_temp2 = b_temp2._next; + } + if ((pcount < bestp) && pcount) + { + bestbet = scolor; + bestp = pcount; + } + } + scolor = scolor + 1; + } + if (bestbet < 0) + { + bestbet = tcolor; + while (bestbet == tcolor) + { + bestbet = floor(random() * 13); + } + } + return bestbet; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotConnect and related functions. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +entity(float num) GetClientEntity = +{ + local entity upsy; + upsy = world; + num = num + 1; + while (num > 0) + { + num = num - 1; + upsy = nextent(upsy); + } + return upsy; +}; + +void(float whichteam, float whatbot, float whatskill) BotConnect = +{ + local float f; + local string h; + local entity uself; + + f = ClientNextAvailable(); + uself = self; + if(f == -1) + { + bprint("Unable to connect a bot, server is full.\n"); + return; + } + + // chat thing + + active_clients = active_clients | ClientBitFlag(f); + bot_count = bot_count + 1; + self = GetClientEntity(f); + if (!saved_bots) + bot_start_topic(1); + self.b_clientno = f; + self.colormap = f + 1; + if (whatbot) + self.netname = BotName(whatbot); + else + self.netname = PickARandomName(); + + + // players can set skill all weird, so leave these checks in + whatskill = rint(whatskill); + if (whatskill > 3) + whatskill = 3; + else if (whatskill < 0) + whatskill = 0; + self.b_skill = whatskill; + + if (teamplay && !coop) + { + if (whichteam) + self.b_pants = FindAnotherColor(uself.team - 1); + else + self.b_pants = uself.team - 1; + self.b_shirt = self.b_pants; + } + + self.team = self.b_pants + 1; + UpdateClient(self); + SetNewParms(); + self.ishuman = 2; + ClientConnect(); + PutClientInServer(); + + // this is risky... could corrupt .way files if done wrong + // If you're not the gambling type, comment this out + + f = ClientBitFlag(self.b_num - 1); + current_bots = current_bots | f; + + if (self.b_num <= 8) + saved_skills1 = (saved_skills1 & (65536 - (3 * f)) | (self.b_skill * f)); + else + { + f = ClientBitFlag(self.b_num - 9); + saved_skills2 = (saved_skills2 & (65536 - (3 * f)) | (self.b_skill * f)); + } + + h = ftos(current_bots); + cvar_set("scratch1", h); + h = ftos(saved_skills1); + cvar_set("scratch2", h); + h = ftos(saved_skills2); + cvar_set("scratch3", h); + self = uself; + +}; + +void(entity bot) BotDisconnect = +{ + local string h; + local entity uself; + uself = self; + self = bot; + + bot_count = bot_count - 1; + current_bots = current_bots - (current_bots & ClientBitFlag(self.b_num - 1)); + h = ftos(current_bots); + cvar_set("scratch1", h); + + + ClientDisconnect(); + + if (self.b_clientno != -1) + { + // the bot's client number is not in use by a real player so we + // must remove it's entry in the rankings + // Quake engine sets all fields to 0, can only do the most important here + self.b_frags = self.frags = 0; + self.netname = ""; + self.classname = ""; + self.health = 0; + self.items = 0; + self.armorvalue = 0; + self.weaponmodel = ""; + self.b_pants = 0; + self.b_shirt = 0; + self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = 0; + UpdateClient(self); + active_clients = active_clients - (active_clients & ClientBitFlag(self.b_clientno)); + self.b_clientno = -1; + } + self = uself; +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotInvalidClientNo +kicks a bot if a player connects and takes the bot's space + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void(float clientno) BotInvalidClientNo = +{ + local entity bot; + + bot = GetClientEntity(clientno); + if(bot.b_clientno > 0) + { + if (!bot.ishuman) + { + bot.b_clientno = -1; + BotDisconnect(bot); + active_clients = active_clients | ClientBitFlag(self.b_clientno); + BotConnect(0, bot.b_num, bot.b_skill); + return; + } + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Waypoint Loading from file + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +void() LoadWaypoint = +{ + local vector org; + local entity tep; + local float r; + org_x = cvar("saved1"); + org_y = cvar("saved2"); + org_z = cvar("saved3"); + + tep = make_waypoint(org); + + r = cvar("saved4"); + + tep.b_aiflags = floor(r / 4); + tep.b_pants = cvar("scratch1"); + tep.b_skill = cvar("scratch2"); + tep.b_shirt = cvar("scratch3"); + tep.b_frags = cvar("scratch4"); +}; + +void() bot_return = +{ + if (time > 2) + { + if ((waypoint_mode == WM_DYNAMIC) || (waypoint_mode == WM_LOADED)) + { + // minor precaution + + if (saved_bots & 1) BotConnect(0, 1, saved_skills1 & 3); + if (saved_bots & 2) BotConnect(0, 2, (saved_skills1 & 12) / 4); + if (saved_bots & 4) BotConnect(0, 3, (saved_skills1 & 48) / 16); + if (saved_bots & 8) BotConnect(0, 4, (saved_skills1 & 192) / 64); + if (saved_bots & 16) BotConnect(0, 5, (saved_skills1 & 768) / 256); + if (saved_bots & 32) BotConnect(0, 6, (saved_skills1 & 3072) / 1024); + if (saved_bots & 64) BotConnect(0, 7, (saved_skills1 & 12288) / 4096); + if (saved_bots & 128) BotConnect(0, 8, (saved_skills1 & 49152) / 16384); + if (saved_bots & 256) BotConnect(0, 9, saved_skills2 & 3); + if (saved_bots & 512) BotConnect(0, 10, (saved_skills2 & 12) / 4); + if (saved_bots & 1024) BotConnect(0, 11, (saved_skills2& 48) / 16); + if (saved_bots & 2048) BotConnect(0, 12, (saved_skills2 & 192) / 64); + if (saved_bots & 4096) BotConnect(0, 13, (saved_skills2 & 768) / 256); + if (saved_bots & 8192) BotConnect(0, 14, (saved_skills2 & 3072) / 1024); + if (saved_bots & 16384) BotConnect(0, 15, (saved_skills2 & 12288) / 4096); + if (saved_bots & 32768) BotConnect(0, 16, (saved_skills2 & 49152) / 16384); + saved_bots = 0; + } + } +}; + + +void() WaypointWatch = +{ + // Waypoint Baywatch + local float bigboobs; + local string h; + + if (max_clients < 2) + return; + if (waypoint_mode != WM_UNINIT) + { + bigboobs = cvar("saved4"); + if (bigboobs != 0) + { + if ((bigboobs & 3) == 1) + ClearAllWays(); + else if ((bigboobs & 3) == 3) + { + FixWaypoints(); + h = ftos(b_options); + cvar_set("saved1", h); + cvar_set("saved4", "0"); + cvar_set("scratch1", "0"); + waypoint_mode = WM_LOADED; + return; + } + LoadWaypoint(); + waypoint_mode = WM_LOADING; + cvar_set("saved4", "0"); + } + } +}; +void() BotFrame = +{ + local float num; + + // for the sake of speed + sv_maxspeed = cvar("sv_maxspeed"); + sv_gravity = cvar("sv_gravity"); + sv_friction = cvar("sv_friction"); + sv_accelerate = cvar("sv_accelerate"); + sv_stopspeed = cvar("sv_stopspeed"); + real_frametime = frametime; // in NQ this is alright + + self = nextent(world); + num = 0; + while (num < max_clients) + { + if (self.ishuman == FALSE) + { + if (active_clients & ClientBitFlag(num)) + { + frik_obstacles(); + CL_KeyMove(); + SV_ClientThink(); + SV_Physics_Client(); + } + } + self = nextent(self); + num = num + 1; + } + WaypointWatch(); + + if (saved_bots) + bot_return(); +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Bot Impulses. Allows the player to perform bot +related functions. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() BotImpulses = +{ + local float f; + + if (self.impulse == 100) + { + f = cvar("skill"); + BotConnect(0, 0, f); + } + else if (self.impulse == 101) + { + f = cvar("skill"); + BotConnect(1, 0, f); + } + else if (self.impulse == 102) + KickABot(); + else if (self.impulse == 103) + botcam_u(); + else if (self.impulse == 104) + bot_way_edit(); + else + return; + + self.impulse = 0; +}; + + + diff --git a/fbxa/bot_ai.qc b/fbxa/bot_ai.qc new file mode 100644 index 0000000..f5bcde6 --- /dev/null +++ b/fbxa/bot_ai.qc @@ -0,0 +1,995 @@ +/*********************************************** +* * +* 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); + } + } + // 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; + local vector org; + + 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 = 20; + 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 < 40) + thisp = thisp + 50; + } + 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 = 25; + } + 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 = 35; + } + 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; + } + } + 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 = 100; + 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. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() bot_angle_set = +{ + local float h; + local vector view; + + if (self.enemy) + { + if (self.enemy.items & 524288) + if (random() > 0.2) + 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); + } + else + { + h = vlen(self.enemy.origin - self.origin) / self.missile_speed; + if (self.enemy.flags & FL_ONGROUND) + view = self.enemy.velocity * h; + else + view = (self.enemy.velocity - (sv_gravity * '0 0 1') * h) * h; + view = self.enemy.origin + view; + // FIXME: ? + traceline(self.enemy.origin, view, FALSE, self); + view = trace_endpos; + + if (self.weapon == 32) + view = view - '0 0 22'; + + view = normalize(view - self.origin); + } + 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; + } + else + self.b_angle_x = 0; + // 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) + { + self.keys = self.keys & 63; + self.v_angle = self.b_angle; + while (self.v_angle_x < -180) + self.v_angle_x = self.v_angle_x + 360; + while (self.v_angle_x > 180) + self.v_angle_x = self.v_angle_x - 360; + + } + else if ((self.enemy == world || self.enemy.movetype == MOVETYPE_PUSH) && self.target1.classname != "player") + { + self.keys = self.keys & 63; + self.v_angle = self.b_angle; + while (self.v_angle_x < -180) + self.v_angle_x = self.v_angle_x + 360; + while (self.v_angle_x > 180) + self.v_angle_x = self.v_angle_x - 360; + } + else if (self.b_skill < 2) // skill 2 handled in bot_phys + { + 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) + { + frik_bot_roam(); + 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 +}; diff --git a/fbxa/bot_ed.qc b/fbxa/bot_ed.qc new file mode 100644 index 0000000..98d3d51 --- /dev/null +++ b/fbxa/bot_ed.qc @@ -0,0 +1,1354 @@ +/*********************************************** +* * +* FrikBot Waypoint Editor * +* "The 'wtf is this doing in my mod' code" * +* * +***********************************************/ + +/* + +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. + +*/ + +float saved1, saved2, saved3, scratch1, scratch2, scratch3, scratch4; +float bytecounter, filecount; + +float MENU_MAIN = 1; +float MENU_WAYPOINTS = 2; +float MENU_LINKS = 3; +float MENU_FLAGS = 4; +float MENU_FLAGS2 = 5; +float MENU_BOTS = 6; +float MENU_WAYLIST = 7; +// 8 = link way +// 9 = telelink way +// 10 = delete link +// 11 = create link X2 +// 12 = delete link x2 +// 13 = confirmation of delete all +// 14 = Teleport to way +// 15 = confirmation of delete point + +void() BSPDumpWaypoints; +void() QCDumpWaypoints; +void() DumpWaypoints; +/* +// source for the menu strings... + +-- Main Menu --\n +[1] >>Waypoint Management\n +[2] >>Link Management \n +[3] >>AI Flag Management \n +[4] >>Bot Management \n +[5] >>Waylist Management \n +[6] [#] Noclip \n +[7] [#] Godmode \n +[8] [#] Hold Select \n +[9] Teleport to Way # \n +[0] Close Menu \n + +// missing from main is show way info +// iffy on the teleport to way thing being on main...seems like either a bot or way list thing + +-- Waypoint Management --\n +[1] Move Waypoint \n +[2] Delete Waypoint \n +[3] Make Waypoint \n +[4] Make Way + Link \n +[5] Make Way + Link X2 \n +[6] Make Way + Telelink \n +[7] Show waypoint info \n +[8] >>Link Management \n +[9] >>AI Flag Management \n +[0] >>Main Menu \n + +-- Link Management --\n +[1] Unlink Waypoint \n +[2] Create Link \n +[3] Create Telelink \n +[4] Delete Link \n +[5] Create Link X2 \n +[6] Delete Link X2 \n +[7] >Make Waypoint \n +[8] >>Waypoint Management\n +[9] >>AI Flag Management \n +[0] >>Main Menu \n + +// Ai flags...ugh + +-- AI Flag Management --\n +[1] [#] Door Flag \n +[2] [#] Precision \n +[3] [#] Surface for air \n +[4] [#] Blind mode \n +[5] [#] Jump \n +[6] [#] Dark \n +[7] [#] Super Jump \n +\n +[9] >>AI Flags page 2 \n +[0] >>Main Menu \n + +-- AI Flags pg. 2--\n +[1] [#] Difficult \n +[2] [#] Wait for plat \n +[3] [#] Ride train \n +[4] [#] Door flag no open\n +[5] [#] Ambush \n +[6] [#] Snipe \n +[7] [#] Trace Test \n +\n +[9] >>AI Flag Management \n +[0] >>Main Menu \n + +-- Bot Management --\n +[1] Add a Test Bot \n +[2] Order Test Bot here \n +[3] Remove Test Bot \n +[4] Stop Test Bot \n +[5] Teleport Bot here \n +[6] Teleport to Way # \n +\n +\n +\n +[0] >>Main Menu \n + +-- Waylist Management --\n +[1] Delete ALL Waypoints \n +[2] Dump Waypoints \n +[3] Check For Errors \n +[4] Save Waypoints \n +[5] [#] Dynamic Mode \n +[6] [#] Dynamic Link \n +[7] [#] WAY output \n +[8] [#] QC output \n +[9] [#] BSP ents output \n +[0] Main Menu \n + +*/ + +void() bot_menu_display = +{ +// build options + local string s1, s2, s3, s4, s5, s6, s7, h; + local entity t; + +// check impulses + if (self.impulse > 0 && self.impulse < 11 && self.b_menu) + { + if (self.b_menu == MENU_MAIN) + { + if (self.impulse == 1) + { + self.b_menu = MENU_WAYPOINTS; + self.b_menu_time = time; + } + else if (self.impulse == 2) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + else if (self.impulse == 3) + { + self.b_menu = MENU_FLAGS; + self.b_menu_time = time; + } + else if (self.impulse == 4) + { + self.b_menu = MENU_BOTS; + self.b_menu_time = time; + } + else if (self.impulse == 5) + { + self.b_menu = MENU_WAYLIST; + self.b_menu_time = time; + } + else if (self.impulse == 6) + { + if (self.movetype == MOVETYPE_NOCLIP) + self.movetype = MOVETYPE_WALK; + else + self.movetype = MOVETYPE_NOCLIP; + self.b_menu_time = time; + + } + else if (self.impulse == 7) + { + if (self.flags & FL_GODMODE) + self.flags = self.flags - FL_GODMODE; + else + self.flags = self.flags | FL_GODMODE; + self.b_menu_time = time; + + } + else if (self.impulse == 8) + { + if (self.b_aiflags & AI_HOLD_SELECT) + self.b_aiflags = self.b_aiflags - AI_HOLD_SELECT; + else + self.b_aiflags = self.b_aiflags | AI_HOLD_SELECT; + self.b_menu_time = time; + } + else if (self.impulse == 9) + { + self.b_menu = 14; + self.b_menu_time = time; + } + else if (self.impulse == 10) + bot_way_edit(); + } + else if (self.b_menu == MENU_WAYPOINTS) + { + if (self.impulse == 1) + { + if (self.current_way) + setorigin(self.current_way, self.origin + self.view_ofs); + } + else if (self.impulse == 2) + { + if (self.current_way) + { + self.b_menu = 15; + self.b_menu_time = time; + self.last_way = self.current_way; + } + } + else if (self.impulse == 3) + { + make_waypoint(self.origin + self.view_ofs); + } + else if (self.impulse == 4) + { + t = make_waypoint(self.origin + self.view_ofs); + if (!LinkWays(self.current_way, t)) + sprint(self, "Unable to link them\n"); + } + else if (self.impulse == 5) + { + t = make_waypoint(self.origin + self.view_ofs); + if (!LinkWays(self.current_way, t)) + sprint(self, "Unable to link old to new\n"); + LinkWays(t, self.current_way); + } + else if (self.impulse == 6) + { + t = make_waypoint(self.origin + self.view_ofs); + if (!TeleLinkWays(self.current_way, t)) + sprint(self, "Unable to link them\n"); + } + else if (self.impulse == 7) + { + if (self.current_way) + { + sprint(self, "\nwaypoint info for waypoint #"); + h = ftos(self.current_way.count); + sprint(self, h); + sprint(self, "\nAI Flag value: "); + h = ftos(self.current_way.b_aiflags); + sprint(self, h); + + if (self.current_way.target1) + { + h = ftos(self.current_way.target1.count); + if (self.current_way.b_aiflags & AI_TELELINK_1) + sprint(self, "\nTelelink1 to:"); + else + sprint(self, "\nLink1 to:"); + sprint(self, h); + } + if (self.current_way.target2) + { + h = ftos(self.current_way.target2.count); + if (self.current_way.b_aiflags & AI_TELELINK_2) + sprint(self, "\nTelelink2 to:"); + else + sprint(self, "\nLink2 to:"); + sprint(self, h); + } + if (self.current_way.target3) + { + h = ftos(self.current_way.target3.count); + if (self.current_way.b_aiflags & AI_TELELINK_3) + sprint(self, "\nTelelink3 to:"); + else + sprint(self, "\nLink3 to:"); + sprint(self, h); + } + if (self.current_way.target4) + { + h = ftos(self.current_way.target4.count); + if (self.current_way.b_aiflags & AI_TELELINK_4) + sprint(self, "\nTelelink4 to:"); + else + sprint(self, "\nLink4 to:"); + sprint(self, h); + } + sprint(self, "\n\n"); + } + + } + if (self.impulse == 8) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + else if (self.impulse == 9) + { + self.b_menu = MENU_FLAGS; + self.b_menu_time = time; + } + else if (self.impulse == 10) + { + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + } + else if (self.b_menu == MENU_LINKS) + { + if (self.impulse == 1) + { + if (self.current_way) + self.current_way.target1 = self.current_way.target2 = self.current_way.target3 = self.current_way.target4 = world; + } + else if (self.impulse == 2) + { + self.b_menu = 8; + self.b_menu_time = time; + self.last_way = self.current_way; + } + else if (self.impulse == 3) + { + self.b_menu = 9; + self.b_menu_time = time; + self.last_way = self.current_way; + } + else if (self.impulse == 4) + { + self.b_menu = 10; + self.b_menu_time = time; + self.last_way = self.current_way; + } + else if (self.impulse == 5) + { + self.b_menu = 11; + self.b_menu_time = time; + self.last_way = self.current_way; + } + else if (self.impulse == 6) + { + self.b_menu = 12; + self.b_menu_time = time; + self.last_way = self.current_way; + } + else if (self.impulse == 7) + make_waypoint(self.origin + self.view_ofs); + else if (self.impulse == 8) + { + self.b_menu = MENU_WAYPOINTS; + self.b_menu_time = time; + } + else if (self.impulse == 9) + { + self.b_menu = MENU_FLAGS; + self.b_menu_time = time; + } + else if (self.impulse == 10) + { + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + } + else if (self.b_menu == MENU_FLAGS) + { + + if (self.current_way) + { + if (self.impulse == 1) + { + if (self.current_way.b_aiflags & AI_DOORFLAG) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_DOORFLAG); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_DOORFLAG; + + self.b_menu_time = time; + } + else if (self.impulse == 2) + { + if (self.current_way.b_aiflags & AI_PRECISION) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_PRECISION); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_PRECISION; + self.b_menu_time = time; + } + else if (self.impulse == 3) + { + if (self.current_way.b_aiflags & AI_SURFACE) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_SURFACE); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_SURFACE; + self.b_menu_time = time; + } + else if (self.impulse == 4) + { + if (self.current_way.b_aiflags & AI_BLIND) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_BLIND); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_BLIND; + self.b_menu_time = time; + } + else if (self.impulse == 5) + { + if (self.current_way.b_aiflags & AI_JUMP) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_JUMP); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_JUMP; + self.b_menu_time = time; + } + else if (self.impulse == 6) + { + if (self.current_way.b_aiflags & AI_DIRECTIONAL) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_DIRECTIONAL); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_DIRECTIONAL; + self.b_menu_time = time; + } + else if (self.impulse == 7) + { + if (self.current_way.b_aiflags & AI_SUPER_JUMP) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_SUPER_JUMP); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_SUPER_JUMP; + self.b_menu_time = time; + } + } + if (self.impulse == 9) + { + self.b_menu = MENU_FLAGS2; + self.b_menu_time = time; + } + else if (self.impulse == 10) + { + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + } + else if (self.b_menu == MENU_FLAGS2) + { + + if (self.current_way) + { + if (self.impulse == 1) + { + if (self.current_way.b_aiflags & AI_DIFFICULT) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_DIFFICULT); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_DIFFICULT; self.b_menu_time = time; + } + else if (self.impulse == 2) + { + if (self.current_way.b_aiflags & AI_PLAT_BOTTOM) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_PLAT_BOTTOM); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_PLAT_BOTTOM; + self.b_menu_time = time; + } + else if (self.impulse == 3) + { + if (self.current_way.b_aiflags & AI_RIDE_TRAIN) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_RIDE_TRAIN); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_RIDE_TRAIN; + self.b_menu_time = time; + } + else if (self.impulse == 4) + { + if (self.current_way.b_aiflags & AI_DOOR_NO_OPEN) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_DOOR_NO_OPEN); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_DOOR_NO_OPEN; + self.b_menu_time = time; + } + else if (self.impulse == 5) + { + if (self.current_way.b_aiflags & AI_AMBUSH) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_AMBUSH); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_AMBUSH; + self.b_menu_time = time; + } + else if (self.impulse == 6) + { + if (self.current_way.b_aiflags & AI_SNIPER) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_SNIPER); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_SNIPER; + self.b_menu_time = time; + } + else if (self.impulse == 7) + { + if (self.current_way.b_aiflags & AI_TRACE_TEST) + self.current_way.b_aiflags = self.current_way.b_aiflags - (self.current_way.b_aiflags & AI_TRACE_TEST); + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_TRACE_TEST; + self.b_menu_time = time; + } + + } + if (self.impulse == 9) + { + self.b_menu = MENU_FLAGS; + self.b_menu_time = time; + } + else if (self.impulse == 10) + { + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + } + + else if (self.b_menu == MENU_BOTS) + { + if (self.impulse == 1) + { + self.impulse = 100; + return; + } + else if (self.impulse == 2) + { + b_temp3 = self; + self = player_head; + while(self) + { + if (!self.ishuman) + { + target_add(b_temp3); + bot_get_path(b_temp3, TRUE); + self = world; + } + else + self = self._next; + } + self = b_temp3; + } + else if (self.impulse == 3) + { + self.impulse = 102; + return; + } + else if (self.impulse == 4) + { + b_temp1 = self; + self = player_head; + while(self) + { + if (!self.ishuman) + { + self.target1 = self.target2 = self.target3 = self.target4 = world; + route_table = world; + } + self = self._next; + } + self = b_temp1; + } + else if (self.impulse == 5) + { + if (self.current_way) + { + b_temp1 = self; + self = player_head; + while(self) + { + if (!self.ishuman) + { + setorigin(self, b_temp1.current_way.origin); + } + self = self._next; + } + self = b_temp1; + } + else + sprint(self, "select a waypoint first\n"); + } + else if (self.impulse == 6) + { + self.b_menu = 14; + self.b_menu_time = time; + } + else if (self.impulse == 10) + { + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + } + else if (self.b_menu == MENU_WAYLIST) + { + if (self.impulse == 1) + { + self.b_menu = 13; + self.b_menu_time = time; + } + else if (self.impulse == 2) + { + if (dump_mode == 0) + DumpWaypoints(); + else if (dump_mode == 1) + QCDumpWaypoints(); + else if (dump_mode == 2) + BSPDumpWaypoints(); + } + else if (self.impulse == 3) + { + t = way_head; + while(t) + { + if ((t.target1 == world) && (t.target2 == world) && (t.target3 == world) && (t.target4 == world)) + { + sprint(self, "Waypoint #"); + h = ftos(t.count); + sprint(self, h); + sprint(self, " has no outbound links\n"); + } + if ((t.target1 == t) || (t.target2 == t) || (t.target3 == t) || (t.target4 == t)) + { + sprint(self, "Waypoint #"); + h = ftos(t.count); + sprint(self, h); + sprint(self, " links to itself (??)\n"); + } + t = t._next; + } + sprint(self, "Error check complete\n"); + } + else if (self.impulse == 4) + { + sprint(self, "not in this version (FBX 0.10.0)\n"); + } + else if (self.impulse == 5) + { + if (waypoint_mode == WM_EDITOR_DYNAMIC) + waypoint_mode = WM_EDITOR; + else + waypoint_mode = WM_EDITOR_DYNAMIC; + self.b_menu_time = time; + + } + else if (self.impulse == 6) + { + if (waypoint_mode == WM_EDITOR_DYNLINK) + waypoint_mode = WM_EDITOR; + else + waypoint_mode = WM_EDITOR_DYNLINK; + self.b_menu_time = time; + } + else if (self.impulse == 7) + { + dump_mode = 0; + self.b_menu_time = time; + } + else if (self.impulse == 8) + { + dump_mode = 1; + self.b_menu_time = time; + } + else if (self.impulse == 9) + { + dump_mode = 2; + self.b_menu_time = time; + } + else if (self.impulse == 10) + { + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + } + else if (self.b_menu == 8) + { + if (self.impulse == 1) + { + if (self.current_way) + { + if (!LinkWays(self.last_way, self.current_way)) + sprint(self, "Unable to link them\n"); + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.impulse == 2) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.b_menu == 9) + { + if (self.impulse == 1) + { + if (self.current_way) + { + if (!TeleLinkWays(self.last_way, self.current_way)) + sprint(self, "Unable to link them\n"); + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.impulse == 2) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.b_menu == 10) + { + if (self.impulse == 1) + { + if (self.current_way) + { + UnlinkWays(self.last_way, self.current_way); + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.impulse == 2) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.b_menu == 11) + { + if (self.impulse == 1) + { + if (self.current_way) + { + if (!LinkWays(self.last_way, self.current_way)) + sprint(self, "Unable to link 1 to 2\n"); + if (!LinkWays(self.current_way, self.last_way)) + sprint(self, "Unable to link 2 to 1\n"); + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.impulse == 2) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.b_menu == 12) + { + if (self.impulse == 1) + { + if (self.current_way) + { + UnlinkWays(self.last_way, self.current_way); + UnlinkWays(self.current_way, self.last_way); + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.impulse == 2) + { + self.b_menu = MENU_LINKS; + self.b_menu_time = time; + } + } + else if (self.b_menu == 13) + { + if (self.impulse == 1) + { + ClearAllWays(); + self.b_menu = MENU_WAYLIST; + self.b_menu_time = time; + } + else if (self.impulse == 2) + { + self.b_menu = MENU_WAYLIST; + self.b_menu_time = time; + } + } + else if (self.b_menu == 14) + { + if (self.impulse == 10) + self.impulse = 0; + self.b_menu_value = self.b_menu_value * 10 + self.impulse; + self.b_menu_time = 0; + } + else if (self.b_menu == 15) + { + if (self.impulse == 1) + { + delete_waypoint(self.last_way); + self.b_menu = MENU_WAYPOINTS; + self.b_menu_time = time; + } + else if (self.impulse == 2) + { + self.b_menu = MENU_WAYPOINTS; + self.b_menu_time = time; + } + } + self.impulse = 0; + + } + if (self.b_menu_time < time) + { + if (self.b_menu == MENU_MAIN) + { + s1 = "-- Main Menu --\n[1] >>Waypoint Management\n[2] >>Link Management \n[3] >>AI Flag Management \n[4] >>Bot Management \n[5] >>Waylist Management \n"; + if (self.movetype == MOVETYPE_NOCLIP) + s2 = "[6] [#] Noclip \n"; + else + s2 = "[6] [ ] Noclip \n"; + + if (self.flags & FL_GODMODE) + s3 = "[7] [#] Godmode \n"; + else + s3 = "[7] [ ] Godmode \n"; + if (self.b_aiflags & AI_HOLD_SELECT) + s4 = "[8] [#] Hold Select \n"; + else + s4 = "[8] [ ] Hold Select \n"; + s5 = "[9] Teleport to Way # \n[0] Close Menu \n"; + } + else if (self.b_menu == MENU_WAYPOINTS) + { + s1 = "-- Waypoint Management --\n[1] Move Waypoint \n[2] Delete Waypoint \n[3] Make Waypoint \n[4] Make Way + Link \n[5] Make Way + Link X2 \n[6] Make Way + Telelink \n[7] Show waypoint info \n[8] >>Link Management \n[9] >>AI Flag Management \n[0] >>Main Menu \n"; + } + else if (self.b_menu == MENU_LINKS) + { + s1 = "-- Link Management --\n[1] Unlink Waypoint \n[2] Create Link \n[3] Create Telelink \n[4] Delete Link \n[5] Create Link X2 \n[6] Delete Link X2 \n[7] >Make Waypoint \n[8] >>Waypoint Management\n[9] >>AI Flag Management \n[0] >>Main Menu \n"; + } + else if (self.b_menu == MENU_FLAGS) + { + if (self.current_way.b_aiflags & AI_DOORFLAG) + s1 = "-- AI Flag Management --\n[1] [#] Door Flag \n"; + else + s1 = "-- AI Flag Management --\n[1] [ ] Door Flag \n"; + + if (self.current_way.b_aiflags & AI_PRECISION) + s2 = "[2] [#] Precision \n"; + else + s2 = "[2] [ ] Precision \n"; + + if (self.current_way.b_aiflags & AI_SURFACE) + s3 = "[3] [#] Surface for air \n"; + else + s3 = "[3] [ ] Surface for air \n"; + + if (self.current_way.b_aiflags & AI_BLIND) + s4 = "[4] [#] Blind mode \n"; + else + s4 = "[4] [ ] Blind mode \n"; + + if (self.current_way.b_aiflags & AI_JUMP) + s5 = "[5] [#] Jump \n"; + else + s5 = "[5] [ ] Jump \n"; + + if (self.current_way.b_aiflags & AI_DIRECTIONAL) + s6 = "[6] [#] Directional \n"; + else + s6 = "[6] [ ] Directional \n"; + + if (self.current_way.b_aiflags & AI_SUPER_JUMP) + s7 = "[7] [#] Super Jump \n\n[9] >>AI Flags page 2 \n[0] >>Main Menu \n"; + else + s7 = "[7] [ ] Super Jump \n\n[9] >>AI Flags page 2 \n[0] >>Main Menu \n"; + } + else if (self.b_menu == MENU_FLAGS2) + { + if (self.current_way.b_aiflags & AI_DIFFICULT) + s1 = "-- AI Flags pg. 2--\n[1] [#] Difficult \n"; + else + s1 = "-- AI Flags pg. 2--\n[1] [ ] Difficult \n"; + + if (self.current_way.b_aiflags & AI_PLAT_BOTTOM) + s2 = "[2] [#] Wait for plat \n"; + else + s2 = "[2] [ ] Wait for plat \n"; + + if (self.current_way.b_aiflags & AI_RIDE_TRAIN) + s3 = "[3] [#] Ride train \n"; + else + s3 = "[3] [ ] Ride train \n"; + + if (self.current_way.b_aiflags & AI_DOOR_NO_OPEN) + s4 = "[4] [#] Door flag no open\n"; + else + s4 = "[4] [ ] Door flag no open\n"; + + if (self.current_way.b_aiflags & AI_AMBUSH) + s5 = "[5] [#] Ambush \n"; + else + s5 = "[5] [ ] Ambush \n"; + + if (self.current_way.b_aiflags & AI_SNIPER) + s6 = "[6] [#] Snipe \n"; + else + s6 = "[6] [ ] Snipe \n"; + + if (self.current_way.b_aiflags & AI_TRACE_TEST) + s7 = "[7] [#] Trace Test \n\n[9] >>AI Flag Management \n[0] >>Main Menu \n"; + else + s7 = "[7] [ ] Trace Test \n\n[9] >>AI Flag Management \n[0] >>Main Menu \n"; + + } + else if (self.b_menu == MENU_BOTS) + { + s1 = "-- Bot Management --\n[1] Add a Test Bot \n[2] Order Test Bot here \n[3] Remove Test Bot \n[4] Stop Test Bot \n[5] Teleport Bot here \n[6] Teleport to Way # \n\n\n\n[0] >>Main Menu \n"; + } + else if (self.b_menu == MENU_WAYLIST) + { + s1 = "-- Waylist Management --\n[1] Delete ALL Waypoints \n[2] Dump Waypoints \n[3] Check For Errors \n[4] Save Waypoints \n"; + + if (waypoint_mode == WM_EDITOR_DYNAMIC) + s2 = "[5] [#] Dynamic Mode \n[6] [#] Dynamic Link \n"; + else if (waypoint_mode == WM_EDITOR_DYNLINK) + s2 = "[5] [ ] Dynamic Mode \n[6] [#] Dynamic Link \n"; + else + s2 = "[5] [ ] Dynamic Mode \n[6] [ ] Dynamic Link \n"; + if (dump_mode == 0) + s3 = "[7] [#] WAY output \n[8] [ ] QC output \n[9] [ ] BSP ents output \n[0] Main Menu \n"; + else if (dump_mode == 1) + s3 = "[7] [ ] WAY output \n[8] [#] QC output \n[9] [ ] BSP ents output \n[0] Main Menu \n"; + else if (dump_mode == 2) + s3 = "[7] [ ] WAY output \n[8] [ ] QC output \n[9] [#] BSP ents output \n[0] Main Menu \n"; + + } + else if (self.b_menu == 8) + s1 = "-- Link Ways --\n\nSelect another way and push 1\nor press 2 to cancel"; + else if (self.b_menu == 9) + s1 = "-- Telelink Ways --\n\nSelect another way and push 1\nor press 2 to cancel"; + else if (self.b_menu == 10) + s1 = "-- Delete Link --\n\nSelect another way and push 1\nor press 2 to cancel"; + else if (self.b_menu == 11) + s1 = "-- Create Link X2 --\n\nSelect another way and push 1\nor press 2 to cancel"; + else if (self.b_menu == 12) + s1 = "-- Delete Link X2 --\n\nSelect another way and push 1\nor press 2 to cancel"; + else if (self.b_menu == 13) + s1 = "-- Delete ALL Ways --\n\nAre you sure? Push 1 to go\nthrough with it, 2 to cancel"; + else if (self.b_menu == 14) + { + s1 = "-- Teleport to Way # --\n\nEnter way number and press\nimpulse 104 to warp\n\nWaypoint #"; + s2 = ftos(self.b_menu_value); + + } + else if (self.b_menu == 15) + s1 = "-- Delete Waypoint --\n\nAre you sure? Push 1 to go\nthrough with it, 2 to cancel"; + frik_big_centerprint(self, s1, s2, s3, s4, s5, s6, s7); + self.b_menu_time = time + 1.25; + } +}; + + +// engage menu +void() bot_way_edit = +{ + local entity t; + local float f; + if (self.b_menu_value) + { + if (self.b_menu == 14) + { + t = WaypointForNum(self.b_menu_value); + if (t) + setorigin(self, t.origin - self.view_ofs); + else + sprint(self, "No waypoint with that number\n"); + + self.b_menu = MENU_MAIN; + self.b_menu_time = time; + } + self.b_menu_value = 0; + return; + } + if (waypoint_mode < WM_EDITOR) + { + self.b_menu = MENU_MAIN; + waypoint_mode = WM_EDITOR; + self.b_menu_time = 0; + cvar_set("saved2", "0"); + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 1); + WriteString(MSG_ALL, "MAKE SURE THE FOLLOWING LINE CONTAINS -CONDEBUG BEFORE PROCEEDING\n"); + localcmd("cmdline\n"); + t = way_head; + while (t) + { + setmodel(t, "progs/s_bubble.spr"); // show the waypoints + t = t._next; + } + if (self.current_way) + setmodel(self.current_way, "progs/s_light.spr"); + } + else + { + saved2 = cvar("saved2"); + if (saved2 != 0) + { + f = self.b_menu; + self.b_menu = floor(saved2/16); + self.impulse = saved2 & 15; + bot_menu_display(); + self.b_menu = f; + cvar_set("saved2", "0"); + return; + } + self.b_menu = 0; + waypoint_mode = WM_LOADED; + t = way_head; + while (t) + { + setmodel(t, string_null); // hide the waypoints + t = t._next; + } + } +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Waypoint Saving to file. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +// bytecount is really iffy +// since there is no true way to determine the length of an ftos +// it uses an approximate of 5 +// various other things are guesses, but I don't cut it at the absolute +// max so it should be okay + +void() PrintWaypoint = +{ + local entity t; + local float needcolon; + local string h; + + if (self.enemy == world) + t = way_head; + else + t = self.enemy._next; + if (bytecounter >= 8000) + { + bprint("exec maps/"); + bprint(mapname); + bprint(".wa"); + h = ftos(filecount); + bprint(h); + filecount = filecount + 1; + bprint("\n// **** break here **** \n"); + bytecounter = 26; + } + if (t == world) + { + remove(self); + fixer = world; + bprint("saved4 3\n// end waypoint dump\n"); + bytecounter = bytecounter + 27; + return; + } + if ((t.origin_x != saved1) || (t.count == 1)) + { + bprint("saved1 "); + h = ftos(t.origin_x); + bprint(h); + saved1 = t.origin_x; + bytecounter = bytecounter + 12; + needcolon = TRUE; + } + if ((t.origin_y != saved2) || (t.count == 1)) + { + if (needcolon) + { + bprint("; "); + bytecounter = bytecounter + 2; + } + else + needcolon = TRUE; + bprint("saved2 "); + h = ftos(t.origin_y); + bprint(h); + bytecounter = bytecounter + 12; + saved2 = t.origin_y; + } + if ((t.origin_z != saved3) || (t.count == 1)) + { + if (needcolon) + { + bprint("; "); + bytecounter = bytecounter + 2; + } + else + needcolon = TRUE; + bprint("saved3 "); + h = ftos(t.origin_z); + bprint(h); + bytecounter = bytecounter + 12; + saved3 = t.origin_z; + } + bytecounter = bytecounter + 1; + bprint("\n"); + needcolon = FALSE; + if ((scratch1 != t.target1.count) || t.count == 1) + { + needcolon = TRUE; + bprint("scratch1 "); + bytecounter = bytecounter + 14; + h = ftos(t.target1.count); + bprint(h); + scratch1 = t.target1.count; + } + if ((scratch2 != t.target2.count) || t.count == 1) + { + if (needcolon) + { + bprint("; "); + bytecounter = bytecounter + 2; + } + else + needcolon = TRUE; + bprint("scratch2 "); + bytecounter = bytecounter + 14; + h = ftos(t.target2.count); + bprint(h); + scratch2 = t.target2.count; + } + if ((scratch3 != t.target3.count) || t.count == 1) + { + if (needcolon) + { + bprint("; "); + bytecounter = bytecounter + 2; + } + else + needcolon = TRUE; + bprint("scratch3 "); + bytecounter = bytecounter + 14; + h = ftos(t.target3.count); + bprint(h); + scratch3 = t.target3.count; + } + if ((scratch4 != t.target4.count) || t.count == 1) + { + if (needcolon) + { + bprint("; "); + bytecounter = bytecounter + 2; + } + else + needcolon = TRUE; + bprint("scratch4 "); + bytecounter = bytecounter + 14; + h = ftos(t.target4.count); + bprint(h); + scratch4 = t.target4.count; + } + bprint("\nsaved4 "); + bytecounter = bytecounter + 19; + if (t.count != 1) + h = ftos(t.b_aiflags * 4 + 2); + else + h = ftos(t.b_aiflags * 4 + 1); + bprint(h); + bprint ("; wait\n"); + self.nextthink = time + 0.01; + self.enemy = t; +}; + +// to allow for 100+ waypoints, we need to trick the runaway loop counter +void() DumpWaypoints = +{ + bytecounter = 50; + filecount = 1; + + bprint("// "); + bprint(world.message); + bprint("- maps/"); + bprint(mapname); + bprint(".way\n"); + bprint("// Ways by "); + bprint(self.netname); + bprint("\n"); + if (!fixer) + { + fixer = spawn(); + fixer.nextthink = time + 0.01; + fixer.think = PrintWaypoint; + fixer.enemy = world; + } +}; + +void() PrintQCWaypoint = +{ + local entity t; + local string h; + + if (self.enemy == world) + t = way_head; + else + t = self.enemy._next; + + if (t == world) + { + remove(self); + fixer = world; + bprint("};\n\n// End dump\n"); + return; + } + bprint(" make_way("); + h = vtos(t.origin); + bprint(h); + bprint(", '"); + h = ftos(t.target1.count); + bprint(h); + bprint(" "); + h = ftos(t.target2.count); + bprint(h); + bprint(" "); + h = ftos(t.target3.count); + bprint(h); + bprint("', "); + h = ftos(t.target4.count); + bprint(h); + bprint(", "); + h = ftos(t.b_aiflags); + bprint(h); + bprint(");\n"); + self.nextthink = time + 0.01; + self.enemy = t; + +}; +void() QCDumpWaypoints = +{ + bprint("/* QC Waypoint Dump - src/frikbot/map_"); + + bprint(mapname); + bprint(".qc\nFor instructions please read the\nreadme.html that comes with FrikBot */\n\nvoid(vector org, vector bit1, float bit4, float flargs) make_way;\n"); + bprint("// Ways by "); + bprint(self.netname); + bprint("\n\n"); + + bprint("void() map_"); + bprint(mapname); + bprint(" =\n{\n"); + + + if (!fixer) + { + fixer = spawn(); + fixer.nextthink = time + 0.01; + fixer.think = PrintQCWaypoint; + fixer.enemy = world; + } +}; + +void() PrintBSPWaypoint = +{ + local entity t; + local string h; + + if (self.enemy == world) + t = way_head; + else + t = self.enemy._next; + + if (t == world) + { + bprint("\n\n// End dump\n"); + remove(self); + fixer = world; + return; + } + bprint("{\n\"classname\" \"waypoint\"\n\"origin\" \""); + h = ftos(t.origin_x); + bprint(h); + bprint(" "); + h = ftos(t.origin_y); + bprint(h); + bprint(" "); + h = ftos(t.origin_z); + bprint(h); + if (t.target1.count) + { + bprint("\"\n\"b_pants\" \""); + h = ftos(t.target1.count); + bprint(h); + } + if (t.target2.count) + { + bprint("\"\n\"b_skill\" \""); + h = ftos(t.target2.count); + bprint(h); + } + if (t.target3.count) + { + bprint("\"\n\"b_shirt\" \""); + h = ftos(t.target3.count); + bprint(h); + } + if (t.target4.count) + { + bprint("\"\n\"b_frags\" \""); + h = ftos(t.target4.count); + bprint(h); + } + if (t.b_aiflags) + { + bprint("\"\n\"b_aiflags\" \""); + h = ftos(t.b_aiflags); + bprint(h); + } + bprint("\"\n}\n"); + self.nextthink = time + 0.01; + self.enemy = t; + +}; +void() BSPDumpWaypoints = +{ + bprint("/* BSP entities Dump - maps/"); + + bprint(mapname); + bprint(".ent\nFor instructions please read the\nreadme.html that comes with FrikBot */\n\n\n"); + + if (!fixer) + { + fixer = spawn(); + fixer.nextthink = time + 0.01; + fixer.think = PrintBSPWaypoint; + fixer.enemy = world; + } +}; diff --git a/fbxa/bot_fight.qc b/fbxa/bot_fight.qc new file mode 100644 index 0000000..8098b9f --- /dev/null +++ b/fbxa/bot_fight.qc @@ -0,0 +1,458 @@ +/*********************************************** +* * +* FrikBot Fight Code * +* "Because I ain't no Ghandi code" * +* * +***********************************************/ + +/* + +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. + +*/ + +.entity avoid; + +float(entity e) bot_size_player = +{ + local float sz; + + sz = e.health + e.armorvalue * e.armortype; + if (e.weapon == 32) + sz = sz + 60; + else if (e.weapon == 64) + sz = sz + 60; + else if (e.weapon == 16) + sz = sz + 50; + else if (e.weapon == 8) + sz = sz + 50; + else if (e.weapon == 4) + sz = sz + 40; + else if (e.weapon == 2) + sz = sz + 40; + else if (e.weapon == 1) + sz = sz + 10; + else if (e.weapon == 4096) + sz = sz - 50; + if (e.items & 4194304) // Quad + sz = sz + 200; + if (e.items & 1048576) // Invul + sz = sz + 300; + if (e.items & 524288) // Invis + sz = sz + 250; + return sz; +}; + +void() bot_dodge_stuff = +{ + local entity foe; + local float foedist, avdist, scandist, foesz, flen, tsz; + local vector v; + + if (waypoint_mode > WM_LOADED) + return; + + self.avoid = world; + + + if (self.enemy) + { + v = self.origin - realorigin(self.enemy); + foedist = vlen(v); + foesz = bot_size_player(self.enemy); + } + else + { + foedist = 3000; + foesz = 9999999; + } + avdist = 256; + + foe = find(world, classname, "grenade"); + while(foe) + { + flen = vlen(foe.origin - self.origin); + if (flen < avdist) + { + avdist = flen; + self.avoid = foe; + } + foe = find(foe, classname, "grenade"); + } + if (!self.avoid) + { + foe = find(world, classname, "missile"); + while(foe) + { + if (foe.owner != self) + { + flen = vlen(foe.origin - self.origin); + if (flen < avdist) + { + avdist = flen; + self.avoid = foe; + } + } + foe = find(foe, classname, "missile"); + } + if (!self.avoid) + { + foe = find(world, classname, "spike"); + while(foe) + { + if (foe.owner != self) + { + flen = vlen(foe.origin - self.origin); + if (flen < avdist) + { + avdist = flen; + self.avoid = foe; + } + } + foe = find(foe, classname, "spike"); + } + } + } + if (coop) + { + if (!self.enemy) + { + foe = findradius(self.origin, foedist); + while(foe) + { + if(foe.flags & FL_MONSTER) + { + if(foe.health > 0) + { + flen = vlen(foe.origin - self.origin); + if (flen < foedist) + { + tsz = bot_size_player(foe); + if (tsz < foesz) + { + if (fisible(foe)) + { + self.enemy = foe; + foedist = flen; + foesz = tsz; + } + } + } + } + } + foe = foe.chain; + } + } + } + else + { + foe = player_head; + while(foe) + { + if(foe != self) + { + if (foe.modelindex != 0) + { + if (foe.health > 0) + { + if (!(teamplay && self.team == foe.team)) + { + flen = vlen(foe.origin - self.origin); + if (flen < foedist) + { + tsz = bot_size_player(foe); + if (tsz < foesz) + { + if (fov(foe) || foe.b_sound > time || self.b_skill == 3) + { + if (fisible(foe)) + { + self.enemy = foe; + foedist = vlen(foe.origin - self.origin); + } + } + } + } + } + } + } + } + foe = foe._next; + } + } +}; + + + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +weapon_range + +_x "sweet spot range" - try to maintain this range if possible +_y minimum range bot can be to be effective (rl/gl) (move away) +_z maximum range bot can be to be effective (lg/axe) (move in) +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +vector(float wep) weapon_range = +{ + if (wep == 4096) // IT_AXE + return '48 0 64'; + else if (wep == 1) // IT_SHOTGUN + return '128 0 99999'; + else if (wep == 2) // IT_SUPER_SHOTGUN + return '128 0 99999'; + else if (wep == 4) // IT_NAILGUN + return '180 0 3000'; + else if (wep == 8) // IT_SUPER_NAILGUN + return '180 0 3000'; + else if (wep == 16) // IT_GRENADE_LAUNCHER + return '180 48 3000'; + else if (wep == 32) // IT_ROCKET_LAUNCHER + return '180 48 3000'; + else if (wep == 64) // IT_LIGHTNING + return '350 0 512'; +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +bot_weapon_switch + +Pick a weapon based on range / ammo + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void(float brange) bot_weapon_switch = +{ + local float it, flag, pulse; + local vector v; + + it = self.items & 127; + + while(it) + { + if ((self.ammo_rockets >= 1) && (it & 32)) + { + flag = 32; + pulse = 7; + } + else if (self.waterlevel <= 1 && self.ammo_cells >= 1 && (it & 64)) + { + flag = 64; + pulse = 8; + } + else if(self.ammo_nails >= 2 && (it & 8)) + { + flag = 8; + pulse = 5; + } + else if ((self.ammo_rockets >= 1) && (it & 16)) + { + flag = 16; + pulse = 6; + } + else if(self.ammo_shells >= 2 && (it & 2)) + { + flag = 2; + pulse = 3; + } + else if(self.ammo_nails >= 1 && (it & 4)) + { + flag = 4; + pulse = 4; + } + else if(self.ammo_shells >= 1 && (it & 1)) + { + flag = 1; + pulse = 2; + } + else + { + if (pulse) + self.impulse = pulse; + return; + } + + if (brange == -1) + { + if (pulse) + self.impulse = pulse; + return; + } + + v = weapon_range(flag); + if (brange < v_y || brange > v_z) + it = it - flag; + else + { + if (pulse) + self.impulse = pulse; + return; + } + } +}; + +void() bot_shoot = +{ + // quick little function to stop making him shoot the wrong way ! Argh + local float g; + g = angcomp(self.v_angle_x, self.b_angle_x); + if (fabs(g) > 30) + return; // argh, too far away + g = angcomp(self.v_angle_y, self.b_angle_y); + if (fabs(g) > 30) + return; // not again! + self.button0 = TRUE; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Bot_fight_style + +This is the core of the bot's thinking when +attacking an enemy. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() bot_fight_style = +{ + local vector v, v1, v2, org; + local float foedist, mysz, foesz; + + + if (self.enemy.health <= 0) + { + self.enemy = world; + return; + } + else if (!self.enemy.takedamage) + { + self.enemy = world; + return; + } + else if (!fisible(self.enemy)) + { + self.enemy = world; + return; + } + + org = realorigin(self.enemy); + makevectors(self.v_angle); + + // decide if I should shoot + + foedist = vlen(org - self.origin); + v = weapon_range(self.weapon); + if (foedist > v_y && foedist < v_z) + { + traceline(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * v_z, FALSE, self); + if (vlen(trace_endpos - (self.origin + self.view_ofs)) >= v_y) + { + // try to avoid shooting teammates + if (trace_ent.classname == "player") + if ((trace_ent.team == self.team && teamplay) || (coop)) + return; + bot_shoot(); + } + } + else + bot_weapon_switch(foedist); + + if (!(self.b_aiflags & (AI_PRECISION | AI_BLIND | AI_OBSTRUCTED))) + { + foesz = bot_size_player(self.enemy); + mysz = bot_size_player(self) + 5; + + if (foesz > mysz) + { + if (teamplay) + { + if (random() < 0.02) + { + bot_start_topic(5); + self.b_chattime = 1; + } + } + + return; + } + else if (mysz < 140) + return; + else if (self.avoid) + { + if (self.avoid.velocity) + v = self.avoid.velocity; + else + v = normalize(self.avoid.origin - self.origin); + v1_x = v_y; + v1_y = v_y * -1; + v2_x = v_y; + v2_y = v_y * -1; + foedist = vlen(self.avoid.origin - (self.origin + v1)); + if (foedist < vlen(self.avoid.origin - (self.origin + v2))) + frik_walkmove(v2); + else + frik_walkmove(v1); + } + else if (!self.enemy.flags & FL_MONSTER) + { + if (foedist + 32 < v_x) + frik_walkmove(self.origin - org); + else if (foedist - 32 > v_x) + frik_walkmove(org - self.origin); + else if (self.wallhug) + frik_walkmove(v_right); + else + frik_walkmove(v_right * -1); + } + } + else + { + foesz = bot_size_player(self.enemy); + mysz = bot_size_player(self) + 5; + + if (foesz > mysz) + return; + else if (mysz < 140) + return; + self.keys = self.keys & 960; + } +}; + + diff --git a/fbxa/bot_misc.qc b/fbxa/bot_misc.qc new file mode 100644 index 0000000..31d37e2 --- /dev/null +++ b/fbxa/bot_misc.qc @@ -0,0 +1,777 @@ +/*********************************************** +* * +* FrikBot Misc Code * +* "Because you can't name it anything else" * +* * +***********************************************/ + +/* +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. + +*/ + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotName + +Sets bot's name and colors + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +string(float r) BotName = +{ + self.b_num = r; + if (r == 1) + { + self.b_pants = 11; + self.b_shirt = 0; + return "Vincent"; + } + else if (r == 2) + { + self.b_pants = 1; + self.b_shirt = 3; + return "Bishop"; + } + else if (r == 3) + { + self.b_pants = 13; + self.b_shirt = 2; + return "Nomad"; + } + else if (r == 4) + { + self.b_pants = 7; + self.b_shirt = 6; + return "Hudson"; + } + else if (r == 5) + { + self.b_pants = 12; + self.b_shirt = 6; + return "Lore"; + } + else if (r == 6) + { + self.b_pants = 4; + self.b_shirt = 4; + return "Servo"; + } + else if (r == 7) + { + self.b_pants = 2; + self.b_shirt = 5; + return "Gort"; + } + else if (r == 8) + { + self.b_pants = 10; + self.b_shirt = 3; + return "Kryten"; + } + else if (r == 9) + { + self.b_pants = 9; + self.b_shirt = 4; + return "Pimp Bot"; + } + else if (r == 10) + { + self.b_pants = 4; + self.b_shirt = 7; + return "Max"; + } + else if (r == 11) + { + self.b_pants = 3; + self.b_shirt = 11; + return "Marvin"; + } + else if (r == 12) + { + self.b_pants = 13; + self.b_shirt = 12; + return "Erwin"; + } + else if (r == 13) + { + self.b_pants = 11; + self.b_shirt = 2; + return "FrikBot"; + } + else if (r == 14) + { + self.b_pants = 0; + self.b_shirt = 2; + return "Krosis"; + } + else if (r == 15) + { + self.b_pants = 8; + self.b_shirt = 9; + return "Gypsy"; + } + else if (r == 16) + { + self.b_pants = 5; + self.b_shirt = 10; + return "Hal"; + } +}; +string () PickARandomName = +{ + if (bot_count > 16) + return "player"; + + local float y, test; + local string h; + local entity t; + y = TRUE; + while(y) + { + test = ceil(random() * 16); + h = BotName(test); + t = find(world, netname, h); + if (t == world) + y = FALSE; + } + return h; +}; + + + +// I didn't like the old code so this is very stripped down + +entity b_originator; +float b_topic; +/* FBX Topics + +b_originator == self + 1 - sign on + 2 - killed targ + 3 - team message "friendly eyes" + 4 - team message "on your back" + 5 - team message "need back up" + 6 - excuses + ---- + 7 - gameover + ---- + 8 - welcoming someone onto server + 9 - ridicule lost frag (killed self?) + 10 - ridicule lost frag (lava) + 11 - lag +b_originator == targ + + +*/ +void(float tpic) bot_start_topic = +{ + if (random() < 0.2) + { + b_topic = tpic; + b_originator = self; + } + else + b_topic = 0; +}; + +void() bot_chat = +{ + local float r; + if (b_options & OPT_NOCHAT) + return; + r = ceil (random() * 6); + + if (self.b_chattime > time) + { + if (self.b_skill < 2) + self.keys = self.button0 = self.button2 = 0; + return; + } + else if (self.b_chattime) + { + if (b_topic == 1) + { + if (b_originator == self) + { + if (r == 1) + { + BotSay(": lo all\n"); + bot_start_topic(8); + } + else if (r == 2) + { + BotSay(": hey everyone\n"); + bot_start_topic(8); + } + else if (r == 3) + { + BotSay(": prepare to be fragged!\n"); + bot_start_topic(0); + } + else if (r == 4) + { + BotSay(": boy this is laggy\n"); + bot_start_topic(11); + } + else if (r == 5) + { + BotSay(": #mm getting some lag here\n"); + bot_start_topic(11); + } + else + { + BotSay(": hi everyone\n"); + bot_start_topic(8); + } + } + } + else if (b_topic == 2) + { + if (b_originator == self) + { + if (r == 1) + BotSay(": take that\n"); + else if (r == 2) + BotSay(": yehaww!\n"); + else if (r == 3) + BotSay(": wh00p\n"); + else if (r == 4) + BotSay(": j00_sawk();\n"); + else if (r == 5) + BotSay(": i rule\n"); + else + BotSay(": eat that\n"); + bot_start_topic(0); + } + } + else if (b_topic == 3) + { + if (b_originator == self) + { + if (r < 3) + BotSayTeam(": friendly eyes\n"); + else + BotSayTeam(": team eyes\n"); + bot_start_topic(0); + } + } + else if (b_topic == 4) + { + if (b_originator == self) + { + if (r < 3) + BotSayTeam(": on your back\n"); + else + BotSayTeam(": I'm with you\n"); + bot_start_topic(0); + } + } + else if (b_topic == 5) + { + if (b_originator == self) + { + if (r < 3) + BotSayTeam(": I need help\n"); + else + BotSayTeam(": need backup\n"); + bot_start_topic(0); + } + } + else if (b_topic == 6) + { + if (b_originator == self) + { + if (r == 1) + { + BotSay(": sun got in my eyes\n"); + bot_start_topic(0); + } + else if (r == 2) + { + BotSay(": mouse needs cleaning\n"); + bot_start_topic(0); + } + else if (r == 3) + { + BotSay(": i meant to do that\n"); + bot_start_topic(0); + } + else if (r == 4) + { + BotSay(": lag\n"); + bot_start_topic(11); + } + else if (r == 5) + { + BotSay(": killer lag\n"); + bot_start_topic(11); + } + else + { + BotSay(": 100% lag\n"); + bot_start_topic(11); + } + } + } + else if (b_topic == 7) + { + if (r == 1) + BotSay(": gg\n"); + else if (r == 2) + BotSay(": gg all\n"); + else if (r == 3) + BotSay(": that was fun\n"); + else if (r == 4) + BotSay(": good game\n"); + else if (r == 5) + BotSay(": pah\n"); + else + BotSay(": hrm\n"); + bot_start_topic(0); + } + else if (b_topic == 8) + { + if (b_originator != self) + { + if (r == 1) + { + BotSay(": heya\n"); + bot_start_topic(0); + } + else if (r == 2) + { + BotSay(": welcome\n"); + bot_start_topic(0); + } + else if (r == 3) + { + BotSayInit(); + BotSay2(": hi "); + BotSay2(b_originator.netname); + BotSay2("\n"); + bot_start_topic(0); + } + else if (r == 4) + { + BotSayInit(); + BotSay2(": hey "); + BotSay2(b_originator.netname); + BotSay2("\n"); + bot_start_topic(0); + } + else if (r == 5) + { + BotSay(": howdy\n"); + bot_start_topic(0); + } + else + { + BotSay(": lo\n"); + bot_start_topic(0); + } + } + } + + else if (b_topic == 9) + { + if (b_originator != self) + { + if (r == 1) + BotSay(": hah\n"); + else if (r == 2) + BotSay(": heheh\n"); + else if (r == 3) + { + BotSayInit(); + BotSay2(": good work "); + BotSay2(b_originator.netname); + BotSay2("\n"); + } + else if (r == 4) + { + BotSayInit(); + BotSay2(": nice1 "); + BotSay2(b_originator.netname); + BotSay2("\n"); + } + else if (r == 5) + BotSay(": lol\n"); + else + BotSay(": :)\n"); + b_topic = 6; + } + } + else if (b_topic == 10) + { + if (b_originator != self) + { + if (r == 1) + BotSay(": have a nice dip?\n"); + else if (r == 2) + BotSay(": bah I hate levels with lava\n"); + else if (r == 3) + { + BotSayInit(); + BotSay2(": good job "); + BotSay2(b_originator.netname); + BotSay2("\n"); + } + else if (r == 4) + { + BotSayInit(); + BotSay2(": nice backflip "); + BotSay2(b_originator.netname); + BotSay2("\n"); + } + else if (r == 5) + BotSay(": watch your step\n"); + else + BotSay(": hehe\n"); + b_topic = 6; + } + } + + else if (b_topic == 11) + { + if (b_originator != self) + { + if (r == 1) + { + BotSayInit(); + BotSay2(": yeah right "); + BotSay2(b_originator.netname); + BotSay2("\n"); + bot_start_topic(0); + } + else if (r == 2) + { + BotSay(": ping\n"); + bot_start_topic(0); + } + else if (r == 3) + { + BotSay(": shuddup, you're an lpb\n"); + bot_start_topic(0); + } + else if (r == 4) + { + BotSay(": lag my eye\n"); + bot_start_topic(0); + } + else if (r == 5) + { + BotSay(": yeah\n"); + bot_start_topic(11); + } + else + { + BotSay(": totally\n"); + bot_start_topic(11); + } + } + } + self.b_chattime = 0; + } + else if (b_topic) + { + if (random() < 0.5) + { + if (self == b_originator) + { + if (b_topic <= 7) + self.b_chattime = time + 2; + } + else + { + if (b_topic >= 7) + self.b_chattime = time + 2; + } + } + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Kick A Bot. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() KickABot = +{ + local entity ty; + ty = find(world, classname, "player"); + while (ty != world) + { + if (!(ty.ishuman)) + { + + BotDisconnect(ty); + ty.ishuman = TRUE; + ty = world; + } + else + ty = find(ty, classname, "player"); + } + +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Simplified origin checking. + +God, I wish I had inline + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +vector(entity ent) realorigin = +{ +// even more simplified... + return (ent.absmin + ent.absmax) * 0.5; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +fisible + +a version of visible that checks for corners +of the bounding boxes + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +float (entity targ) fisible = +{ + local vector spot1, org; + local float thruwater, pc1, pc2; + + org = realorigin(targ); + spot1 = self.origin + self.view_ofs; + + if (targ.solid == SOLID_BSP) + { + traceline (spot1, org, TRUE, self); + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; + return FALSE; + } + else + { + pc1 = pointcontents(org); + pc2 = pointcontents(spot1); + if (targ.classname == "player") + thruwater = FALSE; + else if (pc1 == CONTENT_LAVA) + return FALSE; + else + thruwater = TRUE; + } + + if (pc1 < -1) // targ's origin is in water or other liquid + { + if (pc2 != pc1) + { + // look for their head + traceline (spot1, org + targ.mins, TRUE, self); + // cross the water check + if (trace_inopen) + if (trace_inwater) + if (!thruwater) + return FALSE; + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; + return FALSE; + } + } + else + { + if (pc2 != pc1) + { + traceline (spot1, org + targ.maxs, TRUE, self); + if (trace_inopen) + if (trace_inwater) + if (!thruwater) + return FALSE; + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; + return FALSE; + } + } + traceline (spot1, org, TRUE, self); + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; + traceline (spot1, org + targ.maxs, TRUE, self); + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; + traceline (spot1, org + targ.mins, TRUE, self); + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; + return FALSE; +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Wisible + +goes through movable brushes/entities, used +for waypoints + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +// this is used for waypoint stuff.... +float (entity targ1, entity targ2) wisible = +{ + local vector spot1, spot2; + local entity ignore; + + spot1 = targ1.origin; + spot2 = realorigin(targ2); + + ignore = self; + do + { + traceline (spot1, spot2, TRUE, ignore); + spot1 = realorigin(trace_ent); + ignore = trace_ent; + } while ((trace_ent != world) && (trace_fraction != 1)); + if (trace_endpos == spot2) + return TRUE; + else + return FALSE; +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +sisible + +Now this is getting ridiculous. Simple visible, +used when we need just a simple traceline nothing else + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +float (entity targ) sisible = +{ + traceline (self.origin, targ.origin, TRUE, self); + if (trace_ent == targ) + return TRUE; + else if (trace_fraction == 1) + return TRUE; +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +angcomp + +subtracts one angle from another + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +float (float y1, float y2) angcomp = +{ + y1 = frik_anglemod(y1); + y2 = frik_anglemod(y2); + + local float answer; + answer = y1 - y2; + if (answer > 180) + answer = (360 - answer) * -1; + else if (answer < -180) + answer = answer + 360; + return answer; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +fov + +is the entity in the bot's field of view + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +float (entity targ) fov = +{ + local vector yawn; + local float g; + yawn = realorigin(targ); + yawn = (yawn + targ.view_ofs) - (self.origin + self.view_ofs); + yawn = normalize(yawn); + yawn = vectoangles(yawn); + g = angcomp(self.v_angle_x, yawn_x); + if (fabs(g) > 45) + return FALSE; + g = angcomp(self.v_angle_y, yawn_y); + if (fabs(g) > 60) + return FALSE; + + return TRUE; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +frik_anglemod + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +float(float v) frik_anglemod = +{ + return v - floor(v/360) * 360; +}; \ No newline at end of file diff --git a/fbxa/bot_move.qc b/fbxa/bot_move.qc new file mode 100644 index 0000000..1ad345e --- /dev/null +++ b/fbxa/bot_move.qc @@ -0,0 +1,512 @@ +/*********************************************** +* * +* FrikBot Movement AI * +* "The slightly better movement 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. + +*/ + +void() bot_jump = +{ + // TODO check for precision, etc. + self.button2 = TRUE; +}; + +float(entity e) bot_can_rj = +{ + // this returns true of the bot can rocket/superjump/hook + // if your mod doesn't have an RL you can just return FALSE all the time + // if it has a hook or some other means for the bot to get to high places + // you can check here for that capability + + // am I dumb? + if (e.b_skill == 0) + return FALSE; + + // quad = bad + if (e.items & 4194304) + return FALSE; + + // do I have rockets & RL? + if (!((e.items & 32) && (e.ammo_rockets > 0))) + return FALSE; + + // do I have pent? + if (e.items & 1048576) + return TRUE; + + if (e.health > 50) + return TRUE; + else + return FALSE; +}; + +float(float flag) frik_recognize_plat = +{ + if ((self.classname != "waypoint") && !(self.flags & FL_ONGROUND)) + return FALSE; + traceline(self.origin, self.origin - '0 0 64', TRUE, self); + if (trace_ent != world) + { + if (flag) // afect bot movement too + { + if (self.keys & KEY_MOVEUP) + { + if (trace_ent.velocity_z > 0) + self.keys = self.keys & 960; // 960 is all view keys + } + else if (self.keys & KEY_MOVEDOWN) + { + if (trace_ent.velocity_z < 0) + self.keys = self.keys & 960; + } + } + return TRUE; + } + else + return FALSE; +}; + +float(vector sdir) frik_KeysForDir = +{ + + local vector keydir; + local float outkeys, tang; + outkeys = 0; + if (sdir_x || sdir_y) + { + // Everything is tested against 60 degrees, + // this allows the bot to overlap the keys + // 30 degrees on each diagonal 45 degrees + // might look more realistic + + keydir = vectoangles(sdir); + tang = angcomp(keydir_y, self.v_angle_y); + if ((tang <= 150) && (tang >= 30)) + outkeys = outkeys + KEY_MOVELEFT; + else if ((tang >= -150) && (tang <= -30)) + outkeys = outkeys + KEY_MOVERIGHT; + if (fabs(tang) <= 60) + outkeys = outkeys + KEY_MOVEFORWARD; + else if (fabs(tang) >= 120) + outkeys = outkeys + KEY_MOVEBACK; + } + if (sdir_z > 0.7) + outkeys = outkeys + KEY_MOVEUP; + else if (sdir_z < 0.7) + outkeys = outkeys + KEY_MOVEDOWN; + return outkeys; + +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +frik_obstructed + +Bot has hit a ledge or wall that he should +manuever around. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void(vector whichway, float danger) frik_obstructed = +{ + local float dist; + local vector disway, org; +// TODO: something + if (self.b_aiflags & AI_BLIND) + return; + org = realorigin(self.target1); + + if (danger) + { + self.b_aiflags = self.b_aiflags | AI_DANGER; + self.keys = frik_KeysForDir('0 0 0' - whichway); + } + if (self.b_aiflags & AI_PRECISION) + return; + + + if (self.target1) + { + if (self.b_aiflags & AI_OBSTRUCTED) + { + if (!(self.b_aiflags & AI_DANGER)) + { + self.b_aiflags = self.b_aiflags - AI_OBSTRUCTED; + return; + } + else if (!danger) + return; + } + self.obs_dir = whichway; + disway_x = whichway_y * -1; + disway_y = whichway_x; + dist = vlen(org - (self.origin + disway)); + disway_x = whichway_y; + disway_y = whichway_x * -1; + self.wallhug = vlen(org - (self.origin + disway)) > dist; + self.b_aiflags = self.b_aiflags | AI_OBSTRUCTED; + + } + else + { + disway_x = whichway_y * -1; + disway_y = whichway_x; + dist = vlen(disway - self.obs_dir); + disway_x = whichway_y; + disway_y = whichway_x * -1; + self.wallhug = vlen(disway - self.obs_dir) < dist; + self.obs_dir = whichway; + + self.b_aiflags = self.b_aiflags | AI_OBSTRUCTED; + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +frik_obstacles + +Detects small bumps the bot needs to jump over +or ledges the bot should avoid falling in. + +Also responsible for jumping gaps. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() frik_obstacles = +{ + local vector start, stop, ang; + local float test, conts, dist, hgt; + + if (!(self.flags & FL_ONGROUND)) + return; + if (self.b_aiflags & AI_BLIND) + return; + + ang = normalize(self.velocity); + ang_z = 0; + start = self.origin + ang * 32; // ahem + start_z = self.origin_z + self.maxs_z; + stop = start; + stop_z = self.origin_z + self.mins_z; + traceline(start, stop - '0 0 256', TRUE, self); + if (trace_allsolid || trace_startsolid) + return; + hgt = trace_endpos_z - stop_z; + + if (hgt > 18) + { + bot_jump(); + return; + } + if (hgt >= 0) + return; + + conts = pointcontents(trace_endpos + '0 0 4'); + start = stop - '0 0 8'; + stop = start + ang * 256; + traceline(start, stop, TRUE, self); + test = vlen(trace_endpos - start); + if (test <= 20) + return; // it's a walkable gap, do nothing + ang_x = self.velocity_y * -1; + ang_y = self.velocity_x; + ang = normalize(ang); + traceline(start - (ang * 10), start + (ang * 10), TRUE, self); + if ((trace_fraction != 1) || trace_startsolid) + return; // gap is only 20 wide, walkable + ang = self.velocity; + ang_z = 0; + dist = ((540 / sv_gravity) * vlen(ang))/* + 32*/; + if (test > dist) // I can't make it + { + if (conts < -3) // bad stuff down dare + { + frik_obstructed(ang, TRUE); + return; + } + else + { + if (self.target1) + { + stop = realorigin(self.target1); + if ((stop_z - self.origin_z) < -32) + return; // safe to fall + } + frik_obstructed(ang, FALSE); + return; + } + } + else + { + ang = normalize(ang); + //look for a ledge + traceline(self.origin, self.origin + (ang * (test + 20)), TRUE, self); + if (trace_fraction != 1) + { + if (conts < -3) // bad stuff down dare + { + frik_obstructed(ang, TRUE); + return; + } + else + { + if (self.target1) + { + stop = realorigin(self.target1); + if ((stop_z - self.origin_z) < -32) + return; // safe to fall + } + frik_obstructed(ang, FALSE); + return; + } + } + + if (self.target1) + { + // getting furter away from my target? + test = vlen(self.target1.origin - (ang + self.origin)); + if (test > vlen(self.target1.origin - self.origin)) + { + if (conts < -3) // bad stuff down dare + { + frik_obstructed(ang, TRUE); + return; + } + else + { + frik_obstructed(ang, FALSE); + return; + } + } + } + } + if (hgt < -18) + { + if (self.target1) + { + stop = realorigin(self.target1); + if ((stop_z - self.origin_z) < -32) + return; // safe to fall + } + bot_jump(); + } + // go for it + +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +After frik_obstructed, the bot uses the +following funtion to move "around" the obstacle + +I have no idea how well it will work + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() frik_dodge_obstruction = +{ + local vector way, org; + local float oflags, yaw; + + if (!(self.b_aiflags & AI_OBSTRUCTED)) + return; + if ((self.b_aiflags & (AI_BLIND | AI_PRECISION)) || !(self.flags & FL_ONGROUND)) + { + self.b_aiflags = self.b_aiflags - AI_OBSTRUCTED; + return; + } + + // perform a walkmove check to see if the obs_dir is still obstructed + // walkmove is less forgiving than frik_obstacles, so I dunno + // how well this will work + + oflags = self.flags; + org = self.origin; + + yaw = vectoyaw(self.obs_dir); + if (walkmove(yaw, 32)) + self.b_aiflags = self.b_aiflags - AI_OBSTRUCTED; + else + { + if (self.b_aiflags & AI_DANGER) + { + way = '0 0 0' - self.obs_dir; + } + else if (self.wallhug) + { + way_x = self.obs_dir_y * -1; + way_y = self.obs_dir_x; + } + else + { + way_x = self.obs_dir_y; + way_y = self.obs_dir_x * -1; + } + self.keys = self.keys & 960 + frik_KeysForDir(way); + } + + // fix the bot + + self.origin = org; + self.flags = oflags; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +movetogoal and walkmove replacements + +blah + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() frik_movetogoal = +{ + local vector way, start, stop, ang; + local float g; + + if (self.target1 == world) + { + makevectors(self.v_angle); + frik_walkmove(v_forward); + return; + } + way = realorigin(self.target1) - self.origin; + if (vlen(way) < 25) + { + self.keys = self.keys & 960; + return; + } + + way = normalize(way); + self.keys = self.keys & 960 + frik_KeysForDir(way); + + frik_dodge_obstruction(); + frik_recognize_plat(TRUE); + + if (self.b_aiflags & AI_PRECISION) + { + g = angcomp(self.v_angle_x, self.b_angle_x); + if (fabs(g) > 10) + self.keys = self.keys & 960; + g = angcomp(self.v_angle_y, self.b_angle_y); + if (fabs(g) > 10) + self.keys = self.keys & 960; + } +}; + +float(vector weird) frik_walkmove = +{ + // okay so it's not walkmove + // sue me + self.keys = self.keys & 960 + frik_KeysForDir(weird); + + frik_dodge_obstruction(); + frik_recognize_plat(TRUE); + if (self.b_aiflags & AI_OBSTRUCTED) + return FALSE; + else + return TRUE; +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +The "hook" method of navigation. This nav +system is copyrighted 1999 by Ryan "Frika C" +Smith, keep that in mind when you steal it. + +I brought this back because normal roaming +won't work - the bot gets distracted by it's +own waypoints. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() frik_bot_roam = +{ + local vector org, ang, org1; + local float loopcount, flag, dist; + + loopcount = 26; + flag = FALSE; + while((loopcount > 0) && !flag) + { + loopcount = loopcount - 1; + org = self.origin + self.view_ofs; + ang = self.angles; + ang_y = frik_anglemod(ang_y - 90 + (random() * 180)); + ang_x = 0; // avoid upward sloping + makevectors(ang); + traceline(org, org + v_forward * 2300, TRUE, self); + if (trace_fraction != 1) + { + org1 = trace_endpos; + ang = normalize(trace_plane_normal); + ang_z = 0; // avoid upward sloping + traceline(org1, org1 + (ang * 2300), TRUE, self); + if ((trace_fraction != 1) && (vlen(trace_endpos - org1) >= 64)) + { + org = trace_endpos; + traceline(org, self.origin + self.view_ofs, TRUE, self); + if (trace_fraction != 1) + { + dist = vlen(org1 - org) /2; + org = org1 + (ang * dist); + traceline(org, org - '0 0 48', TRUE, self); + if (trace_fraction != 1) + { + SpawnTempWaypoint(org); + flag = TRUE; + } + } + } + } + } + self.b_angle_y = self.v_angle_y + 10; +}; diff --git a/fbxa/bot_phys.qc b/fbxa/bot_phys.qc new file mode 100644 index 0000000..dd7c835 --- /dev/null +++ b/fbxa/bot_phys.qc @@ -0,0 +1,678 @@ +/*********************************************** +* * +* 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. + +*/ + +/* +========================================= + +Stuff mimicking cl_input.c code + +========================================= +*/ +float(float key) CL_KeyState = +{ + return ((self.keys & key) > 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); + 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 = world; + 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); + +}; +void() RemoveThud = // well sometimes +{ + local entity oself; + if (other == world) + { + 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(0,0)) + 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(0,0)) + { + 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, 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 == world) + { + self.phys_obj = find(world,classname,"phys_obj"); + while (self.phys_obj.owner != self) + { + self.phys_obj = find(self.phys_obj,classname,"phys_obj"); + if (self.phys_obj == world) + { + error("No physics entity spawned!\nMake sure BotInit was called\n"); + } + } + } + + setmodel (self.phys_obj, string_null); + 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)"); + +}; + + diff --git a/fbxa/bot_qw.qc b/fbxa/bot_qw.qc new file mode 100644 index 0000000..77846e7 --- /dev/null +++ b/fbxa/bot_qw.qc @@ -0,0 +1,944 @@ + +/* +====================================== +FrikBot X (Version 0.10.0) QW +====================================== + +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. + + +====================================== +These installation instructions only apply to QuakeWorld (as does this entire +file). For Normal Quake, please refer to bot.qc + +-------------------------------------- +To install on a new mod, do all this: +-------------------------------------- +Place all included bot*.qc files in the subdirectory "frikbot" +in your source folder, then... + +* Add the following lines to progs.src right after the defs.qc line +frikbot/bot_qw.qc +frikbot/bot_way.qc +frikbot/bot_fight.qc +frikbot/bot_ai.qc +frikbot/bot_misc.qc +frikbot/bot_phys.qc +frikbot/bot_move.qc +-------------------------------------- +* Comment out the following functions in defs.qc +sound, stuffcmd, sprint, aim, centerprint, setspawnparms +WriteByte, WriteChar, WriteShort, WriteLong, WriteCoord +WriteAngle, WriteString, WriteEntity +-------------------------------------- +* Add this to worldspawn() in world.qc, right at the very top, before InitBodyQue(); +BotInit(); // FrikBot +-------------------------------------- +* add this line to StartFrame() in world.qc, at the very top +BotFrame(); // FrikBot +-------------------------------------- +* Add these two lines to PlayerPreThink in client.qc at the very top +if (BotPreFrame()) // FrikBot + return; +-------------------------------------- +* Add this line to PlayerPostThink in client.qc at the very top +if (BotPostFrame()) // FrikBot + return; +-------------------------------------- +* Add the following line to the very top of Client Connect in client.qc +ClientInRankings(); // FrikBot +-------------------------------------- +* Add these lines to the very top of ClientDisconnect in client.qc +ClientDisconnected(); // FrikBot +-------------------------------------- +* Add these lines to the very top of SpectatorConnect in spectate.qc +ClientFixRankings(); // FrikBot +-------------------------------------- +*/ + +void() bot_map_load = +{ + // place your qc loaded waypoints here + + if (mapname == "dm1") + map_dm1(); + else if (mapname == "dm2") + map_dm2(); + else if (mapname == "dm3") + map_dm3(); + else if (mapname == "dm4") + map_dm4(); + else if (mapname == "dm5") + map_dm5(); + else if (mapname == "dm6") + map_dm6(); +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Variables and shtuff + +bot.qc has become pretty much a header file +for all variable in the bot... + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +// ----- entity fields --- +.float wallhug, keys, oldkeys, ishuman; +.float b_frags, b_clientno, b_shirt, b_pants; +.float priority, ai_time, b_sound, missile_speed; +.float portal_time, b_skill, switch_wallhug; +.float b_aiflags, b_num, b_chattime; +.float b_entertime, b_userid; // QW shtuff +.float b_menu, b_menu_time, b_menu_value; +.float route_failed, dyn_flags, dyn_time; +.float dyn_plat; +.entity temp_way, last_way, phys_obj; +.entity target1, target2, target3, target4; +.entity _next, _last; +.entity current_way; +.vector b_angle, b_dest, mouse_emu, obs_dir; +.vector movevect, b_dir; +.vector dyn_dest; +.vector punchangle; // HACK - Don't want to screw with bot_phys + +// --------defines----- +float SVC_UPDATENAME = 13; +float SVC_UPDATEFRAGS = 14; +float SVC_UPDATECOLORS = 17; + +// used for the physics & movement AI +float KEY_MOVEUP = 1; +float KEY_MOVEDOWN = 2; +float KEY_MOVELEFT = 4; +float KEY_MOVERIGHT = 8; +float KEY_MOVEFORWARD = 16; +float KEY_MOVEBACK = 32; +float KEY_LOOKUP = 64; +float KEY_LOOKDOWN = 128; +float KEY_LOOKLEFT = 256; +float KEY_LOOKRIGHT = 512; + +// these are aiflags for waypoints +// some overlap to the bot +float AI_TELELINK_1 = 1; // link type +float AI_TELELINK_2 = 2; // link type +float AI_TELELINK_3 = 4; // link type +float AI_TELELINK_4 = 8; // link type +float AI_DOORFLAG = 16; // read ahead +float AI_PRECISION = 32; // read ahead + point +float AI_SURFACE = 64; // point +float AI_BLIND = 128; // read ahead + point +float AI_JUMP = 256; // point + ignore +float AI_DIRECTIONAL = 512; // read ahead + ignore +float AI_PLAT_BOTTOM = 1024; // read ahead +float AI_RIDE_TRAIN = 2048; // read ahead +float AI_SUPER_JUMP = 4096; // point + ignore + route test +float AI_SNIPER = 8192; // point type +float AI_AMBUSH = 16384; // point type +float AI_DOOR_NO_OPEN = 32768; // read ahead +float AI_DIFFICULT = 65536; // route test +float AI_TRACE_TEST = 131072; // route test + +// these are flags for bots/players (dynamic/editor flags) +float AI_OBSTRUCTED = 1; +float AI_HOLD_SELECT = 2; +float AI_ROUTE_FAILED = 2; +float AI_WAIT = 4; +float AI_DANGER = 8; + +// addition masks +float AI_POINT_TYPES = 29152; +float AI_READAHEAD_TYPES = 36528; +float AI_IGNORE_TYPES = 4864; + +float WM_UNINIT = 0; +float WM_DYNAMIC = 1; +float WM_LOADING = 2; +float WM_LOADED = 3; +// editor modes aren't available in QW, but we retain support of them +// since the editor is still built into the AI in places +float WM_EDITOR = 4; +float WM_EDITOR_DYNAMIC = 5; +float WM_EDITOR_DYNLINK = 6; + +float OPT_NOCHAT = 2; + +// -------globals----- +float active_clients1, active_clients2; +float max_clients, real_frametime; +float bot_count, b_options, lasttime; +float waypoint_mode, dump_mode; +float waypoints, direct_route, userid; +float sv_friction, sv_gravity; +float sv_accelerate, sv_maxspeed, sv_stopspeed; +entity fixer; +entity route_table; +entity b_temp1, b_temp2, b_temp3; +entity player_head, phys_head, way_head; +float busy_waypoints; + +float coop = 0; // hack + +// -------ProtoTypes------ +// external +void() ClientConnect; +void() ClientDisconnect; +void() SetNewParms; + +// rankings +float(float clientno) ClientBitFlag; +float() ClientNextAvailable; +void(float whatbot, float whatskill) BotConnect; +void(entity bot) BotDisconnect; +void(float clientno) BotInvalidClientNo; +void(entity who) UpdateClient; + +// waypointing +void() DynamicWaypoint; +entity(vector org) make_waypoint; +void() ClearAllWays; +void() FixWaypoints; +float() begin_route; +void(entity this, float direct) bot_get_path; +void() WaypointThink; +entity(entity start) FindWayPoint; + +// physics & movement +float(entity e) bot_can_rj; +void() bot_jump; +void() frik_bot_roam; +float(vector weird) frik_walkmove; +void() frik_movetogoal; +void() frik_obstacles; +float(float flag) frik_recognize_plat; +float(vector sdir) frik_KeysForDir; +void(vector whichway, float danger) frik_obstructed; +void() SV_Physics_Client; +void() SV_ClientThink; +void() CL_KeyMove; + +// ai & misc +string() PickARandomName; +float(entity targ) fov; +float(float y1, float y2) angcomp; +float(entity targ1, entity targ2) wisible; +float(entity targ) sisible; +float(entity targ) fisible; +vector(entity ent) realorigin; +void(entity ent) target_drop; +void(entity ent) target_add; +void() KickABot; +void() BotImpulses; +void(entity targ, float success) bot_lost; +string(float r) BotName; +float(float v) frik_anglemod; +void() bot_chat; +void(float tpic) bot_start_topic; + + +// ----------Commands--------- +void(entity e, float chan, string samp, float vol, float atten) frik_sound = #8; +void(entity client, string s) frik_stuffcmd = #21; +void(entity client, float level, string s) frik_sprint = #24; +vector(entity e, float sped) frik_aim = #44; +void(entity client, string s) frik_centerprint = #73; +void(entity e) frik_setspawnparms = #78; +void(float to, float f) frik_WriteByte = #52; +void(float to, float f) frik_WriteChar = #53; +void(float to, float f) frik_WriteShort = #54; +void(float to, float f) frik_WriteLong = #55; +void(float to, float f) frik_WriteCoord = #56; +void(float to, float f) frik_WriteAngle = #57; +void(float to, string s) frik_WriteString = #58; +void(float to, entity s) frik_WriteEntity = #59; + +//---------------------------------------------------------------------------- + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Function redclarations. These allow function +designed to work for clients (sprint, so forth) +to mainly not complain when working with a bot + +Although these shouldn't be needed anymore, +as the bots truly are clients now, if you don't +stop the SZ_ buffer from filling up by disabling +direct messages to the bots, it crashes quake :-( + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +void(entity client, string s) stuffcmd = +{ + if (client.ishuman) + frik_stuffcmd(client, s); +}; + +void(entity e) setspawnparms = +{ + if (e.ishuman) + frik_setspawnparms(e); + else + SetNewParms(); +}; +void(entity client, float level, string s) sprint = +{ + if (client.ishuman) + frik_sprint(client, level, s); +}; +void(entity client, string s) centerprint = +{ + if (client.ishuman) + frik_centerprint(client, s); +}; + +vector(entity e, float sped) aim = +{ + e.missile_speed = sped; + return frik_aim(e, sped); +}; + +void(entity e, float chan, string samp, float vol, float atten) sound = +{ + frik_sound(e, chan, samp, vol, atten); + if (samp == "items/inv3.wav") + return; + else if (e.classname == "player") + e.b_sound = time + 1; + else if (other.classname == "player") + other.b_sound = time + 1; +}; +void(float to, float f) WriteByte = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteByte(to, f); +}; +void(float to, float f) WriteChar = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteChar(to, f); +}; +void(float to, float f) WriteShort = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteShort(to, f); +}; +void(float to, float f) WriteLong = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteLong(to, f); +}; +void(float to, float f) WriteCoord = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteCoord(to, f); +}; +void(float to, float f) WriteAngle = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteAngle(to, f); +}; +void(float to, string s) WriteString = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteString(to, s); +}; +void(float to, entity s) WriteEntity = +{ + if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE)) + return; + frik_WriteEntity(to, s); +}; + + +float(float clientno) ClientIsActive = +{ + if(clientno > 16) + { + if(active_clients2 & ClientBitFlag(clientno - 16)) + return TRUE; + else + return FALSE; + } + else + { + if(active_clients1 & ClientBitFlag(clientno)) + return TRUE; + else + return FALSE; + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Stuff mentioned up top +it just links the bot into the mod + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() ClientFixRankings = +{ + local float cno; + if (self.switch_wallhug > time) + return; + self.switch_wallhug = 0; + + b_temp2 = nextent(world); + cno = 0; + + while (cno < max_clients) + { + if (!b_temp2.ishuman) + { + if (ClientIsActive(cno)) + UpdateClient(b_temp2); + } + cno = cno + 1; + b_temp2 = nextent(b_temp2); + } +}; + +void() ClientInRankings = +{ + local float cno; + if (player_head) + player_head._last = self; + self._next = player_head; + player_head = self; + userid = userid + 1; + self.b_userid = userid; + + if (!self.phys_obj) + { + b_temp2 = phys_head; + while (b_temp2 != world && b_temp2.owner != self) + b_temp2 = b_temp2._next; + self.phys_obj = b_temp2; + } + + if (self.ishuman == 2) + { + self.ishuman = FALSE; + return; + } + cno = self.colormap - 1; + BotInvalidClientNo (cno); + if(cno > 16) + active_clients2 = active_clients2 | ClientBitFlag(cno - 16); + else + active_clients1 = active_clients1 | ClientBitFlag(cno); + self.b_clientno = cno; + self.ishuman = TRUE; + self.switch_wallhug = time + 1; +}; + + +void() ClientDisconnected = +{ + if (player_head == self) + player_head = self._next; + if (self._next) + self._next._last = self._last; + if (self._last) + self._last._next = self._next; + if(self.b_clientno > 16) + active_clients2 = active_clients2 - (active_clients2 & ClientBitFlag(self.b_clientno - 16)); + else + active_clients1 = active_clients1 | (active_clients1 & ClientBitFlag(self.b_clientno)); +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotPreFrame & BotPostFrame, used to make the +bot easier to install + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +float () BotPreFrame = +{ + if (self.b_clientno == -1) + return TRUE; + if (self.ishuman) + if (self.switch_wallhug) + ClientFixRankings(); + if (self.b_frags != self.frags) + { + if (self.b_frags > self.frags) + { + if (pointcontents(self.origin) == CONTENT_LAVA) + bot_start_topic(10); + else + bot_start_topic(9); + } + else + bot_start_topic(2); + self.b_frags = self.frags; + if (!self.ishuman) + { + WriteByte(2, 14); + WriteByte(2, self.b_clientno); + WriteShort(2, self.frags); + } + } + DynamicWaypoint(); + return FALSE; +}; +float () BotPostFrame = +{ + if (self.b_clientno == -1) + return TRUE; + if (self.ishuman) + BotImpulses(); + return FALSE; +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Bot Chat code + +The rest of this code is in bot_rank.qc + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +void(string h) BotSay = // simulate talking by composing a 'chat' message +{ + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 3); + WriteByte(MSG_ALL, 1); + WriteString(MSG_ALL, self.netname); + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 3); + WriteByte(MSG_ALL, 2); + WriteString(MSG_ALL, h); +}; +void() BotSayInit = +{ + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 3); + WriteByte(MSG_ALL, 1); + WriteString(MSG_ALL, self.netname); +}; +void(string h) BotSay2 = +{ + WriteByte(MSG_ALL, 8); + WriteByte(MSG_ALL, 3); + WriteByte(MSG_ALL, 2); + WriteString(MSG_ALL, h); +}; +void(string h) BotSayTeam = +{ + // FBX QW doesn't support teamplay...yet +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotInit + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +void() BotInit = +{ + local entity ent, fisent; + local float numents; + + // spawn entities for the physics + ent = nextent(world); + max_clients = 0; + + while(ent != world) + { + max_clients = max_clients + 1; + ent = nextent(ent); + } + + ent = nextent(world); + fisent = world; + while (numents < max_clients) + { + + phys_head = spawn(); + if (fisent) + fisent._next = phys_head; + phys_head._last = fisent; + fisent = phys_head; + ent.phys_obj = phys_head; + phys_head.classname = "phys_obj"; + phys_head.owner = ent; + numents = numents + 1; + ent = nextent(ent); + } + bot_map_load(); +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Rankings 'utilities'. Written by Alan Kivlin, +this code just fools clients by sending precisely +the same network messages as when a real player +signs on to the server. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +void(entity who) UpdateClient = +{ + WriteByte(2, 14 ); + WriteByte(2, who.b_clientno); + WriteShort(2, who.frags); + WriteByte(2, 36); + WriteByte(2, who.b_clientno); + WriteShort(2, 100 * (3 - who.b_skill)); + WriteByte(2, 37); // update entertime + WriteByte(2, who.b_clientno); // client number + WriteLong(2, time - who.b_entertime); // client entertime + WriteByte(2, 40 ); // update userinfo + WriteByte(2, who.b_clientno); // client number + WriteLong(2, who.b_userid); // client userid + WriteByte(2, 92); // \ + WriteByte(2, 98); // b + WriteByte(2, 111); // o + WriteByte(2, 116); // t + WriteByte(2, 116); // t + WriteByte(2, 111); // o + WriteByte(2, 109); // m + WriteByte(2, 99); // c + WriteByte(2, 111); // o + WriteByte(2, 108); // l + WriteByte(2, 111); // o + WriteByte(2, 114); // r + WriteByte(2, 92); // \ + if(who.b_pants > 9) + { + WriteByte(2, 49); + WriteByte(2, 38 + who.b_pants); + } + else + WriteByte(2, 48 + who.b_pants); + WriteByte(2, 92); // \ + WriteByte(2, 116); // t + WriteByte(2, 111); // o + WriteByte(2, 112); // p + WriteByte(2, 99); // c + WriteByte(2, 111); // o + WriteByte(2, 108); // l + WriteByte(2, 111); // o + WriteByte(2, 114); // r + WriteByte(2, 92); // \ + if(who.b_shirt > 9) + { + WriteByte(2, 49); + WriteByte(2, 38 + who.b_shirt); + } + else + WriteByte(2, 48 + who.b_shirt); + WriteByte(2, 92); // \ + WriteByte(2, 116); // t + WriteByte(2, 101); // e + WriteByte(2, 97); // a + WriteByte(2, 109); // m + WriteByte(2, 92); // \ + WriteByte(2, 98); // b + WriteByte(2, 111); // o + WriteByte(2, 116); // t + // FIXME: do teams properly + // note this has no effect on infokey + WriteByte(2, 92 ); // \ + WriteByte(2, 115); // s + WriteByte(2, 107); // k + WriteByte(2, 105); // i + WriteByte(2, 110); // n + WriteByte(2, 92); // \ + WriteByte(2, 98); // b + WriteByte(2, 97); // a + WriteByte(2, 115); // s + WriteByte(2, 101); // e + WriteByte(2, 92); // \ + WriteByte(2, 110); // n + WriteByte(2, 97); // a + WriteByte(2, 109); // m + WriteByte(2, 101); // e + WriteByte(2, 92); // \ + WriteString( 2, who.netname); +}; + +float(float clientno) ClientBitFlag = +{ + local float bitflag; + bitflag = 1; + while(clientno > 0) + { + bitflag = bitflag * 2; + clientno = clientno - 1; + } + return bitflag; +}; + +float() ClientNextAvailable = +{ + local float clientno; + // I want to do this top down, but QW won't let me + clientno = 0; + while(clientno < max_clients) + { + clientno = clientno + 1; + + if(!ClientIsActive(clientno)) + return clientno; + } + return -1; +}; + + +void(entity e1, entity e2, float flag) DeveloperLightning = {}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotConnect and related functions. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ +entity(float num) GetClientEntity = +{ + local entity upsy; + upsy = world; + num = num + 1; + while (num > 0) + { + num = num - 1; + upsy = nextent(upsy); + } + return upsy; +}; + +void(float whatbot, float whatskill) BotConnect = +{ + local float f; + local string h; + local entity uself; + + f = ClientNextAvailable(); + uself = self; + if(f == -1) + { + bprint(PRINT_HIGH, "Unable to connect a bot, server is full.\n"); + return; + } + if(f > 16) + active_clients2 = active_clients2 | ClientBitFlag(f - 16); + else + active_clients1 = active_clients1 | ClientBitFlag(f); + bot_count = bot_count + 1; + self = GetClientEntity(f); + bot_start_topic(1); + self.b_clientno = f; + self.colormap = f + 1; + if (whatbot) + self.netname = BotName(whatbot); + else + self.netname = PickARandomName(); + + // players can set skill all weird, so leave these checks in + whatskill = rint(whatskill); + if (whatskill > 3) + whatskill = 3; + else if (whatskill < 0) + whatskill = 0; + self.b_skill = whatskill; + self.b_entertime = time; + self.team = self.b_pants + 1; + UpdateClient(self); + SetNewParms(); + self.ishuman = 2; + ClientConnect(); + PutClientInServer(); + self = uself; +}; + +void(entity bot) BotDisconnect = +{ + local string h; + local entity uself; + uself = self; + self = bot; + + bot_count = bot_count - 1; + + ClientDisconnect(); + + if (self.b_clientno != -1) + { + // the bot's client number is not in use by a real player so we + // must remove it's entry in the rankings + // Quake engine sets all fields to 0, can only do the most important here + self.b_frags = self.frags = 0; + self.netname = ""; + self.classname = ""; + self.health = 0; + self.items = 0; + self.armorvalue = 0; + self.weaponmodel = ""; + self.b_pants = 0; + self.b_shirt = 0; + self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = 0; + UpdateClient(self); + if(self.b_clientno > 16) + active_clients2 = active_clients2 - (active_clients2 & ClientBitFlag(self.b_clientno - 16)); + else + active_clients1 = active_clients1 | (active_clients1 & ClientBitFlag(self.b_clientno)); + self.b_clientno = -1; + } + self = uself; +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BotInvalidClientNo +kicks a bot if a player connects and takes the bot's space + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void(float clientno) BotInvalidClientNo = +{ + local entity bot; + + bot = GetClientEntity(clientno); + if(bot.b_clientno > 0) + { + if (!bot.ishuman) + { + bot.b_clientno = -1; + BotDisconnect(bot); + if(self.b_clientno > 16) + active_clients2 = active_clients2 | ClientBitFlag(self.b_clientno - 16); + else + active_clients1 = active_clients1 | ClientBitFlag(self.b_clientno); + //BotConnect(bot.b_num, bot.b_skill); + return; + } + } +}; + +void() BotFrame = +{ + local float num; + local string h; + + h = infokey(world, "bot_options"); + b_options = stof(h); + + // for the sake of speed + sv_maxspeed = cvar("sv_maxspeed"); + sv_gravity = cvar("sv_gravity"); + sv_friction = cvar("sv_friction"); + sv_accelerate = cvar("sv_accelerate"); + sv_stopspeed = cvar("sv_stopspeed"); + real_frametime = time - lasttime; // in QW frametime is fuxx0red + lasttime = time; + + self = nextent(world); + num = 0; + while (num < max_clients) + { + if (self.ishuman == FALSE) + { + if (self.b_clientno > 0) + { + frik_obstacles(); + CL_KeyMove(); + SV_ClientThink(); + SV_Physics_Client(); + // this is sickening + if (self.phys_obj) + { + if (self.phys_obj.modelindex != self.modelindex) + { + setmodel(self.phys_obj, "progs/player.mdl"); + self.phys_obj.modelindex = self.modelindex; + } + self.phys_obj.frame = self.frame; + self.phys_obj.angles = self.angles; + self.phys_obj.colormap = self.colormap; + self.phys_obj.effects = self.effects; + } + } + } + self = nextent(self); + num = num + 1; + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Bot Impulses. Allows the player to perform bot +related functions. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() BotImpulses = +{ + local float f; + local string h; + + if (self.impulse == 100) + { + h = infokey(world, "skill"); + f = stof(h); + BotConnect(0, f); + } + else if (self.impulse == 102) + KickABot(); + else + return; + self.impulse = 0; +}; \ No newline at end of file diff --git a/fbxa/bot_way.qc b/fbxa/bot_way.qc new file mode 100644 index 0000000..9002982 --- /dev/null +++ b/fbxa/bot_way.qc @@ -0,0 +1,1001 @@ + /*********************************************** +* * +* FrikBot Waypoints * +* "The better than roaming 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. + +*/ + + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Waypoint Linking code + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +float (entity e1, entity e2) CheckLinked = +{ + if ((e1 == e2) || (e2 == world) || (e1 == world)) + return FALSE; + else if (e1.target1 == e2) + { + if (e1.b_aiflags & AI_TELELINK_1) + return 2; + else return TRUE; + } + else if (e1.target2 == e2) + { + if (e1.b_aiflags & AI_TELELINK_2) + return 2; + else return TRUE; + } + else if (e1.target3 == e2) + { + if (e1.b_aiflags & AI_TELELINK_3) + return 2; + else return TRUE; + } + else if (e1.target4 == e2) + { + if (e1.b_aiflags & AI_TELELINK_4) + return 2; + else return TRUE; + } + + else return FALSE; +}; + + +float (entity e1, entity e2) LinkWays = +{ + if ((e1 == e2) || (e2 == world) || (e1 == world)) + return FALSE; + else if (CheckLinked(e1, e2)) + return FALSE; // already linked!!! + + if (e1.target1 == world) + { + e1.target1 = e2; + return TRUE; + } + else if (e1.target2 == world) + { + e1.target2 = e2; + return TRUE; + } + else if (e1.target3 == world) + { + e1.target3 = e2; + return TRUE; + } + else if (e1.target4 == world) + { + e1.target4 = e2; + return TRUE; + } + else return FALSE; + +}; +// Link Ways part 2, used only for teleporters + +float (entity e1, entity e2) TeleLinkWays = +{ + if ((e1 == e2) || (e2 == world) || (e1 == world)) + return FALSE; + else if (CheckLinked(e1, e2)) + return FALSE; // already linked!!! + + if (e1.target1 == world) + { + e1.target1 = e2; + e1.b_aiflags = e1.b_aiflags | AI_TELELINK_1; + return TRUE; + } + else if (e1.target2 == world) + { + e1.target2 = e2; + e1.b_aiflags = e1.b_aiflags | AI_TELELINK_2; + return TRUE; + } + else if (e1.target3 == world) + { + e1.target3 = e2; + e1.b_aiflags = e1.b_aiflags | AI_TELELINK_3; + return TRUE; + } + else if (e1.target4 == world) + { + e1.target4 = e2; + e1.b_aiflags = e1.b_aiflags | AI_TELELINK_4; + return TRUE; + } + else + return FALSE; + +}; + +void (entity e1, entity e2) UnlinkWays = +{ + if ((e1 == e2) || (e2 == world) || (e1 == world)) + return; + else if (!CheckLinked(e1, e2)) + return; + + if (e1.target1 == e2) + { + e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_1); + e1.target1 = world; + } + if (e1.target2 == e2) + { + e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_2); + e1.target2 = world; + } + if (e1.target3 == e2) + { + e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_3); + e1.target3 = world; + } + if (e1.target4 == e2) + { + e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_4); + e1.target4 = world; + } + +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +FindWaypoint + +This is used quite a bit, by many different +functions big lag causer + +Finds the closest, fisible, waypoint to e + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +entity(entity start) FindWayPoint = +{ + local entity t; + local vector org; + local float dst, tdst; + local entity best; + + org = realorigin(self); + + t = way_head; + if (start != world) + { + dst = vlen(start.origin - org); + best = start; + } + else + { + dst = 100000; + best = world; + } + while(t) + { + // real players cut through ignore types + if (dst < 20) + return best; + if (!(t.b_aiflags & AI_IGNORE_TYPES) || self.ishuman) + { + tdst = vlen(t.origin - org); + if (tdst < dst) + { + if (sisible(t)) + { + dst = tdst; + best = t; + } + } + } + t = t._next; + } + return best; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Waypoint Spawning Code + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +entity way_foot; // Ugh. Do I need a foot for this or not? + +entity(vector org) make_waypoint = +{ + local entity point; + point = spawn(); + point.classname = "waypoint"; + point.search_time = time; // don't double back for me; + point.solid = SOLID_TRIGGER; + point.movetype = MOVETYPE_NONE; + point.items = -1; + setorigin(point, org); + + setsize(point, VEC_HULL_MIN, VEC_HULL_MAX); + waypoints = waypoints + 1; + if (!way_head) + { + way_head = point; + way_foot = point; + } + else + { + way_foot._next = point; + point._last = way_foot; + way_foot = point; + } + + point.count = waypoints; + if (waypoint_mode > WM_LOADED) // editor modes + setmodel(point, "progs/s_bubble.spr"); + return point; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Dynamic Waypoint spawning and linking. Not +very good all things considered. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() DynamicWaypoint = +{ + local entity t; + local float dist, dynlink, dynpoint, editor; + + if (self.teleport_time > self.portal_time) + { + if (!self.flags & FL_WATERJUMP) + { + self.dyn_flags = 2; + if (!self.ishuman) + { + bot_lost(self.target1, TRUE); + self.enemy = world; + } + } + self.portal_time = self.teleport_time; + } +// stacking everything on waypoint_mode might've been good for the editor, +// but it sucks to beat hell for this code. + + +// convert waypoint_mode to something more usable.. + if (waypoint_mode > WM_LOADED) + { + if (self.ishuman) + { + if (waypoint_mode == WM_EDITOR_DYNLINK) + dynlink = 1; + else if (waypoint_mode == WM_EDITOR_DYNAMIC) + dynlink = dynpoint = 1; + editor = 1; + } + } + else if (waypoint_mode == WM_DYNAMIC) + dynlink = dynpoint = 1; + +// if there's nothing for dynamic to do.. + if (!dynpoint) + { + if (!editor) + return; + } +// for speed sake, I won't have bots dynamic waypoint in coop + if (!self.ishuman) + if (coop) + return; + +// don't waypoint in single player + if (max_clients < 2) + return; +// if you're dead + else if (self.health <= 0) + { + if (dynpoint) + { + if (self.current_way) + { + if (pointcontents(self.origin) < -4) + { + if (self.current_way.b_aiflags & AI_BLIND) + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_PRECISION; + else + self.current_way.b_aiflags = self.current_way.b_aiflags | AI_BLIND; + } + } + } + self.dyn_dest = '0 0 0'; + self.current_way = world; + self.dyn_flags = 0; + return; + } + +// you shouldn't be making waypoints mid air + if (dynpoint) + { + if (!((self.flags & FL_ONGROUND) || self.waterlevel == 3)) + { + if (self.dyn_flags != 2) + { + self.dyn_flags = 1; + } + return; + } + } +// keep from doing the rest of this every frame + if (self.dyn_time > time) + return; + self.dyn_time = time + 0.2; + +// display the links for editor mode + if (editor) + { + if (self.current_way) + { + if (self.current_way.target1) + DeveloperLightning(self.current_way, self.current_way.target1, self.current_way.b_aiflags & AI_TELELINK_1); + if (self.current_way.target2) + DeveloperLightning(self.current_way, self.current_way.target2, self.current_way.b_aiflags & AI_TELELINK_2); + if (self.current_way.target3) + DeveloperLightning(self.current_way, self.current_way.target3, self.current_way.b_aiflags & AI_TELELINK_3); + if (self.current_way.target4) + DeveloperLightning(self.current_way, self.current_way.target4, self.current_way.b_aiflags & AI_TELELINK_4); + } + if (self.b_aiflags & AI_HOLD_SELECT) + return; + } + + t = FindWayPoint(self.current_way); + if (t) + { + dist = vlen(self.origin - t.origin); + if (dist < 192) + { + if (dist < 64) + { + + if (t != self.current_way) + { + if (dynlink) + { + if (!self.dyn_flags) + { + if (wisible(t, self.current_way)) + LinkWays(t, self.current_way); + } + if (self.dyn_flags == 2) + TeleLinkWays(self.current_way, t); + else if (wisible(t, self.current_way)) + LinkWays(self.current_way, t); + } + if (editor) + { + setmodel(t, "progs/s_light.spr"); + if (self.current_way) + setmodel(self.current_way, "progs/s_bubble.spr"); + } + } + self.current_way = t; + self.dyn_flags = 0; + } + self.dyn_dest = self.origin + self.view_ofs; + return; + } + } + + if (frik_recognize_plat(FALSE)) + { + if (vlen(trace_ent.velocity) > 0) + { + if (self.dyn_plat) + return; + self.dyn_plat = TRUE; + if (!self.dyn_flags) + self.dyn_flags = 1; + //bprint("on a plat!!!!!\n"); + } + else + self.dyn_plat = FALSE; + } + else + self.dyn_plat = FALSE; + + if (self.dyn_flags == 2) + self.dyn_dest = self.origin + self.view_ofs; + else if (self.dyn_dest == '0 0 0') + self.dyn_dest = self.origin + self.view_ofs; + if (!dynpoint) + return; + t = make_waypoint(self.dyn_dest); + + if (!self.dyn_flags) + { + if (wisible(t, self.current_way)) + LinkWays(t, self.current_way); + } + if (self.dyn_flags == 2) + TeleLinkWays(self.current_way, t); + else if (wisible(t, self.current_way)) + LinkWays(self.current_way, t); + + if (editor) + { + setmodel(t, "progs/s_light.spr"); + if (self.current_way) + setmodel(self.current_way, "progs/s_bubble.spr"); + } + self.current_way = t; + self.dyn_flags = 0; + + self.dyn_dest = self.origin + self.view_ofs; + + if (frik_recognize_plat(FALSE)) + { + if (trace_ent.classname == "door") + t.b_aiflags = t.b_aiflags | AI_DOORFLAG; + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Waypoint Loading from file + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() ClearAllWays = +{ + + local entity t, n; + t = way_head; + while(t) + { + n = t._next; + remove(t); + t = n; + } + way_head = world; + way_foot = world; + waypoints = 0; +}; + +entity(float num) WaypointForNum = +{ + local entity t; + if (!num) + return world; + + t = way_head; + while (t) + { + if (t.count == num) + return t; + t = t._next; + } + return world; +}; + +void() FixThisWaypoint = +{ + self.enemy.target1 = WaypointForNum(self.enemy.b_pants); + self.enemy.target2 = WaypointForNum(self.enemy.b_skill); + self.enemy.target3 = WaypointForNum(self.enemy.b_shirt); + self.enemy.target4 = WaypointForNum(self.enemy.b_frags); + self.enemy = self.enemy._next; + self.nextthink = time; + if (self.enemy == world) + { + remove(self); + fixer = world; + } +}; + +void() FixWaypoints = +{ + if (!fixer) + fixer = spawn(); + fixer.nextthink = time; + fixer.think = FixThisWaypoint; + fixer.enemy = way_head; +}; + + + +void(entity what) delete_waypoint = +{ + local entity t; + + if (way_head == what) + way_head = what._next; + if (way_foot == what) + way_foot = what._last; + if (what._last) + what._last._next = what._next; + if (what._next) + what._next._last = what._last; + waypoints = 0; + t = way_head; + while(t) + { + t.count = waypoints = waypoints + 1; + if (CheckLinked(t, what)) + UnlinkWays(t, what); + t = t._next; + } + if (self.current_way == what) + self.current_way = world; + remove(what); +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +FindRoute & FindThing used by the pathing code +in bot_ai.qc + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +entity(string s) FindThing = +{ + local entity t; + local float tdst, dst; + local entity best; + dst = 100000; + best = world; + t = find (world, classname, s); + while (t != world) + { + tdst = vlen(((t.absmin + t.absmax) * 0.5) - self.origin); + if (tdst < dst) + { + dst = tdst; + best = t; + } + t = find(t, classname, s); + } + return best; +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +FindRoute, this is a key function in the +pathing. The name is a bit misleading, this +code finds the closest waypoint that is part +of a route calculated by the begin_route and +end_route routines This is a definite path to +an object. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +entity(entity lastone) FindRoute = +{ + // kinda like FindWaypoint, only of this bots route though + local entity t, best; + local float dst, tdst, flag; + flag = ClientBitFlag(self.b_clientno); + t = way_head; + dst = 100000; + best = world; + while(t) + { + tdst = vlen(t.origin - self.origin); + if ((tdst < dst) && (t.b_sound & flag)) + { + if ((lastone == world) || (CheckLinked(lastone, t))) + { + dst = tdst; + best = t; + } + } + t = t._next; + } + return best; +}; +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Route & path table management + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() ClearRouteTable = +{ + // cleans up route table + + local entity t; + t = way_head; + while (t) + { + t. keys = FALSE; + t.enemy = world; + t.items = -1; // not in table + t = t._next; + } +}; + +void() ClearMyRoute = +{ + local float flag; + local entity t; + + flag = ClientBitFlag(self.b_clientno); + + t = way_head; + while (t) + { + t.b_sound = t.b_sound - (t.b_sound & flag); + t = t._next; + } +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Mark_path + +After the route has been found, mark it with +bitflags so the table can be used for a +different bot. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + + +void(entity this) mark_path = +{ + local entity t, oself; + local float flag; + + ClearMyRoute(); + + oself = self; + self = this; + t = FindWayPoint(this.current_way); + self = oself; + // FIXME + // ugh, better way to find players please!!! + if (this.classname != "player") + this.current_way = t; + + if (t.enemy == world) + { + bot_lost(this, FALSE); + if (waypoint_mode == WM_DYNAMIC) + self.route_failed = TRUE; + return; + } + + flag = ClientBitFlag(self.b_clientno); + + while(t) + { + if (t.b_sound & flag) + return; + if (t == self.last_way) + return; + t.b_sound = t.b_sound | flag; + t = t.enemy; + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +WaypointThink + +Calculates the routes. We use thinks to avoid +tripping the runaway loop counter + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void(entity e2, float b_bit) FollowLink = +{ + local float dist; + + if (self.b_aiflags & b_bit) + dist = self.items; + else + dist = vlen(self.origin - e2.origin) + self.items; + + // check if this is an RJ link + if (e2.b_aiflags & AI_SUPER_JUMP) + { + if (!bot_can_rj(route_table)) + return; + } + if (e2.b_aiflags & AI_DIFFICULT) + dist = dist + 1000; + + dist = dist + random() * 100; // add a little chaos + + if ((dist < e2.items) || (e2.items == -1)) + { + if (!e2.keys) + busy_waypoints = busy_waypoints + 1; + e2.keys = TRUE; + e2.items = dist; + e2.think = WaypointThink; + e2.nextthink = time; + e2.enemy = self; + } +}; + +void() WaypointThink = +{ + local entity oself; + + if (self.items == -1) + return; + // can you say ugly? + if (self.b_aiflags & AI_TRACE_TEST) + { + if (self.target1) + { + traceline(self.origin, self.target1.origin, TRUE, self); + if (trace_fraction == 1) + FollowLink(self.target1, AI_TELELINK_1); + } + if (self.target2) + { + traceline(self.origin, self.target2.origin, TRUE, self); + if (trace_fraction == 1) + FollowLink(self.target2, AI_TELELINK_2); + } + if (self.target3) + { + traceline(self.origin, self.target3.origin, TRUE, self); + if (trace_fraction == 1) + FollowLink(self.target3, AI_TELELINK_3); + } + if (self.target4) + { + traceline(self.origin, self.target4.origin, TRUE, self); + if (trace_fraction == 1) + FollowLink(self.target4, AI_TELELINK_4); + } + } + else + { + if (self.target1) + FollowLink(self.target1, AI_TELELINK_1); + if (self.target2) + FollowLink(self.target2, AI_TELELINK_2); + if (self.target3) + FollowLink(self.target3, AI_TELELINK_3); + if (self.target4) + FollowLink(self.target4, AI_TELELINK_4); + } + + busy_waypoints = busy_waypoints - 1; + self.keys = FALSE; + + if (busy_waypoints <= 0) + { + if (direct_route) + { + oself = self; + self = route_table; + bot_get_path(self.target1, FALSE); + self = oself; + direct_route = FALSE; + } + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +begin_route and bot_get_path + +PLEASE NOTE: bot_get_path replaces the old +calls to begin_route. + +Routing isn't done all at once now, but in two +stages, the bot will calc a route *THEN* +choose a target, *THEN* mark a path. + +Boy it's confusing. + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +float() begin_route = +{ + if (busy_waypoints > 0) + return FALSE; + + if (route_table != world) + { + if (!route_table.ishuman) + { + if (route_table.b_clientno != -1) + return FALSE; + } + } + + route_table = self; + ClearRouteTable(); + self.last_way = FindWayPoint(self.current_way); + + if (self.last_way != world) + { + self.last_way.items = vlen(self.last_way.origin - self.origin); + self.last_way.nextthink = time; + self.last_way.think = WaypointThink; + self.last_way.keys = TRUE; + busy_waypoints = 1; + return TRUE; + } + else + { + route_table = world; + busy_waypoints = 0; + return FALSE; + } +}; + +void(entity this, float direct) bot_get_path = +{ + if (this == world) + return; + + if (route_table == self) + { + if (busy_waypoints <= 0) + { + route_table = world; + mark_path(this); + } + return; + } + if (direct) + { + if(begin_route()) + direct_route = TRUE; + else + bot_lost(this, FALSE); + return; + } +}; + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +BSP/QC Waypoint loading + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void() waypoint = +{ + self.search_time = time; + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_NONE; + setorigin(self, self.origin); + + setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); + waypoints = waypoints + 1; + if (!way_head) + { + way_head = self; + way_foot = self; + } + else + { + way_foot._next = self; + self._last = way_foot; + way_foot = self; + } + + self.count = waypoints; + waypoint_mode = WM_LOADED; + if (self.count == 1) + { + self.think = FixWaypoints; // wait until all bsp loaded points are spawned + self.nextthink = time; + } +}; + +void(vector org, vector bit1, float bit4, float flargs) make_way = +{ + local entity y; + waypoint_mode = WM_LOADED; + y = make_waypoint(org); + y.b_aiflags = flargs; + y.b_pants = bit1_x; + y.b_skill = bit1_y; + y.b_shirt = bit1_z; + y.b_frags = bit4; + if (y.count == 1) + { + y.think = FixWaypoints; // wait until all qc loaded points are spawned + y.nextthink = time; + } +}; + + +/* +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +Temporary Marker code + +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +*/ + +void(vector org) SpawnTempWaypoint = +{ + local entity tep; + + if (!self.temp_way) + self.temp_way = tep = spawn(); + else + tep = self.temp_way; + + tep.classname = "temp_waypoint"; + tep.search_time = 0; + tep.solid = SOLID_TRIGGER; + tep.movetype = MOVETYPE_NOCLIP; + setorigin(tep, org); + target_add(tep); + setsize(tep, VEC_HULL_MIN, VEC_HULL_MAX); // FIXME: convert these to numerical +}; \ No newline at end of file diff --git a/fbxa/foo.html b/fbxa/foo.html new file mode 100644 index 0000000..78f397f --- /dev/null +++ b/fbxa/foo.html @@ -0,0 +1,446 @@ + +
+
+
+ + + + |
+
+
Introduction++This is the final version of FrikBot. I mean it this time. Really. Stop looking at me like that. This is it. For those that know what this bot is all about, skip the next paragraph and save yourself some time. If not, read on: + +FrikBot is a unique bot for Quake. Maybe you're familiar with Reaper Bots, or the fabulous Frogbot and Omicron bots. These are wonderful opponents to deathmatch against, but for mod authors (the people that make those nice mods you and I play, such as Capture the Flag, etc) they're not so great. Why? I hear you ask - well the bot code heavily intrudes into the rest of the QuakeC (the language you use to make mods for Quake). Because of this, you either need to build the mod onto the bot's source base, or spend a few good months finding each part that links into the mod and carefully replicate it on the new mod. In addition Reaper is not legal to modify and Omicron's source is heavily obfuscated. FrikBot has introduced about 2 years ago as an alternative. It's the first Quake bot (aside from TutorBot) to be heavily geared to mod authors and customization. With the advent of this version, it's also a hoot to play too. + This release (nicknamed FrikBotX or simply FBX) represents several months of slow off and on work and is the 10th (or 12th - I'm not sure) and final release of the bot. FrikBot X, much like the infamous Frogbot relies heavily on hand-built waypoints for excellence in combat. It can however run without them, and it will attempt to create it's own waypoints as it plays. This is not recommended however. + To get started playing FrikBot, read the installation instructions which can be found by clicking the "Installation" button at left (on DHTML capable browsers), or scrolling down (on "normal" browsers). + + + Installation+Please note FrikBot requires the registered version of Quake. + These installation instructions assume you're using some version of Windows. If you're lucky enough to be using a better operating system (read: *nux, BeOS, whatever) you likely don't need me to tell you how to install a Quake mod. You probably also enjoy doing binary-long division during long car rides. If you're using a Macintosh, simply unpack this archive, move it to your Quake folder and click-drag it onto your MacQuake executable. (A Mac user once told me these instructions. If this isn't accurate let me know.) + + +Unzip this file with your favorite zip utility. This is usually Winzip on most systems. After it's unzipped, create a new folder in Quake with the name "FrikBot". Move all files and folders from the archive to this new folder. In the folder you'll see two .bat batch files. These have been set up to run FBX with the standard command line. Run.bat will execute Quake.exe and GLRun.bat will run GLQuake.exe. The latter is a special 3D-Accelerated version of Quake. If you have 3D hardware, I recommend you download this and read the provided instructions. + +FrikBot requires Quake version 1.08 or higher. This is available as a free update from id software. If you use GLQuake or WinQuake or any recent custom engine to run FrikBot, don't worry, you're safe. If however you're still using the Quake.exe that came off your old Quake CD, you can download the update by clicking here. + + +To begin playing a match against the bot, double click the batch file of your choice. Choose the Multiplayer option from the main menu. Choose "New Game" from the Multiplayer menu. Select either IPX or TCP/IP from the next menu. Select a map, fraglimit (if desired), timelimit (if desired) and choose "Begin Game". To start fighting some FrikBots, continue reading in the "Playing" section of this readme. + + Playing FrikBot X+Okay, you know how to install and begin a multiplayer game of FBX (and if you don't, please read the last section). It is recommended you play one of the 6 id deathmatch maps.. These are dm1, dm2, dm3, dm4, dm5 and dm6. If you did not start the game with one of these maps, bring down the console with the ~ key, and type "map dm6" and press enter (minus the quotes). + To add a bot, bring down the console (again with the ~ key) and type "impulse 100" (without the quotes) and press enter. A new bot combatant will enter the game. The bot you have added will probably be a skill 1 (Normal skill) bot. If the bot is too difficult for you, type "impulse 102" in the console and press enter. This will disconnect the bot from the game. Then type "skill 0" to set the game on Easy skill in the console, then "impulse 100" to add an easy level bot. When you're ready for a greater challenge try skill 2 (Hard) and skill 3 (Nightmare) bots. + + Teamplay+FrikBot can also be used in a team match game. The bots can be teammates or your opposition. To get started here, change the map then type "teamplay 1" in the console. Set your desired skill level then use "impulse 100" to add a bot that will be one of your team members. Use "impulse 101" in the console to add enemy team members. + Cooperative+You can also have FBX as a 'friend' as you battle through Quake's single player game. To do this, bring down the console, type "deathmatch 0" and press enter. Type "coop 1" then press enter. Next, use the map command to go to the episode selection - in the console type "map start". Add your bot companions as above (with "impulse 100"), then head off starting the game as you normally would. It is recommended you download waypoints for the id single player maps before playing coop. + + Online+FrikBotX can also be played with the popular internet version of Quake called "QuakeWorld". To do this you need to run the QuakeWorld sever executable "QWSV.exe". If you don't have this, it can be downloaded from QuakeWorld's official website. After starting it, type in "gamedir frikbot" then press enter. Then change the map by using the map command, eg. "map dm6". To adjust the skill level of bots, you must use the QW console and type "localinfo skill x" where x is the desired skill level. Connect with QWCL to your server and add bots with "impulse 100" and remove them with "impulse 102". + + Due to lazy coding the QW version of frikbot does not support teamplay at this time. In addition, the bots will appear to move very choppily, this is due to QW's restricting all non client entities to a very low frames per second. This cannot be avoided without engine modifications. + + When playing QWSV and QWCL on the same machine under Windows 9x it's important to point out that QWSV will not receive enough priority, which will cause unneeded lag. There's an excellent tool to solve this called priority.exe. You can find it at the ever helpful Quake Info Pool. + + Advanced Options+FrikBotX has two options to make you're lives a bit easier. In normal quake you can set them by using the saved1 cvar. The first option is to make the bots return between map changes in DM as they do in Coop. To activate the option type "saved1 1" in the console. After the next map load, bots will return between matches. The next option allows you to disable the bot chatter (it gets on some people's nerves). To do this type "saved 2" in the console. If you wish to both options enabled, add them together and type "saved 3". In QuakeWorld, use localinfo b_options on the server instead of saved1. Good luck. + + Map cycling+Included in the FrikBotX progs.dat is an Omicron-style map cycler. To use it, you must create a maps.cfg file, an example one has been included with this archive. Unlike Omicron, FrikBot's map cycler can deal with an unlimited number of maps. The cfg file must should contain lines in the following format: + + alias map1 "changelevel mapname"+ + Samelevel controls how the map cycler behaves. If it is 0, quake will behave as normal. Samelevel 1 will force FBX to stay on the samelevel, just as normal Quake. However, any setting above this will cause FBX to randomly select a map from the specified number of maps. The number you set it to should be the number of aliases you provided in your maps.cfg. If you'd rather not let it select maps randomly (which can result in repeated maps), setting samelevel to a the negative of the number of maps you have will make an infinite looping rotation. If you have six map aliases, setting samelevel -6 will cycle through them successively. + + Botcam+Using "impulse 103" in the console will allow you to cycle to the view of every bot and player on the server. This view is very interesting, and if you have the time and patience you can record demos from the bot's perspective. Unlike 0.09, FrikBot X will seem much choppier from the bot's perspective. Sorry. + + + + Using Add-on waypoints+The benefits of using add on .way files cannot be stressed enough. Waypoint give the ability to navigate levels perfectly, they make the bot a fierce deathmatch and coop opponent and they actually decrease the load FBX has on the game by a significant amount. + + FrikBot Waypoints can be made using the in game editor or may be supplied to you by friends or in future add-on packs. The best source for additional waypoints is the FrikBot Waypoint Depot, an excellent website created by Akuma and maintained by Quest. + Waypoints usually come as a single .way file, however some maps are very large or intricate and require multiple files. The additional files share the same name, but have the extensions wa1, wa2, wa3, etc. + To use these waypoints you place the .way (and all additional files, if they exist) in your quake\frikbot\maps folder. If installed correctly, when you go to play that map in Quake you will see in the console "Execing maps/mapnam.way". Please note: FBX already contains waypoints for DM1, DM2, DM3, DM4, DM5 and DM6. These internal waypoints are improved versions of those found on FrikBot Waypoint Depot. Do not install the waypoints for the id deathmatch maps unless they have been updated over the internal points. + Occasionally you may run across FrikBot waypoints in two other formats. One is map_mapname.qc. Please note that although Frogbot uses a similar convention, Frogbot waypoints are not compatible. + + To install these you will need the source code to the mod and a QuakeC compiler (May I recommend my own compiler, FrikQCC). To do this, open up the progs.src file from the mod with a text editor. Just after the defs.qc line, add the name of the .qc file you've received. Next, open bot.qc or bot_qw.qc (depending if this is intended for QuakeWorld or Normal Quake) and scroll down past the large installation comment until you find the function bot_map_load. Add a line that reads something like this: + +if (mapname == "mymap") + map_mymap();+ +Where the keyword mymap is a placeholder for the name of the map the waypoints are intended for. Under extremely rare circumstances, you'll encounter a third type of waypoints. These come as an .ent file patch. Please note that although Omicron uses a similar convention, Omicron bot waypoints are NOT compatible. To install these you will need the map's BSP file and the compile tool QBSP. QBSP can be found on id software's FTP in the file q1tools_gpl.zip. You will need to run QBSP with the command line + +qbsp -onlyents mymap + Where mymap is the name of the BSP file you wish to apply waypoints to. Please note: The raw dump of the BSP ents option in the in-game editor is not a complete .ent file. It must be appended to the existing ents first. Read the waypoint editing portion of this readme for complete instructions. + + If you were confused by the above, don't worry. Stick with .way files and you'll be alright. BSP and QC waypoints are intended to get around QuakeWorld's many, many limitations, if you don't use the QuakeWorld FrikBot then you should never need to use those features. + Information for Mod Authors+FrikBot is designed for you. With great certainty I can tell you that after plugging FrikBot into your mod the bot will use all the rules you have changed, will fire your custom weapons and will behave like a client in every way discernable. However, he will probably behave like an incredibly stupid client, but that isn't the point. + If you already have FrikBot installed in your mod skip down to the next section, otherwise, keep reading. All the code to install FrikBot has been included with this archive. The files you need are bot.qc, bot_way.qc, bot_ai.qc, bot_fight.qc, bot_ed.qc, bot_misc.qc, bot_phys.qc and bot_move.qc. If you intend to use the QuakeWorld version of FrikBot, substitute everything I say about bot.qc with "bot_qw.qc", okay? To begin installing FrikBot into your mod, copy these files into your mod's source folder. To be neat I recommend creating a subdirectory called "frikbot" within your source folder and placing the files in there. This is not required though. + + Next thing you should do is open up bot.qc, scroll down past the license and read the instructions carefully. There's a few functions to comment out in defs.qc, you accomplish these by placing two forward slashes in front of the line - "//". Once you've followed the installation instructions, you can compile and play the mod now with FrikBots. Most likely you'll need to modify code in bot_ai.qc to make the bot behave intelligently. The main function in this file is BotAI located at the bottom. From there you can follow up all the calls it makes and understand how the bot thinks. Priority_for_thing is the place to add code if you want the bots to hunt a new item, or add conditions to hunting existing items. I leave the rest to you. Note that you will probably never need to edit bot(_qw).qc (except for when installing qc waypoints), bot_move.qc, bot_way.qc or bot_phys.qc. The last one being highly unlikely as it is more or less a direct port of the engine's physics code, and only needs to be changed when the corresponding code in the engine is changed. + Please also note that the waypoint editor mode can be used to cheat in your mod (Even in multiplayer). If you don't want to allow this, safeguard the entrance by adding a cvar("developer") check to its impulse in BotImpulses in bot.qc. + + When making mods I strongly suggest you do a few things. Open up bot_misc.qc and give the bots new names and colors. Please. I'm tired of seeing a ton of mods with my default names and chat messages. Use a little creativity, and use some extended chars to give the bots "fun names". + Next, I'd ask you avoid using the name "Frik" in your mod's title. Even if all you did was combine FrikBot with another mod, don't call it FrikSomemod. A more appropriate title may be Somemod + FrikBot, or something along those lines. This is not a commandment from Heaven, it's just that I like to use Frik for my own work, and it tends to confuse people seeing my name part of some mod that I had very little to do with. Thanks. + In the past it has been common to include my entire FrikBot readme with the mod. Please do not include this massive .html file, that's the last thing we need. Instead, excerpt here and there and patch together some instructions for the bot. You have permission to use everything in the archive any way you want, this includes this readme. Put a little effort into it, for your user's sake. + If you make your mod for QuakeWorld I really recommend you make the mod open source. Not only will it be much more convenient for server operators, but QC is one of the best ways to bet waypoints into the QW version of FrikBot. Again, open source is the way to go. + If you're really feeling nice, you can place a small thank-you to me in your mods credits. Thanks for listening, now get out there and get coding! + + Common Problems+This portion of the readme will help you if you encounter an error when using FrikBot. This is by far not a comprehensive guide, but it will help you with a number of problems. If you need further help, please see the "Author" section of this readme. Thanks. + + ++ "Unable to connect a bot, server is full"+ ++FrikBot requires client slots like real players. If you're playing a single player game (by choosing Single Player from the main menu, or using the 'map' command), you cannot connect bots because the game can't accept connections. It is recommended you use -listen 16 in the command line. + + If you're getting this message after installing FrikBot X on a mod even though you have multiplayer set up properly, this probably the result of improper installation. Make sure you read the installation instructions correctly (especially the part about BotInit in worldspawn(), world.qc). + + + "CL_ParseServerMessage: svc_updatecolors > MAX_SCOREBOARD"+ ++This (and other errors like it) are typically the result of placing BotInit(); below InitBodyQue(); or some other call in worldspawn. When I say at the top of the function, I mean it! + + "D_SurfCacheGuard: failed"++This problem can occur when you look directly at a waypoint in editor mode when it is +linked to another waypoint at point blank. To fix this, you will need +run the command line parameter "-surfcachesize 1500" when you run +quake. If you still get the message, increase the value until the error +disappears. + + + "Ed_Alloc: No Free Edicts"++This problem can occur with large maps that require a lot of waypoints. Also note that this can also be caused by an entity leak in your mod. id's code has an entity leak with the bubble spawner code in player.qc. The only way to fix this is to recompile the Quake source code after setting +MAX_EDICTS in quakedef.h to 1024 (or higher). + + + + "Cbuf_AddText: Overflow" +++If you receive this message it means you probably didn't split up the .way file properly. Look for the comments made by the editor instructing you to separate the file into multiple parts. + + + "SZ_GetSpace: Overflow without allowoverflow set" +++This is the result of the client network buffer filling up with too much data. If you receive this message it usually means a bot was receiving messages he shouldn't have, and all this data is building up in his outgoing buffer (since the client isn't real, he never collects the data and the game crashes). To fix this find places where you stuffcmd'd or otherwise sent messages to the bot that wouldn't be picked up my function redeclarations. + + + + Waypoints are missing and links are screwed up in DarkPlaces +++Darkplaces caps server activity in listen servers with the sys_ticrate cvar. This can foul the .way file loading, to get around this set "sys_ticrate 0" in the console before changing map. + + Author+I, Ryan Smith, am deranged psychopath that lives in Massachusetts, USA. I started coding for the TRS-80 Model 3 about 18 years ago, graduated to Commodore 64s, and eventually found my way to Doom and Quake. Quake eventually caught a hold of me. Deathmatch became a sort of digital crack and making mods was absolutely the best thing I had done with my computer. At one point in my early naivety I had stumbled across the Reaper Bot. Little did I know this choppy, poorly playing (and mostly cheating) bot was considered the best bot of the time. I had long assumed that a proper bot couldn't be too difficult. + Anyway, you can reach me at the following address should you have any questions or comments about the bot (feedback much appreciated!) frika-c@earthling.net. I'd also like to hear about any improvements you've found, bugs you've encountered or mods you've made of using the bot. Keep in mind this is often a slow address and it may take a few days for me to receive your mail. Also notice I will not reply to any mail asking questions which are answered in this readme or in any material found on my site or at FWD. If your message doesn't contain a technical question, you may not expect a reply. My time is very limited, and though I will read your mail and often times act upon it, I may not reply. +All these rules may sound self centered or something, but I have encountered people that think of e-mail as an instant message service and my mailbox has been filled with mail such as "Why haven't you replied? It's been 5 hours!" etc. + + + FrikBot on the web+ +There are a number of websites you may want to visit for latest news. If you have an active internet connection, just click. + + + +Waypoint Editing+Waypoints are incredibly useful and powerful tools for FrikBot X. To edit the bot's waypoints, you should use FrikBot X's built-in waypoint editor. If you're already familiar with FrikBot Waypoint Studio this should be a fairly easy guide to follow, as the editor in FBX lends much of it's features and design from FBWS. + To begin using the editor, you must first ensure that the file beam.mdl included with this archive is in the mod's progs/ directory. This model file is used to display the links of a waypoint; we'll get to that in a bit. To demonstrate more clearly how waypointing works, load up FBX as you would to fight the bots. Go to the map dm6 (waypoints are included for this map in the progs.dat). Use impulse 104 in the console to start the editor. If everything goes well, you'll now see many tiny white dots ("bubbles") floating mid air. These sprites represent each waypoint on the map. As you near a waypoint, it will change to a very large gold "light ball". This is referred to as the selected waypoint. As each waypoint is selected, it will cast off little red beams that connect to nearby waypoints. These are called 'links'. Each link represents that a bot can travel one way from the selected waypoint to the linked waypoint. Also, when near a teleporter you may see a link that looks like quake lightning and passes straight through walls. This is called a "telelink" and is really a special flagged link telling the bot to travel through a teleporter in between the two waypoints. + Each waypoint can have a maximum of 4 out bound links. Although this may seem like a severe limitation at first, consider you can place as many waypoints as you need in one spot, and link them all together to get more outbound links. There is no limit to the number of inbound links to a single waypoint. + What follows is a basic break down of the editor's commands. This is not meant as a comprehensive getting started in waypointing tutorial. The subject is actually quite simple, and with a little experimenting you should be waypointing like a pro in no time. Be sure to bind the 0 (zero) key to "impulse 10" in order to properly use the editor menus. + + Main Menu+The main menu contains a few basic commands and the ability to switch to any of the other menus in the editor. This is your starting point in the editor. + +
Waypoint Management+Waypoint management is probably the most useful menu in the editor. It contains basic waypoint creation and deletion but also more advanced functions such as make way and link functions. +
Link Management+Link Management is the place to deal with individual links. It's commands somewhat echo Waypoint Management's, but on a link scale. +
AI Flag Management+Every command on the AI Flag management menus is a toggle that will effect the currently selected waypoint. FrikBot 0.09 supported the door flag AI flag and no more (loading FBX waypoints in 0.09 is perfectly acceptable by the way, new flags will merely be ignored). The new flags have many varied and useful effects on the bots way of thinking and action he takes as he uses the waypoint. The best way to understand how to use a few of these is to study how the bot reacts to them, I will however attempt to explain here as much as I can. +
AI Flag Management Page 2+These are the less used AI Flags, though they are useful in some ways. +
Bot Management Menu+This menu allows you to control the FrikBots in unique ways to allow you to accurately test how the bot will behave as they pass waypoints. +
Waylist Management+This menu allows you to control the FrikBots in unique ways to allow you to accurately test how the bot will behave as they pass waypoints. +
Using Waypoint Dumps+ +In order to use the waypoint editor effectively, you must save your waypoints. The way you do this is to make sure you ran quake with the command line option -condebug. Once done tweaking or creating the waypoints, use the Waylist's Dump command to print out all the data on the console. Quit the game. In your frikbot directory you'll find the file qconsole.log. Scroll through it until you find the comments flagging the top and bottom of the dump, take all the data and do the appropriate action: + +
If you have any further questions on this topic, email me. See the Author section for details. + + Shortcuts+Shortcuts are a rapid but advanced way to use the editor. To use them, you need to know the formula: It's menu # times 16 plus the menu option. The menus were covered in this file in numerical order. The main menu is the first menu, 1 * 16, toggle noclip mode is menu option 6, so 16 + 6 is 22. Now that you have the code number for the command, you use it as such: + + bind n "saved2 22; impulse 104"+ + This will allow you to toggle noclip mode in the editor from any menu by merely pressing the N key. This is merely a time saving tool, if you don't understand how to use it, don't worry. + + + + |
+
+ + + + |
+
+
+
+
Introduction++This is the final version of FrikBot. I mean it this time. Really. Stop looking at me like that. This is it. For those that know what this bot is all about, skip the next paragraph and save yourself some time. If not, read on: + +FrikBot is a unique bot for Quake. Maybe you're familiar with Reaper Bots, or the fabulous Frogbot and Omicron bots. These are wonderful opponents to deathmatch against, but for mod authors (the people that make those nice mods you and I play, such as Capture the Flag, etc) they're not so great. Why? I hear you ask - well the bot code heavily intrudes into the rest of the QuakeC (the language you use to make mods for Quake). Because of this, you either need to build the mod onto the bot's source base, or spend a few good months finding each part that links into the mod and carefully replicate it on the new mod. In addition Reaper is not legal to modify and Omicron's source is heavily obfuscated. FrikBot has introduced about 2 years ago as an alternative. It's the first Quake bot (aside from TutorBot) to be heavily geared to mod authors and customization. With the advent of this version, it's also a hoot to play too. + This release (nicknamed FrikBotX or simply FBX) represents several months of slow off and on work and is the 10th (or 12th - I'm not sure) and final release of the bot. FrikBot X, much like the infamous Frogbot relies heavily on hand-built waypoints for excellence in combat. It can however run without them, and it will attempt to create it's own waypoints as it plays. This is not recommended however. + To get started playing FrikBot, read the installation instructions which can be found by clicking the "Installation" button at left (on DHTML capable browsers), or scrolling down (on "normal" browsers). + + +
+
+Installation+Please note FrikBot requires the registered version of Quake. + These installation instructions assume you're using some version of Windows. If you're lucky enough to be using a better operating system (read: *nux, BeOS, whatever) you likely don't need me to tell you how to install a Quake mod. You probably also enjoy doing binary-long division during long car rides. If you're using a Macintosh, simply unpack this archive, move it to your Quake folder and click-drag it onto your MacQuake executable. (A Mac user once told me these instructions. If this isn't accurate let me know.) + + +Unzip this file with your favorite zip utility. This is usually Winzip on most systems. After it's unzipped, create a new folder in Quake with the name "FrikBot". Move all files and folders from the archive to this new folder. In the folder you'll see two .bat batch files. These have been set up to run FBX with the standard command line. Run.bat will execute Quake.exe and GLRun.bat will run GLQuake.exe. The latter is a special 3D-Accelerated version of Quake. If you have 3D hardware, I recommend you download this and read the provided instructions. + +FrikBot requires Quake version 1.08 or higher. This is available as a free update from id software. If you use GLQuake or WinQuake or any recent custom engine to run FrikBot, don't worry, you're safe. If however you're still using the Quake.exe that came off your old Quake CD, you can download the update by clicking here. + + +To begin playing a match against the bot, double click the batch file of your choice. Choose the Multiplayer option from the main menu. Choose "New Game" from the Multiplayer menu. Select either IPX or TCP/IP from the next menu. Select a map, fraglimit (if desired), timelimit (if desired) and choose "Begin Game". To start fighting some FrikBots, continue reading in the "Playing" section of this readme. + +
+
+Playing FrikBot X+Okay, you know how to install and begin a multiplayer game of FBX (and if you don't, please read the last section). It is recommended you play one of the 6 id deathmatch maps.. These are dm1, dm2, dm3, dm4, dm5 and dm6. If you did not start the game with one of these maps, bring down the console with the ~ key, and type "map dm6" and press enter (minus the quotes). + To add a bot, bring down the console (again with the ~ key) and type "impulse 100" (without the quotes) and press enter. A new bot combatant will enter the game. The bot you have added will probably be a skill 1 (Normal skill) bot. If the bot is too difficult for you, type "impulse 102" in the console and press enter. This will disconnect the bot from the game. Then type "skill 0" to set the game on Easy skill in the console, then "impulse 100" to add an easy level bot. When you're ready for a greater challenge try skill 2 (Hard) and skill 3 (Nightmare) bots. + + Teamplay+FrikBot can also be used in a team match game. The bots can be teammates or your opposition. To get started here, change the map then type "teamplay 1" in the console. Set your desired skill level then use "impulse 100" to add a bot that will be one of your team members. Use "impulse 101" in the console to add enemy team members. + Cooperative+You can also have FBX as a 'friend' as you battle through Quake's single player game. To do this, bring down the console, type "deathmatch 0" and press enter. Type "coop 1" then press enter. Next, use the map command to go to the episode selection - in the console type "map start". Add your bot companions as above (with "impulse 100"), then head off starting the game as you normally would. It is recommended you download waypoints for the id single player maps before playing coop. + + Online+FrikBotX can also be played with the popular internet version of Quake called "QuakeWorld". To do this you need to run the QuakeWorld sever executable "QWSV.exe". If you don't have this, it can be downloaded from QuakeWorld's official website. After starting it, type in "gamedir frikbot" then press enter. Then change the map by using the map command, eg. "map dm6". To adjust the skill level of bots, you must use the QW console and type "localinfo skill x" where x is the desired skill level. Connect with QWCL to your server and add bots with "impulse 100" and remove them with "impulse 102". + + Due to lazy coding the QW version of frikbot does not support teamplay at this time. In addition, the bots will appear to move very choppily, this is due to QW's restricting all non client entities to a very low frames per second. This cannot be avoided without engine modifications. + + When playing QWSV and QWCL on the same machine under Windows 9x it's important to point out that QWSV will not receive enough priority, which will cause unneeded lag. There's an excellent tool to solve this called priority.exe. You can find it at the ever helpful Quake Info Pool. + + Advanced Options+FrikBotX has two options to make you're lives a bit easier. In normal quake you can set them by using the saved1 cvar. The first option is to make the bots return between map changes in DM as they do in Coop. To activate the option type "saved1 1" in the console. After the next map load, bots will return between matches. The next option allows you to disable the bot chatter (it gets on some people's nerves). To do this type "saved 2" in the console. If you wish to both options enabled, add them together and type "saved 3". In QuakeWorld, use localinfo b_options on the server instead of saved1. Good luck. + + Map cycling+Included in the FrikBotX progs.dat is an Omicron-style map cycler. To use it, you must create a maps.cfg file, an example one has been included with this archive. Unlike Omicron, FrikBot's map cycler can deal with an unlimited number of maps. The cfg file must should contain lines in the following format: + + alias map1 "changelevel mapname"+ + Samelevel controls how the map cycler behaves. If it is 0, quake will behave as normal. Samelevel 1 will force FBX to stay on the samelevel, just as normal Quake. However, any setting above this will cause FBX to randomly select a map from the specified number of maps. The number you set it to should be the number of aliases you provided in your maps.cfg. If you'd rather not let it select maps randomly (which can result in repeated maps), setting samelevel to a the negative of the number of maps you have will make an infinite looping rotation. If you have six map aliases, setting samelevel -6 will cycle through them successively. + + Botcam+Using "impulse 103" in the console will allow you to cycle to the view of every bot and player on the server. This view is very interesting, and if you have the time and patience you can record demos from the bot's perspective. Unlike 0.09, FrikBot X will seem much choppier from the bot's perspective. Sorry. + + + +
+
+Using Add-on waypoints+The benefits of using add on .way files cannot be stressed enough. Waypoint give the ability to navigate levels perfectly, they make the bot a fierce deathmatch and coop opponent and they actually decrease the load FBX has on the game by a significant amount. + + FrikBot Waypoints can be made using the in game editor or may be supplied to you by friends or in future add-on packs. The best source for additional waypoints is the FrikBot Waypoint Depot, an excellent website created by Akuma and maintained by Quest. + Waypoints usually come as a single .way file, however some maps are very large or intricate and require multiple files. The additional files share the same name, but have the extensions wa1, wa2, wa3, etc. + To use these waypoints you place the .way (and all additional files, if they exist) in your quake\frikbot\maps folder. If installed correctly, when you go to play that map in Quake you will see in the console "Execing maps/mapnam.way". Please note: FBX already contains waypoints for DM1, DM2, DM3, DM4, DM5 and DM6. These internal waypoints are improved versions of those found on FrikBot Waypoint Depot. Do not install the waypoints for the id deathmatch maps unless they have been updated over the internal points. + Occasionally you may run across FrikBot waypoints in two other formats. One is map_mapname.qc. Please note that although Frogbot uses a similar convention, Frogbot waypoints are not compatible. + + To install these you will need the source code to the mod and a QuakeC compiler (May I recommend my own compiler, FrikQCC). To do this, open up the progs.src file from the mod with a text editor. Just after the defs.qc line, add the name of the .qc file you've received. Next, open bot.qc or bot_qw.qc (depending if this is intended for QuakeWorld or Normal Quake) and scroll down past the large installation comment until you find the function bot_map_load. Add a line that reads something like this: + +if (mapname == "mymap") + map_mymap();+ +Where the keyword mymap is a placeholder for the name of the map the waypoints are intended for. Under extremely rare circumstances, you'll encounter a third type of waypoints. These come as an .ent file patch. Please note that although Omicron uses a similar convention, Omicron bot waypoints are NOT compatible. To install these you will need the map's BSP file and the compile tool QBSP. QBSP can be found on id software's FTP in the file q1tools_gpl.zip. You will need to run QBSP with the command line + +qbsp -onlyents mymap + Where mymap is the name of the BSP file you wish to apply waypoints to. Please note: The raw dump of the BSP ents option in the in-game editor is not a complete .ent file. It must be appended to the existing ents first. Read the waypoint editing portion of this readme for complete instructions. + + If you were confused by the above, don't worry. Stick with .way files and you'll be alright. BSP and QC waypoints are intended to get around QuakeWorld's many, many limitations, if you don't use the QuakeWorld FrikBot then you should never need to use those features. +
+
+Information for Mod Authors+FrikBot is designed for you. With great certainty I can tell you that after plugging FrikBot into your mod the bot will use all the rules you have changed, will fire your custom weapons and will behave like a client in every way discernable. However, he will probably behave like an incredibly stupid client, but that isn't the point. + If you already have FrikBot installed in your mod skip down to the next section, otherwise, keep reading. All the code to install FrikBot has been included with this archive. The files you need are bot.qc, bot_way.qc, bot_ai.qc, bot_fight.qc, bot_ed.qc, bot_misc.qc, bot_phys.qc and bot_move.qc. If you intend to use the QuakeWorld version of FrikBot, substitute everything I say about bot.qc with "bot_qw.qc", okay? To begin installing FrikBot into your mod, copy these files into your mod's source folder. To be neat I recommend creating a subdirectory called "frikbot" within your source folder and placing the files in there. This is not required though. + + Next thing you should do is open up bot.qc, scroll down past the license and read the instructions carefully. There's a few functions to comment out in defs.qc, you accomplish these by placing two forward slashes in front of the line - "//". Once you've followed the installation instructions, you can compile and play the mod now with FrikBots. Most likely you'll need to modify code in bot_ai.qc to make the bot behave intelligently. The main function in this file is BotAI located at the bottom. From there you can follow up all the calls it makes and understand how the bot thinks. Priority_for_thing is the place to add code if you want the bots to hunt a new item, or add conditions to hunting existing items. I leave the rest to you. Note that you will probably never need to edit bot(_qw).qc (except for when installing qc waypoints), bot_move.qc, bot_way.qc or bot_phys.qc. The last one being highly unlikely as it is more or less a direct port of the engine's physics code, and only needs to be changed when the corresponding code in the engine is changed. + Please also note that the waypoint editor mode can be used to cheat in your mod (Even in multiplayer). If you don't want to allow this, safeguard the entrance by adding a cvar("developer") check to its impulse in BotImpulses in bot.qc. + + When making mods I strongly suggest you do a few things. Open up bot_misc.qc and give the bots new names and colors. Please. I'm tired of seeing a ton of mods with my default names and chat messages. Use a little creativity, and use some extended chars to give the bots "fun names". + Next, I'd ask you avoid using the name "Frik" in your mod's title. Even if all you did was combine FrikBot with another mod, don't call it FrikSomemod. A more appropriate title may be Somemod + FrikBot, or something along those lines. This is not a commandment from Heaven, it's just that I like to use Frik for my own work, and it tends to confuse people seeing my name part of some mod that I had very little to do with. Thanks. + In the past it has been common to include my entire FrikBot readme with the mod. Please do not include this massive .html file, that's the last thing we need. Instead, excerpt here and there and patch together some instructions for the bot. You have permission to use everything in the archive any way you want, this includes this readme. Put a little effort into it, for your user's sake. + If you make your mod for QuakeWorld I really recommend you make the mod open source. Not only will it be much more convenient for server operators, but QC is one of the best ways to bet waypoints into the QW version of FrikBot. Again, open source is the way to go. + If you're really feeling nice, you can place a small thank-you to me in your mods credits. Thanks for listening, now get out there and get coding! + +
+
+Common Problems+This portion of the readme will help you if you encounter an error when using FrikBot. This is by far not a comprehensive guide, but it will help you with a number of problems. If you need further help, please see the "Author" section of this readme. Thanks. + + ++ "Unable to connect a bot, server is full"+ ++FrikBot requires client slots like real players. If you're playing a single player game (by choosing Single Player from the main menu, or using the 'map' command), you cannot connect bots because the game can't accept connections. It is recommended you use -listen 16 in the command line. + + If you're getting this message after installing FrikBot X on a mod even though you have multiplayer set up properly, this probably the result of improper installation. Make sure you read the installation instructions correctly (especially the part about BotInit in worldspawn(), world.qc). + + + "CL_ParseServerMessage: svc_updatecolors > MAX_SCOREBOARD"+ ++This (and other errors like it) are typically the result of placing BotInit(); below InitBodyQue(); or some other call in worldspawn. When I say at the top of the function, I mean it! + + "D_SurfCacheGuard: failed"++This problem can occur when you look directly at a waypoint in editor mode when it is +linked to another waypoint at point blank. To fix this, you will need +run the command line parameter "-surfcachesize 1500" when you run +quake. If you still get the message, increase the value until the error +disappears. + + + "Ed_Alloc: No Free Edicts"++This problem can occur with large maps that require a lot of waypoints. Also note that this can also be caused by an entity leak in your mod. id's code has an entity leak with the bubble spawner code in player.qc. The only way to fix this is to recompile the Quake source code after setting +MAX_EDICTS in quakedef.h to 1024 (or higher). + + + + "Cbuf_AddText: Overflow" +++If you receive this message it means you probably didn't split up the .way file properly. Look for the comments made by the editor instructing you to separate the file into multiple parts. + + + "SZ_GetSpace: Overflow without allowoverflow set" +++This is the result of the client network buffer filling up with too much data. If you receive this message it usually means a bot was receiving messages he shouldn't have, and all this data is building up in his outgoing buffer (since the client isn't real, he never collects the data and the game crashes). To fix this find places where you stuffcmd'd or otherwise sent messages to the bot that wouldn't be picked up my function redeclarations. + + + + Waypoints are missing and links are screwed up in DarkPlaces +++Darkplaces caps server activity in listen servers with the sys_ticrate cvar. This can foul the .way file loading, to get around this set "sys_ticrate 0" in the console before changing map. + +
+
+
+Author+I, Ryan Smith, am deranged psychopath that lives in Massachusetts, USA. I started coding for the TRS-80 Model 3 about 18 years ago, graduated to Commodore 64s, and eventually found my way to Doom and Quake. Quake eventually caught a hold of me. Deathmatch became a sort of digital crack and making mods was absolutely the best thing I had done with my computer. At one point in my early naivety I had stumbled across the Reaper Bot. Little did I know this choppy, poorly playing (and mostly cheating) bot was considered the best bot of the time. I had long assumed that a proper bot couldn't be too difficult. + Anyway, you can reach me at the following address should you have any questions or comments about the bot (feedback much appreciated!) frika-c@earthling.net. I'd also like to hear about any improvements you've found, bugs you've encountered or mods you've made of using the bot. Keep in mind this is often a slow address and it may take a few days for me to receive your mail. Also notice I will not reply to any mail asking questions which are answered in this readme or in any material found on my site or at FWD. If your message doesn't contain a technical question, you may not expect a reply. My time is very limited, and though I will read your mail and often times act upon it, I may not reply. +All these rules may sound self centered or something, but I have encountered people that think of e-mail as an instant message service and my mailbox has been filled with mail such as "Why haven't you replied? It's been 5 hours!" etc. + +
+
+
+FrikBot on the web+ +There are a number of websites you may want to visit for latest news. If you have an active internet connection, just click. + + +
+
+
+Waypoint Editing+Waypoints are incredibly useful and powerful tools for FrikBot X. To edit the bot's waypoints, you should use FrikBot X's built-in waypoint editor. If you're already familiar with FrikBot Waypoint Studio this should be a fairly easy guide to follow, as the editor in FBX lends much of it's features and design from FBWS. + To begin using the editor, you must first ensure that the file beam.mdl included with this archive is in the mod's progs/ directory. This model file is used to display the links of a waypoint; we'll get to that in a bit. To demonstrate more clearly how waypointing works, load up FBX as you would to fight the bots. Go to the map dm6 (waypoints are included for this map in the progs.dat). Use impulse 104 in the console to start the editor. If everything goes well, you'll now see many tiny white dots ("bubbles") floating mid air. These sprites represent each waypoint on the map. As you near a waypoint, it will change to a very large gold "light ball". This is referred to as the selected waypoint. As each waypoint is selected, it will cast off little red beams that connect to nearby waypoints. These are called 'links'. Each link represents that a bot can travel one way from the selected waypoint to the linked waypoint. Also, when near a teleporter you may see a link that looks like quake lightning and passes straight through walls. This is called a "telelink" and is really a special flagged link telling the bot to travel through a teleporter in between the two waypoints. + Each waypoint can have a maximum of 4 out bound links. Although this may seem like a severe limitation at first, consider you can place as many waypoints as you need in one spot, and link them all together to get more outbound links. There is no limit to the number of inbound links to a single waypoint. + What follows is a basic break down of the editor's commands. This is not meant as a comprehensive getting started in waypointing tutorial. The subject is actually quite simple, and with a little experimenting you should be waypointing like a pro in no time. Be sure to bind the 0 (zero) key to "impulse 10" in order to properly use the editor menus. + + Main Menu+The main menu contains a few basic commands and the ability to switch to any of the other menus in the editor. This is your starting point in the editor. + +
Waypoint Management+Waypoint management is probably the most useful menu in the editor. It contains basic waypoint creation and deletion but also more advanced functions such as make way and link functions. +
Link Management+Link Management is the place to deal with individual links. It's commands somewhat echo Waypoint Management's, but on a link scale. +
AI Flag Management+Every command on the AI Flag management menus is a toggle that will effect the currently selected waypoint. FrikBot 0.09 supported the door flag AI flag and no more (loading FBX waypoints in 0.09 is perfectly acceptable by the way, new flags will merely be ignored). The new flags have many varied and useful effects on the bots way of thinking and action he takes as he uses the waypoint. The best way to understand how to use a few of these is to study how the bot reacts to them, I will however attempt to explain here as much as I can. +
AI Flag Management Page 2+These are the less used AI Flags, though they are useful in some ways. +
Bot Management Menu+This menu allows you to control the FrikBots in unique ways to allow you to accurately test how the bot will behave as they pass waypoints. +
Waylist Management+This menu allows you to control the FrikBots in unique ways to allow you to accurately test how the bot will behave as they pass waypoints. +
Using Waypoint Dumps+ +In order to use the waypoint editor effectively, you must save your waypoints. The way you do this is to make sure you ran quake with the command line option -condebug. Once done tweaking or creating the waypoints, use the Waylist's Dump command to print out all the data on the console. Quit the game. In your frikbot directory you'll find the file qconsole.log. Scroll through it until you find the comments flagging the top and bottom of the dump, take all the data and do the appropriate action: + +
If you have any further questions on this topic, email me. See the Author section for details. + + Shortcuts+Shortcuts are a rapid but advanced way to use the editor. To use them, you need to know the formula: It's menu # times 16 plus the menu option. The menus were covered in this file in numerical order. The main menu is the first menu, 1 * 16, toggle noclip mode is menu option 6, so 16 + 6 is 22. Now that you have the code number for the command, you use it as such: + + bind n "saved2 22; impulse 104"+ + This will allow you to toggle noclip mode in the editor from any menu by merely pressing the N key. This is merely a time saving tool, if you don't understand how to use it, don't worry. + + + |