diff --git a/port/linux/game/network_coop.c b/port/linux/game/network_coop.c index 0c189e94..78cbdcdf 100644 --- a/port/linux/game/network_coop.c +++ b/port/linux/game/network_coop.c @@ -29,6 +29,8 @@ that clients need to see is sent from this file: every tick in rotation, those that have moved since the map loaded more often. A scenario group is identified by its index, which is the same on every machine; a device's own group by the device. +- Breakable surfaces (glass) the host breaks, each once as it breaks and + a few at a time again in rotation (for a late joiner or a lost message). - Named objects (scenery and devices) the scripts create or destroy. network_objects.c already handles units, vehicles, weapons and equipment. Twice a second the host sends which object names currently exist. A @@ -93,6 +95,8 @@ index and tag, since the map placed them at the same index everywhere. #include "objects/object_types.h" #include "objects/damage.h" #include "objects/scenery.h" +#include "physics/breakable_surfaces.h" +#include "structures/structure_bsp_definitions.h" #include "physics/collisions.h" #include "rasterizer/rasterizer_cinematics.h" #include "saved games/game_state.h" @@ -187,6 +191,9 @@ enum _coop_event_cutscene_skipped, /* a unit opened (value TRUE) or closed: a dropship's doors */ _coop_event_unit_open, + /* a breakable surface broke: value its index, frame its BSP, reals the + damage's epicenter */ + _coop_event_surface_broken, }; /* distributed_coop_event.type of an effect: at a cutscene flag (value), or on @@ -1805,6 +1812,14 @@ static void client_apply_event( case _coop_event_cutscene_skipped: client_stop_script_sounds(); break; + case _coop_event_surface_broken: + if (event->frame == global_structure_bsp_index) + { + real_point3d epicenter = { event->reals[0], event->reals[1], event->reals[2] }; + + breakable_surface_port_break(event->value, &epicenter); + } + break; case _coop_event_unit_open: if (distributed_object_index_valid(event->object_index) && network_objects_client_has(event->object_index) && object_try_and_get_and_verify_type(event->object_index, _object_mask_unit)) @@ -2430,6 +2445,51 @@ void network_coop_note_effect( event->value = cutscene_flag_index; } +void network_coop_note_surface_broken( + short breakable_surface_index, + real_point3d const *epicenter) +{ + struct distributed_coop_event *event = event_new(_coop_event_surface_broken); + + if (!event) + return; + event->value = breakable_surface_index; + event->frame = global_structure_bsp_index; + event->reals[0] = epicenter->x; + event->reals[1] = epicenter->y; + event->reals[2] = epicenter->z; +} + +/* host: a few broken surfaces again each refresh, round them all, for a +machine that joined since or lost the message (broken from their middle) */ +static void host_send_broken_surfaces( + void) +{ + enum { SURFACES_PER_REFRESH = 8 }; + static short cursor; + struct structure_bsp *structure_bsp = global_structure_bsp_get(); + short count = (short)structure_bsp->breakable_surfaces.count; + short sent = 0; + short step; + + if (!host_resend.refresh || count <= 0) + return; + for (step = 0; step < count && sent < SURFACES_PER_REFRESH; step++) + { + short index = (short)((cursor + step) % count); + + if (!breakable_surface_extant(index)) + { + struct structure_breakable_surface *surface = TAG_BLOCK_GET_ELEMENT(&structure_bsp->breakable_surfaces, + index, struct structure_breakable_surface); + + network_coop_note_surface_broken(index, &surface->centroid); + sent++; + } + } + cursor = (short)((cursor + step) % count); +} + void network_coop_note_object_effect( long effect_definition_index, long object_index, @@ -2537,6 +2597,7 @@ void network_coop_host_tick( host_send_attachments(); host_send_looping_sounds(); host_send_hud_state(); + host_send_broken_surfaces(); host_send_events(); } diff --git a/port/linux/game/network_coop.h b/port/linux/game/network_coop.h index 30c330d7..1d8ff34a 100644 --- a/port/linux/game/network_coop.h +++ b/port/linux/game/network_coop.h @@ -88,6 +88,10 @@ void network_coop_note_attach(long parent_index, char const *parent_marker_name, char const *child_marker_name); void network_coop_note_detach(long parent_index, long child_index); void network_coop_note_object_effect(long effect_definition_index, long object_index, char const *marker_name); +/* breakable_surfaces.c: a breakable surface (glass) broke, from damage at +`epicenter`. Each machine's own shots break glass too, but with their own +random damage, so the host's breaks are the ones everyone keeps. */ +void network_coop_note_surface_broken(short breakable_surface_index, real_point3d const *epicenter); void network_coop_note_scenery_animation(long object_index, long animation_graph_index, short animation_index, short frame_index); diff --git a/port/linux/game/network_damage.c b/port/linux/game/network_damage.c index e8d76efb..88d4cb46 100644 --- a/port/linux/game/network_damage.c +++ b/port/linux/game/network_damage.c @@ -34,6 +34,10 @@ 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. */ #include "cseries.h" @@ -59,6 +63,8 @@ being hit looks and feels like on the clients). void damage_replay_player_effect(long player_index, struct damage_data *damage, real total_damage); void damage_replay_aftermath(long object_index, struct damage_data *damage, unsigned long being_damaged_flags, real shield_damage, real body_damage, real body_damage_multiplier, short body_part); +void damage_replay_static(long object_index, struct damage_data *damage, short node_index, short region_index, + short material_index); void damage_replay_kill(long object_index, struct damage_data *damage, short node_index, short region_index, short material_index); /* network_distributed.c's */ @@ -142,8 +148,14 @@ enum _damage_event_player_effect, _damage_event_aftermath, _damage_event_kill, + /* damage to destructible scenery or a device, to every machine */ + _damage_event_static, }; +/* the objects every machine has from the map, at the same index, which +damage can break (static_target) */ +#define STATIC_TARGET_TYPES (_object_mask_scenery | _object_mask_device) + /* what a player's damage is, by how the game deals it (the shape of their report of it) */ enum @@ -483,6 +495,14 @@ static boolean distributed_damage_to_data( return TRUE; } +/* whether the object is scenery or a device that damage can destroy */ +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_cause_damage (damage.c) */ /* whether the damage is a weapon's own at the unit that fires it (weapons.c: @@ -629,7 +649,8 @@ 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) && network_objects_client_has(object_index) && + if (distributed_player_is_local(damage->owner_player_index) && + (network_objects_client_has(object_index) || static_target(object_index)) && damage_report_count < MAXIMUM_HIT_REPORTS_PER_TICK) { struct distributed_hit_report *report = &damage_reports[damage_report_count++]; @@ -736,6 +757,21 @@ void network_damage_aftermath( if (game_connection() != _game_connection_network_server) return; + if (static_target(object_index)) + { + if (damage_event_count >= MAXIMUM_DAMAGE_EVENTS_PER_TICK - RESERVED_KILL_EVENTS) + return; + event = &damage_events[damage_event_count++]; + csmemset(event, 0, sizeof(*event)); + event->kind = _damage_event_static; + event->player_index = NO_PLAYER; + event->object_index = object_index; + distributed_damage_from_data(damage, &event->damage); + event->node_index = node_index; + event->region_index = region_index; + event->material_index = material_index; + return; + } /* (units only: items and the like the objects' states place) */ unit = (struct unit_datum *)object_try_and_get_and_verify_type(object_index, _object_mask_unit); if (!unit) @@ -1719,6 +1755,8 @@ static boolean distributed_report_valid( return FALSE; target = (struct object_datum *)object_try_and_get_and_verify_type(report->object_index, _object_mask_biped | _object_mask_vehicle | _object_mask_weapon | _object_mask_equipment); + if (!target && static_target(report->object_index)) + target = object_get(report->object_index); if (!target || !tag_index_is_group(report->damage.definition_index, DAMAGE_EFFECT_DEFINITION_TAG)) return FALSE; /* numbers the game can take */ @@ -2160,6 +2198,15 @@ void network_damage_handle_events( players_show_telefragged(victim_player_index); } 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, + event->material_index)) + { + damage_replay_static(event->object_index, &damage, event->node_index, event->region_index, + event->material_index); + } + break; } } } diff --git a/source/objects/damage.c b/source/objects/damage.c index aab1f8d7..0ba84ac2 100644 --- a/source/objects/damage.c +++ b/source/objects/damage.c @@ -2654,6 +2654,20 @@ void damage_replay_kill( distributed_damage_authorized = FALSE; } +/* ... damage to scenery or a device, dealt as the host dealt it: the copy +breaks, plays its effects and is destroyed as the host's was */ +void damage_replay_static( + long object_index, + struct damage_data *damage, + short node_index, + short region_index, + short material_index) +{ + distributed_damage_authorized = TRUE; + object_cause_damage(damage, object_index, node_index, region_index, material_index, NULL); + distributed_damage_authorized = FALSE; +} + /* the host's: what damage_set_network_state takes */ void damage_get_network_state( long object_index, diff --git a/source/physics/breakable_surfaces.c b/source/physics/breakable_surfaces.c index 8b2bd348..4d243789 100644 --- a/source/physics/breakable_surfaces.c +++ b/source/physics/breakable_surfaces.c @@ -25,6 +25,7 @@ BREAKABLE_SURFACES.C #include "sound/game_sound.h" #include "structures/structure_bsp_definitions.h" #include "tag_files/tag_groups.h" +#include "network_coop.h" /* port: port/linux/game/network_coop.c */ /* ---------- structures */ @@ -189,6 +190,8 @@ void breakable_surface_damage( BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE); breakable_surface_effect(breakable_surface_index, damage_data, seed_surface_index); + /* port: and on network co-op's clients */ + network_coop_note_surface_broken(breakable_surface_index, &damage_data->epicenter); } } } @@ -236,6 +239,8 @@ void breakable_surface_damage_area_of_effect( breakable_surface_get(breakable_surface_index)->vitality = 0.0f; BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE); breakable_surface_effect(breakable_surface_index, damage_data, breakable_surface->collision_surface_index); + /* port: and on network co-op's clients */ + network_coop_note_surface_broken(breakable_surface_index, &damage_data->epicenter); } } } @@ -244,6 +249,29 @@ void breakable_surface_damage_area_of_effect( return; } +void breakable_surface_port_break( + short breakable_surface_index, + real_point3d const *epicenter) +{ + struct structure_bsp *structure_bsp = global_structure_bsp_get(); + struct structure_breakable_surface *breakable_surface; + struct damage_data damage; + + if (breakable_surface_index < 0 || breakable_surface_index >= structure_bsp->breakable_surfaces.count || + !breakable_surface_extant(breakable_surface_index)) + { + return; + } + breakable_surface = TAG_BLOCK_GET_ELEMENT(&structure_bsp->breakable_surfaces, breakable_surface_index, + struct structure_breakable_surface); + /* (the shards fly away from the epicenter, all the effect reads) */ + csmemset(&damage, 0, sizeof(damage)); + damage.epicenter = *epicenter; + breakable_surface_get(breakable_surface_index)->vitality = 0.0f; + BIT_VECTOR_SET_FLAG((long *)breakable_surface_flags_get(), breakable_surface_index, FALSE); + breakable_surface_effect(breakable_surface_index, &damage, breakable_surface->collision_surface_index); +} + /* ---------- private code */ static void breakable_surface_effect( diff --git a/source/physics/breakable_surfaces.h b/source/physics/breakable_surfaces.h index 122737e7..0133eb46 100644 --- a/source/physics/breakable_surfaces.h +++ b/source/physics/breakable_surfaces.h @@ -74,6 +74,12 @@ struct breakable_surface_particle_effect struct breakable_surface_datum *breakable_surface_get( short breakable_surface_index); +/* port: breaks a surface as damage at `epicenter` would, shards and all, +for a network co-op client the host told (port/linux/game/network_coop.c); +nothing if it is already broken */ +void breakable_surface_port_break( + short breakable_surface_index, + real_point3d const *epicenter); void breakable_surfaces_initialize( void); void breakable_surfaces_dispose(