mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-26 17:01:08 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04cfd1a12c | ||
|
|
2b40b42f75 | ||
|
|
7fb941b0b2 | ||
|
|
933cad12b3 | ||
|
|
3a2c4649e8 | ||
|
|
32604c8e7e | ||
|
|
07c8430b5a | ||
|
|
667c2f29a0 | ||
|
|
6036adfa2d | ||
|
|
a689a8a3fa |
No files matched your search
@@ -647,7 +647,10 @@ its name alone beside it, has its name given back as the map lists find it.
|
||||
players' ammunition and grenade counts from the host only once a
|
||||
round trip has passed since it last fired, reloaded or threw (what
|
||||
the host says before then is from before). How old what a unit
|
||||
carries is travels in 16 bits.
|
||||
carries is travels in 16 bits. A client keeps an inventory whose
|
||||
unit or weapons have not come yet (the stream can trail the datagram)
|
||||
and applies it in full once they have, the newest of a unit's winning;
|
||||
one is dropped `EMPTY_INVENTORY_TICKS` after the newest came.
|
||||
- Players take the units the host spawns them with, seats are the
|
||||
host's (a client's own player's once it has ridden otherwise for
|
||||
longer than a round trip), and the CTF flags and oddballs are the same
|
||||
|
||||
@@ -61,11 +61,20 @@ being hit looks and feels like on the clients).
|
||||
#include "objects/damage_effect_definitions.h"
|
||||
#include "effects/effect_definitions.h"
|
||||
#include "physics/collision_model_definitions.h"
|
||||
#include "physics/collisions.h"
|
||||
#include "items/weapon_definitions.h"
|
||||
#include "items/projectile_definitions.h"
|
||||
#include "scenario/scenario.h"
|
||||
#include "units/units.h"
|
||||
#include "network_distributed.h"
|
||||
#ifdef HALO_ANDROID
|
||||
#include "halo_vr.h"
|
||||
|
||||
/* port (Mjolnir VR): how far from the unit's camera a VR player's weapon
|
||||
hand may be for the line of sight (world units, 1.2 m: vr.lean_limit's
|
||||
0.35 m and an arm) */
|
||||
#define VR_SIGHT_HAND_REACH 0.4f
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
|
||||
@@ -151,6 +160,10 @@ enum
|
||||
PLAYER_TRAIL_REPORT_SLACK_TICKS = TICKS_PER_SECOND,
|
||||
PLAYER_TRAIL_POINTS = 128,
|
||||
PLAYER_TRAIL_TICKS = PLAYER_TRAIL_INTERVAL_TICKS * PLAYER_TRAIL_POINTS,
|
||||
REPORT_LINE_OF_SIGHT_TICKS = 2 * TICKS_PER_SECOND,
|
||||
REPORT_LINE_OF_SIGHT_POINTS = REPORT_LINE_OF_SIGHT_TICKS / PLAYER_TRAIL_INTERVAL_TICKS,
|
||||
MAXIMUM_STUCK_PROJECTILES = 16,
|
||||
STUCK_PROJECTILE_TICKS = REPORT_MAXIMUM_AGE_TICKS + 2 * TICKS_PER_SECOND,
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -182,6 +195,7 @@ enum
|
||||
/* the vehicle they drive running into a unit: at the unit (the origin),
|
||||
from the vehicle (the epicenter), area damage (physics.c) */
|
||||
_damage_source_collision_bit,
|
||||
_damage_source_attached_bit,
|
||||
};
|
||||
|
||||
/* struct distributed_damage_event kill flags */
|
||||
@@ -372,6 +386,15 @@ static short damage_event_count;
|
||||
/* ... dealing a client's report */
|
||||
static boolean damage_dealing_report;
|
||||
/* ... each player's weapons of late, and hits left */
|
||||
struct damage_stuck_projectile
|
||||
{
|
||||
long projectile_index;
|
||||
long object_index;
|
||||
long damage_index;
|
||||
long time;
|
||||
boolean spent;
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
long definition_indices[MAXIMUM_RECENT_WEAPONS];
|
||||
@@ -379,6 +402,7 @@ static struct
|
||||
/* (the grenades thrown, not yet gone off: NONE for none) */
|
||||
long throw_times[MAXIMUM_GRENADE_THROWS];
|
||||
short throw_types[MAXIMUM_GRENADE_THROWS];
|
||||
struct damage_stuck_projectile stuck[MAXIMUM_STUCK_PROJECTILES];
|
||||
long driven_time;
|
||||
/* (the blow of the player's unit with no weapon: units.c's
|
||||
unit_unarmed_melee_damage) */
|
||||
@@ -422,6 +446,9 @@ static struct damage_trail_point
|
||||
real_point3d position;
|
||||
real speed;
|
||||
real radius;
|
||||
real eye;
|
||||
boolean mounted;
|
||||
real_point3d muzzle;
|
||||
} damage_trails[MAXIMUM_TRACKED_PLAYERS][PLAYER_TRAIL_POINTS];
|
||||
/* ... what each weapon or grenade deals, as the tags have it (their walk
|
||||
is long): the hits a second of a damage effect from it, how it deals it
|
||||
@@ -1021,6 +1048,8 @@ static byte distributed_projectile_deals(
|
||||
{
|
||||
SET_FLAG(kinds, _damage_source_impact_bit, TRUE);
|
||||
}
|
||||
if (projectile->projectile.attached_detonation_damage.index == damage_index)
|
||||
SET_FLAG(kinds, _damage_source_attached_bit, TRUE);
|
||||
kinds |= distributed_effect_deals(projectile->projectile.effect.index, damage_index, depth + 1, &from_here);
|
||||
kinds |= distributed_effect_deals(projectile->projectile.super_detonation.index, damage_index, depth + 1,
|
||||
&from_here);
|
||||
@@ -1372,6 +1401,40 @@ static long static_target_near(
|
||||
return nearest;
|
||||
}
|
||||
|
||||
static boolean distributed_vehicle_muzzle(
|
||||
long vehicle_index,
|
||||
real_point3d *muzzle)
|
||||
{
|
||||
struct unit_datum *vehicle = unit_get(vehicle_index);
|
||||
struct object_datum *weapon;
|
||||
struct object_marker marker;
|
||||
long weapon_index = NONE;
|
||||
short slot = vehicle->unit.current_weapon_index;
|
||||
short index;
|
||||
|
||||
if (slot >= 0 && slot < MAXIMUM_WEAPONS_PER_UNIT && vehicle->unit.weapon_object_indices[slot] != NONE &&
|
||||
object_try_and_get(vehicle->unit.weapon_object_indices[slot]))
|
||||
{
|
||||
weapon_index = vehicle->unit.weapon_object_indices[slot];
|
||||
}
|
||||
for (index = 0; weapon_index == NONE && index < MAXIMUM_WEAPONS_PER_UNIT; index++)
|
||||
{
|
||||
if (vehicle->unit.weapon_object_indices[index] != NONE &&
|
||||
object_try_and_get(vehicle->unit.weapon_object_indices[index]))
|
||||
{
|
||||
weapon_index = vehicle->unit.weapon_object_indices[index];
|
||||
}
|
||||
}
|
||||
if (weapon_index == NONE)
|
||||
return FALSE;
|
||||
weapon = object_get(weapon_index);
|
||||
object_get_marker_by_name(TEST_FLAG(weapon->object.flags, _object_invisible_bit) &&
|
||||
weapon->object.parent_object_index != NONE ? weapon->object.parent_object_index : weapon_index,
|
||||
"primary trigger", &marker, 1);
|
||||
*muzzle = marker.matrix.position;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* where each player's unit and vehicle are this tick (and whether they
|
||||
drive it), noted; and every few ticks where their unit is */
|
||||
static void distributed_note_targets(
|
||||
@@ -1411,27 +1474,155 @@ static void distributed_note_targets(
|
||||
|
||||
tick->objects[player_index][1].driving = vehicle && vehicle->unit.driver_object_index == unit_index;
|
||||
}
|
||||
if (game_time_get() % PLAYER_TRAIL_INTERVAL_TICKS == 0)
|
||||
{
|
||||
struct damage_trail_point *point =
|
||||
&damage_trails[player_index][(game_time_get() / PLAYER_TRAIL_INTERVAL_TICKS) & (PLAYER_TRAIL_POINTS - 1)];
|
||||
struct damage_history_tick const *last =
|
||||
&damage_history[(game_time_get() - 1) & (TARGET_HISTORY_TICKS - 1)];
|
||||
boolean appeared = unit_index != NONE &&
|
||||
(last->time != game_time_get() - 1 || last->objects[player_index][0].object_index == NONE);
|
||||
|
||||
point->time = unit_index != NONE ? game_time_get() : NONE;
|
||||
if (unit_index != NONE)
|
||||
if (game_time_get() % PLAYER_TRAIL_INTERVAL_TICKS == 0 || appeared)
|
||||
{
|
||||
point->position = tick->objects[player_index][0].position;
|
||||
point->speed = tick->objects[player_index][0].speed;
|
||||
point->radius = 0.0f;
|
||||
if (tick->objects[player_index][1].object_index != NONE)
|
||||
struct damage_trail_point *point =
|
||||
&damage_trails[player_index][(game_time_get() / PLAYER_TRAIL_INTERVAL_TICKS) & (PLAYER_TRAIL_POINTS - 1)];
|
||||
|
||||
point->time = unit_index != NONE ? game_time_get() : NONE;
|
||||
if (unit_index != NONE)
|
||||
{
|
||||
point->speed = MAX(point->speed, tick->objects[player_index][1].speed);
|
||||
point->radius = object_get(tick->objects[player_index][1].object_index)->object.bounding_sphere_radius;
|
||||
real_point3d camera;
|
||||
|
||||
point->position = tick->objects[player_index][0].position;
|
||||
point->speed = tick->objects[player_index][0].speed;
|
||||
point->radius = 0.0f;
|
||||
unit_get_camera_position(unit_index, &camera);
|
||||
point->eye = camera.z - point->position.z;
|
||||
point->mounted = FALSE;
|
||||
if (tick->objects[player_index][1].object_index != NONE)
|
||||
{
|
||||
long vehicle_index = tick->objects[player_index][1].object_index;
|
||||
struct unit_datum *vehicle = (struct unit_datum *)object_try_and_get_and_verify_type(
|
||||
vehicle_index, _object_mask_vehicle);
|
||||
|
||||
point->speed = MAX(point->speed, tick->objects[player_index][1].speed);
|
||||
point->radius = object_get(vehicle_index)->object.bounding_sphere_radius;
|
||||
if (vehicle && (vehicle->unit.driver_object_index == unit_index ||
|
||||
vehicle->unit.gunner_object_index == unit_index))
|
||||
{
|
||||
point->mounted = distributed_vehicle_muzzle(vehicle_index, &point->muzzle);
|
||||
}
|
||||
}
|
||||
#ifdef HALO_ANDROID
|
||||
/* port (Mjolnir VR): a VR player of another machine fires
|
||||
from the weapon in their hand (network_vr.c), which may
|
||||
be past a corner the unit's camera is behind: the hand is
|
||||
a muzzle as a vehicle's gun is, with the line from the
|
||||
eye to it clear (no hand through a wall), and no further
|
||||
from the eye than a lean and an arm reach */
|
||||
else
|
||||
{
|
||||
real vr_origin[3];
|
||||
|
||||
if (halo_vr_network_weapon_origin(player_index, unit_index, vr_origin))
|
||||
{
|
||||
real dx = vr_origin[0] - camera.x;
|
||||
real dy = vr_origin[1] - camera.y;
|
||||
real dz = vr_origin[2] - camera.z;
|
||||
|
||||
if (dx * dx + dy * dy + dz * dz <= VR_SIGHT_HAND_REACH * VR_SIGHT_HAND_REACH)
|
||||
{
|
||||
point->muzzle.x = vr_origin[0];
|
||||
point->muzzle.y = vr_origin[1];
|
||||
point->muzzle.z = vr_origin[2];
|
||||
point->mounted = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void distributed_note_stuck_projectiles(
|
||||
void)
|
||||
{
|
||||
struct object_iterator iterator;
|
||||
struct object_datum *projectile;
|
||||
|
||||
object_iterator_new(&iterator, _object_mask_projectile, 0);
|
||||
while ((projectile = (struct object_datum *)object_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
struct damage_stuck_projectile *stuck;
|
||||
long damage_index;
|
||||
short player_index;
|
||||
short index;
|
||||
short slot = NONE;
|
||||
short slot_rank = 0;
|
||||
boolean found = FALSE;
|
||||
|
||||
if (projectile->object.parent_object_index == NONE ||
|
||||
projectile->object.parent_object_index == projectile->object.owner_object_index ||
|
||||
projectile->object.owner_player_index == NONE || !player_try_and_get(projectile->object.owner_player_index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
player_index = (short)DATUM_INDEX_TO_ABSOLUTE_INDEX(projectile->object.owner_player_index);
|
||||
damage_index = projectile_definition_get(projectile->definition_index)->projectile.attached_detonation_damage.index;
|
||||
if (player_index >= MAXIMUM_TRACKED_PLAYERS || damage_index == NONE)
|
||||
continue;
|
||||
for (index = 0; index < MAXIMUM_STUCK_PROJECTILES; index++)
|
||||
{
|
||||
struct damage_stuck_projectile const *note = &damage_players[player_index].stuck[index];
|
||||
short rank;
|
||||
|
||||
if (note->time != NONE && note->projectile_index == iterator.index)
|
||||
{
|
||||
slot = index;
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
rank = note->time == NONE || game_time_get() - note->time > STUCK_PROJECTILE_TICKS ? 0 : note->spent ? 1 : 2;
|
||||
if (slot == NONE || rank < slot_rank ||
|
||||
(rank == slot_rank && note->time < damage_players[player_index].stuck[slot].time))
|
||||
{
|
||||
slot = index;
|
||||
slot_rank = rank;
|
||||
}
|
||||
}
|
||||
stuck = &damage_players[player_index].stuck[slot];
|
||||
if (!found)
|
||||
{
|
||||
stuck->projectile_index = iterator.index;
|
||||
stuck->damage_index = damage_index;
|
||||
stuck->spent = FALSE;
|
||||
}
|
||||
stuck->object_index = projectile->object.parent_object_index;
|
||||
stuck->time = game_time_get();
|
||||
}
|
||||
}
|
||||
|
||||
static boolean distributed_stuck_detonation(
|
||||
short player_index,
|
||||
long object_index,
|
||||
long damage_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
distributed_note_stuck_projectiles();
|
||||
for (index = 0; index < MAXIMUM_STUCK_PROJECTILES; index++)
|
||||
{
|
||||
struct damage_stuck_projectile *stuck = &damage_players[player_index].stuck[index];
|
||||
|
||||
if (stuck->time != NONE && !stuck->spent && game_time_get() - stuck->time <= STUCK_PROJECTILE_TICKS &&
|
||||
stuck->object_index == object_index && stuck->damage_index == damage_index)
|
||||
{
|
||||
stuck->spent = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* whether the host had the history's object within the tolerance of the
|
||||
position (with how far it moves in a few ticks) */
|
||||
static boolean distributed_history_near(
|
||||
@@ -1453,9 +1644,12 @@ vehicle), unless it is not there. */
|
||||
static short distributed_target_seen(
|
||||
long object_index,
|
||||
real_point3d const *position,
|
||||
long ticks)
|
||||
long ticks,
|
||||
real_point3d *seen_position)
|
||||
{
|
||||
boolean latest = FALSE;
|
||||
boolean seen = FALSE;
|
||||
real seen_distance = 0.0f;
|
||||
short column_player_index = NONE;
|
||||
short column_slot = 0;
|
||||
long back;
|
||||
@@ -1464,6 +1658,8 @@ static short distributed_target_seen(
|
||||
{
|
||||
long time = game_time_get() - back;
|
||||
struct damage_history_tick const *tick = &damage_history[time & (TARGET_HISTORY_TICKS - 1)];
|
||||
real reach;
|
||||
real distance;
|
||||
|
||||
if (tick->time != time)
|
||||
break;
|
||||
@@ -1493,12 +1689,18 @@ static short distributed_target_seen(
|
||||
}
|
||||
if (back == 0)
|
||||
latest = TRUE;
|
||||
if (distributed_history_near(&tick->objects[column_player_index][column_slot].position,
|
||||
tick->objects[column_player_index][column_slot].speed, position, REPORT_HISTORY_TOLERANCE))
|
||||
reach = REPORT_HISTORY_TOLERANCE +
|
||||
REPORT_HISTORY_LEAD_TICKS * tick->objects[column_player_index][column_slot].speed;
|
||||
distance = distributed_distance_squared(position, &tick->objects[column_player_index][column_slot].position);
|
||||
if (distance <= reach * reach && (!seen || distance < seen_distance))
|
||||
{
|
||||
return TRUE;
|
||||
seen = TRUE;
|
||||
seen_distance = distance;
|
||||
*seen_position = tick->objects[column_player_index][column_slot].position;
|
||||
}
|
||||
}
|
||||
if (seen)
|
||||
return TRUE;
|
||||
/* (one that was a player's, a body, a vehicle left: where it is now) */
|
||||
return latest ? FALSE : NONE;
|
||||
}
|
||||
@@ -1801,6 +2003,83 @@ static boolean distributed_report_paid(
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static boolean distributed_report_sight_clear(
|
||||
unsigned long flags,
|
||||
real_point3d const *from,
|
||||
real_point3d const *impact,
|
||||
real_point3d const *centre)
|
||||
{
|
||||
struct collision_result collision;
|
||||
|
||||
return !collision_test_line(flags, from, centre, NONE, &collision) ||
|
||||
(!collision_test_line(flags, from, impact, NONE, &collision) &&
|
||||
!collision_test_line(flags, impact, centre, NONE, &collision));
|
||||
}
|
||||
|
||||
/* whether the structure left the shooter a line to the target from the
|
||||
newest of their living trail points in the window (none: not alive to fire
|
||||
it, refused). The eye and the target position are matched over windows, not
|
||||
to one moment: deliberate, the lag compensation the other checks allow. */
|
||||
static boolean distributed_report_line_of_sight(
|
||||
long machine_index,
|
||||
short player_index,
|
||||
real_point3d const *impact_point,
|
||||
real_point3d const *centre,
|
||||
real radius,
|
||||
struct damage_reach const *reach,
|
||||
long host_time)
|
||||
{
|
||||
unsigned long flags = FLAG(_collision_test_structure_bit) |
|
||||
FLAG(_collision_test_front_facing_surfaces_bit) |
|
||||
FLAG(_collision_test_ignore_invisible_surfaces_bit) | FLAG(_collision_test_ignore_breakable_surfaces_bit) |
|
||||
FLAG(_collision_test_ignore_two_sided_surfaces_bit);
|
||||
real ticks = reach->ticks > 0.0f ? MIN(reach->ticks, (real)PLAYER_TRAIL_TICKS) : (real)REPORT_LINE_OF_SIGHT_TICKS;
|
||||
long latest_made = game_time_get() - (long)distributed_machine_round_trip_ticks(machine_index) -
|
||||
PLAYER_TRAIL_REPORT_SLACK_TICKS;
|
||||
long newest = game_time_get() / PLAYER_TRAIL_INTERVAL_TICKS;
|
||||
long earliest;
|
||||
long bucket;
|
||||
short traced = 0;
|
||||
real_point3d impact = *impact_point;
|
||||
struct collision_result collision;
|
||||
|
||||
{
|
||||
real distance = (real)sqrt(distributed_distance_squared(centre, impact_point));
|
||||
|
||||
if (distance > radius && distance > 0.0f)
|
||||
{
|
||||
real scale = radius / distance;
|
||||
|
||||
impact.x = centre->x + (impact_point->x - centre->x) * scale;
|
||||
impact.y = centre->y + (impact_point->y - centre->y) * scale;
|
||||
impact.z = centre->z + (impact_point->z - centre->z) * scale;
|
||||
}
|
||||
}
|
||||
if (host_time < latest_made)
|
||||
host_time = latest_made;
|
||||
earliest = host_time - (long)ticks - TARGET_HISTORY_SLACK_TICKS - PLAYER_TRAIL_INTERVAL_TICKS;
|
||||
for (bucket = newest; bucket >= 0 && newest - bucket < PLAYER_TRAIL_POINTS &&
|
||||
(bucket + 1) * PLAYER_TRAIL_INTERVAL_TICKS > earliest && traced < REPORT_LINE_OF_SIGHT_POINTS; bucket--)
|
||||
{
|
||||
struct damage_trail_point const *trail_point = &damage_trails[player_index][bucket & (PLAYER_TRAIL_POINTS - 1)];
|
||||
real_point3d eye;
|
||||
|
||||
if (trail_point->time == NONE || trail_point->time / PLAYER_TRAIL_INTERVAL_TICKS != bucket)
|
||||
continue;
|
||||
traced++;
|
||||
eye = trail_point->position;
|
||||
eye.z += trail_point->eye;
|
||||
if (distributed_report_sight_clear(flags, &eye, &impact, centre))
|
||||
return TRUE;
|
||||
if (trail_point->mounted && !collision_test_line(flags, &eye, &trail_point->muzzle, NONE, &collision) &&
|
||||
distributed_report_sight_clear(flags, &trail_point->muzzle, &impact, centre))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* whether the host takes the report, which it makes the damage as the
|
||||
host deals it (the cheaper checks first, and the report paid for before the
|
||||
history is looked through: a flood of reports costs its sender no more than
|
||||
@@ -1834,6 +2113,7 @@ static boolean distributed_report_valid(
|
||||
real reach;
|
||||
struct damage_reach fired_reach;
|
||||
short seen;
|
||||
real_point3d seen_position;
|
||||
short grenade_type;
|
||||
short throw_index = NONE;
|
||||
|
||||
@@ -1955,8 +2235,7 @@ static boolean distributed_report_valid(
|
||||
return FALSE;
|
||||
}
|
||||
/* what hits at a point (a shot, an explosion) within its reach of where
|
||||
the host had the player since it was fired, where the tags bound that
|
||||
(no line of sight: a wall between is not looked for) */
|
||||
the host had the player since it was fired, where the tags bound that */
|
||||
if (!melee && !collision && fired_reach.distance >= 0.0f &&
|
||||
!distributed_player_could_fire(machine_index, player_index, &report->damage.origin, &fired_reach,
|
||||
report->host_time))
|
||||
@@ -1966,7 +2245,7 @@ static boolean distributed_report_valid(
|
||||
/* the target about where the host had it when the shooter saw it: a
|
||||
player's unit or vehicle as far back as the report was made, else (what
|
||||
no player has) about where it is */
|
||||
seen = distributed_target_seen(report->object_index, &report->target_position, ticks);
|
||||
seen = distributed_target_seen(report->object_index, &report->target_position, ticks, &seen_position);
|
||||
if (seen == FALSE)
|
||||
return FALSE;
|
||||
if (seen == NONE)
|
||||
@@ -1982,6 +2261,25 @@ static boolean distributed_report_valid(
|
||||
(real)sqrt(velocity.i * velocity.i + velocity.j * velocity.j + velocity.k * velocity.k);
|
||||
if (!(distributed_distance_squared(&report->target_position, &origin) <= tolerance * tolerance))
|
||||
return FALSE;
|
||||
seen_position = origin;
|
||||
}
|
||||
/* a point hit needs a line to the target, unless it is the detonation of
|
||||
a projectile the host has had stuck to it: that flew there, no line now */
|
||||
if (!melee && !collision && !area &&
|
||||
!(TEST_FLAG(kinds, _damage_source_attached_bit) &&
|
||||
distributed_stuck_detonation(player_index, report->object_index, report->damage.definition_index)))
|
||||
{
|
||||
real_point3d centre, target_origin;
|
||||
|
||||
object_get_origin(report->object_index, &target_origin);
|
||||
centre.x = seen_position.x + target->object.bounding_sphere_center.x - target_origin.x;
|
||||
centre.y = seen_position.y + target->object.bounding_sphere_center.y - target_origin.y;
|
||||
centre.z = seen_position.z + target->object.bounding_sphere_center.z - target_origin.z;
|
||||
if (!distributed_report_line_of_sight(machine_index, player_index, &report->damage.origin, ¢re,
|
||||
target->object.bounding_sphere_radius, &fired_reach, report->host_time))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
/* the damage as the host deals it: what a client's hit can be, from the
|
||||
player's own */
|
||||
@@ -2245,6 +2543,7 @@ void network_damage_host_tick(
|
||||
distributed_log_reports();
|
||||
distributed_note_weapons();
|
||||
distributed_note_targets();
|
||||
distributed_note_stuck_projectiles();
|
||||
distributed_note_event_destinations();
|
||||
for (machine_number = 0; machine_number < machine_count; machine_number++)
|
||||
{
|
||||
@@ -2480,6 +2779,8 @@ void network_damage_new_game(
|
||||
damage_players[player_index].unarmed_melee_damage_index = NONE;
|
||||
for (index = 0; index < MAXIMUM_GRENADE_THROWS; index++)
|
||||
damage_players[player_index].throw_times[index] = NONE;
|
||||
for (index = 0; index < MAXIMUM_STUCK_PROJECTILES; index++)
|
||||
damage_players[player_index].stuck[index].time = NONE;
|
||||
damage_players[player_index].driven_time = -RECENT_WEAPON_TICKS - 1;
|
||||
damage_players[player_index].hit_seconds = HIT_REPORT_BURST_SECONDS;
|
||||
damage_explosions[player_index].time = NONE;
|
||||
|
||||
@@ -142,6 +142,12 @@ enum
|
||||
/* ... those of the kinds the host has, made since the last tick, checked
|
||||
(more, and all of them are) */
|
||||
MAXIMUM_CLIENT_NEW_OBJECTS = 1024,
|
||||
/* ... the host's inventories it could not apply in full yet, one for
|
||||
each unit that carries anything (a vehicle is a unit, its weapons are
|
||||
objects) whose objects the stream has not brought: most of them while
|
||||
it joins, few after. Room for as many as there are players; past that
|
||||
the oldest goes, and its unit waits for the host's next inventory of it */
|
||||
MAXIMUM_CLIENT_RETRIED_INVENTORIES = MAXIMUM_TRACKED_PLAYERS,
|
||||
MAXIMUM_ENTRIES_PER_MESSAGE = 64,
|
||||
/* the objects the host has at the same indices everywhere */
|
||||
NETWORKED_OBJECT_TYPES =
|
||||
@@ -470,6 +476,20 @@ static struct distributed_picked_up_weapon
|
||||
long definition_index;
|
||||
long time;
|
||||
} objects_client_picked_up_weapons[MAXIMUM_LOCAL_PLAYERS];
|
||||
/* ... the host's inventories that named a unit or a weapon not here yet. An
|
||||
inventory goes as a datagram, and units and weapons are made on the stream,
|
||||
which can trail it, so a player just spawned can be told what it carries
|
||||
before its unit or its weapons are here, and would carry nothing until the
|
||||
host's next inventory of it, a second later. Each is kept, the newest of its
|
||||
unit's, and applied once, the tick all it names is here; let go of once
|
||||
three of the host's refreshes pass without a newer (the host has stopped
|
||||
sending it) */
|
||||
static struct distributed_retried_inventory
|
||||
{
|
||||
struct distributed_inventory inventory;
|
||||
long time;
|
||||
} objects_client_retried_inventories[MAXIMUM_CLIENT_RETRIED_INVENTORIES];
|
||||
static short objects_client_retried_inventory_count;
|
||||
/* ... whether it failed to make one of the host's objects since it last
|
||||
asked for them (it says so when it asks again) */
|
||||
static boolean objects_client_ask_again;
|
||||
@@ -2846,6 +2866,80 @@ static boolean distributed_client_own_settled(
|
||||
return game_time_get() - time > (long)ceil(round_trip) + INVENTORY_INTERVAL_TICKS + OWN_INVENTORY_MARGIN_TICKS;
|
||||
}
|
||||
|
||||
/* whether the weapon the host has in a slot is here (none always is) */
|
||||
static boolean distributed_client_weapon_here(
|
||||
long weapon_index)
|
||||
{
|
||||
return weapon_index == NONE ||
|
||||
(network_objects_client_has(weapon_index) && object_try_and_get_and_verify_type(weapon_index, _object_mask_weapon));
|
||||
}
|
||||
|
||||
/* whether the unit an inventory names and every weapon it names are here */
|
||||
static boolean distributed_client_inventory_whole(
|
||||
struct distributed_inventory const *inventory)
|
||||
{
|
||||
short weapon_slot;
|
||||
|
||||
if (!network_objects_client_has(inventory->unit_index) ||
|
||||
!object_try_and_get_and_verify_type(inventory->unit_index, _object_mask_unit))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
for (weapon_slot = 0; weapon_slot < MAXIMUM_WEAPONS_PER_UNIT; weapon_slot++)
|
||||
{
|
||||
if (!distributed_client_weapon_here(inventory->weapon_indices[weapon_slot]))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the inventory kept to apply once all it names is here, the newest of its
|
||||
unit's (in its entry, else a new one, else the oldest's) */
|
||||
static void distributed_client_retry_inventory(
|
||||
struct distributed_inventory const *inventory)
|
||||
{
|
||||
struct distributed_retried_inventory *retried = NULL;
|
||||
short index;
|
||||
|
||||
for (index = 0; !retried && index < objects_client_retried_inventory_count; index++)
|
||||
{
|
||||
if (objects_client_retried_inventories[index].inventory.unit_index == inventory->unit_index)
|
||||
retried = &objects_client_retried_inventories[index];
|
||||
}
|
||||
if (!retried && objects_client_retried_inventory_count < MAXIMUM_CLIENT_RETRIED_INVENTORIES)
|
||||
retried = &objects_client_retried_inventories[objects_client_retried_inventory_count++];
|
||||
if (!retried)
|
||||
{
|
||||
short oldest = 0;
|
||||
|
||||
for (index = 1; index < objects_client_retried_inventory_count; index++)
|
||||
{
|
||||
if (objects_client_retried_inventories[index].time < objects_client_retried_inventories[oldest].time)
|
||||
oldest = index;
|
||||
}
|
||||
retried = &objects_client_retried_inventories[oldest];
|
||||
}
|
||||
retried->inventory = *inventory;
|
||||
retried->time = game_time_get();
|
||||
}
|
||||
|
||||
/* the unit's kept inventory, if it has one, kept no more */
|
||||
static void distributed_client_forget_retried_inventory(
|
||||
long unit_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < objects_client_retried_inventory_count; index++)
|
||||
{
|
||||
if (objects_client_retried_inventories[index].inventory.unit_index == unit_index)
|
||||
{
|
||||
objects_client_retried_inventories[index] =
|
||||
objects_client_retried_inventories[--objects_client_retried_inventory_count];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* the unit carries what the host's does */
|
||||
static void distributed_client_apply_inventory(
|
||||
struct distributed_inventory const *inventory)
|
||||
@@ -2855,6 +2949,12 @@ static void distributed_client_apply_inventory(
|
||||
boolean local;
|
||||
short weapon_slot;
|
||||
|
||||
/* (what is here of it now, and the rest once it is: the newest of the
|
||||
unit's is the one kept) */
|
||||
if (distributed_client_inventory_whole(inventory))
|
||||
distributed_client_forget_retried_inventory(inventory->unit_index);
|
||||
else
|
||||
distributed_client_retry_inventory(inventory);
|
||||
if (!network_objects_client_has(inventory->unit_index) ||
|
||||
!object_try_and_get_and_verify_type(inventory->unit_index, _object_mask_unit))
|
||||
{
|
||||
@@ -2886,11 +2986,8 @@ static void distributed_client_apply_inventory(
|
||||
if (weapon_index == wanted_index)
|
||||
continue;
|
||||
/* (not here yet: as it is, until it is) */
|
||||
if (wanted_index != NONE &&
|
||||
(!network_objects_client_has(wanted_index) || !object_try_and_get_and_verify_type(wanted_index, _object_mask_weapon)))
|
||||
{
|
||||
if (!distributed_client_weapon_here(wanted_index))
|
||||
continue;
|
||||
}
|
||||
if (weapon_index != NONE)
|
||||
unit_network_drop_weapon(inventory->unit_index, weapon_slot);
|
||||
if (wanted_index != NONE)
|
||||
@@ -3290,6 +3387,35 @@ static void distributed_client_carry_unsteered_vehicles(
|
||||
}
|
||||
}
|
||||
|
||||
/* the kept inventories all of whose unit and weapons have come: applied once,
|
||||
as if they had come now */
|
||||
static void distributed_client_apply_retried_inventories(
|
||||
void)
|
||||
{
|
||||
short index = 0;
|
||||
|
||||
while (index < objects_client_retried_inventory_count)
|
||||
{
|
||||
struct distributed_retried_inventory *retried = &objects_client_retried_inventories[index];
|
||||
|
||||
if (game_time_get() - retried->time >= EMPTY_INVENTORY_TICKS)
|
||||
{
|
||||
*retried = objects_client_retried_inventories[--objects_client_retried_inventory_count];
|
||||
}
|
||||
else if (distributed_client_inventory_whole(&retried->inventory))
|
||||
{
|
||||
struct distributed_inventory inventory = retried->inventory;
|
||||
|
||||
*retried = objects_client_retried_inventories[--objects_client_retried_inventory_count];
|
||||
distributed_client_apply_inventory(&inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void network_objects_client_tick(
|
||||
void)
|
||||
{
|
||||
@@ -3309,6 +3435,7 @@ void network_objects_client_tick(
|
||||
(word)sizeof(message), _distributed_to_host_reliably);
|
||||
objects_client_ask_again = FALSE;
|
||||
}
|
||||
distributed_client_apply_retried_inventories();
|
||||
if (objects_client_synchronized)
|
||||
distributed_client_remove_own_objects();
|
||||
/* (too many to look at one by one later: all of them) */
|
||||
@@ -3373,6 +3500,7 @@ void network_objects_new_game(
|
||||
objects_client_creating = FALSE;
|
||||
objects_client_deleting = FALSE;
|
||||
objects_client_new_object_count = 0;
|
||||
objects_client_retried_inventory_count = 0;
|
||||
objects_client_check_all = TRUE;
|
||||
objects_client_local_free_index = LOCAL_OBJECTS_FIRST_INDEX;
|
||||
objects_client_object_count = NONE;
|
||||
|
||||
@@ -21,7 +21,8 @@ a VR player of another (players.c, weapons.c, units.c) as long as their
|
||||
hands keep coming, else as before.
|
||||
|
||||
What the hands decide is only how the game looks: damage is the shooter's
|
||||
report, which the host checks as any other (network_damage.c).
|
||||
report, which the host checks as any other (network_damage.c), except that
|
||||
its line of sight may start at the weapon hand as well as the camera.
|
||||
|
||||
A machine that does not know the message (a build without this, of the same
|
||||
network version) drops it, as it drops any type past its own
|
||||
|
||||
@@ -7812,6 +7812,7 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat
|
||||
x, y + height, x + width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
}
|
||||
glBindVertexArray(device.vertex_array);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
platform_video_swap();
|
||||
}
|
||||
#ifndef HALO_GLES
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
#include "harness.h"
|
||||
#include <math.h>
|
||||
|
||||
typedef unsigned short word;
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#include "config.inc"
|
||||
|
||||
/* A client's world: the host's objects, each here or not yet (the stream
|
||||
makes them, and can trail the host's inventories, which go as datagrams);
|
||||
units that carry weapons of one magazine. */
|
||||
#define OBJECT_COUNT 256
|
||||
#define OBJECT(n) (0x10000L | (n))
|
||||
#define TAG_BLOCK_GET_ELEMENT(block, index, type) (&((type *)(block)->address)[index])
|
||||
struct tag_block { long count; void *address; };
|
||||
struct weapon_magazine_definition { short rounds_total_initial, rounds_total_maximum, rounds_loaded_maximum; };
|
||||
struct weapon_definition { struct { struct tag_block magazines; } weapon; };
|
||||
struct weapon_magazine { short rounds_total, rounds_loaded; };
|
||||
struct weapon_datum { long definition_index; struct { struct weapon_magazine magazines[2]; real age; } weapon; };
|
||||
struct unit_datum
|
||||
{
|
||||
struct { unsigned long damage_flags; } object;
|
||||
struct
|
||||
{
|
||||
long player_index;
|
||||
long weapon_object_indices[MAXIMUM_WEAPONS_PER_UNIT];
|
||||
short current_weapon_index, desired_weapon_index;
|
||||
char grenade_counts[NUMBER_OF_UNIT_GRENADE_TYPES];
|
||||
short grenade_throw_state, current_grenade_index, desired_grenade_index;
|
||||
} unit;
|
||||
};
|
||||
struct player_datum { short local_player_index; };
|
||||
static struct { boolean here; short type; } objects[OBJECT_COUNT];
|
||||
static struct unit_datum units[OBJECT_COUNT];
|
||||
static struct weapon_datum weapons[OBJECT_COUNT];
|
||||
static struct weapon_magazine_definition magazine_definition = {0, 500, 100};
|
||||
static struct weapon_definition weapon_definition = {{{1, &magazine_definition}}};
|
||||
static struct player_datum player;
|
||||
static long now;
|
||||
|
||||
static boolean here(long index)
|
||||
{
|
||||
return index != NONE && (index >> 16) == 1 && (index & 0xFFFF) < OBJECT_COUNT && objects[index & 0xFFFF].here;
|
||||
}
|
||||
static boolean network_objects_client_has(long index) { return here(index); }
|
||||
static void *object_try_and_get_and_verify_type(long index, unsigned long mask)
|
||||
{
|
||||
if (!here(index) || !TEST_FLAG(mask, objects[index & 0xFFFF].type))
|
||||
return NULL;
|
||||
return objects[index & 0xFFFF].type == _object_type_weapon ? (void *)&weapons[index & 0xFFFF] : (void *)&units[index & 0xFFFF];
|
||||
}
|
||||
static struct unit_datum *unit_get(long index)
|
||||
{
|
||||
CHECK(object_try_and_get_and_verify_type(index, _object_mask_unit), "not a unit here: %lx", index);
|
||||
return &units[index & 0xFFFF];
|
||||
}
|
||||
static struct weapon_datum *weapon_try_and_get(long index) { return object_try_and_get_and_verify_type(index, _object_mask_weapon); }
|
||||
static struct weapon_definition *weapon_definition_get(long index) { return &weapon_definition; }
|
||||
static long game_time_get(void) { return now; }
|
||||
static struct player_datum *player_get(long index) { return &player; }
|
||||
static boolean distributed_player_is_local(long player_index) { return FALSE; }
|
||||
static boolean distributed_client_own_settled(long time) { return TRUE; }
|
||||
static void distributed_client_release_item(long index, boolean dropped) {}
|
||||
static void unit_network_drop_weapon(long unit_index, short slot) { unit_get(unit_index)->unit.weapon_object_indices[slot] = NONE; }
|
||||
static void unit_network_add_weapon(long unit_index, long weapon_index, short slot)
|
||||
{
|
||||
unit_get(unit_index)->unit.weapon_object_indices[slot] = weapon_index;
|
||||
}
|
||||
struct distributed_own_inventory;
|
||||
static void distributed_client_note_own_inventory(struct distributed_own_inventory *own, long unit_index, boolean compare) {}
|
||||
#include "under_test.inc"
|
||||
|
||||
/* the stream makes one of the host's objects here: a unit carrying nothing,
|
||||
or a weapon */
|
||||
static void arrive(long index, short type)
|
||||
{
|
||||
struct unit_datum *unit = &units[index & 0xFFFF];
|
||||
short slot;
|
||||
|
||||
objects[index & 0xFFFF].here = TRUE;
|
||||
objects[index & 0xFFFF].type = type;
|
||||
memset(unit, 0, sizeof(*unit));
|
||||
unit->unit.player_index = NONE;
|
||||
unit->unit.current_weapon_index = NONE;
|
||||
unit->unit.current_grenade_index = NONE;
|
||||
for (slot = 0; slot < MAXIMUM_WEAPONS_PER_UNIT; slot++)
|
||||
unit->unit.weapon_object_indices[slot] = NONE;
|
||||
}
|
||||
|
||||
/* the host's inventory of a unit carrying two weapons (NONE: none) */
|
||||
static struct distributed_inventory carrying(long unit_index, long first, long second, char grenades)
|
||||
{
|
||||
struct distributed_inventory inventory;
|
||||
short slot;
|
||||
|
||||
memset(&inventory, 0, sizeof(inventory));
|
||||
inventory.unit_index = unit_index;
|
||||
inventory.grenade_counts[0] = grenades;
|
||||
inventory.current_weapon_index = 0;
|
||||
for (slot = 0; slot < MAXIMUM_WEAPONS_PER_UNIT; slot++)
|
||||
{
|
||||
inventory.weapon_indices[slot] = slot == 0 ? first : slot == 1 ? second : NONE;
|
||||
inventory.rounds_total[slot][0] = (short)(100 * (slot + 1));
|
||||
inventory.rounds_loaded[slot][0] = (short)(10 * (slot + 1));
|
||||
}
|
||||
return inventory;
|
||||
}
|
||||
|
||||
/* the host's inventory comes, as network_objects_handle_inventories takes it */
|
||||
static void receive(struct distributed_inventory inventory)
|
||||
{
|
||||
distributed_client_apply_inventory(&inventory);
|
||||
}
|
||||
|
||||
static boolean kept(long unit_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < objects_client_retried_inventory_count; index++)
|
||||
{
|
||||
if (objects_client_retried_inventories[index].inventory.unit_index == unit_index)
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static long held(long unit_index, short slot) { return unit_get(unit_index)->unit.weapon_object_indices[slot]; }
|
||||
|
||||
static void reset(void)
|
||||
{
|
||||
memset(objects, 0, sizeof(objects));
|
||||
memset(&player, 0, sizeof(player));
|
||||
memset(objects_client_retried_inventories, 0, sizeof(objects_client_retried_inventories));
|
||||
objects_client_retried_inventory_count = 0;
|
||||
now = 1000;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char const *case_name = argv[1];
|
||||
long unit = OBJECT(1), first = OBJECT(2), second = OBJECT(3);
|
||||
short index;
|
||||
|
||||
reset();
|
||||
/* a player spawned: its inventory comes before its unit and its weapon,
|
||||
is kept, and is applied in full the tick they have all come */
|
||||
CASE("unit-late")
|
||||
{
|
||||
receive(carrying(unit, first, NONE, 3));
|
||||
CHECK(kept(unit) && objects_client_retried_inventory_count == 1, "unit-late: not kept");
|
||||
arrive(first, _object_type_weapon);
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(kept(unit), "unit-late: let go of before its unit came");
|
||||
arrive(unit, _object_type_biped);
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(held(unit, 0) == first, "unit-late: its weapon not given once it came");
|
||||
CHECK(units[1].unit.grenade_counts[0] == 3 && weapons[2].weapon.magazines[0].rounds_total == 100 &&
|
||||
weapons[2].weapon.magazines[0].rounds_loaded == 10, "unit-late: its grenades or rounds not given");
|
||||
CHECK(!kept(unit) && objects_client_retried_inventory_count == 0, "unit-late: kept once applied");
|
||||
}
|
||||
/* its unit here, one of its weapons not: what is here is given at once,
|
||||
the rest once it comes */
|
||||
CASE("weapon-late")
|
||||
{
|
||||
arrive(unit, _object_type_biped);
|
||||
arrive(first, _object_type_weapon);
|
||||
receive(carrying(unit, first, second, 2));
|
||||
CHECK(held(unit, 0) == first && held(unit, 1) == NONE, "weapon-late: what is here not given at once");
|
||||
CHECK(kept(unit), "weapon-late: not kept for the weapon to come");
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(kept(unit) && held(unit, 1) == NONE, "weapon-late: applied before its weapon came");
|
||||
arrive(second, _object_type_weapon);
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(held(unit, 1) == second && weapons[3].weapon.magazines[0].rounds_total == 200,
|
||||
"weapon-late: the weapon not given once it came");
|
||||
CHECK(!kept(unit), "weapon-late: kept once applied");
|
||||
}
|
||||
/* a unit's newer inventory takes its older one's place */
|
||||
CASE("newest")
|
||||
{
|
||||
receive(carrying(unit, first, NONE, 1));
|
||||
now++;
|
||||
receive(carrying(unit, first, NONE, 4));
|
||||
CHECK(objects_client_retried_inventory_count == 1, "newest: %d kept of one unit", objects_client_retried_inventory_count);
|
||||
arrive(unit, _object_type_biped);
|
||||
arrive(first, _object_type_weapon);
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(units[1].unit.grenade_counts[0] == 4, "newest: not the newest applied");
|
||||
}
|
||||
/* the list full: one more takes the oldest's place, and a kept unit's
|
||||
newer one its own */
|
||||
CASE("full")
|
||||
{
|
||||
for (index = 0; index < MAXIMUM_CLIENT_RETRIED_INVENTORIES; index++)
|
||||
{
|
||||
now++;
|
||||
receive(carrying(OBJECT(17 + index), NONE, NONE, 1));
|
||||
}
|
||||
CHECK(objects_client_retried_inventory_count == MAXIMUM_CLIENT_RETRIED_INVENTORIES,
|
||||
"full: %d kept of %d", objects_client_retried_inventory_count, MAXIMUM_CLIENT_RETRIED_INVENTORIES);
|
||||
now++;
|
||||
receive(carrying(OBJECT(200), NONE, NONE, 1));
|
||||
CHECK(!kept(OBJECT(17)) && kept(OBJECT(18)) && kept(OBJECT(200)), "full: not the oldest's place taken");
|
||||
now++;
|
||||
receive(carrying(OBJECT(201), NONE, NONE, 1));
|
||||
CHECK(!kept(OBJECT(18)) && kept(OBJECT(19)) && kept(OBJECT(201)), "full: not the next oldest's taken");
|
||||
now++;
|
||||
receive(carrying(OBJECT(19), NONE, NONE, 2));
|
||||
CHECK(kept(OBJECT(19)) && kept(OBJECT(20)), "full: a kept unit's newer one took another's place");
|
||||
CHECK(objects_client_retried_inventory_count == MAXIMUM_CLIENT_RETRIED_INVENTORIES, "full: %d kept",
|
||||
objects_client_retried_inventory_count);
|
||||
}
|
||||
/* a unit's whole inventory, come once all it names is here: its kept one
|
||||
is let go of, and never applied over it */
|
||||
CASE("forget")
|
||||
{
|
||||
long other = OBJECT(6);
|
||||
|
||||
receive(carrying(unit, first, NONE, 1));
|
||||
receive(carrying(other, NONE, NONE, 1));
|
||||
arrive(unit, _object_type_biped);
|
||||
arrive(first, _object_type_weapon);
|
||||
receive(carrying(unit, first, NONE, 5));
|
||||
CHECK(!kept(unit) && kept(other) && objects_client_retried_inventory_count == 1, "forget: not let go of");
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(units[1].unit.grenade_counts[0] == 5, "forget: the older applied over the newer");
|
||||
}
|
||||
/* let go of EMPTY_INVENTORY_TICKS after the newest of its unit's came,
|
||||
and not applied when its unit comes after */
|
||||
CASE("expiry")
|
||||
{
|
||||
long newest = now + EMPTY_INVENTORY_TICKS / 2;
|
||||
|
||||
receive(carrying(unit, first, NONE, 1));
|
||||
now = newest;
|
||||
receive(carrying(unit, first, NONE, 1));
|
||||
now = newest + EMPTY_INVENTORY_TICKS - 1;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(kept(unit), "expiry: let go of %d ticks after the newest", EMPTY_INVENTORY_TICKS - 1);
|
||||
now = newest + EMPTY_INVENTORY_TICKS;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(!kept(unit), "expiry: kept %d ticks after the newest", EMPTY_INVENTORY_TICKS);
|
||||
arrive(unit, _object_type_biped);
|
||||
arrive(first, _object_type_weapon);
|
||||
now++;
|
||||
distributed_client_apply_retried_inventories();
|
||||
CHECK(held(unit, 0) == NONE, "expiry: applied after it was let go of");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
"""A client's inventories from the host whose unit or weapons have not come yet: kept, and applied once they have."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from harness import CHECK_FAILED, build, constant, enum_with, function, mutated, read, run, structure
|
||||
|
||||
CASES = ['unit-late', 'weapon-late', 'newest', 'full', 'forget', 'expiry']
|
||||
CONTROLS = [
|
||||
('else\n\t\tdistributed_client_retry_inventory(inventory);', 'else\n\t\t(void)0;', 'unit-late'),
|
||||
('distributed_client_apply_inventory(&inventory);', '(void)inventory;', 'unit-late'),
|
||||
('if (!distributed_client_weapon_here(inventory->weapon_indices[weapon_slot]))', 'if (FALSE)', 'weapon-late'),
|
||||
('if (objects_client_retried_inventories[index].inventory.unit_index == inventory->unit_index)', 'if (FALSE)',
|
||||
'newest'),
|
||||
('.time < objects_client_retried_inventories[oldest].time', '.time > objects_client_retried_inventories[oldest].time',
|
||||
'full'),
|
||||
('objects_client_retried_inventory_count < MAXIMUM_CLIENT_RETRIED_INVENTORIES',
|
||||
'objects_client_retried_inventory_count < MAXIMUM_CLIENT_RETRIED_INVENTORIES - 1', 'full'),
|
||||
('objects_client_retried_inventories[index] =\n\t\t\t\tobjects_client_retried_inventories[--objects_client_retried_inventory_count];',
|
||||
'--objects_client_retried_inventory_count;', 'forget'),
|
||||
('distributed_client_forget_retried_inventory(inventory->unit_index);\n\telse', '(void)0;\n\telse', 'forget'),
|
||||
('- retried->time >= EMPTY_INVENTORY_TICKS', '- retried->time > EMPTY_INVENTORY_TICKS', 'expiry'),
|
||||
('\tretried->inventory = *inventory;\n\tretried->time = game_time_get();',
|
||||
'\tif (retried->inventory.unit_index != inventory->unit_index)\n\t\tretried->time = game_time_get();\n'
|
||||
'\tretried->inventory = *inventory;', 'expiry'),
|
||||
]
|
||||
|
||||
|
||||
def declaration(source, first, last):
|
||||
"""The text from one marker to the end of another, as a static's declaration with its structure."""
|
||||
start = source.index(first)
|
||||
return source[start:source.index(last, start) + len(last)]
|
||||
|
||||
|
||||
def generated(fault=None):
|
||||
network = read('port/linux/game/network_objects.c')
|
||||
units = read('source/units/units.h')
|
||||
limits = read('port/linux/game/network_distributed.h') + read('port/linux/include/halo_port_limits.h')
|
||||
config = '\n'.join([
|
||||
'#define TICKS_PER_SECOND %d' % constant(read('source/cseries/cseries.h'), 'TICKS_PER_SECOND'),
|
||||
'#define MAXIMUM_OBJECTS_PER_MAP %d' % constant(read('source/objects/objects.h'), 'MAXIMUM_OBJECTS_PER_MAP'),
|
||||
'#define MAXIMUM_TRACKED_PLAYERS %d' % constant(limits, 'MAXIMUM_TRACKED_PLAYERS'),
|
||||
re.search(r'^#define AGE_SCALE .*$', network, re.M).group(0),
|
||||
enum_with(read('source/objects/object_types.h'), '_object_mask_unit'),
|
||||
enum_with(read('source/objects/objects.h'), '_object_dead_bit'),
|
||||
enum_with(read('source/game/players.h'), 'MAXIMUM_LOCAL_PLAYERS'),
|
||||
enum_with(units, 'MAXIMUM_WEAPONS_PER_UNIT ='),
|
||||
enum_with(units, 'NUMBER_OF_UNIT_GRENADE_TYPES\n'),
|
||||
enum_with(units, '_unit_grenade_throw_idle ='),
|
||||
enum_with(network, 'EMPTY_INVENTORY_TICKS ='),
|
||||
structure(network, 'distributed_inventory'),
|
||||
]) + '\n'
|
||||
code = '\n'.join([
|
||||
declaration(network, 'static struct distributed_own_inventory\n',
|
||||
'objects_client_own_inventories[MAXIMUM_LOCAL_PLAYERS];'),
|
||||
declaration(network, 'static struct distributed_retried_inventory\n',
|
||||
'static short objects_client_retried_inventory_count;'),
|
||||
function(network, 'distributed_client_weapon_here'),
|
||||
function(network, 'distributed_client_inventory_whole'),
|
||||
function(network, 'distributed_client_retry_inventory'),
|
||||
function(network, 'distributed_client_forget_retried_inventory'),
|
||||
function(network, 'distributed_client_apply_inventory'),
|
||||
function(network, 'distributed_client_apply_retried_inventories'),
|
||||
])
|
||||
if fault:
|
||||
code = mutated(code, *fault)
|
||||
return (('config.inc', config), ('under_test.inc', code))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('case', CASES)
|
||||
def test_case(case):
|
||||
status, output = run(build('retried_inventories', generated()), case)
|
||||
assert status == 0, output
|
||||
|
||||
|
||||
@pytest.mark.parametrize('before,after,case', CONTROLS)
|
||||
def test_negative_control(before, after, case):
|
||||
status, output = run(build('retried_inventories', generated((before, after))), case)
|
||||
assert status == CHECK_FAILED, output
|
||||
|
||||
|
||||
def test_wired():
|
||||
# Each client tick applies the kept ones; a new game keeps none.
|
||||
network = read('port/linux/game/network_objects.c')
|
||||
assert 'distributed_client_apply_retried_inventories();' in function(network, 'network_objects_client_tick')
|
||||
assert 'objects_client_retried_inventory_count = 0;' in function(network, 'network_objects_new_game')
|
||||
Reference in new issue
Block a user