// ---------------------------------------------------------------------------- // #using // ---------------------------------------------------------------------------- #using scripts\codescripts\struct; #using scripts\shared\_oob; #using scripts\shared\callbacks_shared; #using scripts\shared\clientfield_shared; #using scripts\shared\gameskill_shared; #using scripts\shared\math_shared; #using scripts\shared\scoreevents_shared; #using scripts\shared\statemachine_shared; #using scripts\shared\system_shared; #using scripts\shared\array_shared; #using scripts\shared\util_shared; #using scripts\shared\vehicle_shared; #using scripts\shared\vehicle_death_shared; #using scripts\shared\vehicle_ai_shared; #using scripts\shared\ai\systems\blackboard; #using scripts\shared\ai\blackboard_vehicle; #using scripts\shared\turret_shared; #using scripts\shared\weapons\_spike_charge_siegebot; #using scripts\mp\killstreaks\_killstreaks; #using scripts\mp\killstreaks\_killstreak_bundles; #using scripts\mp\gametypes\_loadout; // number similiarly to Gunner1, Gunner2, etc. // relative to right axis // relative to forward // relative to forward // relative to forward // relative to forward // relative to forward // relative to forward // to re-aim when rocket will shoot through a wall // how many frames not check wall block after becoming blocked #namespace siegebot; function autoexec __init__sytem__() { system::register("siegebot_mp",&__init__,undefined,undefined); } #using_animtree( "generic" ); function __init__() { vehicle::add_main_callback( "siegebot_mp", &siegebot_initialize ); clientfield::register( "vehicle", "siegebot_retract_right_arm", 15000, 1, "int" ); clientfield::register( "vehicle", "siegebot_retract_left_arm", 15000, 1, "int" ); callback::on_disconnect( &on_player_disconnected ); } function siegebot_initialize() { self useanimtree( #animtree ); blackboard::CreateBlackBoardForEntity( self ); self Blackboard::RegisterVehicleBlackBoardAttributes(); self.health = self.healthdefault; self.spawnTime = GetTime(); self.is_oob_kill_target = true; self.isStunned = false; self.missiles_disabled = false; self.numberRockets = 3; self vehicle::friendly_fire_shield(); //Target_Set( self, ( 0, 0, 84 ) ); self.targetOffset = ( 0, 0, 84 ); self EnableAimAssist(); self SetNearGoalNotifyDist( 40 ); self.fovcosine = 0.5; // +/-60 degrees = 120 fov self.fovcosinebusy = 0.5; self.maxsightdistsqrd = ( (10000) * (10000) ); assert( isdefined( self.scriptbundlesettings ) ); self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings ); self.goalRadius = 9999999; self.goalHeight = 5000; self SetGoal( self.origin, false, self.goalRadius, self.goalHeight ); self.overrideVehicleDamage = &siegebot_callback_damage; self siegebot_update_difficulty(); self SetGunnerTurretOnTargetRange( 0, self.settings.gunner_turret_on_target_range ); self ASMRequestSubstate( "locomotion@movement" ); if( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { self ASMSetAnimationRate( 0.5 ); self HidePart( "tag_turret_canopy_animate" ); self HidePart( "tag_turret_panel_01_d0" ); self HidePart( "tag_turret_panel_02_d0" ); self HidePart( "tag_turret_panel_03_d0" ); self HidePart( "tag_turret_panel_04_d0" ); self HidePart( "tag_turret_panel_05_d0" ); } else if( self.vehicletype == "zombietron_veh_siegebot" ) { self ASMSetAnimationRate( 1.429 ); } self initJumpStruct(); //disable some cybercom abilities if( IsDefined( level.vehicle_initializer_cb ) ) { [[level.vehicle_initializer_cb]]( self ); } self.ignoreFireFly = true; self.ignoreDecoy = true; self vehicle_ai::InitThreatBias(); self thread vehicle_ai::target_hijackers(); self.ignoreme = true; self.killstreakType = "siegebot"; killstreak_bundles::register_killstreak_bundle( self.killstreakType ); self.maxhealth = killstreak_bundles::get_max_health( self.killstreakType ); self.heatlh = self.maxhealth; self thread monitor_enter_exit_vehicle(); self thread watch_game_ended(); self thread watch_emped(); self thread watch_death(); } function siegebot_update_difficulty() { value = gameskill::get_general_difficulty_level(); scale_up = mapfloat( 0, 9, 0.8, 2.0, value ); scale_down = mapfloat( 0, 9, 1.0, 0.5, value ); self.difficulty_scale_up = scale_up; self.difficulty_scale_down = scale_down; } function defaultRole() { self vehicle_ai::init_state_machine_for_role( "default" ); self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update; self vehicle_ai::get_state_callbacks( "combat" ).exit_func = &state_combat_exit; self vehicle_ai::get_state_callbacks( "driving" ).update_func = &siegebot_driving; self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update; self vehicle_ai::get_state_callbacks( "pain" ).update_func = &pain_update; self vehicle_ai::get_state_callbacks( "emped" ).enter_func = &emped_enter; self vehicle_ai::get_state_callbacks( "emped" ).update_func = &emped_update; self vehicle_ai::get_state_callbacks( "emped" ).exit_func = &emped_exit; self vehicle_ai::get_state_callbacks( "emped" ).reenter_func = &emped_reenter; self vehicle_ai::add_state( "jump", &state_jump_enter, &state_jump_update, &state_jump_exit ); vehicle_ai::add_utility_connection( "combat", "jump", &state_jump_can_enter ); vehicle_ai::add_utility_connection( "jump", "combat" ); self vehicle_ai::add_state( "unaware", undefined, &state_unaware_update, undefined ); // don't use this for now unless we really need it vehicle_ai::StartInitialState( "combat" ); } // ---------------------------------------------- // State: death // ---------------------------------------------- function state_death_update( params ) { self endon( "death" ); self endon( "nodeath_thread" ); // Need to prep the death model StreamerModelHint( self.deathmodel, 6 ); death_type = vehicle_ai::get_death_type( params ); if ( !isdefined( death_type ) ) { params.death_type = "gibbed"; death_type = params.death_type; } self clean_up_spawned(); self SetTurretSpinning( false ); self stopMovementAndSetBrake(); self vehicle::set_damage_fx_level( 0 ); self playsound("veh_quadtank_sparks"); if ( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { self ASMSetAnimationRate( 1.0 ); } self.turretRotScale = 3; self SetTurretTargetRelativeAngles( (0,0,0), 0 ); self SetTurretTargetRelativeAngles( (0,0,0), 1 ); self SetTurretTargetRelativeAngles( (0,0,0), 2 ); self ASMRequestSubstate( "death@stationary" ); self waittill( "model_swap" ); // give the streamer time to load self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay ); self vehicle::do_death_dynents(); self vehicle_death::death_radius_damage(); self waittill( "bodyfall large" ); // falling damage self RadiusDamage( self.origin + (0,0,10), self.radius * 0.8, 150, 60, self, "MOD_CRUSH" ); //BadPlace_Box( "", 0, self.origin, 50, "neutral" ); vehicle_ai::waittill_asm_complete( "death@stationary", 3 ); self thread vehicle_death::CleanUp(); self vehicle_death::FreeWhenSafe(); } // ---------------------------------------------- // State: scripted // ---------------------------------------------- function siegebot_driving( params ) { self thread siegebot_player_fireupdate(); self thread siegebot_kill_on_tilting(); self ClearTargetEntity(); self CancelAIMove(); self ClearVehGoalPos(); } function siegebot_kill_on_tilting() { self endon( "death" ); self endon( "exit_vehicle" ); tileCount = 0; while( 1 ) { selfup = AnglesToUp( self.angles ); worldup = ( 0, 0, 1 ); if ( VectorDot( selfup, worldup ) < 0.64 ) // angle diff more than 50 degree { tileCount += 1; } else { tileCount = 0; } if ( tileCount > 20 ) // more than 1 full second { driver = self GetSeatOccupant( 0 ); self Kill( self.origin ); } {wait(.05);}; } } function siegebot_player_fireupdate() { self endon( "death" ); self endon( "exit_vehicle" ); weapon = self SeatGetWeapon( 2 ); driver = self GetSeatOccupant( 0 ); if ( !isdefined( driver ) ) return; self thread siegebot_player_aimUpdate(); while( isdefined( driver ) ) { if( driver FragButtonPressed() && !self.missiles_disabled ) { self FireWeapon( 2 ); wait weapon.fireTime; } else { {wait(.05);}; } } } function siegebot_player_aimUpdate() { self endon( "death" ); self endon( "exit_vehicle" ); rocket_wall_blocked_count = 0; use_old_trace = true; while( 1 ) { if ( rocket_wall_blocked_count == 0 && self does_rocket_shoot_through_wall( use_old_trace ) ) { rocket_wall_blocked_count = 10; use_old_trace = true; } if ( rocket_wall_blocked_count > 0 ) { aim_origin = self GetTagOrigin( "tag_turret" ); ref_angles = self GetTagAngles( "tag_turret" ); forward = AnglesToForward( ref_angles ); right = AnglesToRight( ref_angles ); aim_origin += ( forward * 100 ) + ( right * 40 ); // util::debug_sphere( aim_origin, 12, ( 1, 0, 0 ), 0.5, 1 ); aim_origin += ( 0, 0, 500 ); // aim down too self SetGunnerTargetVec( aim_origin, 2 - 1 ); rocket_wall_blocked_count--; } else { self SetGunnerTargetVec( self GetGunnerTargetVec( 0 ), 2 - 1 ); use_old_trace = false; } {wait(.05);}; } } // State: scripted ---------------------------------- // ---------------------------------------------- // State: emped // ---------------------------------------------- function emped_enter( params ) { if ( !isdefined( self.abnormal_status ) ) { self.abnormal_status = spawnStruct(); } self.abnormal_status.emped = true; self.abnormal_status.attacker = params.notify_param[1]; self.abnormal_status.inflictor = params.notify_param[2]; self vehicle::toggle_emp_fx( true ); } function emped_update( params ) { self endon( "death" ); self endon( "change_state" ); self stopMovementAndSetBrake(); if ( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { self ASMSetAnimationRate( 1.0 ); } asmState = "damage_2@pain"; self ASMRequestSubstate( asmState ); self vehicle_ai::waittill_asm_complete( asmState, 3 ); self SetBrake( 0 ); self vehicle_ai::evaluate_connections(); } function emped_exit( params ) { } function emped_reenter( params ) { return false; } // State: emped ---------------------------------- // ---------------------------------------------- // State: pain // ---------------------------------------------- function pain_toggle( enabled ) { self._enablePain = enabled; } function pain_update( params ) { self endon( "death" ); self endon( "change_state" ); self stopMovementAndSetBrake(); if ( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { self ASMSetAnimationRate( 1.0 ); } if ( self.newDamageLevel == 3 ) { asmState = "damage_2@pain"; } else { asmState = "damage_1@pain"; } self ASMRequestSubstate( asmState ); self vehicle_ai::waittill_asm_complete( asmState, 1.5 ); self SetBrake( 0 ); self vehicle_ai::evaluate_connections(); } // State: pain ---------------------------------- // ---------------------------------------------- // State: unaware // ---------------------------------------------- function state_unaware_update( params ) { self endon( "death" ); self endon( "change_state" ); self SetTurretTargetRelativeAngles( (0,90,0), 1 ); self SetTurretTargetRelativeAngles( (0,90,0), 2 ); self thread Movement_Thread_Unaware(); while ( true ) { self vehicle_ai::evaluate_connections(); wait 1; } } function Movement_Thread_Unaware() { self endon( "death" ); self endon( "change_state" ); self notify( "end_movement_thread" ); self endon( "end_movement_thread" ); while( true ) { self.current_pathto_pos = self GetNextMovePosition_unaware(); foundpath = self SetVehGoalPos( self.current_pathto_pos, false, true ); if ( foundPath ) { locomotion_start(); self thread path_update_interrupt(); self vehicle_ai::waittill_pathing_done(); self notify( "near_goal" ); // kill path_update_interrupt thread self CancelAIMove(); self ClearVehGoalPos(); Scan(); } else { wait 1; } {wait(.05);}; } } function GetNextMovePosition_unaware() { if( self.goalforced ) { return self.goalpos; } minSearchRadius = 500; maxSearchRadius = 1500; halfHeight = 400; spacing = 80; queryResult = PositionQuery_Source_Navigation( self.origin, minSearchRadius, maxSearchRadius, halfHeight, spacing, self, spacing ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); forward = AnglesToForward( self.angles ); foreach ( point in queryResult.data ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "random" ] = randomFloatRange( 0, 30 ); #/ point.score += randomFloatRange( 0, 30 );; pointDirection = VectorNormalize( point.origin - self.origin ); factor = VectorDot( pointDirection, forward ); if ( factor > 0.7 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionDiff" ] = 600; #/ point.score += 600;; } else if ( factor > 0 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionDiff" ] = 0; #/ point.score += 0;; } else if ( factor > -0.5 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionDiff" ] = -600; #/ point.score += -600;; } else { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionDiff" ] = -1200; #/ point.score += -1200;; } } vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult ); self vehicle_ai::PositionQuery_DebugScores( queryResult ); if( queryResult.data.size == 0 ) return self.origin; return queryResult.data[0].origin; } // ---------------------------------------------- // State: jump // ---------------------------------------------- function clean_up_spawned() { if ( isdefined( self.jump ) && isDefined(self.jump.linkEnt) ) { self.jump.linkEnt Delete(); } } function clean_up_spawnedOnDeath(entToWatch) { self endon("death"); entToWatch waittill("death"); self delete(); } function initJumpStruct() { if ( isdefined( self.jump ) ) { self Unlink(); self.jump.linkEnt Delete(); self.jump Delete(); } self.jump = spawnstruct(); self.jump.linkEnt = Spawn( "script_origin", self.origin ); self.jump.linkEnt thread clean_up_spawnedOnDeath(self); self.jump.in_air = false; self.jump.highgrounds = struct::get_array( "balcony_point" ); self.jump.groundpoints = struct::get_array( "ground_point" ); //assert( self.jump.highgrounds.size > 0 ); //assert( self.jump.groundpoints.size > 0 ); } function state_jump_can_enter( from_state, to_state, connection ) { if(( isdefined( self.noJumping ) && self.noJumping )) return false; return ( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ); } function state_jump_enter( params ) { goal = params.jumpgoal; trace = PhysicsTrace( goal + ( 0, 0, 500 ), goal - ( 0, 0, 10000 ), ( -10, -10, -10 ), ( 10, 10, 10 ), self, (1 << 1) ); if ( false ) { /#debugstar( goal, 60000, (0,1,0) ); #/ /#debugstar( trace[ "position" ], 60000, (0,1,0) ); #/ /#line(goal, trace[ "position" ], (0,1,0), 1, false, 60000 ); #/ } if ( trace[ "fraction" ] < 1 ) { goal = trace[ "position" ]; } self.jump.goal = goal; params.scaleForward = 40; params.gravityForce = (0, 0, -6); params.upByHeight = 50; params.landingState = "land@jump"; self pain_toggle( false ); self stopMovementAndSetBrake(); } function state_jump_update( params ) { self endon( "change_state" ); self endon( "death" ); goal = self.jump.goal; self face_target( goal ); self.jump.linkEnt.origin = self.origin; self.jump.linkEnt.angles = self.angles; {wait(.05);}; self LinkTo( self.jump.linkEnt ); self.jump.in_air = true; if ( false ) { /#debugstar( goal, 60000, (0,1,0) ); #/ /#debugstar( goal + (0,0,100), 60000, (0,1,0) ); #/ /#line(goal, goal + (0,0,100), (0,1,0), 1, false, 60000 ); #/ } // calculate distance and forces totalDistance = Distance2D(goal, self.jump.linkEnt.origin); forward = ( ((goal - self.jump.linkEnt.origin) / totalDistance)[0], ((goal - self.jump.linkEnt.origin) / totalDistance)[1], 0 ); upByDistance = MapFloat( 500, 2000, 46, 52, totalDistance ); antiGravityByDistance = 0;//MapFloat( 500, 2000, 0, 0.5, totalDistance ); initVelocityUp = (0,0,1) * ( upByDistance + params.upByHeight ); initVelocityForward = forward * params.scaleForward * MapFloat( 500, 2000, 0.8, 1, totalDistance ); velocity = initVelocityUp + initVelocityForward; // start jumping if ( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { self ASMSetAnimationRate( 1.0 ); } self ASMRequestSubstate( "inair@jump" ); self waittill( "engine_startup" ); self vehicle::impact_fx( self.settings.startupfx1 ); self waittill( "leave_ground" ); self vehicle::impact_fx( self.settings.takeofffx1 ); while( true ) { distanceToGoal = Distance2D(self.jump.linkEnt.origin, goal); antiGravityScaleUp = 1.0;//MapFloat( 0, 0.5, 0.6, 0, abs( 0.5 - distanceToGoal / totalDistance ) ); antiGravityScale = 1.0;//MapFloat( (self.radius * 1.0), (self.radius * 3), 0, 1, distanceToGoal ); antiGravity = (0,0,0);//antiGravityScale * antiGravityScaleUp * (-params.gravityForce) + (0,0,antiGravityByDistance); if ( false ) /#line(self.jump.linkEnt.origin, self.jump.linkEnt.origin + antiGravity, (0,1,0), 1, false, 60000 ); #/ velocityForwardScale = MapFloat( (self.radius * 1), (self.radius * 4), 0.2, 1, distanceToGoal ); velocityForward = initVelocityForward * velocityForwardScale; if ( false ) /#line(self.jump.linkEnt.origin, self.jump.linkEnt.origin + velocityForward, (0,1,0), 1, false, 60000 ); #/ oldVerticleSpeed = velocity[2]; velocity = (0,0, velocity[2]); velocity += velocityForward + params.gravityForce + antiGravity; if ( oldVerticleSpeed > 0 && velocity[2] < 0 ) { self ASMRequestSubstate( "fall@jump" ); } if ( velocity[2] < 0 && self.jump.linkEnt.origin[2] + velocity[2] < goal[2] ) { break; } heightThreshold = goal[2] + 110; oldHeight = self.jump.linkEnt.origin[2]; self.jump.linkEnt.origin += velocity; if ( self.jump.linkEnt.origin[2] < heightThreshold && ( oldHeight > heightThreshold || ( oldVerticleSpeed > 0 && velocity[2] < 0 ) ) ) { self notify( "start_landing" ); self ASMRequestSubstate( params.landingState ); } if ( false ) /#debugstar( self.jump.linkEnt.origin, 60000, (1,0,0) ); #/ {wait(.05);}; } // landed self.jump.linkEnt.origin = ( self.jump.linkEnt.origin[0], self.jump.linkEnt.origin[1], 0 ) + ( 0, 0, goal[2] ); self notify( "land_crush" ); // don't damage player, but crush player vehicle foreach( player in level.players ) { player._takedamage_old = player.takedamage; player.takedamage = false; } self RadiusDamage( self.origin + ( 0,0,15 ), self.radiusdamageradius, self.radiusdamagemax, self.radiusdamagemin, self, "MOD_EXPLOSIVE" ); foreach( player in level.players ) { player.takedamage = player._takedamage_old; player._takedamage_old = undefined; if ( Distance2DSquared( self.origin, player.origin ) < ( (200) * (200) ) ) { direction = ( ( player.origin - self.origin )[0], ( player.origin - self.origin )[1], 0 ); if ( Abs( direction[0] ) < 0.01 && Abs( direction[1] ) < 0.01 ) { direction = ( RandomFloatRange( 1, 2 ), RandomFloatRange( 1, 2 ), 0 ); } direction = VectorNormalize( direction ); strength = 700; player SetVelocity( player GetVelocity() + direction * strength ); if ( player.health > 80 ) { player DoDamage( player.health - 70, self.origin, self ); } } } self vehicle::impact_fx( self.settings.landingfx1 ); self stopMovementAndSetBrake(); //rumble for landing from jump //self clientfield::increment( "sarah_rumble_on_landing" ); wait 0.3; self Unlink(); {wait(.05);}; self.jump.in_air = false; self notify ( "jump_finished" ); vehicle_ai::Cooldown( "jump", 7 ); self vehicle_ai::waittill_asm_complete( params.landingState, 3 ); self vehicle_ai::evaluate_connections(); } function state_jump_exit( params ) { } // ---------------------------------------------- // State: combat // ---------------------------------------------- function state_combat_update( params ) { self endon( "death" ); self endon( "change_state" ); self thread Movement_Thread(); self thread Attack_Thread_machinegun(); self thread Attack_Thread_rocket(); } function state_combat_exit( params ) { self ClearTurretTarget(); self SetTurretSpinning( false ); } function locomotion_start() { if( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { self ASMSetAnimationRate( 0.5 ); } self ASMRequestSubstate( "locomotion@movement" ); } function GetNextMovePosition_tactical() { if( self.goalforced ) { return self.goalpos; } maxSearchRadius = 800; halfHeight = 400; innerSpacing = 50; outerSpacing = 60; queryResult = PositionQuery_Source_Navigation( self.origin, 0, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); if( isdefined( self.enemy ) ) { PositionQuery_Filter_Sight( queryResult, self.enemy.origin, self GetEye() - self.origin, self, 0, self.enemy ); self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, self.enemy, self.settings.engagementDistMin, self.settings.engagementDistMax ); } foreach ( point in queryResult.data ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "random" ] = randomFloatRange( 0, 30 ); #/ point.score += randomFloatRange( 0, 30 );; if( point.disttoorigin2d < 120 ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "tooCloseToSelf" ] = (120 - point.disttoorigin2d) * -1.5; #/ point.score += (120 - point.disttoorigin2d) * -1.5;; } if( isdefined( self.enemy ) ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "engagementDist" ] = -point.distAwayFromEngagementArea; #/ point.score += -point.distAwayFromEngagementArea;; if ( !point.visibility ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "visibility" ] = -600; #/ point.score += -600;; } } } vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult ); self vehicle_ai::PositionQuery_DebugScores( queryResult ); if( queryResult.data.size == 0 ) return self.origin; return queryResult.data[0].origin; } function path_update_interrupt() { self endon( "death" ); self endon( "change_state" ); self endon( "near_goal" ); self endon( "reached_end_node" ); canSeeEnemyCount = 0; old_enemy = self.enemy; startPath = GetTime(); // assume we just started a new path old_origin = self.origin; move_dist = 300; wait 1.5; while( 1 ) { self SetMaxSpeedScale( 1 ); self SetMaxAccelerationScale( 1 ); self SetSpeed( self.settings.defaultMoveSpeed ); if ( isdefined( self.enemy ) ) { selfDistToTarget = Distance2D( self.origin, self.enemy.origin ); farEngagementDist = self.settings.engagementDistMax + 150; closeEngagementDist = self.settings.engagementDistMin - 150; if( self VehCanSee( self.enemy ) ) { self SetLookAtEnt( self.enemy ); // try to keep the basic orientation towards the enemy self SetTurretTargetEnt( self.enemy ); // check the distance so we don't trigger a new path when we are already moving if( selfDistToTarget < farEngagementDist && selfDistToTarget > closeEngagementDist ) { canSeeEnemyCount++; // Stop if we can see our enemy for a bit if( canSeeEnemyCount > 3 && ( vehicle_ai::TimeSince( startPath ) > 5 || Distance2DSquared( old_origin, self.origin ) > ( (move_dist) * (move_dist) ) ) ) { self notify( "near_goal" ); } } else { // too far go fast self SetMaxSpeedScale( 2.5 ); self SetMaxAccelerationScale( 3 ); self SetSpeed( self.settings.defaultMoveSpeed * 2 ); } } else if( (!self VehSeenRecently( self.enemy, 1.5 ) && self VehSeenRecently( self.enemy, 15 )) || selfDistToTarget > farEngagementDist ) // move fast if we just lost sight of our target or we are too far { self SetMaxSpeedScale( 1.8 ); self SetMaxAccelerationScale( 2 ); self SetSpeed( self.settings.defaultMoveSpeed * 1.5 ); } } else { canSeeEnemyCount = 0; } if ( isdefined( self.enemy ) ) { if( !isdefined( old_enemy ) ) { self notify( "near_goal" ); // new enemy } else if( self.enemy != old_enemy ) { self notify( "near_goal" ); // new enemy } if( self VehCanSee( self.enemy ) && distance2dSquared( self.origin, self.enemy.origin ) < ( (150) * (150) ) && Distance2DSquared( old_origin, self.enemy.origin ) > ( (151) * (151) ) ) // don't walk pass the player { self notify( "near_goal" ); } } wait 0.2; } } function weapon_doors_state( isOpen, waittime = 0 ) { self endon( "death" ); self notify( "weapon_doors_state" ); self endon( "weapon_doors_state" ); if ( isdefined( waittime ) && waittime > 0 ) { wait waittime; } self vehicle::toggle_ambient_anim_group( 1, isOpen ); } function Movement_Thread() { self endon( "death" ); self endon( "change_state" ); self notify( "end_movement_thread" ); self endon( "end_movement_thread" ); while ( 1 ) { self.current_pathto_pos = self GetNextMovePosition_tactical(); if( self.vehicletype === "spawner_enemy_boss_siegebot_zombietron" ) { if ( vehicle_ai::IsCooldownReady( "jump" ) ) { params = SpawnStruct(); params.jumpgoal = self.current_pathto_pos; locomotion_start(); wait 0.5; self vehicle_ai::evaluate_connections( undefined, params ); wait 0.5; } } foundpath = self SetVehGoalPos( self.current_pathto_pos, false, true ); if ( foundPath ) { if ( isdefined( self.enemy ) && self VehSeenRecently( self.enemy, 1 ) ) { self SetLookAtEnt( self.enemy ); self SetTurretTargetEnt( self.enemy ); } locomotion_start(); self thread path_update_interrupt(); self vehicle_ai::waittill_pathing_done(); self notify( "near_goal" ); // kill path_update_interrupt thread self CancelAIMove(); self ClearVehGoalPos(); if ( isdefined( self.enemy ) && self VehSeenRecently( self.enemy, 2 ) ) { self face_target( self.enemy.origin ); } } wait 1; startAdditionalWaiting = GetTime(); while ( isdefined( self.enemy ) && self VehCanSee( self.enemy ) && vehicle_ai::TimeSince( startAdditionalWaiting ) < 1.5 ) { wait 0.4; } } } function stopMovementAndSetBrake() { self notify( "end_movement_thread" ); self notify( "near_goal" ); self CancelAIMove(); self ClearVehGoalPos(); self ClearTurretTarget(); self ClearLookAtEnt(); self SetBrake( 1 ); } function face_target( position, targetAngleDiff ) { if ( !isdefined( targetAngleDiff ) ) { targetAngleDiff = 30; } v_to_enemy = ( (position - self.origin)[0], (position - self.origin)[1], 0 ); v_to_enemy = VectorNormalize( v_to_enemy ); goalAngles = VectortoAngles( v_to_enemy ); angleDiff = AbsAngleClamp180( self.angles[1] - goalAngles[1] ); if ( angleDiff <= targetAngleDiff ) { return; } self SetLookAtOrigin( position ); self SetTurretTargetVec( position ); self locomotion_start(); angleAdjustingStart = GetTime(); while( angleDiff > targetAngleDiff && vehicle_ai::TimeSince( angleAdjustingStart ) < 4 ) { angleDiff = AbsAngleClamp180( self.angles[1] - goalAngles[1] ); {wait(.05);}; } self ClearVehGoalPos(); self ClearLookAtEnt(); self ClearTurretTarget(); self CancelAIMove(); } function Scan() { angles = self GetTagAngles( "tag_barrel" ); angles = (0,angles[1],0); // get rid of pitch rotate = 360; while( rotate > 0 ) { angles += (0,30,0); rotate -= 30; forward = AnglesToForward( angles ); aimpos = self.origin + forward * 1000; self SetTurretTargetVec( aimpos ); msg = self util::waittill_any_timeout( 0.5, "turret_on_target" ); wait 0.1; if( isdefined( self.enemy ) && self VehCanSee( self.enemy ) ) // use VehCanSee, if recently attacked this will return true and not use FOV check { self SetTurretTargetEnt( self.enemy ); self SetLookAtEnt( self.enemy ); self face_target( self.enemy ); return; } } // return the turret to forward forward = AnglesToForward( self.angles ); aimpos = self.origin + forward * 1000; self SetTurretTargetVec( aimpos ); msg = self util::waittill_any_timeout( 3.0, "turret_on_target" ); self ClearTurretTarget(); } function Attack_Thread_machinegun() { self endon( "death" ); self endon( "change_state" ); self endon( "end_attack_thread" ); self notify( "end_machinegun_attack_thread" ); self endon( "end_machinegun_attack_thread" ); self.turretrotscale = 1 * self.difficulty_scale_up; spinning = false; while( 1 ) { if( isdefined( self.enemy ) && self VehCanSee( self.enemy ) ) { self SetLookAtEnt( self.enemy ); self SetTurretTargetEnt( self.enemy ); if ( !spinning ) { spinning = true; self SetTurretSpinning( true ); wait 0.5; continue; } self SetGunnerTargetEnt( self.enemy, (0,0,0), 0 ); self SetGunnerTargetEnt( self.enemy, (0,0,0), 1 ); self vehicle_ai::fire_for_time( RandomFloatRange( 0.75, 1.5 ) * self.difficulty_scale_up, 1 ); if( isdefined( self.enemy ) && IsAI( self.enemy ) ) { wait( RandomFloatRange( 0.1, 0.2 ) ); } else { wait( RandomFloatRange( 0.2, 0.3 ) ) * self.difficulty_scale_down; } } else { spinning = false; self SetTurretSpinning( false ); self ClearGunnerTarget( 0 ); self ClearGunnerTarget( 1 ); wait 0.4; } } } function Attack_Rocket( target ) { if ( isdefined( target ) ) { self SetTurretTargetEnt( target ); self SetGunnerTargetEnt( target, (0,0,-10), 2 ); msg = self util::waittill_any_timeout( 1, "turret_on_target" ); self FireWeapon( 2, target, (0,0,-10) ); self ClearGunnerTarget( 1 ); } } function Attack_Thread_Rocket() { self endon( "death" ); self endon( "change_state" ); self endon( "end_attack_thread" ); self notify( "end_rocket_attack_thread" ); self endon( "end_rocket_attack_thread" ); vehicle_ai::Cooldown( "rocket", 3 ); while( 1 ) { if ( isdefined( self.enemy ) && self VehSeenRecently( self.enemy, 3 ) && ( vehicle_ai::IsCooldownReady( "rocket", 1.5 ) ) ) { self SetGunnerTargetEnt( self.enemy, (0,0,0), 0 ); self SetGunnerTargetEnt( self.enemy, (0,0,-10), 2 ); self thread weapon_doors_state( true ); wait 1.5; if ( isdefined( self.enemy ) && self VehSeenRecently( self.enemy, 1 ) ) { vehicle_ai::Cooldown( "rocket", 5 ); Attack_Rocket( self.enemy ); wait 1; if ( isdefined( self.enemy ) ) { Attack_Rocket( self.enemy ); } self thread weapon_doors_state( false, 1 ); } else { self thread weapon_doors_state( false ); } } else { self ClearGunnerTarget( 0 ); self ClearGunnerTarget( 1 ); wait 0.4; } } } function monitor_enter_exit_vehicle() { self endon( "death" ); player = undefined; while( 1 ) { self vehicle_unoccupied( player ); self waittill( "enter_vehicle", player ); self vehicle_occupied( player ); self waittill( "exit_vehicle", player ); } } function vehicle_occupied( player ) { self clientfield::set( "enemyvehicle", 1 ); /# // self thread arm_test(); // if testing, comment out watch_left_arm() and watch_right_arm() below #/ self.ignoreme = false; self thread siegebot_player_fireupdate(); self thread weapon_doors_state( true ); self thread watch_left_arm(); self thread watch_right_arm(); if ( IsPlayer( player ) ) { player.using_map_vehicle = true; player.current_map_vehicle = self; player.ignoreme = true; self.current_driver = player; player SetClientUIVisibilityFlag( "weapon_hud_visible", 0 ); player vehicle::update_damage_as_occupant( self.maxhealth - self.health, self.maxhealth ); player DisableWeaponCycling(); self thread watch_rockets( player ); self update_emped_driver_visuals(); player.siegebot_kills = undefined; player Ghost(); } } function vehicle_unoccupied( player ) { self clientfield::set( "enemyvehicle", 0 ); self.ignoreme = true; self thread weapon_doors_state( false ); if ( IsPlayer( player ) ) { player.using_map_vehicle = undefined; player.current_map_vehicle = undefined; player.ignoreme = false; player SetClientUIVisibilityFlag( "weapon_hud_visible", 1 ); player EnableWeaponCycling(); update_emped_visuals( player, false ); player Show(); } self.current_driver = undefined; //if ( self oob::IsTouchingAnyOOBTrigger() ) //{ // self destroy_siegebot(); //} } function siegebot_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) { time_alive = GetTime() - self.spawnTime; if ( time_alive < 500 && sMeansOfDeath == "MOD_TRIGGER_HURT" ) return 0; iDamage = self killstreaks::OnDamagePerWeapon( "siegebot", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth * 0.4, undefined, 0, undefined, true, 1.0 ); fmj = loadout::isFMJDamage( weapon, sMeansOfDeath, eAttacker ); if( ( isdefined( fmj ) && fmj ) && ( !isdefined( weapon.isHeroWeapon ) || !weapon.isHeroWeapon ) ) { iDamage = iDamage / 2; } if ( vehicle_ai::should_emp( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) ) { minEmpDownTime = 0.8 * self.settings.empdowntime; maxEmpDownTime = 1.2 * self.settings.empdowntime; self notify ( "emped", RandomFloatRange( minEmpDownTime, maxEmpDownTime ), eAttacker, eInflictor ); } if(!isdefined(self.damageLevel))self.damageLevel=0; newDamageLevel = vehicle::should_update_damage_fx_level( self.health, iDamage, self.healthdefault ); if ( newDamageLevel > self.damageLevel ) { self.damageLevel = newDamageLevel; vehicle::set_damage_fx_level( self.damageLevel ); } driver = self GetSeatOccupant( 0 ); if ( isPlayer( driver ) ) { driver vehicle::update_damage_as_occupant( self.maxhealth - ( self.health - iDamage ), self.maxhealth ); if ( iDamage > self.health ) { driver Show(); } } return iDamage; } function watch_emped() { self endon( "death" ); while ( 1 ) { self waittill( "emped", down_time, attacker, inflictor ); self thread emped( down_time ); } } function emped( down_time ) { self notify( "emped_singleton" ); self endon( "death" ); self endon( "emped_singleton" ); self SetBrake( 1 ); // TODO: not working ask expect... the handbrake doesn't work to slow down the siegebot self.emped = true; self update_emped_driver_visuals(); wait down_time; self SetBrake( 0 ); self.emped = false; self update_emped_driver_visuals(); } function update_emped_driver_visuals() { update_emped_visuals( self GetSeatOccupant( 0 ), self.emped ); } function update_emped_visuals( driver, emped ) { if ( IsPlayer( driver ) ) { value = ( (isdefined(emped)?emped:0) ? 1 : 0 ); driver clientfield::set_to_player( "empd", value ); driver clientfield::set_to_player( "static_postfx", value ); driver SetEMPJammed( value ); } } function watch_game_ended() { self endon( "death" ); level waittill("game_ended"); self thread wait_then_hide( 3.0 ); self destroy_siegebot(); } function destroy_siegebot() { self DoDamage( self.health + 1, self.origin + (0, 0, 60), undefined, undefined, "none", "MOD_EXPLOSIVE", 0 ); } function wait_then_hide( wait_time ) { wait wait_time; // hide vehicle after destruction if ( isdefined( self ) ) { self Hide(); } } function watch_death() { self notify( "siegebot_watch_death" ); self endon( "siegebot_watch_death" ); self waittill( "death" ); self process_siegebot_kill( self.current_driver ); // driver = self GetSeatOccupant( 0 ); // NOTE: this always fails, so use self.current_driver instead if ( IsPlayer( self.current_driver ) ) { self vehicle_unoccupied( self.current_driver ); } // Need to prep the death model StreamerModelHint( self.deathmodel, 6 ); // self waittill( "model_swap" ); // give the streamer time to load (NOTE: this wait doesn't work right now) self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay ); self vehicle::do_death_dynents(); self vehicle_death::death_radius_damage(); self vehicle_death::DeleteWhenSafe( 0.25 ); } function process_siegebot_kill( driver ) { if ( !isdefined( self ) ) return; if ( self.team == "neutral" ) return; if ( !IsPlayer( driver ) ) return; if ( IsPlayer( self.attacker ) ) { if ( driver == self.attacker ) return; scoreevents::processScoreEvent( "destroyed_siegebot", self.attacker ); } if ( isdefined( self.attackers ) ) { foreach( kill_assist in self.attackers ) { if ( IsPlayer( kill_assist ) ) { if ( self.attacker === kill_assist ) continue; if ( !isdefined( self.attacker ) || kill_assist.team == self.attacker.team ) { scoreevents::processScoreEvent( "destroyed_siegebot_assist", kill_assist ); } } } } } function reload_rockets( player ) { bundle = level.killstreakBundle[ "siegebot" ]; self disable_missiles(); // setup the "reload" time for the player's vehicle HUD weapon_wait_duration_ms = Int( bundle.ksWeaponReloadTime * 1000 ); player SetVehicleWeaponWaitDuration( weapon_wait_duration_ms ); player SetVehicleWeaponWaitEndTime( GetTime() + weapon_wait_duration_ms ); wait ( bundle.ksWeaponReloadTime ); self set_rocket_count( 3 ); wait 0.4; if ( !self.isStunned ) self enable_missiles(); } function set_rocket_count( rocket_count ) { self.numberRockets = rocket_count; self update_client_ammo( self.numberRockets ); } function enable_missiles() { self.missiles_disabled = false; self DisableGunnerFiring( 2 - 1, false ); } function disable_missiles() { self.missiles_disabled = true; self DisableGunnerFiring( 2 - 1, true ); } function watch_rockets( player ) { self endon( "death" ); self endon( "exit_vehicle" ); if ( self.numberRockets <= 0 ) { self reload_rockets( player ); } else { self update_client_ammo( self.numberRockets ); } if ( !self.isStunned ) self enable_missiles(); while( true ) { player waittill( "missile_fire", missile ); missile.ignore_team_kills = self.ignore_team_kills; self set_rocket_count( self.numberRockets - 1 ); // self perform_recoil_missile_turret( player ); // not needed for siegebot if ( self.numberRockets <= 0 ) self reload_rockets( player ); } } function update_client_ammo( ammo_count, driver_only_update = false ) // self == vehicle { if ( !driver_only_update ) { self clientfield::set( "ai_tank_missile_fire", ammo_count ); } if ( IsPlayer( self.current_driver ) ) { self.current_driver clientfield::increment_to_player( "ai_tank_update_hud", 1 ); } } /# function arm_test() { self notify( "arm_test" ); self endon( "arm_test" ); level endon( "game_ended" ); delay = 10.0; while( 1 ) { self thread retract_left_arm(); self thread retract_right_arm(); wait delay; self thread extend_left_arm(); self thread extend_right_arm(); wait delay; } } #/ function retract_left_arm() { if(!isdefined(self.left_arm_retracted))self.left_arm_retracted=false; if ( self.left_arm_retracted ) return; self.left_arm_retracted = true; self UseAnimTree( #animtree ); self clientfield::set( "siegebot_retract_left_arm", 1 ); self ClearAnim( %ai_siegebot_base_mp_left_arm_extend, 0.2 ); self SetAnim( %ai_siegebot_base_mp_left_arm_retract, 1.0 ); } function extend_left_arm() { if(!isdefined(self.left_arm_retracted))self.left_arm_retracted=false; if ( !self.left_arm_retracted ) return; self.left_arm_retracted = false; self UseAnimTree( #animtree ); self clientfield::set( "siegebot_retract_left_arm", 0 ); self ClearAnim( %ai_siegebot_base_mp_left_arm_retract, 0.2 ); self SetAnim( %ai_siegebot_base_mp_left_arm_extend, 1.0, 0.0 ); wait 0.1; if ( self.left_arm_retracted == false ) // if still extended self ClearAnim( %ai_siegebot_base_mp_left_arm_extend, 0.1 ); } function retract_right_arm() { if(!isdefined(self.right_arm_retracted))self.right_arm_retracted=false; if ( self.right_arm_retracted ) return; self.right_arm_retracted = true; self UseAnimTree( #animtree ); self clientfield::set( "siegebot_retract_right_arm", 1 ); self ClearAnim( %ai_siegebot_base_mp_right_arm_extend, 0.2 ); self SetAnim( %ai_siegebot_base_mp_right_arm_retract, 1.0 ); } function extend_right_arm() { if(!isdefined(self.right_arm_retracted))self.right_arm_retracted=false; if ( !self.right_arm_retracted ) return; self.right_arm_retracted = false; self UseAnimTree( #animtree ); self clientfield::set( "siegebot_retract_right_arm", 0 ); self ClearAnim( %ai_siegebot_base_mp_right_arm_retract, 0.2 ); self SetAnim( %ai_siegebot_base_mp_right_arm_extend, 1.0 ); wait 0.1; if ( self.right_arm_retracted == false ) // if still extended self ClearAnim( %ai_siegebot_base_mp_right_arm_extend, 0.1 ); } function watch_left_arm() { self endon( "death" ); self endon( "exit_vehicle" ); wait RandomFloatRange( 0.05, 0.3 ); while( 1 ) { ref_origin = self GetTagOrigin( "tag_turret" ); ref_angles = self GetTagAngles( "tag_turret" ); forward = AnglesToForward( ref_angles ); right = AnglesToRight( ref_angles ); ref_origin += ( right * -60 ); trace_start = ref_origin + ( forward * 40 ); trace_end = ref_origin + ( forward * -30 ); // util::debug_sphere( ref_origin, 8, ( 1, 0, 1 ), 0.5, 1 ); // util::debug_sphere( trace_start, 8, ( 1, 0, 1 ), 0.5, 1 ); // util::debug_sphere( trace_end, 8, ( 1, 0, 1 ), 0.5, 1 ); trace = PhysicsTrace( trace_start, trace_end, (-8, -8, -8), (8, 8, 8), self, (1 << 0) | (1 << 3) ); if ( trace["fraction"] < 1.0 ) self retract_left_arm(); else self extend_left_arm(); wait 0.2; } } function watch_right_arm() { self endon( "death" ); self endon( "exit_vehicle" ); wait RandomFloatRange( 0.05, 0.3 ); while( 1 ) { ref_origin = self GetTagOrigin( "tag_turret" ); ref_angles = self GetTagAngles( "tag_turret" ); forward = AnglesToForward( ref_angles ); right = AnglesToRight( ref_angles ); ref_origin += ( right * 60 ); trace_start = ref_origin + ( forward * 40 ); trace_end = ref_origin + ( forward * -30 ); // util::debug_sphere( ref_origin, 8, ( 1, 0, 1 ), 0.5, 1 ); // util::debug_sphere( trace_start, 8, ( 1, 0, 1 ), 0.5, 1 ); // util::debug_sphere( trace_end, 8, ( 1, 0, 1 ), 0.5, 1 ); trace = PhysicsTrace( trace_start, trace_end, (-8, -8, -8), (8, 8, 8), self, (1 << 0) | (1 << 3) ); if ( trace["fraction"] < 1.0 ) self retract_right_arm(); else self extend_right_arm(); wait 0.2; } } function does_rocket_shoot_through_wall( use_old_trace ) { if ( use_old_trace && isdefined( self.rocket_wall_origin_offset ) ) { base_tag_angles = self GetTagAngles( "tag_turret" ); base_forward = AnglesToForward( base_tag_angles ); base_right = AnglesToRight( base_tag_angles ); base_up = AnglesToUp( base_tag_angles ); offset = self.rocket_wall_origin_offset; ref_origin = self.origin + ( offset[0] * base_forward ) + ( offset[1] * base_right ) + ( offset[2] * base_up ); ref_angles = base_tag_angles + self.rocket_wall_angles_offset; } else { ref_origin = self GetTagOrigin( "tag_gunner_flash2b" ); ref_angles = self GetTagAngles( "tag_gunner_flash2b" ); } forward = AnglesToForward( ref_angles ); trace_start = ref_origin + ( forward * 12 ); trace_end = ref_origin + ( forward * -12 ); // util::debug_sphere( trace_start, 8, ( 1, 0, 1 ), 0.5, 1 ); // util::debug_sphere( trace_end, 8, ( 1, 0, 1 ), 0.5, 1 ); trace = PhysicsTrace( trace_start, trace_end, (-2, -2, -2), (2, 2, 2), self, (1 << 0) | (1 << 3) ); shoot_through_wall = ( trace["fraction"] < 1.0 ); if ( shoot_through_wall ) { if ( !isdefined( base_tag_angles ) ) { base_tag_angles = self GetTagAngles( "tag_turret" ); base_forward = AnglesToForward( base_tag_angles ); base_right = AnglesToRight( base_tag_angles ); base_up = AnglesToUp( base_tag_angles ); } ref_offset = ref_origin - self.origin;; self.rocket_wall_origin_offset = ( VectorDot( ref_offset, base_forward), VectorDot( ref_offset, base_right ), VectorDot( ref_offset, base_up ) ) ; self.rocket_wall_angles_offset = ref_angles - base_tag_angles; } return shoot_through_wall; } function on_player_disconnected() { player = self; if ( isdefined( player ) && isdefined( player.current_map_vehicle ) ) { player.current_map_vehicle notify( "exit_vehicle", player ); } }