Compare commits

..
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 bc45338214 Bots get stuck in corners less
A path the game finds but cannot steer round the objects on it (the other
players among them: obstacle avoidance failed) is walked as smoothed,
instead of none (which had bots walk straight into walls); a step aside
settles a bump. A bot goes on to the next point of its path when it is
in plain view on its own level, walks the last 3 world units straight
only when nothing is in the way, and counts itself stuck when it makes no
way to its point for 6 ticks (only while it walks one): then it steps
aside, finds its way anew, skips a corner's point it cannot reach, jumps
at 12 and goes elsewhere later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:33:58 +02:00
Stefan MaierandClaude Opus 5.5 a663d1b100 Bots take good weapons in a fight too, and value strong ones more
Within 10 world units a bot goes for a weapon or powerup worth at least 4
while it fights. Strong weapons are worth a longer way (worth squared over
distance, falling off over 15 world units), and with two weapons one is
worth what it betters the worse by and 1.5 more, so rocket launchers and
sniper rifles are taken, not only the shotgun close by. The pistol counts
for a little less up close, the assault and plasma rifles more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:27:24 +02:00
Stefan MaierandClaude Opus 5.5 94b17a7913 Bots play capture the flag, king of the hill and oddball (phase 4)
The flags and balls (weapons that are flags) are gathered each tick with
where they are and who carries them. In CTF a bot carrying the enemy's
flag runs it to its own stand; the rest hunt whoever carries their flag,
touch it home where it lies dropped, or go for the enemy's (every third
guards its own). In king of the hill they go to the hill (the game's goal)
and keep moving in it. In oddball they go for the ball, taken as a weapon
is, hunt its carrier, and carrying it keep away. Carrying, a bot does not
fire, and looks where it runs. The last 3 world units to a goal are
walked straight, over what the path goes round (a flag on its stand).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:22:31 +02:00
+560 -28
View File
@@ -24,6 +24,14 @@ player holding the action button does), ammunition for one it has when it
runs low, an overshield, camouflage, a health pack when hurt, grenades.
In a fight it changes to its other weapon when the one in hand is empty,
and now and then lobs a grenade at an enemy some way off.
In the objective games a bot plays for the objective, fighting on the way:
- capture the flag: the carrier of the enemy's flag runs it home; the rest
hunt whoever carries their own flag, return it where it lies dropped, or
else go for the enemy's flag (every third bot stays to guard its own);
- king of the hill: to the hill, and around in it;
- oddball: to the ball (taken as a weapon is), hunting whoever carries it;
with it, away from the enemy, striking those who come near.
*/
#include "cseries.h"
@@ -38,6 +46,7 @@ and now and then lobs a grenade at an enemy some way off.
#include "items/items.h"
#include "items/weapons.h"
#include "tag_files/tag_files.h"
#include "scenario/scenario_definitions.h"
#include <string.h>
#include "scenario/scenario.h"
@@ -94,6 +103,14 @@ struct bot
short error_ticks;
real_point3d last_position;
short stuck_ticks;
/* the point it walks to, how near it was, and a step aside (ticks
left, and which way) when it makes no way */
real_point3d walk_point;
real walk_distance;
short sidestep_ticks;
real sidestep;
/* whether it walks to a point this tick (not only weaving in a fight) */
boolean walking;
real wander_yaw;
short weave_phase;
/* where it is going, and the way there */
@@ -107,12 +124,35 @@ struct bot
long item_look_time;
long grenade_time;
long weapon_switch_time;
/* the objective, as it played it this tick: where it goes, whom it
hunts first, and whether it carries the flag or ball (no firing) */
boolean has_objective;
real_point3d objective;
long objective_unit_index;
boolean carrying;
};
/* the objective game's things this tick: the flags or balls (weapons), whose
team or ball number, where, and who carries them */
enum
{
MAXIMUM_OBJECTIVE_ITEMS = 8,
};
struct objective_item
{
long item_index;
short owner;
real_point3d position;
long carrier_player_index;
boolean handled;
};
/* ---------- globals */
/* (one search at a time, shared: 80 kB) */
static struct path_state bot_path_state;
static struct path_debug_storage bot_path_debug;
static struct
{
@@ -125,6 +165,14 @@ static struct
long grenades;
long pickups;
long switches;
long stucks;
long long_stucks;
long fail_start;
long fail_goal;
long fail_search;
/* the objective game's things (bots_update_objectives) */
short objective_item_count;
struct objective_item objective_items[MAXIMUM_OBJECTIVE_ITEMS];
/* how well they play (bots.skill, as the game began) */
short reaction_ticks;
real turn_rate;
@@ -230,7 +278,10 @@ static boolean bot_find_path(
bot->path.valid = FALSE;
if (start_surface == NONE)
{
bots.fail_start++;
return FALSE;
}
/* (the goal's surface: under it, as a biped would stand there) */
{
struct collision_result collision;
@@ -246,15 +297,42 @@ static boolean bot_find_path(
}
}
if (goal_surface == NONE)
{
bots.fail_goal++;
return FALSE;
}
/* (a little wider than a biped: paths that keep off walls and corners) */
path_input_new(&input, 0.25f, FALSE, unit_index);
path_input_set_start(&input, &start, start_surface);
path_state_new(&input, &bot_path_state, NULL);
bot_path_debug.path_traverse_result = 0;
bot_path_debug.path_build_result = 0;
path_state_new(&input, &bot_path_state, &bot_path_debug);
path_state_destination(&bot_path_state, &goal, goal_surface, BOT_GOAL_RADIUS);
if (!path_state_find(&bot_path_state) || !path_state_build_path(&bot_path_state, &bot->path))
if (path_state_find(&bot_path_state) && !path_state_build_path(&bot_path_state, &bot->path) &&
bot_path_debug.path_build_result == _path_build_result_obstacle_avoidance_failed &&
bot_path_debug.smoothed_step_count > 0)
{
/* (a way there, but none round the objects on it, the other players
among them: the way, smoothed, without; a step aside settles a
bump) */
short step;
csmemset(&bot->path, 0, sizeof(bot->path));
bot->path.valid = TRUE;
bot->path.step_count = (char)MIN(bot_path_debug.smoothed_step_count, 4);
for (step = 0; step < bot->path.step_count; step++)
bot->path.steps[step] = bot_path_debug.smoothed_steps[step];
bot->path.endpoint.point = bot->path.steps[bot->path.step_count - 1].point;
bot->path.endpoint.surface_index = bot->path.steps[bot->path.step_count - 1].surface_index;
bot->path.steps_finish_path = bot_path_debug.raw_step_count <= 4;
bots.paths_found++;
return TRUE;
}
if (!bot->path.valid)
{
bot->path.valid = FALSE;
bots.paths_failed++;
bots.fail_search++;
return FALSE;
}
bots.paths_found += bot->path.valid != FALSE;
@@ -262,14 +340,69 @@ static boolean bot_find_path(
}
/* the point it walks to now: the path's next, or the goal itself */
/* whether a biped walks straight from one point to the other: nothing of
the level between them at its knees */
static boolean bot_clear_walk(
long unit_index,
real_point3d const *from,
real_point3d const *to)
{
struct collision_result collision;
real_point3d start = *from;
real_vector3d vector;
start.z += 0.3f;
vector.i = to->x - from->x;
vector.j = to->y - from->y;
vector.k = to->z - from->z;
return !collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
&start, &vector, unit_index, &collision);
}
static boolean bot_next_point_now(
struct bot *bot,
long unit_index,
real_point3d const *position,
real_point3d *point);
/* the point it walks to now (kept, to tell whether it gets nearer) */
static boolean bot_next_point(
struct bot *bot,
long unit_index,
real_point3d const *position,
real_point3d *point)
{
if (!bot_next_point_now(bot, unit_index, position, point))
return FALSE;
if (distance3d(point, &bot->walk_point) > 0.2f)
bot->walk_distance = 1000000.0f;
bot->walk_point = *point;
bot->walking = TRUE;
return TRUE;
}
static boolean bot_next_point_now(
struct bot *bot,
long unit_index,
real_point3d const *position,
real_point3d *point)
{
if (!bot->has_goal)
return FALSE;
/* (the last steps: straight there, over what the path goes round, as
a flag on its stand, where a carrier must step to score, when nothing
of the level is in the way) */
{
real dx = bot->goal.x - position->x;
real dy = bot->goal.y - position->y;
if (dx * dx + dy * dy < 3.0f * 3.0f && fabsf(bot->goal.z - position->z) < 0.6f &&
bot_clear_walk(unit_index, position, &bot->goal))
{
*point = bot->goal;
return TRUE;
}
}
if (game_time_get() - bot->path_time >= PATH_REFRESH_TICKS || game_time_get() < bot->path_time)
{
bot->path_time = game_time_get();
@@ -285,6 +418,20 @@ static boolean bot_next_point(
if (dx * dx + dy * dy > BOT_STEP_RADIUS * BOT_STEP_RADIUS)
{
/* (the next point in plain view: on to it, not round by this
one, a corner's) */
real_point3d const *next = bot->path.step_index + 1 < bot->path.step_count ?
&bot->path.steps[(short)bot->path.step_index + 1].point : &bot->path.endpoint.point;
/* (on its own level only: one above may be in view over a
ledge that a ramp leads round to) */
if (fabsf(next->z - position->z) < 0.6f && bot_clear_walk(unit_index, position, next))
{
bot->path.step_index++;
if (bot->path.step_index >= bot->path.step_count)
break;
continue;
}
*point = step->point;
return TRUE;
}
@@ -398,7 +545,9 @@ static real bot_weapon_suits(
if (strstr(name, "plasma pistol") || strstr(name, "needler"))
return worth;
if (strstr(name, "pistol"))
return distance > 10.0f ? worth + 2.0f : worth;
return distance > 10.0f ? worth + 1.0f : worth - 0.5f;
if (strstr(name, "assault rifle") || strstr(name, "plasma rifle"))
return distance < 12.0f ? worth + 1.5f : worth;
return worth;
}
@@ -463,9 +612,11 @@ static real bot_item_worth(
return bot_weapon_has_ammunition(carried_index) ? 0.0f : 4.0f;
worst = MIN(worst, bot_weapon_worth(carried_weapon->definition_index));
}
/* (with two, worth what it betters the worse by, and a little for a
change of weapon) */
if (carried < 2)
return worth;
return worth - worst > 0.5f ? worth - worst : 0.0f;
return worth - worst > 0.5f ? worth - worst + 1.5f : 0.0f;
}
return 0.0f;
}
@@ -473,7 +624,9 @@ static real bot_item_worth(
/* the thing most worth going for near the bot, or NONE */
static long bot_find_item(
struct unit_datum const *unit,
real_point3d const *position)
real_point3d const *position,
real range,
real least_worth)
{
struct object_iterator iterator;
long best = NONE;
@@ -489,12 +642,13 @@ static long bot_find_item(
object_get_origin(iterator.index, &origin);
distance = distance3d(position, &origin);
if (distance > BOT_ITEM_RANGE)
if (distance > range)
continue;
worth = bot_item_worth(unit, iterator.index);
if (worth <= 0.0f)
if (worth <= 0.0f || worth < least_worth)
continue;
score = worth / (1.0f + distance / 8.0f);
/* (the strong ones worth a longer way) */
score = worth * worth / (1.0f + distance / 15.0f);
if (score > best_score)
{
best_score = score;
@@ -548,6 +702,249 @@ static void bot_take_item(
}
}
/* ---------- objectives */
static long bots_game_type(
void)
{
return game_engine ? game_engine->type : NONE;
}
/* the flags or balls this tick (once for all bots) */
static void bots_update_objectives(
void)
{
struct object_iterator iterator;
long type = bots_game_type();
bots.objective_item_count = 0;
if (type != game_engine_ctf && type != game_engine_oddball)
return;
object_iterator_new(&iterator, _object_mask_weapon, 0);
while (object_iterator_next(&iterator) && bots.objective_item_count < MAXIMUM_OBJECTIVE_ITEMS)
{
struct weapon_datum *weapon;
struct objective_item *item;
short index;
if (!weapon_is_flag(iterator.index))
continue;
weapon = weapon_get(iterator.index);
item = &bots.objective_items[bots.objective_item_count++];
item->item_index = iterator.index;
item->owner = weapon->object.owner_team_index;
/* (bit 6: the flag was taken from its stand, game_engine_ctf.c) */
item->handled = TEST_FLAG(weapon->weapon.flags, 6);
item_get_position_even_if_in_inventory(iterator.index, &item->position);
item->carrier_player_index = NONE;
for (index = 0; index < MAXIMUM_TRACKED_PLAYERS; index++)
{
struct player_datum *player = distributed_player(index);
if (player && player->unit_index != NONE && unit_has_weapon(player->unit_index, iterator.index))
{
item->carrier_player_index = DATUM_INDEX_NEW(index, player->identifier);
break;
}
}
}
}
/* a team's flag stand (FALSE: none) */
static boolean bots_ctf_base(
long team_index,
real_point3d *position)
{
struct scenario *scenario = global_scenario_get();
struct game_variant *variant = game_engine_get_variant();
long slot = variant && variant->game_engine_variant.ctf.assault ? (team_index + 1) % 2 : team_index;
long index;
if (!scenario)
return FALSE;
for (index = 0; index < scenario->netgame_flags.count; index++)
{
struct scenario_netgame_flag *flag =
TAG_BLOCK_GET_ELEMENT(&scenario->netgame_flags, index, struct scenario_netgame_flag);
if (flag->type == _netgame_flag_ctf_flag && flag->team_index == slot)
{
*position = flag->position;
return TRUE;
}
}
return FALSE;
}
/* what the objective asks of the bot now (bot->has_objective and the rest) */
static void bot_play_objective_now(
struct bot *bot,
struct player_datum const *player,
long player_index,
real_point3d const *position);
static void bot_play_objective(
struct bot *bot,
struct player_datum const *player,
long player_index,
real_point3d const *position)
{
boolean was_carrying = bot->carrying;
bot_play_objective_now(bot, player, player_index, position);
if (bot->carrying != was_carrying)
{
platform_log("bots: player %ld %s", DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index),
bot->carrying ? "has taken the flag or ball" : "no longer carries it");
}
}
static void bot_play_objective_now(
struct bot *bot,
struct player_datum const *player,
long player_index,
real_point3d const *position)
{
long type = bots_game_type();
short index;
bot->has_objective = FALSE;
bot->objective_unit_index = NONE;
bot->carrying = FALSE;
if (type == game_engine_king)
{
if (game_engine_get_goal_in_use(0))
{
real_point3d center;
real dx, dy;
game_engine_get_goal_position(&center, 0);
center.z -= 0.63f;
dx = center.x - position->x;
dy = center.y - position->y;
bot->has_objective = TRUE;
bot->objective = center;
/* (on the hill: about in it, not on its middle) */
if (dx * dx + dy * dy < 1.5f * 1.5f)
{
bot->objective.x += (real)cos(bot->weave_phase * 0.05f) * 1.0f;
bot->objective.y += (real)sin(bot->weave_phase * 0.05f) * 1.0f;
}
}
return;
}
if (type == game_engine_ctf)
{
struct objective_item *own = NULL;
struct objective_item *enemy = NULL;
real_point3d base;
for (index = 0; index < bots.objective_item_count; index++)
{
struct objective_item *item = &bots.objective_items[index];
if (item->owner == player->team_index)
own = item;
else
enemy = item;
}
/* carrying theirs: home */
if (enemy && enemy->carrier_player_index == player_index)
{
bot->carrying = TRUE;
if (bots_ctf_base(player->team_index, &base))
{
bot->has_objective = TRUE;
bot->objective = base;
}
return;
}
/* ours carried off: after its carrier */
if (own && own->carrier_player_index != NONE)
{
struct player_datum *carrier = player_try_and_get(own->carrier_player_index);
bot->has_objective = TRUE;
bot->objective = own->position;
bot->objective_unit_index = carrier ? carrier->unit_index : NONE;
return;
}
/* ours dropped: touched, it goes home */
if (own && own->handled)
{
bot->has_objective = TRUE;
bot->objective = own->position;
return;
}
/* (every third guards its own) */
if ((DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index) % 3) == 2 && bots_ctf_base(player->team_index, &base))
{
bot->has_objective = TRUE;
bot->objective = base;
if (distance3d(position, &base) < 4.0f)
{
bot->objective.x += (real)cos(bot->weave_phase * 0.03f) * 3.0f;
bot->objective.y += (real)sin(bot->weave_phase * 0.03f) * 3.0f;
}
return;
}
/* theirs: to take it, or to cover its carrier */
if (enemy)
{
bot->has_objective = TRUE;
bot->objective = enemy->position;
}
return;
}
if (type == game_engine_oddball)
{
struct objective_item *nearest = NULL;
real nearest_distance = 1000000.0f;
for (index = 0; index < bots.objective_item_count; index++)
{
struct objective_item *item = &bots.objective_items[index];
real distance;
if (item->carrier_player_index == player_index)
{
bot->carrying = TRUE;
return;
}
/* (one a teammate carries: not to be chased) */
if (item->carrier_player_index != NONE && game_engine_has_teams())
{
struct player_datum *carrier = player_try_and_get(item->carrier_player_index);
if (carrier && carrier->team_index == player->team_index)
continue;
}
distance = distance3d(position, &item->position);
if (distance < nearest_distance)
{
nearest_distance = distance;
nearest = item;
}
}
if (nearest)
{
bot->has_objective = TRUE;
bot->objective = nearest->position;
if (nearest->carrier_player_index != NONE)
{
struct player_datum *carrier = player_try_and_get(nearest->carrier_player_index);
bot->objective_unit_index = carrier ? carrier->unit_index : NONE;
}
else
{
/* (lying there: taken as a weapon is) */
bot->item_index = nearest->item_index;
}
}
}
}
/* the throttle that walks a unit facing yaw towards a point */
static void bot_walk_to(
struct player_action *action,
@@ -582,6 +979,7 @@ static void bot_think(
real moved;
csmemset(action, 0, sizeof(*action));
bot->walking = FALSE;
action->desired_weapon_index = NONE;
action->desired_grenade_index = NONE;
action->desired_zoom_level = NONE;
@@ -617,10 +1015,23 @@ static void bot_think(
bot->wander_yaw = bot->yaw;
}
/* the enemy: noticed after a moment, kept while in sight */
/* what the objective game asks */
bot_play_objective(bot, player, bot->player_index, &position);
/* the enemy: noticed after a moment, kept while in sight (the one the
objective hunts first, when it is in sight) */
{
long target = bot_find_target(player, &eye);
if (bot->objective_unit_index != NONE)
{
real_point3d head;
unit_get_camera_position(bot->objective_unit_index, &head);
if (distance3d(&eye, &head) < bots.sight_range && bot_can_see(player->unit_index, &eye, &head))
target = bot->objective_unit_index;
}
if (target != bot->target_unit_index)
{
bot->target_unit_index = target;
@@ -684,9 +1095,52 @@ static void bot_think(
bots.switches++;
}
}
/* closes on it along its way, weaving, until near */
bot->weave_phase++;
if (distance > BOT_CLOSE_RANGE)
/* (a good weapon or powerup close by: taken in the fight too) */
if (!bot->carrying && (game_time_get() - bot->item_look_time >= ITEM_LOOK_TICKS / 2 ||
game_time_get() < bot->item_look_time))
{
bot->item_look_time = game_time_get();
bot->item_index = bot_find_item(unit, &position, 10.0f, 4.0f);
}
if (bot->item_index != NONE && !bot->carrying && object_try_and_get(bot->item_index) &&
bot_item_worth(unit, bot->item_index) > 0.0f)
{
real_point3d item_position;
real_point3d point;
object_get_origin(bot->item_index, &item_position);
bot->goal = item_position;
bot->has_goal = TRUE;
if (bot_next_point(bot, player->unit_index, &position, &point))
bot_walk_to(action, bot->yaw, &position, &point);
if (distance3d(&position, &item_position) < 2.0f)
bot_take_item(bot, player, unit, action);
}
/* closes on it along its way, weaving, until near */
else if (bot->has_objective && bot->objective_unit_index != bot->target_unit_index)
{
/* (the objective first: it fights on the way) */
real_point3d point;
bot->goal = bot->objective;
bot->has_goal = TRUE;
if (bot_next_point(bot, player->unit_index, &position, &point))
{
bot_walk_to(action, bot->yaw, &position, &point);
/* (carrying it: looking where it runs, unless struck at) */
if (bot->carrying && distance > 1.5f)
{
real dx = point.x - position.x;
real dy = point.y - position.y;
if (dx * dx + dy * dy > 0.0001f)
wanted_yaw = (real)atan2(dy, dx);
wanted_pitch = 0.0f;
}
}
}
else if (distance > BOT_CLOSE_RANGE)
{
real_point3d point;
@@ -696,14 +1150,14 @@ static void bot_think(
bot_walk_to(action, bot->yaw, &position, &point);
}
action->throttle.j += (real)sin(bot->weave_phase * 0.08f) * 0.6f;
firing = distance < BOT_FIRE_RANGE &&
firing = !bot->carrying && distance < BOT_FIRE_RANGE &&
fabsf(bot_angle_difference(wanted_yaw, bot->yaw)) < BOT_FIRE_CONE &&
fabsf(wanted_pitch - bot->pitch) < BOT_FIRE_CONE;
/* (a melee blow when it is upon it) */
if (distance < 1.0f)
SET_FLAG(action->control_flags, _unit_control_use_equipment_bit, TRUE);
/* (a grenade now and then, lobbed at one some way off) */
if (distance > BOT_GRENADE_MINIMUM && distance < BOT_GRENADE_MAXIMUM &&
if (!bot->carrying && distance > BOT_GRENADE_MINIMUM && distance < BOT_GRENADE_MAXIMUM &&
game_time_get() - bot->grenade_time > GRENADE_INTERVAL_TICKS && bot_random() < 0.02f)
{
short type;
@@ -729,10 +1183,22 @@ static void bot_think(
{
real_point3d point;
/* something worth having near: there first */
if (game_time_get() - bot->item_look_time >= ITEM_LOOK_TICKS || game_time_get() < bot->item_look_time)
/* the objective: there (and the ball, taken there) */
if (bot->has_objective)
{
long item = bot_find_item(unit, &position);
bot->goal = bot->objective;
bot->has_goal = TRUE;
bot->goal_time = game_time_get();
if (bots_game_type() == game_engine_oddball && bot->item_index != NONE &&
distance3d(&position, &bot->goal) < 2.0f)
{
bot_take_item(bot, player, unit, action);
}
}
/* something worth having near: there first */
else if (game_time_get() - bot->item_look_time >= ITEM_LOOK_TICKS || game_time_get() < bot->item_look_time)
{
long item = bot_find_item(unit, &position, BOT_ITEM_RANGE, 0.0f);
bot->item_look_time = game_time_get();
if (item != bot->item_index)
@@ -747,7 +1213,7 @@ static void bot_think(
}
}
}
if (bot->item_index != NONE)
if (bot->item_index != NONE && !bot->has_objective)
{
/* (taken or gone: on) */
if (!object_try_and_get(bot->item_index) || bot_item_worth(unit, bot->item_index) <= 0.0f)
@@ -764,7 +1230,7 @@ static void bot_think(
}
}
/* hunts, or roams: along its way, looking where it goes */
if (bot->item_index == NONE &&
if (bot->item_index == NONE && !bot->has_objective &&
(!bot->has_goal || game_time_get() - bot->goal_time > 90 ||
distance3d(&position, &bot->goal) < BOT_GOAL_RADIUS))
{
@@ -780,6 +1246,18 @@ static void bot_think(
bot->wander_yaw = wanted_yaw = (real)atan2(dy, dx);
bot_walk_to(action, bot->yaw, &position, &point);
}
if (bot->carrying && !bot->has_objective &&
(game_time_get() - bot->goal_time > 60 || distance3d(&position, &bot->goal) < BOT_GOAL_RADIUS))
{
/* (the ball: away, somewhere) */
real angle = bot_random() * 2.0f * _pi;
bot->goal = position;
bot->goal.x += (real)cos(angle) * 8.0f;
bot->goal.y += (real)sin(angle) * 8.0f;
bot->has_goal = TRUE;
bot->goal_time = game_time_get();
}
if (bot->stuck_ticks > 20)
{
/* (stuck: somewhere else) */
@@ -810,16 +1288,54 @@ static void bot_think(
SET_FLAG(action->control_flags, _unit_control_weapon_primary_trigger_bit, TRUE);
}
/* stuck: hardly moved while walking (it jumps, and then turns) */
/* stuck: making no way to the point it walks to (against a wall, in
a corner): soon a step aside and its way found anew, then a jump, and
in the end somewhere else */
if (bot->sidestep_ticks > 0)
{
bot->sidestep_ticks--;
action->throttle.j = bot->sidestep;
action->throttle.i *= 0.3f;
}
moved = distance3d(&position, &bot->last_position);
bot->last_position = position;
action->throttle.i = PIN(action->throttle.i, -1.0f, 1.0f);
action->throttle.j = PIN(action->throttle.j, -1.0f, 1.0f);
if ((fabsf(action->throttle.i) > 0.5f || fabsf(action->throttle.j) > 0.5f) && moved < 0.02f)
if (bot->walking && (fabsf(action->throttle.i) > 0.5f || fabsf(action->throttle.j) > 0.5f))
{
bot->stuck_ticks++;
if (bot->stuck_ticks == 8)
SET_FLAG(action->control_flags, _unit_control_jump_bit, TRUE);
real distance = (real)sqrt((bot->walk_point.x - position.x) * (bot->walk_point.x - position.x) +
(bot->walk_point.y - position.y) * (bot->walk_point.y - position.y));
boolean progress = distance < bot->walk_distance - 0.015f || moved > 0.06f;
bot->walk_distance = MIN(bot->walk_distance, distance);
if (progress && moved > 0.02f)
{
bot->stuck_ticks = 0;
}
else
{
bot->stuck_ticks++;
if (bot->stuck_ticks == 6)
{
bot->sidestep = bot_random() < 0.5f ? 1.0f : -1.0f;
bot->sidestep_ticks = 10;
/* (near a point of its path it cannot reach, at a corner: the
next one) */
if (distance < 1.5f && bot->path.valid && bot->path.step_index < bot->path.step_count)
{
bot->path.step_index++;
bot->sidestep_ticks = 4;
}
bots.stucks++;
bot->path_time = game_time_get() - PATH_REFRESH_TICKS;
bot->walk_distance = 1000000.0f;
}
if (bot->stuck_ticks == 12)
SET_FLAG(action->control_flags, _unit_control_jump_bit, TRUE);
if (bot->stuck_ticks == 24)
bots.long_stucks++;
}
}
else
{
@@ -891,6 +1407,7 @@ void bots_tick(
bsp->pathfinding_surfaces.count, bsp->pathfinding_edges.count);
}
}
bots_update_objectives();
for (index = 0; index < MAXIMUM_TRACKED_PLAYERS; index++)
{
struct player_datum *player = distributed_player(index);
@@ -915,9 +1432,10 @@ void bots_tick(
if (count && game_time_get() - bots.logged_time >= 300)
{
bots.logged_time = game_time_get();
platform_log("bots: driving %d; paths found %ld, not %ld; weapon swaps asked %ld; grenades %ld; changes %ld",
(int)count, bots.paths_found, bots.paths_failed, bots.pickups, bots.grenades, bots.switches);
bots.paths_found = bots.paths_failed = bots.pickups = bots.grenades = bots.switches = 0;
platform_log("bots: driving %d; paths found %ld, not %ld; weapon swaps asked %ld; grenades %ld; changes %ld; stuck %ld, long %ld (no start %ld, no goal %ld, no way %ld)",
(int)count, bots.paths_found, bots.paths_failed, bots.pickups, bots.grenades, bots.switches, bots.stucks, bots.long_stucks, bots.fail_start, bots.fail_goal, bots.fail_search);
bots.fail_start = bots.fail_goal = bots.fail_search = 0;
bots.paths_found = bots.paths_failed = bots.pickups = bots.grenades = bots.switches = bots.stucks = bots.long_stucks = 0;
for (index = 0; index < MAXIMUM_TRACKED_PLAYERS; index++)
{
struct player_datum *player = distributed_player(index);
@@ -942,8 +1460,22 @@ void bots_tick(
strncat(text, slot == unit->unit.current_weapon_index ? "* " : " ", sizeof(text) - strlen(text) - 1);
}
}
platform_log("bots: player %d carries %s, grenades %d/%d", index, text, unit->unit.grenade_counts[0],
unit->unit.grenade_counts[1]);
{
struct bot *bot = bot_get(DATUM_INDEX_NEW(index, player->identifier));
platform_log("bots: player %d (team %ld) carries %s, grenades %d/%d%s%s", index, player->team_index, text,
unit->unit.grenade_counts[0], unit->unit.grenade_counts[1],
bot && bot->has_objective ? ", to the objective" : "", bot && bot->carrying ? ", CARRYING" : "");
if (bot && bot->carrying && bot->has_objective)
{
real_point3d origin;
object_get_origin(player->unit_index, &origin);
platform_log("bots: carrier at (%.1f %.1f %.1f), goal (%.1f %.1f %.1f), %.2f off; path %s",
origin.x, origin.y, origin.z, bot->objective.x, bot->objective.y, bot->objective.z,
distance3d(&origin, &bot->objective), bot->path.valid ? "found" : "none");
}
}
}
}
}