mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-24 01:00:30 +02:00
Merge pull request #450 from OpenCommunityEdition/fix-scenery-shields-network
Scenery shields break, and tripmines go off, for clients as for the host
This commit is contained in:
8 files changed
+254
-21
No files matched your search
+12
-1
@@ -240,7 +240,18 @@ own, plays too; version 32 sends the host's mouse aim assist policy
|
||||
ignore, and has a client's input say whether its player plays co-op with
|
||||
friendly fire; version 33 sends with the game's settings whether co-op's
|
||||
players hurt their AI allies (Server Setup's ALLY FRIENDLY FIRE: the host
|
||||
then deals the players' hits of them no damage).
|
||||
then deals the players' hits of them no damage); version 34 takes clients'
|
||||
hits on, and sends the host's damage to, the map's scenery and devices
|
||||
whose shield is all damage can break (Rat Race's energy shields, a Custom
|
||||
Edition map's tripmines), which version 17 left out as having no body to
|
||||
destroy, and has every machine run those shields' stun and recharge itself;
|
||||
and the host deals what such an object deals itself (its shield or body
|
||||
breaking: a tripmine a client's player threw going off under the host's
|
||||
vehicle), which a client no longer reports and the host took from no one.
|
||||
One made in the game, a tripmine, is at an index of its own on each machine:
|
||||
the host takes a client's hit on its copy as one on the host's nearest where
|
||||
the client had it, and a client the host's damage to one as damage to its
|
||||
own copy nearest the damage.
|
||||
|
||||
`port/linux/network_format.txt` records the wire format of the current
|
||||
version (`tools/network_format.py`): `test_linux_port.py` fails when the
|
||||
|
||||
@@ -34,10 +34,19 @@ being hit looks and feels like on the clients).
|
||||
- The damage to a unit goes to the machines of the players it concerns
|
||||
(the unit's, its riders', the damage's owner's) and those sent the
|
||||
unit's player this tick, which see it; a killing blow to all.
|
||||
- Scenery and devices that can be destroyed (lights, panels) are the
|
||||
map's, at the same index on every machine. A client reports its hits on
|
||||
them like any other, and every client deals the host's damage to its own
|
||||
copy, which breaks and plays its effects as the host's did.
|
||||
- Scenery and devices that can be destroyed (lights, panels), or whose
|
||||
shields can be broken (energy shields, a map's tripmines), are the map's,
|
||||
at the same index on every machine. A client reports its hits on them like
|
||||
any other, and every client deals the host's damage to its own copy, which
|
||||
breaks and plays its effects as the host's did. Their shields' stun and
|
||||
recharge each machine runs itself (damage.c's objects_update_shields), as
|
||||
the host sends no state of them: the same damage, and the same recharge.
|
||||
What one of them deals itself, its shield or body breaking (a tripmine
|
||||
going off), the host deals whoever owns it, and no client reports: a
|
||||
client's player's tripmine is set off where the host has it. One made in
|
||||
the game (a tripmine a grenade's effect places) is at an index of its own
|
||||
on each machine: a machine given another's index of one takes its own
|
||||
nearest where the other had it (static_target_near).
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
@@ -216,6 +225,10 @@ from the target */
|
||||
#define REPORT_TARGET_TOLERANCE 3.0f
|
||||
#define REPORT_TARGET_LEAD_TICKS 15.0f
|
||||
#define REPORT_IMPACT_TOLERANCE 2.0f
|
||||
/* ... and how far beyond its size a static target made in the game may be
|
||||
from where another machine had its own copy (a tripmine: the same grenade's
|
||||
landing on each machine, static_target_near) */
|
||||
#define STATIC_TARGET_MATCH_TOLERANCE 1.0f
|
||||
/* ... and from where the host had a player's unit or vehicle then (with how
|
||||
far it moves in a few ticks: a client's copy runs a little ahead of the
|
||||
host's word on it), and further for a vehicle's (its size, and as far as a
|
||||
@@ -499,12 +512,31 @@ static boolean distributed_damage_to_data(
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* whether the object is scenery or a device that damage can destroy */
|
||||
/* whether the object is scenery or a device that damage can destroy, or
|
||||
whose shield it can break (a shield that is all the object is: Rat Race's
|
||||
energy shields, a map's tripmines) */
|
||||
static boolean static_target(
|
||||
long object_index)
|
||||
{
|
||||
return object_try_and_get_and_verify_type(object_index, STATIC_TARGET_TYPES) &&
|
||||
object_get_maximum_body_vitality(object_index, TRUE) > 0.0f;
|
||||
(object_get_maximum_body_vitality(object_index, TRUE) > 0.0f ||
|
||||
object_get_maximum_shield_vitality(object_index, TRUE) > 0.0f);
|
||||
}
|
||||
|
||||
boolean network_damage_static_target(
|
||||
long object_index)
|
||||
{
|
||||
return static_target(object_index);
|
||||
}
|
||||
|
||||
/* whether the damage is a static target's own (an effect of its shield or
|
||||
body breaking: a map's tripmine going off), which the host deals whoever its
|
||||
owner is: a client's player's tripmine is set off where the host has it (a
|
||||
vehicle running over it), so their machine has no hit of it to report */
|
||||
static boolean static_target_damage(
|
||||
struct damage_data const *damage)
|
||||
{
|
||||
return damage->owner_object_index != NONE && static_target(damage->owner_object_index);
|
||||
}
|
||||
|
||||
/* ---------- object_cause_damage (damage.c) */
|
||||
@@ -652,8 +684,9 @@ boolean network_damage_deals(
|
||||
|
||||
damage_replay_player_effect(unit->unit.player_index, &effect, 0.0f);
|
||||
}
|
||||
/* a hit of this machine's own player's, on the host's object */
|
||||
if (distributed_player_is_local(damage->owner_player_index) &&
|
||||
/* a hit of this machine's own player's, on the host's object (not a
|
||||
static target's own damage, which the host deals: static_target_damage) */
|
||||
if (distributed_player_is_local(damage->owner_player_index) && !static_target_damage(damage) &&
|
||||
(network_objects_client_has(object_index) || static_target(object_index)) &&
|
||||
damage_report_count < MAXIMUM_HIT_REPORTS_PER_TICK)
|
||||
{
|
||||
@@ -678,9 +711,11 @@ boolean network_damage_deals(
|
||||
return FALSE;
|
||||
}
|
||||
/* (a client's player's, whose machine reports it; but one whose machine
|
||||
has left deals it here: their grenades and rockets still in flight) */
|
||||
has left deals it here: their grenades and rockets still in flight; and
|
||||
a static target's own, which no machine reports: static_target_damage) */
|
||||
if (game_connection() == _game_connection_network_server && !damage_dealing_report &&
|
||||
damage->owner_player_index != NONE && !distributed_player_is_local(damage->owner_player_index))
|
||||
damage->owner_player_index != NONE && !distributed_player_is_local(damage->owner_player_index) &&
|
||||
!static_target_damage(damage))
|
||||
{
|
||||
struct player_datum *owner = player_try_and_get(damage->owner_player_index);
|
||||
|
||||
@@ -1301,6 +1336,41 @@ static real distributed_distance_squared(
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
/* this machine's static target nearest the point where another machine had
|
||||
one, within its size and the reach of the point, else NONE. A static target
|
||||
made in the game (a tripmine a grenade's effect places) is at an index of
|
||||
its own on each machine, as the map's are not, so another machine's index of
|
||||
it names no such object here */
|
||||
static long static_target_near(
|
||||
real_point3d const *point,
|
||||
real reach)
|
||||
{
|
||||
struct object_iterator iterator;
|
||||
long nearest = NONE;
|
||||
real nearest_distance = 0.0f;
|
||||
|
||||
object_iterator_new(&iterator, STATIC_TARGET_TYPES, 0);
|
||||
while (object_iterator_next(&iterator))
|
||||
{
|
||||
real_point3d origin;
|
||||
real distance;
|
||||
real limit;
|
||||
|
||||
if (!static_target(iterator.index))
|
||||
continue;
|
||||
object_get_origin(iterator.index, &origin);
|
||||
distance = distributed_distance_squared(point, &origin);
|
||||
limit = object_get(iterator.index)->object.bounding_sphere_radius + reach;
|
||||
if (distance <= limit * limit && (nearest == NONE || distance < nearest_distance))
|
||||
{
|
||||
nearest = iterator.index;
|
||||
nearest_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
/* 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(
|
||||
@@ -1962,8 +2032,26 @@ void network_damage_handle_reports(
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
struct distributed_hit_report const *report = &reports[index];
|
||||
struct distributed_hit_report found;
|
||||
struct damage_data damage;
|
||||
|
||||
/* (a hit on the client's copy of a static target made in the game,
|
||||
at an index of its own there: the host's where the client had it) */
|
||||
if (distributed_object_index_valid(report->object_index) &&
|
||||
distributed_point_valid(&report->target_position, REPORT_WORLD_BOUND) &&
|
||||
!object_try_and_get_and_verify_type(report->object_index,
|
||||
_object_mask_biped | _object_mask_vehicle | _object_mask_weapon | _object_mask_equipment) &&
|
||||
!static_target(report->object_index))
|
||||
{
|
||||
long object_index = static_target_near(&report->target_position, STATIC_TARGET_MATCH_TOLERANCE);
|
||||
|
||||
if (object_index != NONE)
|
||||
{
|
||||
found = *report;
|
||||
found.object_index = object_index;
|
||||
report = &found;
|
||||
}
|
||||
}
|
||||
if (!distributed_report_valid(machine_index, report, &damage))
|
||||
{
|
||||
damage_rejected_reports++;
|
||||
@@ -2255,15 +2343,33 @@ void network_damage_handle_events(
|
||||
}
|
||||
break;
|
||||
case _damage_event_static:
|
||||
if (distributed_object_index_valid(event->object_index) && static_target(event->object_index) &&
|
||||
distributed_damage_indices_valid(event->object_index, event->node_index, event->region_index,
|
||||
{
|
||||
long object_index = event->object_index;
|
||||
|
||||
/* (one made in the game, at an index of its own here: this machine's
|
||||
copy at the damage, within its reach, an explosion's its radius) */
|
||||
if (distributed_object_index_valid(object_index) && !static_target(object_index) &&
|
||||
!object_try_and_get_and_verify_type(object_index, _object_mask_all))
|
||||
{
|
||||
real reach = REPORT_IMPACT_TOLERANCE;
|
||||
|
||||
if (TEST_FLAG(damage.flags, _damage_area_of_effect_bit) &&
|
||||
tag_index_is_group(damage.definition_index, DAMAGE_EFFECT_DEFINITION_TAG))
|
||||
{
|
||||
reach += damage_effect_definition_get(damage.definition_index)->cutoff_radius;
|
||||
}
|
||||
object_index = static_target_near(&damage.origin, reach);
|
||||
}
|
||||
if (distributed_object_index_valid(object_index) && static_target(object_index) &&
|
||||
distributed_damage_indices_valid(object_index, event->node_index, event->region_index,
|
||||
event->material_index))
|
||||
{
|
||||
damage_replay_static(event->object_index, &damage, event->node_index, event->region_index,
|
||||
damage_replay_static(object_index, &damage, event->node_index, event->region_index,
|
||||
event->material_index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ is newer (network_client_manager.c). A host advertises it, with its netcode,
|
||||
in its game's advertisement's reserved bytes (network_server_message_handler.c),
|
||||
which hosts built before there was a version send as zeros: version 0.
|
||||
Raise it with any change to what the machines send each other. */
|
||||
#define HALO_PORT_NETWORK_VERSION 33
|
||||
#define HALO_PORT_NETWORK_VERSION 34
|
||||
/* ... the advertisement's reserved bytes: the version (a little-endian word),
|
||||
then flags */
|
||||
#define HALO_PORT_ADVERTISED_VERSION_OFFSET 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version 33
|
||||
version 34
|
||||
bound CHAT_TEXT_SIZE 64
|
||||
bound DISCORD_ID_SIZE 24
|
||||
bound DISCORD_NAME_SIZE 40
|
||||
|
||||
+10
-5
@@ -149,15 +149,20 @@ void network_damage_player_effect(long player_index, struct damage_data const *d
|
||||
void network_damage_aftermath(long object_index, struct damage_data const *damage, unsigned long being_damaged_flags,
|
||||
real shield_damage, real body_damage, real body_damage_multiplier, short body_part, short node_index,
|
||||
short region_index, short material_index, long victim_player_index);
|
||||
boolean network_damage_static_target(long object_index);
|
||||
|
||||
/* set while a distributed client carries out a kill it does not decide (an
|
||||
act of god: the host's word, network_distributed.c, or the world's) */
|
||||
static boolean distributed_damage_authorized;
|
||||
|
||||
/* whether this machine runs objects' shields (their stun, recharge and
|
||||
/* whether this machine runs the object's shields (their stun, recharge and
|
||||
overcharge): not a client of the distributed netcode, which has the host's
|
||||
(damage_set_network_state) */
|
||||
#define objects_update_shields() (!network_game_distributed_client())
|
||||
(damage_set_network_state), but for the map's scenery and devices that
|
||||
damage can break, whose damage every machine deals as the host did and of
|
||||
which the host sends no state (network_damage.c's static targets: a
|
||||
scenery's shield, broken, came back on no client) */
|
||||
#define objects_update_shields(object_index) \
|
||||
(!network_game_distributed_client() || network_damage_static_target(object_index))
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -2033,10 +2038,10 @@ void object_damage_update(
|
||||
FLAG(_object_die_act_of_god_no_statistics_bit));
|
||||
}
|
||||
|
||||
if (objects_update_shields())
|
||||
if (objects_update_shields(object_index))
|
||||
SET_FLAG(object->object.damage_flags, _object_shield_charging_bit, FALSE);
|
||||
damage_flags = object->object.damage_flags;
|
||||
if (objects_update_shields() &&
|
||||
if (objects_update_shields(object_index) &&
|
||||
object->object.maximum_shield_vitality > 0.f &&
|
||||
!TEST_FLAG(damage_flags, _object_dead_bit))
|
||||
{
|
||||
|
||||
@@ -71,6 +71,8 @@ static boolean distributed_damage_is_recoil(struct damage_data const *damage, lo
|
||||
static void damage_replay_player_effect(long index, struct damage_data *damage, real amount) { }
|
||||
static boolean network_objects_client_has(long index) { return TRUE; }
|
||||
static boolean static_target(long index) { return FALSE; }
|
||||
/* (no static targets here, so no damage is one's own: test_static_target) */
|
||||
static boolean static_target_damage(struct damage_data const *damage) { return FALSE; }
|
||||
static void distributed_damage_from_data(struct damage_data const *damage, struct damage_data *result) { *result = *damage; }
|
||||
static void object_get_origin(long index, real_point3d *point) { memset(point, 0, sizeof(*point)); }
|
||||
static long distributed_latest_host_time(void) { return 10; }
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "harness.h"
|
||||
/* network_damage.c's static_target: the map's scenery and devices whose hits
|
||||
every machine shares, those with a body damage can destroy and those whose
|
||||
shield is all it can break. Objects: 0 scenery with a body, 1 scenery with a
|
||||
shield only (an energy shield, a tripmine), 2 scenery with neither, 3 a device
|
||||
with a shield only, 4 a unit with a shield (not one: its own state is sent) */
|
||||
enum { _object_mask_scenery = 1, _object_mask_device = 2, _object_mask_unit = 4 };
|
||||
#define STATIC_TARGET_TYPES (_object_mask_scenery | _object_mask_device)
|
||||
static struct { long mask; real body, shield; } objects[] = {
|
||||
{ _object_mask_scenery, 50.0f, 0.0f }, { _object_mask_scenery, 0.0f, 80.0f }, { _object_mask_scenery, 0.0f, 0.0f },
|
||||
{ _object_mask_device, 0.0f, 20.0f }, { _object_mask_unit, 75.0f, 75.0f },
|
||||
};
|
||||
static void *object_try_and_get_and_verify_type(long object_index,long mask)
|
||||
{
|
||||
return object_index>=0&&object_index<5&&(objects[object_index].mask&mask)?&objects[object_index]:NULL;
|
||||
}
|
||||
static real object_get_maximum_body_vitality(long object_index,boolean ignore_difficulty)
|
||||
{
|
||||
(void)ignore_difficulty;return objects[object_index].body;
|
||||
}
|
||||
static real object_get_maximum_shield_vitality(long object_index,boolean ignore_difficulty)
|
||||
{
|
||||
(void)ignore_difficulty;return objects[object_index].shield;
|
||||
}
|
||||
struct damage_data { long owner_object_index; };
|
||||
/* where each object is, and its size (static_target_near) */
|
||||
static real_point3d origins[] = { { 0, 0, 0 }, { 10, 0, 0 }, { 10.5f, 0, 0 }, { 13, 0, 0 }, { 10.1f, 0, 0 } };
|
||||
struct object_datum { struct { real bounding_sphere_radius; } object; };
|
||||
static struct object_datum datums[5] = { { { 0.2f } }, { { 0.2f } }, { { 0.2f } }, { { 0.2f } }, { { 0.5f } } };
|
||||
#define object_get(index) (&datums[index])
|
||||
static void object_get_origin(long object_index,real_point3d *origin){*origin=origins[object_index];}
|
||||
struct object_iterator { long index; long next; unsigned long mask; };
|
||||
static void object_iterator_new(struct object_iterator *iterator,unsigned long mask,byte flags)
|
||||
{
|
||||
(void)flags;iterator->next=0;iterator->mask=mask;iterator->index=NONE;
|
||||
}
|
||||
static void *object_iterator_next(struct object_iterator *iterator)
|
||||
{
|
||||
while(iterator->next<5){long index=iterator->next++;if(objects[index].mask&iterator->mask){iterator->index=index;return &datums[index];}}
|
||||
return NULL;
|
||||
}
|
||||
#include "under_test.inc"
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
const char *case_name=argc>1?argv[1]:"";
|
||||
CASE("body") {
|
||||
CHECK(static_target(0),"scenery with a body");
|
||||
} else CASE("shield") {
|
||||
CHECK(static_target(1),"scenery with a shield only");
|
||||
CHECK(static_target(3),"a device with a shield only");
|
||||
} else CASE("neither") {
|
||||
CHECK(!static_target(2),"scenery damage cannot break");
|
||||
CHECK(!static_target(4),"a unit, whose state the host sends");
|
||||
CHECK(!static_target(NONE),"no object");
|
||||
} else CASE("damage") {
|
||||
/* a tripmine's own explosion (its owner the tripmine), and a player's grenade's (the grenade, no static target) */
|
||||
struct damage_data tripmine={1},grenade={4},none={NONE},light={0};
|
||||
CHECK(static_target_damage(&tripmine),"a shield scenery's own damage");
|
||||
CHECK(static_target_damage(&light),"a destructible scenery's own damage");
|
||||
CHECK(!static_target_damage(&grenade),"what a unit or projectile owns");
|
||||
CHECK(!static_target_damage(&none),"damage with no owner object");
|
||||
} else CASE("near") {
|
||||
/* another machine's copy of a tripmine (object 1, a shield) where this machine has it, and not too far */
|
||||
real_point3d at={10.3f,0,0},away={11.6f,0,0},nothing={30,0,0};
|
||||
CHECK(static_target_near(&at,1.0f)==1,"the nearest static target, %ld",static_target_near(&at,1.0f));
|
||||
CHECK(static_target_near(&away,1.0f)==NONE,"no unit, nothing damage cannot break, none farther");
|
||||
CHECK(static_target_near(¬hing,1.0f)==NONE,"none within reach");
|
||||
CHECK(static_target_near(&at,0.0f)==NONE,"its size and the reach only");
|
||||
{ real_point3d between={12,0,0}; CHECK(static_target_near(&between,2.0f)==3,"the nearer of two, %ld",static_target_near(&between,2.0f)); }
|
||||
} else return 2;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"""The scenery and devices whose damage every machine shares: a body to destroy, or a shield to break."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from harness import CHECK_FAILED, build, function, mutated, read, run
|
||||
|
||||
|
||||
def generated(fault=None):
|
||||
source = read('port/linux/game/network_damage.c')
|
||||
code = '\n'.join(function(source, name) for name in ('static_target', 'static_target_damage',
|
||||
'distributed_distance_squared', 'static_target_near'))
|
||||
if fault:
|
||||
code = mutated(code, *fault)
|
||||
return (('under_test.inc', code),)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('case', ['body', 'shield', 'neither', 'damage', 'near'])
|
||||
def test_case(case):
|
||||
status, output = run(build('static_target', generated()), case)
|
||||
assert status == 0, output
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fault,case', [
|
||||
# (the shield-only objects left out, as before: Rat Race's shields, a map's tripmines)
|
||||
(('||\n\t\t\tobject_get_maximum_shield_vitality(object_index, TRUE) > 0.0f', ''), 'shield'),
|
||||
(('STATIC_TARGET_TYPES)', 'STATIC_TARGET_TYPES | _object_mask_unit)'), 'neither'),
|
||||
(('object_get_maximum_body_vitality(object_index, TRUE) > 0.0f ||', ''), 'body'),
|
||||
(('damage->owner_object_index != NONE && static_target(damage->owner_object_index)', 'FALSE'), 'damage'),
|
||||
(('distance < nearest_distance', 'distance > nearest_distance'), 'near'),
|
||||
(('if (!static_target(iterator.index))\n\t\t\tcontinue;', ''), 'near'),
|
||||
])
|
||||
def test_negative_control(fault, case):
|
||||
status, output = run(build('static_target', generated(fault)), case)
|
||||
assert status == CHECK_FAILED, output
|
||||
Reference in new issue
Block a user