diff --git a/README.md b/README.md index 200e3ec3..b5df4ee3 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,7 @@ * Grief: added Smoke Grenades to the Mystery Box * Grief: added Richtofen Head meat powerup model * Grief: Brutus spawns every 4-6 minutes +* Grief: Brutus can lock perks and the Mystery Box #### Docks * Added Grief game mode diff --git a/scripts/zm/replaced/_zm_ai_brutus.gsc b/scripts/zm/replaced/_zm_ai_brutus.gsc index ea70dac6..f9350f31 100644 --- a/scripts/zm/replaced/_zm_ai_brutus.gsc +++ b/scripts/zm/replaced/_zm_ai_brutus.gsc @@ -4,6 +4,318 @@ #include maps\mp\zombies\_zm_ai_brutus; #include maps\mp\zombies\_zm_score; +init() +{ + level.brutus_spawners = getentarray( "brutus_zombie_spawner", "script_noteworthy" ); + + if ( level.brutus_spawners.size == 0 ) + return; + + array_thread( level.brutus_spawners, ::add_spawn_function, ::brutus_prespawn ); + + for ( i = 0; i < level.brutus_spawners.size; i++ ) + { + level.brutus_spawners[i].is_enabled = 1; + level.brutus_spawners[i].script_forcespawn = 1; + } + + level.brutus_spawn_positions = getstructarray( "brutus_location", "script_noteworthy" ); + level thread setup_interaction_matrix(); + level.sndbrutusistalking = 0; + level.brutus_health = 500; + level.brutus_health_increase = 1000; + level.brutus_round_count = 0; + level.brutus_last_spawn_round = 0; + level.brutus_count = 0; + level.brutus_max_count = 1; + level.brutus_damage_percent = 0.1; + level.brutus_helmet_shots = 5; + level.brutus_team_points_for_death = 500; + level.brutus_player_points_for_death = 250; + level.brutus_points_for_helmet = 250; + level.brutus_alarm_chance = 100; + level.brutus_min_alarm_chance = 100; + level.brutus_alarm_chance_increment = 10; + level.brutus_max_alarm_chance = 200; + level.brutus_min_round_fq = 4; + level.brutus_max_round_fq = 7; + level.brutus_reset_dist_sq = 262144; + level.brutus_aggro_dist_sq = 16384; + level.brutus_aggro_earlyout = 12; + level.brutus_blocker_pieces_req = 1; + level.brutus_zombie_per_round = 1; + level.brutus_players_in_zone_spawn_point_cap = 120; + level.brutus_teargas_duration = 7; + level.player_teargas_duration = 2; + level.brutus_teargas_radius = 64; + level.num_pulls_since_brutus_spawn = 0; + level.brutus_min_pulls_between_box_spawns = 4; + level.brutus_explosive_damage_for_helmet_pop = 1500; + level.brutus_explosive_damage_increase = 600; + level.brutus_failed_paths_to_teleport = 4; + level.brutus_do_prologue = 1; + level.brutus_min_spawn_delay = 10.0; + level.brutus_max_spawn_delay = 60.0; + level.brutus_respawn_after_despawn = 1; + level.brutus_in_grief = 0; + + if ( getdvar( "ui_gametype" ) == "zgrief" ) + level.brutus_in_grief = 1; + + level.brutus_shotgun_damage_mod = 1.5; + level.brutus_custom_goalradius = 48; + registerclientfield( "actor", "helmet_off", 9000, 1, "int" ); + registerclientfield( "actor", "brutus_lock_down", 9000, 1, "int" ); + level thread maps\mp\zombies\_zm_ai_brutus::brutus_spawning_logic(); + + level thread maps\mp\zombies\_zm_ai_brutus::get_brutus_interest_points(); + + level.custom_perk_validation = maps\mp\zombies\_zm_ai_brutus::check_perk_machine_valid; + level.custom_craftable_validation = maps\mp\zombies\_zm_ai_brutus::check_craftable_table_valid; + level.custom_plane_validation = maps\mp\zombies\_zm_ai_brutus::check_plane_valid; +} + +setup_interaction_matrix() +{ + level.interaction_types = []; + + level.interaction_types["magic_box"] = spawnstruct(); + level.interaction_types["magic_box"].priority = 0; + level.interaction_types["magic_box"].animstate = "zm_lock_magicbox"; + level.interaction_types["magic_box"].notify_name = "box_lock_anim"; + level.interaction_types["magic_box"].action_notetrack = "locked"; + level.interaction_types["magic_box"].end_notetrack = "lock_done"; + level.interaction_types["magic_box"].validity_func = ::is_magic_box_valid; + level.interaction_types["magic_box"].get_func = ::get_magic_boxes; + level.interaction_types["magic_box"].value_func = ::get_dist_score; + level.interaction_types["magic_box"].interact_func = ::magic_box_lock; + level.interaction_types["magic_box"].spawn_bias = 1000; + level.interaction_types["magic_box"].num_times_to_scale = 1; + level.interaction_types["magic_box"].unlock_cost = 2000; + + level.interaction_types["perk_machine"] = spawnstruct(); + level.interaction_types["perk_machine"].priority = 1; + level.interaction_types["perk_machine"].animstate = "zm_lock_perk_machine"; + level.interaction_types["perk_machine"].notify_name = "perk_lock_anim"; + level.interaction_types["perk_machine"].action_notetrack = "locked"; + level.interaction_types["perk_machine"].validity_func = ::is_perk_machine_valid; + level.interaction_types["perk_machine"].get_func = ::get_perk_machines; + level.interaction_types["perk_machine"].value_func = ::get_dist_score; + level.interaction_types["perk_machine"].interact_func = ::perk_machine_lock; + level.interaction_types["perk_machine"].spawn_bias = 800; + level.interaction_types["perk_machine"].num_times_to_scale = 3; + level.interaction_types["perk_machine"].unlock_cost = 2000; + + if ( !is_gametype_active( "zgrief" ) ) + { + level.interaction_types["blocker"] = spawnstruct(); + level.interaction_types["blocker"].priority = 5; + level.interaction_types["blocker"].animstate = "zm_smash_blocker"; + level.interaction_types["blocker"].notify_name = "board_smash_anim"; + level.interaction_types["blocker"].action_notetrack = "fire"; + level.interaction_types["blocker"].validity_func = ::is_blocker_valid; + level.interaction_types["blocker"].get_func = ::get_blockers; + level.interaction_types["blocker"].value_func = ::get_dist_score; + level.interaction_types["blocker"].interact_func = ::blocker_smash; + level.interaction_types["blocker"].spawn_bias = 50; + + level.interaction_types["trap"] = spawnstruct(); + level.interaction_types["trap"].priority = 3; + level.interaction_types["trap"].animstate = "zm_smash_trap"; + level.interaction_types["trap"].notify_name = "trap_smash_anim"; + level.interaction_types["trap"].action_notetrack = "fire"; + level.interaction_types["trap"].validity_func = ::is_trap_valid; + level.interaction_types["trap"].get_func = ::get_traps; + level.interaction_types["trap"].value_func = ::get_dist_score; + level.interaction_types["trap"].interact_func = ::trap_smash; + level.interaction_types["trap"].spawn_bias = 400; + level.interaction_types["trap"].interaction_z_offset = -15; + + level.interaction_types["craftable_table"] = spawnstruct(); + level.interaction_types["craftable_table"].priority = 2; + level.interaction_types["craftable_table"].animstate = "zm_smash_craftable_table"; + level.interaction_types["craftable_table"].notify_name = "table_smash_anim"; + level.interaction_types["craftable_table"].action_notetrack = "fire"; + level.interaction_types["craftable_table"].validity_func = ::is_craftable_table_valid; + level.interaction_types["craftable_table"].get_func = ::get_craftable_tables; + level.interaction_types["craftable_table"].value_func = ::get_dist_score; + level.interaction_types["craftable_table"].interact_func = ::craftable_table_lock; + level.interaction_types["craftable_table"].spawn_bias = 600; + level.interaction_types["craftable_table"].num_times_to_scale = 1; + level.interaction_types["craftable_table"].unlock_cost = 2000; + level.interaction_types["craftable_table"].interaction_z_offset = -15; + level.interaction_types["craftable_table"].interaction_yaw_offset = 270; + level.interaction_types["craftable_table"].fx_z_offset = -44; + level.interaction_types["craftable_table"].fx_yaw_offset = 270; + + level.interaction_types["plane_ramp"] = spawnstruct(); + level.interaction_types["plane_ramp"].priority = 4; + level.interaction_types["plane_ramp"].animstate = "zm_lock_plane_ramp"; + level.interaction_types["plane_ramp"].notify_name = "plane_lock_anim"; + level.interaction_types["plane_ramp"].action_notetrack = "locked"; + level.interaction_types["plane_ramp"].end_notetrack = "lock_done"; + level.interaction_types["plane_ramp"].validity_func = ::is_plane_ramp_valid; + level.interaction_types["plane_ramp"].get_func = ::get_plane_ramps; + level.interaction_types["plane_ramp"].value_func = ::get_dist_score; + level.interaction_types["plane_ramp"].interact_func = ::plane_ramp_lock; + level.interaction_types["plane_ramp"].spawn_bias = 500; + level.interaction_types["plane_ramp"].num_times_to_scale = 3; + level.interaction_types["plane_ramp"].unlock_cost = 2000; + level.interaction_types["plane_ramp"].interaction_z_offset = -60; + level.interaction_types["plane_ramp"].fx_z_offset = -60; + level.interaction_types["plane_ramp"].fx_x_offset = 70; + level.interaction_types["plane_ramp"].fx_yaw_offset = 90; + } + + level.interaction_priority = []; + interaction_types = getarraykeys( level.interaction_types ); + + for ( i = 0; i < interaction_types.size; i++ ) + { + int_type = interaction_types[i]; + interaction = level.interaction_types[int_type]; + assert( !isdefined( level.interaction_priority[interaction.priority] ) ); + level.interaction_priority[interaction.priority] = int_type; + } +} + +brutus_find_flesh() +{ + self endon( "death" ); + level endon( "intermission" ); + + if ( level.intermission ) + return; + + self.ai_state = "idle"; + self.helitarget = 1; + self.ignoreme = 0; + self.nododgemove = 1; + self.ignore_player = []; + self thread brutus_watch_for_gondola(); + self thread brutus_stuck_watcher(); + self thread brutus_goal_watcher(); + self thread watch_for_player_dist(); + + while ( true ) + { + if ( self.not_interruptable ) + { + wait 0.05; + continue; + } + + player = brutus_get_closest_valid_player(); + brutus_zone = get_zone_from_position( self.origin ); + + if ( !isdefined( brutus_zone ) ) + { + brutus_zone = self.prev_zone; + + if ( !isdefined( brutus_zone ) ) + { + wait 1; + continue; + } + } + + player_zone = undefined; + self.prev_zone = brutus_zone; + + if ( !isdefined( player ) ) + self.priority_item = self get_priority_item_for_brutus( brutus_zone, 1 ); + else + { + player_zone = player get_player_zone(); + + if ( isdefined( player_zone ) ) + self.priority_item = self get_priority_item_for_brutus( player_zone ); + else + self.priority_item = self get_priority_item_for_brutus( brutus_zone, 1 ); + } + + if ( isdefined( player ) && distancesquared( self.origin, player.origin ) < level.brutus_aggro_dist_sq && isdefined( player_zone ) && should_brutus_aggro( player_zone, brutus_zone ) ) + { + self.favorite_enemy = player; + self.goal_pos = player.origin; + brutus_start_basic_find_flesh(); + } + else if ( isdefined( self.priority_item ) ) + { + brutus_stop_basic_find_flesh(); + self.goalradius = 12; + self.custom_goalradius_override = 12; + self.goal_pos = self get_interact_offset( self.priority_item, self.ai_state ); + self setgoalpos( self.goal_pos ); + } + else if ( isdefined( player ) ) + { + self.favorite_enemy = player; + self.goal_pos = self.favorite_enemy.origin; + brutus_start_basic_find_flesh(); + } + else + { + self.goal_pos = self.origin; + self.ai_state = "idle"; + self setanimstatefromasd( "zm_idle" ); + self setgoalpos( self.goal_pos ); + } + + wait 1; + } +} + +get_brutus_spawn_pos_val( brutus_pos ) +{ + score = 0; + zone_name = brutus_pos.zone_name; + + if ( !maps\mp\zombies\_zm_zonemgr::zone_is_enabled( zone_name ) ) + return 0; + + a_players_in_zone = get_players_in_zone( zone_name, 1 ); + + if ( a_players_in_zone.size == 0 ) + return 0; + else + { + n_score_addition = 1; + + for ( i = 0; i < a_players_in_zone.size; i++ ) + { + if ( findpath( brutus_pos.origin, a_players_in_zone[i].origin, self, 0, 0 ) ) + { + n_dist = distance2d( brutus_pos.origin, a_players_in_zone[i].origin ); + n_score_addition += linear_map( n_dist, 2000, 0, 0, level.brutus_players_in_zone_spawn_point_cap ); + } + } + + if ( n_score_addition > level.brutus_players_in_zone_spawn_point_cap ) + n_score_addition = level.brutus_players_in_zone_spawn_point_cap; + + score += n_score_addition; + } + + interaction_types = getarraykeys( level.interaction_types ); + interact_array = level.interaction_types; + + for ( i = 0; i < interaction_types.size; i++ ) + { + int_type = interaction_types[i]; + interaction = interact_array[int_type]; + interact_points = [[ interaction.get_func ]]( zone_name ); + + for ( j = 0; j < interact_points.size; j++ ) + { + if ( interact_points[j] [[ interaction.validity_func ]]() ) + score += interaction.spawn_bias; + } + } + + return score; +} + brutus_spawn( starting_health, has_helmet, helmet_hits, explosive_dmg_taken, zone_name ) { level.num_pulls_since_brutus_spawn = 0; diff --git a/scripts/zm/zm_prison/zm_prison_reimagined.gsc b/scripts/zm/zm_prison/zm_prison_reimagined.gsc index b7fca6f1..d40467f0 100644 --- a/scripts/zm/zm_prison/zm_prison_reimagined.gsc +++ b/scripts/zm/zm_prison/zm_prison_reimagined.gsc @@ -22,6 +22,8 @@ main() replaceFunc(maps\mp\zm_alcatraz_utility::blundergat_upgrade_station, scripts\zm\replaced\zm_alcatraz_utility::blundergat_upgrade_station); replaceFunc(maps\mp\zm_alcatraz_weap_quest::grief_soul_catcher_state_manager, scripts\zm\replaced\zm_alcatraz_weap_quest::grief_soul_catcher_state_manager); replaceFunc(maps\mp\zombies\_zm_afterlife::afterlife_add, scripts\zm\replaced\_zm_afterlife::afterlife_add); + replaceFunc(maps\mp\zombies\_zm_ai_brutus::init, scripts\zm\replaced\_zm_ai_brutus::init); + replaceFunc(maps\mp\zombies\_zm_ai_brutus::get_brutus_spawn_pos_val, scripts\zm\replaced\_zm_ai_brutus::get_brutus_spawn_pos_val); replaceFunc(maps\mp\zombies\_zm_ai_brutus::brutus_spawn, scripts\zm\replaced\_zm_ai_brutus::brutus_spawn); replaceFunc(maps\mp\zombies\_zm_ai_brutus::brutus_health_increases, scripts\zm\replaced\_zm_ai_brutus::brutus_health_increases); replaceFunc(maps\mp\zombies\_zm_ai_brutus::brutus_cleanup_at_end_of_grief_round, scripts\zm\replaced\_zm_ai_brutus::brutus_cleanup_at_end_of_grief_round);