From af7ad15d3c3bac0e459a719f6036f25db444b1e2 Mon Sep 17 00:00:00 2001 From: justinfilip Date: Sat, 10 Oct 2026 13:25:34 -0500 Subject: [PATCH] Keep a respawned vehicle on the name its teleporter uses. The timer was leaving the named vehicle on the pad and creating another on top of it. The home now follows that named vehicle, and a pad that already has one does not get a second. --- source/game/game_engine.c | 105 ++++++++++++++++++++++++++++++++++++-- source/objects/objects.c | 17 ++++++ source/objects/objects.h | 4 ++ 3 files changed, 122 insertions(+), 4 deletions(-) diff --git a/source/game/game_engine.c b/source/game/game_engine.c index 84bb011c..51063a48 100644 --- a/source/game/game_engine.c +++ b/source/game/game_engine.c @@ -4662,9 +4662,64 @@ static struct real_vector3d forward; real_vector3d up; long idle_since; + /* (the scenario name the map's scripts teleport; NONE when it had none) */ + short name_index; } game_engine_vehicle_homes[MAXIMUM_VEHICLE_HOMES]; static short game_engine_vehicle_home_count = NONE; +/* still on the pad the map put it on: further than this, and empty, it can +come back */ +#define VEHICLE_HOME_AWAY_SQUARED 4.0f + +/* the vehicle this home should follow. The recorded one, if it is still +alive. A map script often destroys that one and creates it again under the +same name: that one is this home's vehicle, and a second is not put on the +pad with it. */ +static long game_engine_vehicle_home_current( + short home) +{ + long object_index = game_engine_vehicle_homes[home].object_index; + struct unit_datum *vehicle = unit_try_and_get(object_index); + long named_index; + struct object_iterator iterator; + + if (vehicle && !TEST_FLAG(vehicle->object.damage_flags, _object_dead_bit)) + return object_index; + + named_index = game_engine_vehicle_homes[home].name_index == NONE ? NONE : + object_index_from_name_index(game_engine_vehicle_homes[home].name_index); + vehicle = unit_try_and_get(named_index); + if (vehicle && !TEST_FLAG(vehicle->object.damage_flags, _object_dead_bit)) + { + game_engine_vehicle_homes[home].object_index = named_index; + return named_index; + } + + object_iterator_new(&iterator, _object_mask_vehicle, 0); + while (object_iterator_next(&iterator)) + { + struct object_datum *object = object_get(iterator.index); + real dx = object->object.position.x - game_engine_vehicle_homes[home].position.x; + real dy = object->object.position.y - game_engine_vehicle_homes[home].position.y; + real dz = object->object.position.z - game_engine_vehicle_homes[home].position.z; + + if (TEST_FLAG(object->object.damage_flags, _object_dead_bit) || + object->definition_index != game_engine_vehicle_homes[home].definition_index || + dx * dx + dy * dy + dz * dz > VEHICLE_HOME_AWAY_SQUARED) + { + continue; + } + game_engine_vehicle_homes[home].object_index = iterator.index; + if (game_engine_vehicle_homes[home].name_index == NONE) + game_engine_vehicle_homes[home].name_index = object->object.name_index; + else + object_claim_scenario_name(iterator.index, game_engine_vehicle_homes[home].name_index); + return iterator.index; + } + + return NONE; +} + static void game_engine_update_vehicle_respawn( void) { @@ -4692,11 +4747,13 @@ static void game_engine_update_vehicle_respawn( object_get_orientation(iterator.index, &game_engine_vehicle_homes[home].forward, &game_engine_vehicle_homes[home].up); game_engine_vehicle_homes[home].idle_since = NONE; + game_engine_vehicle_homes[home].name_index = object->object.name_index; } } for (index = 0; index < game_engine_vehicle_home_count; index++) { - struct unit_datum *vehicle = unit_try_and_get(game_engine_vehicle_homes[index].object_index); + long object_index = game_engine_vehicle_home_current(index); + struct unit_datum *vehicle = unit_try_and_get(object_index); boolean waiting = TRUE; if (vehicle && !TEST_FLAG(vehicle->object.damage_flags, _object_dead_bit)) @@ -4710,7 +4767,7 @@ static void game_engine_update_vehicle_respawn( /* (empty: no rider among its children, which hold its own weapons too) */ waiting = vehicle->unit.driver_object_index == NONE && vehicle->unit.gunner_object_index == NONE && - dx * dx + dy * dy + dz * dz > 4.0f; + dx * dx + dy * dy + dz * dz > VEHICLE_HOME_AWAY_SQUARED; for (child_index = vehicle->object.first_child_object_index; waiting && child_index != NONE; child_index = object_get(child_index)->object.next_object_index) { @@ -4725,14 +4782,54 @@ static void game_engine_update_vehicle_respawn( else if (now - game_engine_vehicle_homes[index].idle_since >= respawn_ticks) { struct object_placement_data placement_data; + struct object_iterator iterator; + long stale_index = game_engine_vehicle_homes[index].object_index; + long named_index = game_engine_vehicle_homes[index].name_index == NONE ? NONE : + object_index_from_name_index(game_engine_vehicle_homes[index].name_index); + long on_pad = NONE; - if (vehicle) - object_delete(game_engine_vehicle_homes[index].object_index); + /* (a vehicle of this kind is already there: follow that one, + rather than putting another on top of it) */ + object_iterator_new(&iterator, _object_mask_vehicle, 0); + while (object_iterator_next(&iterator)) + { + struct object_datum *occupant = object_get(iterator.index); + real dx = occupant->object.position.x - game_engine_vehicle_homes[index].position.x; + real dy = occupant->object.position.y - game_engine_vehicle_homes[index].position.y; + real dz = occupant->object.position.z - game_engine_vehicle_homes[index].position.z; + + if (iterator.index == object_index || + TEST_FLAG(occupant->object.damage_flags, _object_dead_bit) || + occupant->definition_index != game_engine_vehicle_homes[index].definition_index || + dx * dx + dy * dy + dz * dz > VEHICLE_HOME_AWAY_SQUARED) + { + continue; + } + on_pad = iterator.index; + break; + } + if (on_pad != NONE) + { + game_engine_vehicle_homes[index].object_index = on_pad; + if (game_engine_vehicle_homes[index].name_index == NONE) + game_engine_vehicle_homes[index].name_index = object_get(on_pad)->object.name_index; + else + object_claim_scenario_name(on_pad, game_engine_vehicle_homes[index].name_index); + game_engine_vehicle_homes[index].idle_since = NONE; + continue; + } + if (unit_try_and_get(stale_index)) + object_delete(stale_index); + if (named_index != stale_index && unit_try_and_get(named_index)) + object_delete(named_index); object_placement_data_new(&placement_data, game_engine_vehicle_homes[index].definition_index, NONE); placement_data.position = game_engine_vehicle_homes[index].position; placement_data.forward = game_engine_vehicle_homes[index].forward; placement_data.up = game_engine_vehicle_homes[index].up; game_engine_vehicle_homes[index].object_index = object_new(&placement_data); + object_claim_scenario_name( + game_engine_vehicle_homes[index].object_index, + game_engine_vehicle_homes[index].name_index); game_engine_vehicle_homes[index].idle_since = NONE; } } diff --git a/source/objects/objects.c b/source/objects/objects.c index 4c9f6a1d..4f8a8b0b 100644 --- a/source/objects/objects.c +++ b/source/objects/objects.c @@ -4665,6 +4665,23 @@ static void object_name_list_new( return; } +/* the name is free: deleting the vehicle that held it cleared the slot. +A name something else still holds stays with that object. */ +void object_claim_scenario_name( + long object_index, + short name_index) +{ + if (object_index == NONE || + !VALID_INDEX(name_index, MIN(global_scenario_get()->object_names.count, MAXIMUM_OBJECT_NAMES_PER_SCENARIO)) || + object_name_list[name_index] != NONE) + { + return; + } + + object_name_list_new(object_index, name_index); + return; +} + static void object_name_list_delete( long object_index) { diff --git a/source/objects/objects.h b/source/objects/objects.h index ee691bc0..96dbee3f 100644 --- a/source/objects/objects.h +++ b/source/objects/objects.h @@ -432,6 +432,10 @@ void object_permute_region(long object_index, char *permutation_name, short desi boolean object_get_function_value(long object_index, short function_index, real *value_reference); short objects_in_clusters_by_indices(unsigned long class_flags, short cluster_count, short const *cluster_indices, short maximum_object_count, long *object_indices); long object_index_from_name_index(short name_index); +/* port: this object takes a scenario name nobody holds, the way a placed +vehicle does. A respawned vehicle keeps the name of the one it replaces, so +a script that teleports that name still finds it. */ +void object_claim_scenario_name(long object_index, short name_index); void objects_disconnect_from_structure_bsp(void); boolean object_visible_to_any_player(long object_index); void object_pvs_activate(long object_index);