diff --git a/aitype/ally_hero_ghost_socom.gsc b/aitype/ally_hero_ghost_socom.gsc new file mode 100644 index 0000000..9f9b182 --- /dev/null +++ b/aitype/ally_hero_ghost_socom.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_hero_ghost_socom (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_seal_soccom_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "glock"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "m4_grunt"; + + character\character_hero_seal_soccom_ghost::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_hero_seal_soccom_ghost::precache(); + + precacheItem("m4_grunt"); + precacheItem("glock"); + precacheItem("fraggrenade"); +} diff --git a/aitype/ally_hero_soap_socom.gsc b/aitype/ally_hero_soap_socom.gsc new file mode 100644 index 0000000..02995a9 --- /dev/null +++ b/aitype/ally_hero_soap_socom.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_hero_soap_socom (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_seal_soccom_assault_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "glock"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "m4_grunt"; + + character\character_hero_seal_soccom_soap::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_hero_seal_soccom_soap::precache(); + + precacheItem("m4_grunt"); + precacheItem("glock"); + precacheItem("fraggrenade"); +} diff --git a/aitype/ally_marine_ar_m16basic.gsc b/aitype/ally_marine_ar_m16basic.gsc new file mode 100644 index 0000000..979d271 --- /dev/null +++ b/aitype/ally_marine_ar_m16basic.gsc @@ -0,0 +1,78 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_marine_AR_m16basic (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_complete_sp_usmc_zach" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "colt45"; + self.sidearm = "colt45"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "m16_basic"; + + switch( codescripts\character::get_random_character(7) ) + { + case 0: + character\character_sp_usmc_james::main(); + break; + case 1: + character\character_sp_usmc_sami::main(); + break; + case 2: + character\character_sp_usmc_at4::main(); + break; + case 3: + character\character_sp_usmc_ryan::main(); + break; + case 4: + character\character_sp_usmc_sami_goggles::main(); + break; + case 5: + character\character_sp_usmc_zach::main(); + break; + case 6: + character\character_sp_usmc_zach_goggles::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_sp_usmc_james::precache(); + character\character_sp_usmc_sami::precache(); + character\character_sp_usmc_at4::precache(); + character\character_sp_usmc_ryan::precache(); + character\character_sp_usmc_sami_goggles::precache(); + character\character_sp_usmc_zach::precache(); + character\character_sp_usmc_zach_goggles::precache(); + + precacheItem("m16_basic"); + precacheItem("colt45"); + precacheItem("colt45"); + precacheItem("fraggrenade"); +} diff --git a/aitype/ally_marine_snpr_m40a3.gsc b/aitype/ally_marine_snpr_m40a3.gsc new file mode 100644 index 0000000..426d417 --- /dev/null +++ b/aitype/ally_marine_snpr_m40a3.gsc @@ -0,0 +1,74 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_marine_SNPR_m40a3 (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_complete_sp_usmc_zach" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "colt45"; + self.sidearm = "colt45"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "m40a3"; + + switch( codescripts\character::get_random_character(6) ) + { + case 0: + character\character_sp_usmc_james::main(); + break; + case 1: + character\character_sp_usmc_sami::main(); + break; + case 2: + character\character_sp_usmc_ryan::main(); + break; + case 3: + character\character_sp_usmc_sami_goggles::main(); + break; + case 4: + character\character_sp_usmc_zach::main(); + break; + case 5: + character\character_sp_usmc_zach_goggles::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_sp_usmc_james::precache(); + character\character_sp_usmc_sami::precache(); + character\character_sp_usmc_ryan::precache(); + character\character_sp_usmc_sami_goggles::precache(); + character\character_sp_usmc_zach::precache(); + character\character_sp_usmc_zach_goggles::precache(); + + precacheItem("m40a3"); + precacheItem("colt45"); + precacheItem("colt45"); + precacheItem("fraggrenade"); +} diff --git a/aitype/ally_pilot_zach_desert.gsc b/aitype/ally_pilot_zach_desert.gsc new file mode 100644 index 0000000..7570e84 --- /dev/null +++ b/aitype/ally_pilot_zach_desert.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_pilot_zach_desert (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_complete_sp_cobra_pilot_desert_zack" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 149; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\character_sp_pilot_zack_desert::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_sp_pilot_zack_desert::precache(); + + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/ally_us_army_ar.gsc b/aitype/ally_us_army_ar.gsc new file mode 100644 index 0000000..9397a79 --- /dev/null +++ b/aitype/ally_us_army_ar.gsc @@ -0,0 +1,114 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_us_army_AR (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_us_army_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(12) ) + { + case 0: + self.weapon = "m16_basic"; + break; + case 1: + self.weapon = "m240"; + break; + case 2: + self.weapon = "m16_grenadier"; + break; + case 3: + self.weapon = "m240_reflex"; + break; + case 4: + self.weapon = "m16_acog"; + break; + case 5: + self.weapon = "m240_acog"; + break; + case 6: + self.weapon = "m4_grenadier"; + break; + case 7: + self.weapon = "scar_h_acog"; + break; + case 8: + self.weapon = "m4_grunt"; + break; + case 9: + self.weapon = "scar_h_shotgun"; + break; + case 10: + self.weapon = "scar_h_reflex"; + break; + case 11: + self.weapon = "scar_h_grenadier"; + break; + } + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\character_us_army_assault_a::main(); + break; + case 1: + character\character_us_army_assault_b::main(); + break; + case 2: + character\character_us_army_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_us_army_assault_a::precache(); + character\character_us_army_assault_b::precache(); + character\character_us_army_assault_c::precache(); + + precacheItem("m16_basic"); + precacheItem("m240"); + precacheItem("m16_grenadier"); + precacheItem("m203"); + precacheItem("m240_reflex"); + precacheItem("m16_acog"); + precacheItem("m240_acog"); + precacheItem("m4_grenadier"); + precacheItem("m203_m4"); + precacheItem("scar_h_acog"); + precacheItem("m4_grunt"); + precacheItem("scar_h_shotgun"); + precacheItem("scar_h_shotgun_attach"); + precacheItem("scar_h_reflex"); + precacheItem("scar_h_grenadier"); + precacheItem("scar_h_m203"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/ally_us_army_ar_paul.gsc b/aitype/ally_us_army_ar_paul.gsc new file mode 100644 index 0000000..a2254ac --- /dev/null +++ b/aitype/ally_us_army_ar_paul.gsc @@ -0,0 +1,101 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_ally_us_army_AR_paul (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_us_army_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(12) ) + { + case 0: + self.weapon = "m16_basic"; + break; + case 1: + self.weapon = "m240"; + break; + case 2: + self.weapon = "m16_grenadier"; + break; + case 3: + self.weapon = "m240_reflex"; + break; + case 4: + self.weapon = "m16_acog"; + break; + case 5: + self.weapon = "m240_acog"; + break; + case 6: + self.weapon = "m4_grenadier"; + break; + case 7: + self.weapon = "scar_h_acog"; + break; + case 8: + self.weapon = "m4_grunt"; + break; + case 9: + self.weapon = "scar_h_shotgun"; + break; + case 10: + self.weapon = "scar_h_reflex"; + break; + case 11: + self.weapon = "scar_h_grenadier"; + break; + } + + character\character_us_army_assault_paul::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\character_us_army_assault_paul::precache(); + + precacheItem("m16_basic"); + precacheItem("m240"); + precacheItem("m16_grenadier"); + precacheItem("m203"); + precacheItem("m240_reflex"); + precacheItem("m16_acog"); + precacheItem("m240_acog"); + precacheItem("m4_grenadier"); + precacheItem("m203_m4"); + precacheItem("scar_h_acog"); + precacheItem("m4_grunt"); + precacheItem("scar_h_shotgun"); + precacheItem("scar_h_shotgun_attach"); + precacheItem("scar_h_reflex"); + precacheItem("scar_h_grenadier"); + precacheItem("scar_h_m203"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/civilian_business_male_1.gsc b/aitype/civilian_business_male_1.gsc new file mode 100644 index 0000000..2b1873b --- /dev/null +++ b/aitype/civilian_business_male_1.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_business_male_1 (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_complete_civilian_suit_male_1" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\character_city_civ_male_a::main(); +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_city_civ_male_a::precache(); + +} diff --git a/aitype/civilian_city_female.gsc b/aitype/civilian_city_female.gsc new file mode 100644 index 0000000..ac40108 --- /dev/null +++ b/aitype/civilian_city_female.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_city_female (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_city_civ_female_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\character_city_civ_female_a::main(); +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_city_civ_female_a::precache(); + +} diff --git a/aitype/civilian_urban_female.gsc b/aitype/civilian_urban_female.gsc new file mode 100644 index 0000000..ae91f6c --- /dev/null +++ b/aitype/civilian_urban_female.gsc @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_urban_female (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_city_civ_female_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + switch( codescripts\character::get_random_character(2) ) + { + case 0: + character\character_civilian_urban_female_a::main(); + break; + case 1: + character\character_civilian_urban_female_b::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_civilian_urban_female_a::precache(); + character\character_civilian_urban_female_b::precache(); + +} diff --git a/aitype/civilian_urban_female_drone.gsc b/aitype/civilian_urban_female_drone.gsc new file mode 100644 index 0000000..279dbbd --- /dev/null +++ b/aitype/civilian_urban_female_drone.gsc @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_urban_female_drone (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_city_civ_female_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + switch( codescripts\character::get_random_character(2) ) + { + case 0: + character\character_civilian_urban_fem_a_drone::main(); + break; + case 1: + character\character_civilian_urban_fem_b_drone::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_civilian_urban_fem_a_drone::precache(); + character\character_civilian_urban_fem_b_drone::precache(); + +} diff --git a/aitype/civilian_urban_male.gsc b/aitype/civilian_urban_male.gsc new file mode 100644 index 0000000..5f100b2 --- /dev/null +++ b/aitype/civilian_urban_male.gsc @@ -0,0 +1,74 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_urban_male (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_urban_civ_male_aa" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + switch( codescripts\character::get_random_character(7) ) + { + case 0: + character\character_civilian_urban_male_aa_wht::main(); + break; + case 1: + character\character_civilian_urban_male_ab_wht::main(); + break; + case 2: + character\character_civilian_urban_male_ac_wht::main(); + break; + case 3: + character\character_city_civ_male_a::main(); + break; + case 4: + character\character_civilian_urban_male_ba_wht::main(); + break; + case 5: + character\character_civilian_urban_male_bb_wht::main(); + break; + case 6: + character\character_civilian_urban_male_bc_wht::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_civilian_urban_male_aa_wht::precache(); + character\character_civilian_urban_male_ab_wht::precache(); + character\character_civilian_urban_male_ac_wht::precache(); + character\character_city_civ_male_a::precache(); + character\character_civilian_urban_male_ba_wht::precache(); + character\character_civilian_urban_male_bb_wht::precache(); + character\character_civilian_urban_male_bc_wht::precache(); + +} diff --git a/aitype/civilian_urban_male_drone.gsc b/aitype/civilian_urban_male_drone.gsc new file mode 100644 index 0000000..20168ea --- /dev/null +++ b/aitype/civilian_urban_male_drone.gsc @@ -0,0 +1,74 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_urban_male_drone (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_city_civ_male_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + switch( codescripts\character::get_random_character(7) ) + { + case 0: + character\character_civilian_urban_male_aa_drone::main(); + break; + case 1: + character\character_civilian_urban_male_ab_drone::main(); + break; + case 2: + character\character_civilian_urban_male_ac_drone::main(); + break; + case 3: + character\character_city_civ_male_a_drone::main(); + break; + case 4: + character\character_civilian_urban_male_ba_drone::main(); + break; + case 5: + character\character_civilian_urban_male_bb_drone::main(); + break; + case 6: + character\character_civilian_urban_male_bc_drone::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_civilian_urban_male_aa_drone::precache(); + character\character_civilian_urban_male_ab_drone::precache(); + character\character_civilian_urban_male_ac_drone::precache(); + character\character_city_civ_male_a_drone::precache(); + character\character_civilian_urban_male_ba_drone::precache(); + character\character_civilian_urban_male_bb_drone::precache(); + character\character_civilian_urban_male_bc_drone::precache(); + +} diff --git a/aitype/civilian_worker_male.gsc b/aitype/civilian_worker_male.gsc new file mode 100644 index 0000000..09fad5b --- /dev/null +++ b/aitype/civilian_worker_male.gsc @@ -0,0 +1,58 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_civilian_worker_male (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_work_civ_male_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\character_civilian_worker_a::main(); + break; + case 1: + character\character_civilian_worker_b::main(); + break; + case 2: + character\character_civilian_worker_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\character_civilian_worker_a::precache(); + character\character_civilian_worker_b::precache(); + character\character_civilian_worker_c::precache(); + +} diff --git a/aitype/enemy_fsb_ar.gsc b/aitype/enemy_fsb_ar.gsc new file mode 100644 index 0000000..ed48a9a --- /dev/null +++ b/aitype/enemy_fsb_ar.gsc @@ -0,0 +1,73 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_FSB_AR (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "riotshield.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "elite"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "usp"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(6) ) + { + case 0: + self.weapon = "tavor_acog"; + break; + case 1: + self.weapon = "tavor_mars"; + break; + case 2: + self.weapon = "fn2000"; + break; + case 3: + self.weapon = "fn2000_acog"; + break; + case 4: + self.weapon = "fn2000_reflex"; + break; + case 5: + self.weapon = "fn2000_scope"; + break; + } + + character\character_opforce_fsb_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_opforce_fsb_assault_a::precache(); + + precacheItem("tavor_acog"); + precacheItem("tavor_mars"); + precacheItem("fn2000"); + precacheItem("fn2000_acog"); + precacheItem("fn2000_reflex"); + precacheItem("fn2000_scope"); + precacheItem("usp"); + precacheItem("fraggrenade"); +} diff --git a/aitype/enemy_merc_ar.gsc b/aitype/enemy_merc_ar.gsc new file mode 100644 index 0000000..c56c46a --- /dev/null +++ b/aitype/enemy_merc_ar.gsc @@ -0,0 +1,123 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_merc_AR (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_airborne_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "glock"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(15) ) + { + case 0: + self.weapon = "ak47_woodland"; + break; + case 1: + self.weapon = "ak47_digital_reflex"; + break; + case 2: + self.weapon = "ak47_woodland_grenadier"; + break; + case 3: + self.weapon = "ak47_digital_acog"; + break; + case 4: + self.weapon = "ak47_woodland_eotech"; + break; + case 5: + self.weapon = "famas_woodland"; + break; + case 6: + self.weapon = "tavor_woodland_acog"; + break; + case 7: + self.weapon = "tavor_mars"; + break; + case 8: + self.weapon = "tavor_woodland_eotech"; + break; + case 9: + self.weapon = "tavor_reflex"; + break; + case 10: + self.weapon = "fn2000_eotech"; + break; + case 11: + self.weapon = "famas_woodland_reflex"; + break; + case 12: + self.weapon = "fn2000_reflex"; + break; + case 13: + self.weapon = "famas_woodland_eotech"; + break; + case 14: + self.weapon = "fn2000_acog"; + break; + } + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\character_opforce_merc_assault_a::main(); + break; + case 1: + character\character_opforce_merc_assault_b::main(); + break; + case 2: + character\character_opforce_merc_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_opforce_merc_assault_a::precache(); + character\character_opforce_merc_assault_b::precache(); + character\character_opforce_merc_assault_c::precache(); + + precacheItem("ak47_woodland"); + precacheItem("ak47_digital_reflex"); + precacheItem("ak47_woodland_grenadier"); + precacheItem("gl_ak47_woodland"); + precacheItem("ak47_digital_acog"); + precacheItem("ak47_woodland_eotech"); + precacheItem("famas_woodland"); + precacheItem("tavor_woodland_acog"); + precacheItem("tavor_mars"); + precacheItem("tavor_woodland_eotech"); + precacheItem("tavor_reflex"); + precacheItem("fn2000_eotech"); + precacheItem("famas_woodland_reflex"); + precacheItem("fn2000_reflex"); + precacheItem("famas_woodland_eotech"); + precacheItem("fn2000_acog"); + precacheItem("glock"); + precacheItem("fraggrenade"); +} diff --git a/aitype/enemy_merc_smg_p90_silencer.gsc b/aitype/enemy_merc_smg_p90_silencer.gsc new file mode 100644 index 0000000..0525569 --- /dev/null +++ b/aitype/enemy_merc_smg_p90_silencer.gsc @@ -0,0 +1,78 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_merc_SMG_p90_silencer (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_complete_sp_spetsnaz_boris" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "p90_silencer"; + + switch( codescripts\character::get_random_character(7) ) + { + case 0: + character\character_sp_spetsnaz_boris::main(); + break; + case 1: + character\character_sp_spetsnaz_demetry::main(); + break; + case 2: + character\character_sp_spetsnaz_vlad::main(); + break; + case 3: + character\character_sp_spetsnaz_yuri::main(); + break; + case 4: + character\character_sp_spetsnaz_collins::main(); + break; + case 5: + character\character_sp_spetsnaz_geoff::main(); + break; + case 6: + character\character_sp_spetsnaz_derik::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_sp_spetsnaz_boris::precache(); + character\character_sp_spetsnaz_demetry::precache(); + character\character_sp_spetsnaz_vlad::precache(); + character\character_sp_spetsnaz_yuri::precache(); + character\character_sp_spetsnaz_collins::precache(); + character\character_sp_spetsnaz_geoff::precache(); + character\character_sp_spetsnaz_derik::precache(); + + precacheItem("p90_silencer"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/enemy_opforce_ar_ak74u.gsc b/aitype/enemy_opforce_ar_ak74u.gsc new file mode 100644 index 0000000..4e8ac17 --- /dev/null +++ b/aitype/enemy_opforce_ar_ak74u.gsc @@ -0,0 +1,82 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_opforce_AR_ak74u (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_sp_opforce_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "ak74u"; + + switch( codescripts\character::get_random_character(8) ) + { + case 0: + character\character_sp_opforce_b::main(); + break; + case 1: + character\character_sp_opforce_c::main(); + break; + case 2: + character\character_sp_opforce_d::main(); + break; + case 3: + character\character_sp_opforce_e::main(); + break; + case 4: + character\character_sp_opforce_f::main(); + break; + case 5: + character\character_sp_opforce_collins::main(); + break; + case 6: + character\character_sp_opforce_geoff::main(); + break; + case 7: + character\character_sp_opforce_derik::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_sp_opforce_b::precache(); + character\character_sp_opforce_c::precache(); + character\character_sp_opforce_d::precache(); + character\character_sp_opforce_e::precache(); + character\character_sp_opforce_f::precache(); + character\character_sp_opforce_collins::precache(); + character\character_sp_opforce_geoff::precache(); + character\character_sp_opforce_derik::precache(); + + precacheItem("ak74u"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/enemy_opforce_at_rpg7.gsc b/aitype/enemy_opforce_at_rpg7.gsc new file mode 100644 index 0000000..d1b7885 --- /dev/null +++ b/aitype/enemy_opforce_at_rpg7.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_opforce_AT_RPG7 (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_sp_opforce_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "player_rpg.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "ak47"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 768.000000, 512.000000 ); + self setEngagementMaxDist( 1024.000000, 1500.000000 ); + } + + self.weapon = "rpg"; + + character\character_sp_opforce_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_sp_opforce_a::precache(); + + precacheItem("rpg"); + precacheItem("ak47"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/enemy_opforce_lmg_rpd.gsc b/aitype/enemy_opforce_lmg_rpd.gsc new file mode 100644 index 0000000..c4999bf --- /dev/null +++ b/aitype/enemy_opforce_lmg_rpd.gsc @@ -0,0 +1,99 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_opforce_LMG_rpd (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_sp_opforce_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 512.000000, 400.000000 ); + self setEngagementMaxDist( 1024.000000, 1250.000000 ); + } + + switch( codescripts\character::get_random_weapon(4) ) + { + case 0: + self.weapon = "rpd"; + break; + case 1: + self.weapon = "rpd_acog"; + break; + case 2: + self.weapon = "rpd_grip"; + break; + case 3: + self.weapon = "rpd_reflex"; + break; + } + + switch( codescripts\character::get_random_character(8) ) + { + case 0: + character\character_sp_opforce_b::main(); + break; + case 1: + character\character_sp_opforce_c::main(); + break; + case 2: + character\character_sp_opforce_d::main(); + break; + case 3: + character\character_sp_opforce_e::main(); + break; + case 4: + character\character_sp_opforce_f::main(); + break; + case 5: + character\character_sp_opforce_collins::main(); + break; + case 6: + character\character_sp_opforce_geoff::main(); + break; + case 7: + character\character_sp_opforce_derik::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_sp_opforce_b::precache(); + character\character_sp_opforce_c::precache(); + character\character_sp_opforce_d::precache(); + character\character_sp_opforce_e::precache(); + character\character_sp_opforce_f::precache(); + character\character_sp_opforce_collins::precache(); + character\character_sp_opforce_geoff::precache(); + character\character_sp_opforce_derik::precache(); + + precacheItem("rpd"); + precacheItem("rpd_acog"); + precacheItem("rpd_grip"); + precacheItem("rpd_reflex"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/enemy_opforce_shtgn_winchester.gsc b/aitype/enemy_opforce_shtgn_winchester.gsc new file mode 100644 index 0000000..d764e16 --- /dev/null +++ b/aitype/enemy_opforce_shtgn_winchester.gsc @@ -0,0 +1,82 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_enemy_opforce_SHTGN_winchester (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_sp_opforce_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 280.000000, 400.000000 ); + } + + self.weapon = "winchester1200"; + + switch( codescripts\character::get_random_character(8) ) + { + case 0: + character\character_sp_opforce_b::main(); + break; + case 1: + character\character_sp_opforce_c::main(); + break; + case 2: + character\character_sp_opforce_d::main(); + break; + case 3: + character\character_sp_opforce_e::main(); + break; + case 4: + character\character_sp_opforce_f::main(); + break; + case 5: + character\character_sp_opforce_collins::main(); + break; + case 6: + character\character_sp_opforce_geoff::main(); + break; + case 7: + character\character_sp_opforce_derik::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\character_sp_opforce_b::precache(); + character\character_sp_opforce_c::precache(); + character\character_sp_opforce_d::precache(); + character\character_sp_opforce_e::precache(); + character\character_sp_opforce_f::precache(); + character\character_sp_opforce_collins::precache(); + character\character_sp_opforce_geoff::precache(); + character\character_sp_opforce_derik::precache(); + + precacheItem("winchester1200"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_army_ar.gsc b/aitype/nx_ally_us_army_ar.gsc new file mode 100644 index 0000000..d67725c --- /dev/null +++ b/aitype/nx_ally_us_army_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_army_ar (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_army_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27"; + + character\nx_character_us_army_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_army_assault_a::precache(); + + precacheItem("asmk27"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_delta_leader.gsc b/aitype/nx_ally_us_delta_leader.gsc new file mode 100644 index 0000000..49ce617 --- /dev/null +++ b/aitype/nx_ally_us_delta_leader.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_delta_leader (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_hero_baker_body_delta" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_reflex_atacs"; + + character\nx_character_hero_baker_delta::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_baker_delta::precache(); + + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_exfil_baker.gsc b/aitype/nx_ally_us_exfil_baker.gsc new file mode 100644 index 0000000..4f9a72e --- /dev/null +++ b/aitype/nx_ally_us_exfil_baker.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_exfil_baker (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_hero_baker_body_delta" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_reflex_atacs"; + + character\nx_character_hero_baker_exfil::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_baker_exfil::precache(); + + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_lunar_crew.gsc b/aitype/nx_ally_us_lunar_crew.gsc new file mode 100644 index 0000000..464214d --- /dev/null +++ b/aitype/nx_ally_us_lunar_crew.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_lunar_crew (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_civ_astronaut_body_complete_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_civilian.csv"; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "moon"; + self.accuracy = 0.3; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\nx_character_us_lunar_crew::main(); +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\nx_character_us_lunar_crew::precache(); + + + //---------------- + maps\_moon_civilian::main(); //init anims. + //---------------- +} diff --git a/aitype/nx_ally_us_secretservice.gsc b/aitype/nx_ally_us_secretservice.gsc new file mode 100644 index 0000000..c95e6a0 --- /dev/null +++ b/aitype/nx_ally_us_secretservice.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_secretservice (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_secretservice_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + self.weapon = "ump55"; + + character\nx_character_us_secretservice::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_secretservice::precache(); + + precacheItem("ump55"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_secretservice_agent1.gsc b/aitype/nx_ally_us_secretservice_agent1.gsc new file mode 100644 index 0000000..0430c7c --- /dev/null +++ b/aitype/nx_ally_us_secretservice_agent1.gsc @@ -0,0 +1,57 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_secretservice_agent1 (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_secretservice_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + switch( codescripts\character::get_random_weapon(2) ) + { + case 0: + self.weapon = "ump55"; + break; + case 1: + self.weapon = "ump55_reflex"; + break; + } + + character\nx_character_us_secretservice_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_secretservice_a::precache(); + + precacheItem("ump55"); + precacheItem("ump55_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_secretservice_agent2.gsc b/aitype/nx_ally_us_secretservice_agent2.gsc new file mode 100644 index 0000000..9fe2880 --- /dev/null +++ b/aitype/nx_ally_us_secretservice_agent2.gsc @@ -0,0 +1,57 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_secretservice_agent2 (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_secretservice_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + switch( codescripts\character::get_random_weapon(2) ) + { + case 0: + self.weapon = "ump55"; + break; + case 1: + self.weapon = "ump55_reflex"; + break; + } + + character\nx_character_us_secretservice_b::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_secretservice_b::precache(); + + precacheItem("ump55"); + precacheItem("ump55_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_secretservice_agent3.gsc b/aitype/nx_ally_us_secretservice_agent3.gsc new file mode 100644 index 0000000..04c4cc5 --- /dev/null +++ b/aitype/nx_ally_us_secretservice_agent3.gsc @@ -0,0 +1,57 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_secretservice_agent3 (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_secretservice_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + switch( codescripts\character::get_random_weapon(2) ) + { + case 0: + self.weapon = "ump55"; + break; + case 1: + self.weapon = "ump55_reflex"; + break; + } + + character\nx_character_us_secretservice_c::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_secretservice_c::precache(); + + precacheItem("ump55"); + precacheItem("ump55_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_space_assault.gsc b/aitype/nx_ally_us_space_assault.gsc new file mode 100644 index 0000000..c40e846 --- /dev/null +++ b/aitype/nx_ally_us_space_assault.gsc @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_space_assault (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "allies"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunarrifle"; + + character\nx_character_us_space_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_space_assault_a::precache(); + + precacheItem("lunarrifle"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_ally_us_space_assault_crack.gsc b/aitype/nx_ally_us_space_assault_crack.gsc new file mode 100644 index 0000000..3b66006 --- /dev/null +++ b/aitype/nx_ally_us_space_assault_crack.gsc @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_space_assault_crack (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "allies"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunarrifle"; + + character\nx_character_us_space_assault_crack_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_space_assault_crack_a::precache(); + + precacheItem("lunarrifle"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_ally_us_space_assault_nohelm.gsc b/aitype/nx_ally_us_space_assault_nohelm.gsc new file mode 100644 index 0000000..b706de5 --- /dev/null +++ b/aitype/nx_ally_us_space_assault_nohelm.gsc @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_space_assault_nohelm (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "allies"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunarrifle"; + + character\nx_character_us_space_assault_nohelm_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_space_assault_nohelm_a::precache(); + + precacheItem("lunarrifle"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_ally_us_specops_ar.gsc b/aitype/nx_ally_us_specops_ar.gsc new file mode 100644 index 0000000..797c6df --- /dev/null +++ b/aitype/nx_ally_us_specops_ar.gsc @@ -0,0 +1,73 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_ar (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(6) ) + { + case 0: + self.weapon = "xm108"; + break; + case 1: + self.weapon = "xm108_atacs"; + break; + case 2: + self.weapon = "xm108_dark"; + break; + case 3: + self.weapon = "xm108_reflex"; + break; + case 4: + self.weapon = "xm108_reflex_dark"; + break; + case 5: + self.weapon = "xm108_reflex_atacs"; + break; + } + + character\nx_character_us_specops_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_specops_assault_a::precache(); + + precacheItem("xm108"); + precacheItem("xm108_atacs"); + precacheItem("xm108_dark"); + precacheItem("xm108_reflex"); + precacheItem("xm108_reflex_dark"); + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_high_baker.gsc b/aitype/nx_ally_us_specops_high_baker.gsc new file mode 100644 index 0000000..99a9b57 --- /dev/null +++ b/aitype/nx_ally_us_specops_high_baker.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_high_baker (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108"; + + character\nx_character_hero_high_baker::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_high_baker::precache(); + + precacheItem("xm108"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_jenkins.gsc b/aitype/nx_ally_us_specops_jenkins.gsc new file mode 100644 index 0000000..78afbb8 --- /dev/null +++ b/aitype/nx_ally_us_specops_jenkins.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_jenkins (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_reflex_atacs"; + + character\nx_character_hero_jenkins::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_jenkins::precache(); + + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_leader.gsc b/aitype/nx_ally_us_specops_leader.gsc new file mode 100644 index 0000000..2ceb6d0 --- /dev/null +++ b/aitype/nx_ally_us_specops_leader.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_leader (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_reflex_atacs"; + + character\nx_character_hero_baker::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_baker::precache(); + + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_leader_hdphone.gsc b/aitype/nx_ally_us_specops_leader_hdphone.gsc new file mode 100644 index 0000000..f20f3e0 --- /dev/null +++ b/aitype/nx_ally_us_specops_leader_hdphone.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_leader_hdphone (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_reflex_atacs"; + + character\nx_character_hero_baker_hdphone::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_baker_hdphone::precache(); + + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_leader_pchute.gsc b/aitype/nx_ally_us_specops_leader_pchute.gsc new file mode 100644 index 0000000..5cb2ee3 --- /dev/null +++ b/aitype/nx_ally_us_specops_leader_pchute.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_leader_pchute (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_parachute_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_reflex_atacs"; + + character\nx_character_hero_baker_pchute::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_baker_pchute::precache(); + + precacheItem("xm108_reflex_atacs"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_leader_silenced.gsc b/aitype/nx_ally_us_specops_leader_silenced.gsc new file mode 100644 index 0000000..db0c9e9 --- /dev/null +++ b/aitype/nx_ally_us_specops_leader_silenced.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_leader_silenced (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "m4_silencer_reflex"; + + character\nx_character_hero_high_baker::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_high_baker::precache(); + + precacheItem("m4_silencer_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_leader_suit.gsc b/aitype/nx_ally_us_specops_leader_suit.gsc new file mode 100644 index 0000000..bd2ba5a --- /dev/null +++ b/aitype/nx_ally_us_specops_leader_suit.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_leader_suit (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_secretservice_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lancer"; + + character\nx_character_hero_baker_suit::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_baker_suit::precache(); + + precacheItem("lancer"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_squadmate.gsc b/aitype/nx_ally_us_specops_squadmate.gsc new file mode 100644 index 0000000..794177b --- /dev/null +++ b/aitype/nx_ally_us_specops_squadmate.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_squadmate (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_dark"; + + character\nx_character_hero_gypsy::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_gypsy::precache(); + + precacheItem("xm108_dark"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_squadmate_pchute.gsc b/aitype/nx_ally_us_specops_squadmate_pchute.gsc new file mode 100644 index 0000000..88d5281 --- /dev/null +++ b/aitype/nx_ally_us_specops_squadmate_pchute.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_squadmate_pchute (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_parachute_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_dark"; + + character\nx_character_hero_gypsy_pchute::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_gypsy_pchute::precache(); + + precacheItem("xm108_dark"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_specops_squadmate_silenced.gsc b/aitype/nx_ally_us_specops_squadmate_silenced.gsc new file mode 100644 index 0000000..a28c030 --- /dev/null +++ b/aitype/nx_ally_us_specops_squadmate_silenced.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_specops_squadmate_silenced (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "m4_silencer_reflex"; + + character\nx_character_hero_jenkins_delta::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_jenkins_delta::precache(); + + precacheItem("m4_silencer_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_squad1_jumpsuit.gsc b/aitype/nx_ally_us_squad1_jumpsuit.gsc new file mode 100644 index 0000000..d8d36bf --- /dev/null +++ b/aitype/nx_ally_us_squad1_jumpsuit.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_squad1_jumpsuit (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_work_civ_male_d" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_dark"; + + character\nx_character_hero_squad1_jumpsuit::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_squad1_jumpsuit::precache(); + + precacheItem("xm108_dark"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_squad2_jumpsuit.gsc b/aitype/nx_ally_us_squad2_jumpsuit.gsc new file mode 100644 index 0000000..58d0ae7 --- /dev/null +++ b/aitype/nx_ally_us_squad2_jumpsuit.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_squad2_jumpsuit (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_work_civ_male_d" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108_dark"; + + character\nx_character_hero_squad2_jumpsuit::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_squad2_jumpsuit::precache(); + + precacheItem("xm108_dark"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_ally_us_swat_ar.gsc b/aitype/nx_ally_us_swat_ar.gsc new file mode 100644 index 0000000..50756d7 --- /dev/null +++ b/aitype/nx_ally_us_swat_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_ally_us_swat_ar (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_swat_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 100; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "xm108"; + + character\nx_character_us_swat_a::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_us_swat_a::precache(); + + precacheItem("xm108"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_civilian_china_cabdriver.gsc b/aitype/nx_civilian_china_cabdriver.gsc new file mode 100644 index 0000000..b20722b --- /dev/null +++ b/aitype/nx_civilian_china_cabdriver.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_civilian_china_cabdriver (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_urban_civ_male_bb" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + self.weapon = "none"; + + character\nx_civ_china_cabdriver::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_civ_china_cabdriver::precache(); + +} diff --git a/aitype/nx_civilian_china_suit.gsc b/aitype/nx_civilian_china_suit.gsc new file mode 100644 index 0000000..de22a49 --- /dev/null +++ b/aitype/nx_civilian_china_suit.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_civilian_china_suit (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_city_civ_male_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + self.weapon = "none"; + + character\nx_civ_china_suit::main(); +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\nx_civ_china_suit::precache(); + +} diff --git a/aitype/nx_civilian_china_urban_male.gsc b/aitype/nx_civilian_china_urban_male.gsc new file mode 100644 index 0000000..48b29bd --- /dev/null +++ b/aitype/nx_civilian_china_urban_male.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_civilian_china_urban_male (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_urban_civ_male_bb" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + self.weapon = "none"; + + character\nx_civ_china_urban_male::main(); +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\nx_civ_china_urban_male::precache(); + +} diff --git a/aitype/nx_civilian_hero_vp.gsc b/aitype/nx_civilian_hero_vp.gsc new file mode 100644 index 0000000..741e0de --- /dev/null +++ b/aitype/nx_civilian_hero_vp.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_civilian_hero_vp (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_secretservice_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + self.weapon = "none"; + + character\nx_character_hero_vp::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_vp::precache(); + +} diff --git a/aitype/nx_civilian_hero_vp_injured.gsc b/aitype/nx_civilian_hero_vp_injured.gsc new file mode 100644 index 0000000..e397ad4 --- /dev/null +++ b/aitype/nx_civilian_hero_vp_injured.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_civilian_hero_vp_injured (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_hero_vp_body_inj" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "civilian"; + self.subclass = "regular"; + self.accuracy = 0.01; + self.health = 1000; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 0.000000, 0.000000 ); + } + + self.weapon = "none"; + + character\nx_character_hero_vp_inj::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_vp_inj::precache(); + +} diff --git a/aitype/nx_civilian_us_lunar_crew.gsc b/aitype/nx_civilian_us_lunar_crew.gsc new file mode 100644 index 0000000..2ec2415 --- /dev/null +++ b/aitype/nx_civilian_us_lunar_crew.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_civilian_us_lunar_crew (0.5 0.5 0.5) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_civ_astronaut_body_complete_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_civilian.csv"; + self.team = "neutral"; + self.type = "civilian"; + self.subclass = "moon"; + self.accuracy = 0.3; + self.health = 30; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\nx_character_us_lunar_crew::main(); +} + +spawner() +{ + self setspawnerteam("neutral"); +} + +precache() +{ + character\nx_character_us_lunar_crew::precache(); + + + //---------------- + maps\_moon_civilian::main(); //init anims. + //---------------- +} diff --git a/aitype/nx_opfor_china_army_ar.gsc b/aitype/nx_opfor_china_army_ar.gsc new file mode 100644 index 0000000..bcb7d97 --- /dev/null +++ b/aitype/nx_opfor_china_army_ar.gsc @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_army_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_character_china_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27_reflex"; + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\nx_character_china_assault_a::main(); + break; + case 1: + character\nx_character_china_assault_b::main(); + break; + case 2: + character\nx_character_china_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_assault_a::precache(); + character\nx_character_china_assault_b::precache(); + character\nx_character_china_assault_c::precache(); + + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_china_army_rpg.gsc b/aitype/nx_opfor_china_army_rpg.gsc new file mode 100644 index 0000000..c68895f --- /dev/null +++ b/aitype/nx_opfor_china_army_rpg.gsc @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_army_rpg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_army_body_assault_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "asmk27_reflex"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 768.000000, 512.000000 ); + self setEngagementMaxDist( 1024.000000, 1500.000000 ); + } + + self.weapon = "rpgx_straight"; + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\nx_character_china_assault_a::main(); + break; + case 1: + character\nx_character_china_assault_b::main(); + break; + case 2: + character\nx_character_china_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_assault_a::precache(); + character\nx_character_china_assault_b::precache(); + character\nx_character_china_assault_c::precache(); + + precacheItem("rpgx_straight"); + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_army_shotgun.gsc b/aitype/nx_opfor_china_army_shotgun.gsc new file mode 100644 index 0000000..0a8a725 --- /dev/null +++ b/aitype/nx_opfor_china_army_shotgun.gsc @@ -0,0 +1,63 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_army_shotgun (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_character_china_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 280.000000, 400.000000 ); + } + + self.weapon = "srm1216"; + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\nx_character_china_assault_a::main(); + break; + case 1: + character\nx_character_china_assault_b::main(); + break; + case 2: + character\nx_character_china_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_assault_a::precache(); + character\nx_character_china_assault_b::precache(); + character\nx_character_china_assault_c::precache(); + + precacheItem("srm1216"); + precacheItem("SRM1216DIST"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_army_smg.gsc b/aitype/nx_opfor_china_army_smg.gsc new file mode 100644 index 0000000..ce0e345 --- /dev/null +++ b/aitype/nx_opfor_china_army_smg.gsc @@ -0,0 +1,71 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_army_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_army_body_assault_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + switch( codescripts\character::get_random_weapon(2) ) + { + case 0: + self.weapon = "ump55"; + break; + case 1: + self.weapon = "ump55_reflex"; + break; + } + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\nx_character_china_assault_a::main(); + break; + case 1: + character\nx_character_china_assault_b::main(); + break; + case 2: + character\nx_character_china_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_assault_a::precache(); + character\nx_character_china_assault_b::precache(); + character\nx_character_china_assault_c::precache(); + + precacheItem("ump55"); + precacheItem("ump55_reflex"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_army_sniper.gsc b/aitype/nx_opfor_china_army_sniper.gsc new file mode 100644 index 0000000..a6d9d6b --- /dev/null +++ b/aitype/nx_opfor_china_army_sniper.gsc @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_army_sniper (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_us_army_body_assault_b" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "asmk27_reflex"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 1; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 1250.000000, 1024.000000 ); + self setEngagementMaxDist( 1600.000000, 2400.000000 ); + } + + self.weapon = "yevgeny"; + + switch( codescripts\character::get_random_character(3) ) + { + case 0: + character\nx_character_china_assault_a::main(); + break; + case 1: + character\nx_character_china_assault_b::main(); + break; + case 2: + character\nx_character_china_assault_c::main(); + break; + } +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_assault_a::precache(); + character\nx_character_china_assault_b::precache(); + character\nx_character_china_assault_c::precache(); + + precacheItem("yevgeny"); + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_space_ar.gsc b/aitype/nx_opfor_china_space_ar.gsc new file mode 100644 index 0000000..004fdcd --- /dev/null +++ b/aitype/nx_opfor_china_space_ar.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.35; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunarrifle_burst"; + + character\nx_character_china_space_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_space_assault_a::precache(); + + precacheItem("lunarrifle_burst"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_space_ar_l3.gsc b/aitype/nx_opfor_china_space_ar_l3.gsc new file mode 100644 index 0000000..88eb00b --- /dev/null +++ b/aitype/nx_opfor_china_space_ar_l3.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_ar_l3 (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.35; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunar_ecr_reflex"; + + character\nx_character_ec_space_assault_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_space_assault_b::precache(); + + precacheItem("lunar_ecr_reflex"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_space_assault.gsc b/aitype/nx_opfor_china_space_assault.gsc new file mode 100644 index 0000000..fc882f6 --- /dev/null +++ b/aitype/nx_opfor_china_space_assault.gsc @@ -0,0 +1,61 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_assault (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.35; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(2) ) + { + case 0: + self.weapon = "lunarrifle_burst"; + break; + case 1: + self.weapon = "lunarrifle_altsound_burst"; + break; + } + + character\nx_character_china_space_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_space_assault_a::precache(); + + precacheItem("lunarrifle_burst"); + precacheItem("lunarrifle_altsound_burst"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_space_assault_nohelm.gsc b/aitype/nx_opfor_china_space_assault_nohelm.gsc new file mode 100644 index 0000000..a8ec67d --- /dev/null +++ b/aitype/nx_opfor_china_space_assault_nohelm.gsc @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_assault_nohelm (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.35; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunarrifle_burst"; + + character\nx_character_china_space_nohelm::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_space_nohelm::precache(); + + precacheItem("lunarrifle_burst"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_space_breacher.gsc b/aitype/nx_opfor_china_space_breacher.gsc new file mode 100644 index 0000000..ac0e343 --- /dev/null +++ b/aitype/nx_opfor_china_space_breacher.gsc @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_breacher (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.35; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "breacher"; + + character\nx_character_china_space_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_space_assault_a::precache(); + + precacheItem("breacher"); + precacheItem("m9"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_space_lmg_l3.gsc b/aitype/nx_opfor_china_space_lmg_l3.gsc new file mode 100644 index 0000000..f160cf1 --- /dev/null +++ b/aitype/nx_opfor_china_space_lmg_l3.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_lmg_l3 (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.25; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 384.000000, 100.000000 ); + self setEngagementMaxDist( 1024.000000, 1250.000000 ); + } + + self.weapon = "lunar_glo_reflex"; + + character\nx_character_ec_space_assault_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_space_assault_b::precache(); + + precacheItem("lunar_glo_reflex"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_space_shotgun_l3.gsc b/aitype/nx_opfor_china_space_shotgun_l3.gsc new file mode 100644 index 0000000..93d4ed6 --- /dev/null +++ b/aitype/nx_opfor_china_space_shotgun_l3.gsc @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_shotgun_l3 (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 280.000000, 400.000000 ); + } + + self.weapon = "lunar_srm1216"; + + character\nx_character_ec_space_assault_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_space_assault_b::precache(); + + precacheItem("lunar_srm1216"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_space_smg.gsc b/aitype/nx_opfor_china_space_smg.gsc new file mode 100644 index 0000000..732f5a4 --- /dev/null +++ b/aitype/nx_opfor_china_space_smg.gsc @@ -0,0 +1,53 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "uzi_moon"; + + character\nx_character_china_space_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_space_assault_a::precache(); + + precacheItem("uzi_moon"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_space_smg_l3.gsc b/aitype/nx_opfor_china_space_smg_l3.gsc new file mode 100644 index 0000000..78def28 --- /dev/null +++ b/aitype/nx_opfor_china_space_smg_l3.gsc @@ -0,0 +1,53 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_smg_l3 (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "ecr_smg"; + + character\nx_character_ec_space_assault_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_space_assault_b::precache(); + + precacheItem("ecr_smg"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_space_sniper_l3.gsc b/aitype/nx_opfor_china_space_sniper_l3.gsc new file mode 100644 index 0000000..cd01ffb --- /dev/null +++ b/aitype/nx_opfor_china_space_sniper_l3.gsc @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_space_sniper_l3 (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_sniper.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "lunar_ec_sniper"; + + character\nx_character_ec_space_assault_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_space_assault_b::precache(); + + precacheItem("lunar_ec_sniper"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); + + //---------------- + maps\_moon_actor::main(); +maps\_sniper_glint::main(); + //---------------- +} diff --git a/aitype/nx_opfor_china_specops_ar.gsc b/aitype/nx_opfor_china_specops_ar.gsc new file mode 100644 index 0000000..e3f7126 --- /dev/null +++ b/aitype/nx_opfor_china_specops_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_specops_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27_reflex"; + + character\nx_character_china_specops_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_specops_assault_a::precache(); + + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_specops_rpg.gsc b/aitype/nx_opfor_china_specops_rpg.gsc new file mode 100644 index 0000000..cc26b7c --- /dev/null +++ b/aitype/nx_opfor_china_specops_rpg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_specops_rpg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "asmk27_reflex"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 768.000000, 512.000000 ); + self setEngagementMaxDist( 1024.000000, 1500.000000 ); + } + + self.weapon = "rpgx_straight"; + + character\nx_character_china_specops_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_specops_assault_a::precache(); + + precacheItem("rpgx_straight"); + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_specops_rpg_nodamage.gsc b/aitype/nx_opfor_china_specops_rpg_nodamage.gsc new file mode 100644 index 0000000..0828205 --- /dev/null +++ b/aitype/nx_opfor_china_specops_rpg_nodamage.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_specops_rpg_nodamage (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "asmk27_reflex"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 768.000000, 512.000000 ); + self setEngagementMaxDist( 1024.000000, 1500.000000 ); + } + + self.weapon = "rpg_nodamage"; + + character\nx_character_china_specops_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_specops_assault_a::precache(); + + precacheItem("rpg_nodamage"); + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_specops_shotgun.gsc b/aitype/nx_opfor_china_specops_shotgun.gsc new file mode 100644 index 0000000..c56a121 --- /dev/null +++ b/aitype/nx_opfor_china_specops_shotgun.gsc @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_specops_shotgun (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 280.000000, 400.000000 ); + } + + self.weapon = "srm1216"; + + character\nx_character_china_specops_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_specops_assault_a::precache(); + + precacheItem("srm1216"); + precacheItem("SRM1216DIST"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_china_specops_smg.gsc b/aitype/nx_opfor_china_specops_smg.gsc new file mode 100644 index 0000000..7b4d022 --- /dev/null +++ b/aitype/nx_opfor_china_specops_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_china_specops_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_china_specops_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "mpx"; + + character\nx_character_china_specops_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_china_specops_assault_a::precache(); + + precacheItem("mpx"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_ec_lab_ar.gsc b/aitype/nx_opfor_ec_lab_ar.gsc new file mode 100644 index 0000000..0cea218 --- /dev/null +++ b/aitype/nx_opfor_ec_lab_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27_reflex"; + + character\nx_character_ec_lab::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab::precache(); + + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_lab_hazmat_ar.gsc b/aitype/nx_opfor_ec_lab_hazmat_ar.gsc new file mode 100644 index 0000000..3f8ea20 --- /dev/null +++ b/aitype/nx_opfor_ec_lab_hazmat_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_hazmat_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_hazmat_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27_reflex"; + + character\nx_character_ec_lab_hazmat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab_hazmat::precache(); + + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_lab_hazmat_smg.gsc b/aitype/nx_opfor_ec_lab_hazmat_smg.gsc new file mode 100644 index 0000000..30c0e92 --- /dev/null +++ b/aitype/nx_opfor_ec_lab_hazmat_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_hazmat_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_hazmat_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "mpx"; + + character\nx_character_ec_lab_hazmat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab_hazmat::precache(); + + precacheItem("mpx"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_lab_mask_ar.gsc b/aitype/nx_opfor_ec_lab_mask_ar.gsc new file mode 100644 index 0000000..8d8246e --- /dev/null +++ b/aitype/nx_opfor_ec_lab_mask_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_mask_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27_reflex"; + + character\nx_character_ec_lab_mask::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab_mask::precache(); + + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_lab_mask_shotgun.gsc b/aitype/nx_opfor_ec_lab_mask_shotgun.gsc new file mode 100644 index 0000000..7e3af40 --- /dev/null +++ b/aitype/nx_opfor_ec_lab_mask_shotgun.gsc @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_mask_shotgun (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "srm1216"; + + character\nx_character_ec_lab_mask::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab_mask::precache(); + + precacheItem("srm1216"); + precacheItem("SRM1216DIST"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_lab_mask_smg.gsc b/aitype/nx_opfor_ec_lab_mask_smg.gsc new file mode 100644 index 0000000..ca3f0f1 --- /dev/null +++ b/aitype/nx_opfor_ec_lab_mask_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_mask_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "mpx"; + + character\nx_character_ec_lab_mask::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab_mask::precache(); + + precacheItem("mpx"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_lab_smg.gsc b/aitype/nx_opfor_ec_lab_smg.gsc new file mode 100644 index 0000000..a5112b1 --- /dev/null +++ b/aitype/nx_opfor_ec_lab_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_lab_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_lab_body_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "mpx"; + + character\nx_character_ec_lab::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_lab::precache(); + + precacheItem("mpx"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_security_guard_ar.gsc b/aitype/nx_opfor_ec_security_guard_ar.gsc new file mode 100644 index 0000000..99116b6 --- /dev/null +++ b/aitype/nx_opfor_ec_security_guard_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_security_guard_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_secret_service_smg" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "asmk27_reflex"; + + character\nx_character_ec_security_guard::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_guard::precache(); + + precacheItem("asmk27_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_security_guard_smg.gsc b/aitype/nx_opfor_ec_security_guard_smg.gsc new file mode 100644 index 0000000..ae4c4de --- /dev/null +++ b/aitype/nx_opfor_ec_security_guard_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_security_guard_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_secret_service_smg" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "mpx"; + + character\nx_character_ec_security_guard::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_guard::precache(); + + precacheItem("mpx"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_security_swat_ar.gsc b/aitype/nx_opfor_ec_security_swat_ar.gsc new file mode 100644 index 0000000..775129a --- /dev/null +++ b/aitype/nx_opfor_ec_security_swat_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_security_swat_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "ak47_reflex"; + + character\nx_character_ec_security_swat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat::precache(); + + precacheItem("ak47_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_security_swat_shotgun.gsc b/aitype/nx_opfor_ec_security_swat_shotgun.gsc new file mode 100644 index 0000000..69562d7 --- /dev/null +++ b/aitype/nx_opfor_ec_security_swat_shotgun.gsc @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_security_swat_shotgun (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 280.000000, 400.000000 ); + } + + self.weapon = "srm1216"; + + character\nx_character_ec_security_swat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat::precache(); + + precacheItem("srm1216"); + precacheItem("SRM1216DIST"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_security_swat_smg.gsc b/aitype/nx_opfor_ec_security_swat_smg.gsc new file mode 100644 index 0000000..626bba6 --- /dev/null +++ b/aitype/nx_opfor_ec_security_swat_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_security_swat_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "mpx"; + + character\nx_character_ec_security_swat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat::precache(); + + precacheItem("mpx"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_ec_space_ar.gsc b/aitype/nx_opfor_ec_space_ar.gsc new file mode 100644 index 0000000..0934578 --- /dev/null +++ b/aitype/nx_opfor_ec_space_ar.gsc @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_ec_space_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_ec_space_assault_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "moon_actor.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "moon"; + self.accuracy = 0.35; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "lunarrifle_burst"; + + character\nx_character_ec_space_assault_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_space_assault_b::precache(); + + precacheItem("lunarrifle_burst"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_exfil_swat_a_ar.gsc b/aitype/nx_opfor_exfil_swat_a_ar.gsc new file mode 100644 index 0000000..a1a3464 --- /dev/null +++ b/aitype/nx_opfor_exfil_swat_a_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_exfil_swat_a_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "ak47_reflex"; + + character\nx_character_exfil_swat_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_exfil_swat_a::precache(); + + precacheItem("ak47_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_exfil_swat_b_ar.gsc b/aitype/nx_opfor_exfil_swat_b_ar.gsc new file mode 100644 index 0000000..2bb3c62 --- /dev/null +++ b/aitype/nx_opfor_exfil_swat_b_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_exfil_swat_b_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "ak47_reflex"; + + character\nx_character_exfil_swat_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_exfil_swat_b::precache(); + + precacheItem("ak47_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_exfil_swat_shot_a_ar.gsc b/aitype/nx_opfor_exfil_swat_shot_a_ar.gsc new file mode 100644 index 0000000..d5f124e --- /dev/null +++ b/aitype/nx_opfor_exfil_swat_shot_a_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_exfil_swat_shot_a_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "ak47_reflex"; + + character\nx_character_exfil_swat_shot_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_exfil_swat_shot_a::precache(); + + precacheItem("ak47_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_exfil_swat_shot_b_ar.gsc b/aitype/nx_opfor_exfil_swat_shot_b_ar.gsc new file mode 100644 index 0000000..c7f8b54 --- /dev/null +++ b/aitype/nx_opfor_exfil_swat_shot_b_ar.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_exfil_swat_shot_b_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "ak47_reflex"; + + character\nx_character_exfil_swat_shot_b::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_exfil_swat_shot_b::precache(); + + precacheItem("ak47_reflex"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_jak_cop_smg.gsc b/aitype/nx_opfor_jak_cop_smg.gsc new file mode 100644 index 0000000..cfc1c7a --- /dev/null +++ b/aitype/nx_opfor_jak_cop_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_jak_cop_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_secret_service_smg" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "fal"; + + character\nx_character_ec_security_guard::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_guard::precache(); + + precacheItem("fal"); + precacheItem("beretta"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_jak_swat_ar.gsc b/aitype/nx_opfor_jak_swat_ar.gsc new file mode 100644 index 0000000..2f43a1c --- /dev/null +++ b/aitype/nx_opfor_jak_swat_ar.gsc @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_jak_swat_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(4) ) + { + case 0: + self.weapon = "fal"; + break; + case 1: + self.weapon = "fal_reflex"; + break; + case 2: + self.weapon = "m4_silencer"; + break; + case 3: + self.weapon = "m4_grenadier"; + break; + } + + character\nx_character_ec_security_swat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat::precache(); + + precacheItem("fal"); + precacheItem("fal_reflex"); + precacheItem("m4_silencer"); + precacheItem("m4_grenadier"); + precacheItem("m203_m4"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_jak_swat_bloody_ar.gsc b/aitype/nx_opfor_jak_swat_bloody_ar.gsc new file mode 100644 index 0000000..62ab55a --- /dev/null +++ b/aitype/nx_opfor_jak_swat_bloody_ar.gsc @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_jak_swat_bloody_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(4) ) + { + case 0: + self.weapon = "fal"; + break; + case 1: + self.weapon = "fal_reflex"; + break; + case 2: + self.weapon = "m4_silencer"; + break; + case 3: + self.weapon = "m4_grenadier"; + break; + } + + character\nx_character_ec_security_swat_bloody::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat_bloody::precache(); + + precacheItem("fal"); + precacheItem("fal_reflex"); + precacheItem("m4_silencer"); + precacheItem("m4_grenadier"); + precacheItem("m203_m4"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_jak_swat_riot.gsc b/aitype/nx_opfor_jak_swat_riot.gsc new file mode 100644 index 0000000..edddf0a --- /dev/null +++ b/aitype/nx_opfor_jak_swat_riot.gsc @@ -0,0 +1,79 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_jak_swat_riot (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "riotshield"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "riotshield"; + self.sidearm = "usp"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(7) ) + { + case 0: + self.weapon = "fal"; + break; + case 1: + self.weapon = "fal_reflex"; + break; + case 2: + self.weapon = "ak47"; + break; + case 3: + self.weapon = "ak47_acog"; + break; + case 4: + self.weapon = "ak47_reflex"; + break; + case 5: + self.weapon = "ak47_grenadier"; + break; + case 6: + self.weapon = "fal_acog"; + break; + } + + character\nx_character_ec_security_swat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat::precache(); + + precacheItem("fal"); + precacheItem("fal_reflex"); + precacheItem("ak47"); + precacheItem("ak47_acog"); + precacheItem("ak47_reflex"); + precacheItem("ak47_grenadier"); + precacheItem("gl_ak47"); + precacheItem("fal_acog"); + precacheItem("riotshield"); + precacheItem("usp"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_jak_swat_smg.gsc b/aitype/nx_opfor_jak_swat_smg.gsc new file mode 100644 index 0000000..d65319e --- /dev/null +++ b/aitype/nx_opfor_jak_swat_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_jak_swat_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="body_opforce_fsb_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "m9"; + self.sidearm = "m9"; + self.grenadeWeapon = "flash_grenade"; + self.grenadeAmmo = 2; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 768.000000 ); + } + + self.weapon = "ump45"; + + character\nx_character_ec_security_swat::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_ec_security_swat::precache(); + + precacheItem("ump45"); + precacheItem("m9"); + precacheItem("m9"); + precacheItem("flash_grenade"); +} diff --git a/aitype/nx_opfor_mexican_army_ar.gsc b/aitype/nx_opfor_mexican_army_ar.gsc new file mode 100644 index 0000000..a62cf6b --- /dev/null +++ b/aitype/nx_opfor_mexican_army_ar.gsc @@ -0,0 +1,66 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_ar (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(4) ) + { + case 0: + self.weapon = "ak47"; + break; + case 1: + self.weapon = "ak47_acog"; + break; + case 2: + self.weapon = "ak47_grenadier"; + break; + case 3: + self.weapon = "ak47_reflex"; + break; + } + + character\nx_character_mexican_army_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_assault_a::precache(); + + precacheItem("ak47"); + precacheItem("ak47_acog"); + precacheItem("ak47_grenadier"); + precacheItem("gl_ak47"); + precacheItem("ak47_reflex"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_mexican_army_ar_grenadier.gsc b/aitype/nx_opfor_mexican_army_ar_grenadier.gsc new file mode 100644 index 0000000..9a29b8e --- /dev/null +++ b/aitype/nx_opfor_mexican_army_ar_grenadier.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_ar_grenadier (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 1024.000000 ); + } + + self.weapon = "ak47_grenadier"; + + character\nx_character_mexican_army_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_assault_a::precache(); + + precacheItem("ak47_grenadier"); + precacheItem("gl_ak47"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_mexican_army_ar_short.gsc b/aitype/nx_opfor_mexican_army_ar_short.gsc new file mode 100644 index 0000000..d17c642 --- /dev/null +++ b/aitype/nx_opfor_mexican_army_ar_short.gsc @@ -0,0 +1,58 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_ar_short (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 128.000000, 0.000000 ); + self setEngagementMaxDist( 512.000000, 1024.000000 ); + } + + switch( codescripts\character::get_random_weapon(2) ) + { + case 0: + self.weapon = "ak74u"; + break; + case 1: + self.weapon = "ak74u_reflex"; + break; + } + + character\nx_character_mexican_army_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_assault_a::precache(); + + precacheItem("ak74u"); + precacheItem("ak74u_reflex"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_mexican_army_lmg.gsc b/aitype/nx_opfor_mexican_army_lmg.gsc new file mode 100644 index 0000000..52de1b1 --- /dev/null +++ b/aitype/nx_opfor_mexican_army_lmg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_lmg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_lmg_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 512.000000, 400.000000 ); + self setEngagementMaxDist( 1024.000000, 1250.000000 ); + } + + self.weapon = "rpd"; + + character\nx_character_mexican_army_lmg_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_lmg_a::precache(); + + precacheItem("rpd"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_mexican_army_rpg.gsc b/aitype/nx_opfor_mexican_army_rpg.gsc new file mode 100644 index 0000000..1b137a5 --- /dev/null +++ b/aitype/nx_opfor_mexican_army_rpg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_rpg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_assault_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = "player_rpg.csv"; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "ak47"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "rpg"; + + character\nx_character_mexican_army_assault_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_assault_a::precache(); + + precacheItem("rpg"); + precacheItem("ak47"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_mexican_army_shotgun.gsc b/aitype/nx_opfor_mexican_army_shotgun.gsc new file mode 100644 index 0000000..ccf65f4 --- /dev/null +++ b/aitype/nx_opfor_mexican_army_shotgun.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_shotgun (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_shotgun_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "beretta"; + self.sidearm = "beretta"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 0.000000, 0.000000 ); + self setEngagementMaxDist( 280.000000, 400.000000 ); + } + + self.weapon = "m1014"; + + character\nx_character_mexican_army_shotgun_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_shotgun_a::precache(); + + precacheItem("m1014"); + precacheItem("beretta"); + precacheItem("beretta"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_mexican_army_smg.gsc b/aitype/nx_opfor_mexican_army_smg.gsc new file mode 100644 index 0000000..d69af65 --- /dev/null +++ b/aitype/nx_opfor_mexican_army_smg.gsc @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_mexican_army_smg (1.0 0.25 0.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_mexican_army_digital_body_smg_a" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "axis"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = "usp_silencer"; + self.sidearm = "usp_silencer"; + self.grenadeWeapon = "fraggrenade"; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "mp5"; + + character\nx_character_mexican_army_smg_a::main(); +} + +spawner() +{ + self setspawnerteam("axis"); +} + +precache() +{ + character\nx_character_mexican_army_smg_a::precache(); + + precacheItem("mp5"); + precacheItem("usp_silencer"); + precacheItem("usp_silencer"); + precacheItem("fraggrenade"); +} diff --git a/aitype/nx_opfor_russian_doctor.gsc b/aitype/nx_opfor_russian_doctor.gsc new file mode 100644 index 0000000..bb90c25 --- /dev/null +++ b/aitype/nx_opfor_russian_doctor.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_russian_doctor (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_hospital_doctor_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\nx_character_hero_hospital_doctor::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_character_hero_hospital_doctor::precache(); + +} diff --git a/aitype/nx_opfor_russian_leader.gsc b/aitype/nx_opfor_russian_leader.gsc new file mode 100644 index 0000000..1271329 --- /dev/null +++ b/aitype/nx_opfor_russian_leader.gsc @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +/*QUAKED actor_nx_opfor_russian_leader (0.0 0.25 1.0) (-16 -16 0) (16 16 72) SPAWNER FORCESPAWN UNDELETABLE PERFECTENEMYINFO DONTSHAREENEMYINFO +defaultmdl="nx_russian_leader_body" +"count" -- max AI to ever spawn from this spawner +SPAWNER -- makes this a spawner instead of a guy +FORCESPAWN -- will try to delete an AI if spawning fails from too many AI +UNDELETABLE -- this AI (or AI spawned from here) cannot be deleted to make room for FORCESPAWN guys +PERFECTENEMYINFO -- this AI when spawned will get a snapshot of perfect info about all enemies +DONTSHAREENEMYINFO -- do not get shared info about enemies at spawn time from teammates +*/ +main() +{ + self.animTree = ""; + self.additionalAssets = ""; + self.team = "allies"; + self.type = "human"; + self.subclass = "regular"; + self.accuracy = 0.2; + self.health = 150; + self.secondaryweapon = ""; + self.sidearm = ""; + self.grenadeWeapon = ""; + self.grenadeAmmo = 0; + + if ( isAI( self ) ) + { + self setEngagementMinDist( 256.000000, 0.000000 ); + self setEngagementMaxDist( 768.000000, 1024.000000 ); + } + + self.weapon = "none"; + + character\nx_russian_leader::main(); +} + +spawner() +{ + self setspawnerteam("allies"); +} + +precache() +{ + character\nx_russian_leader::precache(); + +} diff --git a/animscripts/animmode.gsc b/animscripts/animmode.gsc new file mode 100644 index 0000000..218e5a2 --- /dev/null +++ b/animscripts/animmode.gsc @@ -0,0 +1,126 @@ +main() +{ + self endon( "death" ); + self endon( "stop_animmode" ); + self notify( "killanimscript" ); + self._tag_entity endon( self._anime ); + + if ( isdefined( self._custom_anim_thread ) ) + { + self thread [[ self._custom_anim_thread ]](); + self._custom_anim_thread = undefined; + } + + loop = isdefined( self._custom_anim_loop ) && self._custom_anim_loop; + if ( loop ) + { + self endon( "stop_loop" ); + self._custom_anim_loop = undefined; + } + else + { + thread notify_on_end( self._anime ); + } + + anime = self._anime; + self._anime = undefined; + + arraySize = 0; + if ( loop ) + { + arraySize = level._scr_anim[ self._animname ][ anime ].size; + animationName = level._scr_anim[ self._animname ][ anime ][ randomint( arraySize ) ]; + } + else + { + animationName = level._scr_anim[ self._animname ][ anime ]; + } + + origin = getstartOrigin( self._tag_entity.origin, self._tag_entity.angles, animationName ); + angles = getstartAngles( self._tag_entity.origin, self._tag_entity.angles, animationName ); + + newOrigin = self getDropToFloorPosition( origin ); + if ( isdefined( newOrigin ) ) + origin = newOrigin; + else + println( "Custom animation may be playing in solid for entity '" + self getentnum() + "'\n" ); + + if ( !isdefined( self.noTeleport ) ) + self teleport( origin, angles ); + + self.pushable = 0; + + clear_time = 0.3; + blend_time = 0.2; + + if ( isdefined( self.anim_blend_time_override ) ) + { + clear_time = self.anim_blend_time_override; + blend_time = self.anim_blend_time_override; + } + + self animMode( self._animmode ); + self clearAnim( self.root_anim, clear_time ); + +// self setAnim( %body, 1, 0 ); // The %body node should always have weight 1. + self OrientMode( "face angle", angles[ 1 ] ); + + anim_string = "custom_animmode"; + self setflaggedanimrestart( anim_string, animationName, 1, blend_time, 1 ); + self._tag_entity thread maps\_anim::start_notetrack_wait( self, anim_string, anime, self._animname ); + self._tag_entity thread maps\_anim::animscriptDoNoteTracksThread( self, anim_string, anime ); + + //thread maps\_debug::drawArrowForever( self._tag_entity.origin, self._tag_entity.angles ); + + tag_entity = self._tag_entity; + self._tag_entity = undefined; + self._animmode = undefined; + + self endon( "killanimscript" ); + + endMarker = "end"; + + if ( !loop ) + { + if ( animHasNoteTrack( animationName, "finish" ) ) + endMarker = "finish"; + else if ( animHasNoteTrack( animationName, "stop anim" ) ) + endMarker = "stop anim"; + } + + while ( 1 ) + { + self waittillmatch( anim_string, endMarker ); + + if ( loop ) + { + animationName = level._scr_anim[ self._animname ][ anime ][ randomint( arraySize ) ]; + self SetFlaggedAnimKnobLimitedRestart( anim_string, animationName, 1, 0.2, 1 ); + + if ( isdefined( tag_entity ) ) + { + tag_entity thread maps\_anim::start_notetrack_wait( self, anim_string, anime, self._animname ); + tag_entity thread maps\_anim::animscriptDoNoteTracksThread( self, anim_string, anime ); + } + } + else + { + break; + } + } + + if ( endMarker != "end" ) + self OrientMode( "face motion" ); + + self notify( "finished_custom_animmode" + anime ); +} + +notify_on_end( msg ) +{ + self endon( "death" ); + self endon( "finished_custom_animmode" + msg ); + + self waittill( "killanimscript" ); + + self notify( "finished_custom_animmode" + msg ); +} \ No newline at end of file diff --git a/animscripts/animset.gsc b/animscripts/animset.gsc new file mode 100644 index 0000000..dc0d160 --- /dev/null +++ b/animscripts/animset.gsc @@ -0,0 +1,1524 @@ +#include animscripts\Utility; +#include common_scripts\Utility; + +#using_animtree( "generic_human" ); + + +//////////////////////////////////////////// +// Initialize anim sets +// +// anim.initAnimSet is used as a temporary buffer, because variables, including arrays, can't be passed by reference +// Set it up in each init_animset_* function and then store it in anim.animset.* +// This allows using helpers such as "set_animarray_stance_change" for different sets +//////////////////////////////////////////// + +init_anim_sets() +{ + anim.animsets = spawnstruct(); + anim.animsets.move = []; + anim.animSetLoaded = "earth"; + + // combat stand + init_animset_default_stand(); + init_animset_cqb_stand(); + init_animset_pistol_stand(); + init_animset_rpg_stand(); + init_animset_shotgun_stand(); + init_animset_heat_stand(); + + // combat crouch + init_animset_default_crouch(); + init_animset_rpg_crouch(); + init_animset_shotgun_crouch(); + + // combat prone + init_animset_default_prone(); + + // move + init_animset_run_move(); + init_animset_walk_move(); + init_animset_cqb_move(); + init_animset_heat_run_move(); + + // move aim + init_animset_run_aim_tracking(); + + init_moving_turn_animations(); + + //death + init_animset_death(); + + //pain + init_animset_pain(); + + //wall cover anims + init_animset_crouch_wall(); + init_animset_stand_wall(); + + // Melee + init_animset_melee(); + + //gib + init_animset_gib(); + + init_animset_run_n_gun(); + + init_ambush_sidestep_anims(); + + init_heat_reload_function(); + + //left cover anims + init_animarray_standing_left(); + init_animarray_crouching_left(); + + //right cover anims + init_animarray_standing_right(); + init_animarray_crouching_right(); + + initGrenades(); + + init_noder_anims(); + + //aim anims + init_standing_animarray_aiming(); + init_crouching_animarray_aiming(); + + //reactions + init_reaction_anims(); +} + +initGrenades() +{ + for ( i = 0; i < level._players.size; i++ ) + { + player = level._players[ i ]; + player.grenadeTimers[ "fraggrenade" ] = randomIntRange( 1000, 20000 ); + player.grenadeTimers[ "moon_grenade" ] = randomIntRange( 1000, 20000 ); + player.grenadeTimers[ "flash_grenade" ] = randomIntRange( 1000, 20000 ); + player.grenadeTimers[ "double_grenade" ] = randomIntRange( 1000, 60000 ); + player.numGrenadesInProgressTowardsPlayer = 0; + player.lastGrenadeLandedNearPlayerTime = -1000000; + player.lastFragGrenadeToPlayerStart = -1000000; + player thread animscripts\init_common::setNextPlayerGrenadeTime(); + } + anim.grenadeTimers[ "AI_fraggrenade" ] = randomIntRange( 0, 20000 ); + anim.grenadeTimers[ "AI_moon_grenade" ] = randomIntRange( 0, 20000 ); + anim.grenadeTimers[ "AI_flash_grenade" ] = randomIntRange( 0, 20000 ); + anim.grenadeTimers[ "AI_smoke_grenade_american" ] = randomIntRange( 0, 20000 ); + + /# + thread animscripts\combat_utility::grenadeTimerDebug(); + #/ + + initGrenadeThrowAnims(); +} + +init_animset_run_aim_tracking() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_down" ] = %walk_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %walk_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %walk_aim_6; + anim.initAnimSet[ "add_aim_up" ] = %walk_aim_8; + + assert( !isdefined( anim.animsets.runAimTracking ) ); + anim.animsets.runAimTracking = anim.initAnimSet; +} + +init_animset_melee() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "attack" ] = %melee_1; + anim.initAnimSet[ "charge" ] = %run_2_melee_charge; + anim.initAnimSet[ "run" ] = %run_lowready_F; + anim.initAnimSet[ "stand_to_melee" ] = %stand_2_melee_1; + + assert( !isdefined( anim.animsets.melee ) ); + anim.animsets.melee = anim.initAnimSet; +} + + +init_animset_run_move() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "sprint" ] = %sprint_loop_distant; + anim.initAnimSet[ "sprint_short" ] = %sprint1_loop; + anim.initAnimSet[ "prone" ] = %prone_crawl; + + anim.initAnimSet[ "straight" ] = %run_lowready_F; + + anim.initAnimSet[ "move_f" ] = %walk_forward; + anim.initAnimSet[ "move_l" ] = %walk_left; + anim.initAnimSet[ "move_r" ] = %walk_right; + anim.initAnimSet[ "move_b" ] = %walk_backward; //this looks too fast to be natural + + anim.initAnimSet[ "crouch" ] = %crouch_fastwalk_F; + anim.initAnimSet[ "crouch_l" ] = %crouch_fastwalk_L; + anim.initAnimSet[ "crouch_r" ] = %crouch_fastwalk_R; + anim.initAnimSet[ "crouch_b" ] = %crouch_fastwalk_B; + + anim.initAnimSet[ "stairs_up" ] = %traverse_stair_run_01; + anim.initAnimSet[ "stairs_down" ] = %traverse_stair_run_down; + + assert( !isdefined( anim.animsets.move[ "run" ] ) ); + anim.animsets.move[ "run" ] = anim.initAnimSet; +} + + +init_animset_heat_run_move() +{ + assert( isdefined( anim.animsets.move[ "run" ] ) ); + anim.initAnimSet = anim.animsets.move[ "run" ]; + + anim.initAnimSet[ "straight" ] = %heat_run_loop; + + assert( !isdefined( anim.animsets.move[ "heat_run" ] ) ); + anim.animsets.move[ "heat_run" ] = anim.initAnimSet; +} + + +init_animset_walk_move() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "sprint" ] = %sprint_loop_distant; + anim.initAnimSet[ "sprint_short" ] = %sprint1_loop; + anim.initAnimSet[ "prone" ] = %prone_crawl; + + anim.initAnimSet[ "straight" ] = %walk_CQB_F; + + anim.initAnimSet[ "move_f" ] = %walk_CQB_F; + anim.initAnimSet[ "move_l" ] = %walk_left; + anim.initAnimSet[ "move_r" ] = %walk_right; + anim.initAnimSet[ "move_b" ] = %walk_backward; + + anim.initAnimSet[ "crouch" ] = %crouch_fastwalk_F; + anim.initAnimSet[ "crouch_l" ] = %crouch_fastwalk_L; + anim.initAnimSet[ "crouch_r" ] = %crouch_fastwalk_R; + anim.initAnimSet[ "crouch_b" ] = %crouch_fastwalk_B; + + anim.initAnimSet[ "stairs_up" ] = %traverse_stair_run; + anim.initAnimSet[ "stairs_down" ] = %traverse_stair_run_down_01; + + assert( !isdefined( anim.animsets.move[ "walk" ] ) ); + anim.animsets.move[ "walk" ] = anim.initAnimSet; +} + + +init_animset_cqb_move() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "sprint" ] = %sprint_loop_distant; + anim.initAnimSet[ "sprint_short" ] = %sprint1_loop; + anim.initAnimSet[ "straight" ] = %run_CQB_F_search_v1; // %run_CQB_F_search_v2 + anim.initAnimSet[ "straight_variation" ] = %run_CQB_F_search_v2; + + anim.initAnimSet[ "move_f" ] = %walk_CQB_F; + anim.initAnimSet[ "move_l" ] = %walk_left; + anim.initAnimSet[ "move_r" ] = %walk_right; + anim.initAnimSet[ "move_b" ] = %walk_backward; + + anim.initAnimSet[ "stairs_up" ] = %traverse_stair_run; + anim.initAnimSet[ "stairs_down" ] = %traverse_stair_run_down_01; + + assert( !isdefined( anim.animsets.move[ "cqb" ] ) ); + anim.animsets.move[ "cqb" ] = anim.initAnimSet; +} + + +init_animset_pistol_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %pistol_stand_aim_8_add; + anim.initAnimSet[ "add_aim_down" ] = %pistol_stand_aim_2_add; + anim.initAnimSet[ "add_aim_left" ] = %pistol_stand_aim_4_add; + anim.initAnimSet[ "add_aim_right" ] = %pistol_stand_aim_6_add; + anim.initAnimSet[ "straight_level" ] = %pistol_stand_aim_5; + + anim.initAnimSet[ "fire" ] = %pistol_stand_fire_A; + anim.initAnimSet[ "single" ] = array( %pistol_stand_fire_A ); + + anim.initAnimSet[ "reload" ] = array( %pistol_stand_reload_A ); + anim.initAnimSet[ "reload_crouchhide" ] = array(); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + set_animarray_standing_turns_pistol(); + + anim.initAnimSet[ "add_turn_aim_up" ] = %pistol_stand_aim_8_alt; + anim.initAnimSet[ "add_turn_aim_down" ] = %pistol_stand_aim_2_alt; + anim.initAnimSet[ "add_turn_aim_left" ] = %pistol_stand_aim_4_alt; + anim.initAnimSet[ "add_turn_aim_right" ] = %pistol_stand_aim_6_alt; + + assert( !isdefined( anim.animsets.pistolStand ) ); + anim.animsets.pistolStand = anim.initAnimSet; +} + +init_animset_rpg_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %RPG_stand_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %RPG_stand_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %RPG_stand_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %RPG_stand_aim_6; + anim.initAnimSet[ "straight_level" ] = %RPG_stand_aim_5; + + anim.initAnimSet[ "fire" ] = %RPG_stand_fire; + anim.initAnimSet[ "single" ] = array( %exposed_shoot_semi1 ); + + anim.initAnimSet[ "reload" ] = array( %RPG_stand_reload ); + anim.initAnimSet[ "reload_crouchhide" ] = array(); + + anim.initAnimSet[ "exposed_idle" ] = array( %RPG_stand_idle ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.rpgStand ) ); + anim.animsets.rpgStand = anim.initAnimSet; +} + +init_animset_shotgun_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %shotgun_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %shotgun_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %shotgun_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %shotgun_aim_6; + anim.initAnimSet[ "straight_level" ] = %shotgun_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_shoot_auto_v3; + anim.initAnimSet[ "single" ] = array( %shotgun_stand_fire_1A, %shotgun_stand_fire_1B ); + + set_animarray_burst_and_semi_fire_stand(); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "reload" ] = array( %shotgun_stand_reload_A, %shotgun_stand_reload_B, %shotgun_stand_reload_C, %shotgun_stand_reload_C, %shotgun_stand_reload_C );// ( C is standing, want it more often ) + anim.initAnimSet[ "reload_crouchhide" ] = array( %shotgun_stand_reload_A, %shotgun_stand_reload_B ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.shotgunStand ) ); + anim.animsets.shotgunStand = anim.initAnimSet; +} + +init_animset_cqb_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %CQB_stand_aim8; + anim.initAnimSet[ "add_aim_down" ] = %CQB_stand_aim2; + anim.initAnimSet[ "add_aim_left" ] = %CQB_stand_aim4; + anim.initAnimSet[ "add_aim_right" ] = %CQB_stand_aim6; + + anim.initAnimSet[ "straight_level" ] = %CQB_stand_aim5; + + anim.initAnimSet[ "fire" ] = %exposed_shoot_auto_v3; + anim.initAnimSet[ "single" ] = array( %exposed_shoot_semi1 ); + set_animarray_burst_and_semi_fire_stand(); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "reload" ] = array( %CQB_stand_reload_steady ); + anim.initAnimSet[ "reload_crouchhide" ] = array( %CQB_stand_reload_knee ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.cqbStand ) ); + anim.animsets.cqbStand = anim.initAnimSet; +} + +init_animset_heat_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %heat_stand_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %heat_stand_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %heat_stand_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %heat_stand_aim_6; + + anim.initAnimSet[ "straight_level" ] = %heat_stand_aim_5; + + anim.initAnimSet[ "fire" ] = %heat_stand_fire_auto; + anim.initAnimSet[ "single" ] = array( %heat_stand_fire_single ); + animscripts\init_common::set_animarray_custom_burst_and_semi_fire_stand( %heat_stand_fire_burst ); + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "exposed_idle" ] = array( %heat_stand_idle, /*%heat_stand_twitchA, %heat_stand_twitchB, %heat_stand_twitchC,*/ %heat_stand_scanA, %heat_stand_scanB ); + //heat_stand_scanA + //heat_stand_scanB + + anim.initAnimSet[ "reload" ] = array( %heat_exposed_reload ); + anim.initAnimSet[ "reload_crouchhide" ] = array(); + + set_animarray_stance_change(); + + anim.initAnimSet[ "turn_left_45" ] = %heat_stand_turn_L; + anim.initAnimSet[ "turn_left_90" ] = %heat_stand_turn_L; + anim.initAnimSet[ "turn_left_135" ] = %heat_stand_turn_180; + anim.initAnimSet[ "turn_left_180" ] = %heat_stand_turn_180; + anim.initAnimSet[ "turn_right_45" ] = %heat_stand_turn_R; + anim.initAnimSet[ "turn_right_90" ] = %heat_stand_turn_R; + anim.initAnimSet[ "turn_right_135" ] = %heat_stand_turn_180; + anim.initAnimSet[ "turn_right_180" ] = %heat_stand_turn_180; + + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.heatStand ) ); + anim.animsets.heatStand = anim.initAnimSet; +} + +init_animset_default_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %exposed_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %exposed_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %exposed_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %exposed_aim_6; + + anim.initAnimSet[ "straight_level" ] = %exposed_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_shoot_auto_v3; + anim.initAnimSet[ "single" ] = array( %exposed_shoot_semi1 ); + set_animarray_burst_and_semi_fire_stand(); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + anim.initAnimSet[ "exposed_grenade" ] = array( %exposed_grenadeThrowB, %exposed_grenadeThrowC ); + + anim.initAnimSet[ "reload" ] = array( %exposed_reload );// %exposed_reloadb, %exposed_reloadc + anim.initAnimSet[ "reload_crouchhide" ] = array( %exposed_reloadb ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.defaultStand ) ); + anim.animsets.defaultStand = anim.initAnimSet; +} + + +init_animset_default_crouch() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %exposed_crouch_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %exposed_crouch_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %exposed_crouch_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %exposed_crouch_aim_6; + anim.initAnimSet[ "straight_level" ] = %exposed_crouch_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_crouch_shoot_auto_v2; + anim.initAnimSet[ "single" ] = array( %exposed_crouch_shoot_semi1 ); + set_animarray_burst_and_semi_fire_crouch(); + + anim.initAnimSet[ "reload" ] = array( %exposed_crouch_reload ); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ); + + set_animarray_stance_change(); + set_animarray_crouching_turns(); + set_animarray_add_turn_aims_crouch(); + + assert( !isdefined( anim.animsets.defaultCrouch ) ); + anim.animsets.defaultCrouch = anim.initAnimSet; +} + +init_animset_rpg_crouch() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %RPG_crouch_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %RPG_crouch_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %RPG_crouch_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %RPG_crouch_aim_6; + anim.initAnimSet[ "straight_level" ] = %RPG_crouch_aim_5; + + anim.initAnimSet[ "fire" ] = %RPG_crouch_fire; + anim.initAnimSet[ "single" ] = array( %RPG_crouch_fire ); + + anim.initAnimSet[ "reload" ] = array( %RPG_crouch_reload ); + + anim.initAnimSet[ "exposed_idle" ] = array( %RPG_crouch_idle ); + + set_animarray_stance_change(); + set_animarray_crouching_turns(); + set_animarray_add_turn_aims_crouch(); + + assert( !isdefined( anim.animsets.rpgCrouch ) ); + anim.animsets.rpgCrouch = anim.initAnimSet; +} + + +init_animset_shotgun_crouch() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %exposed_crouch_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %exposed_crouch_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %exposed_crouch_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %exposed_crouch_aim_6; + anim.initAnimSet[ "straight_level" ] = %exposed_crouch_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_crouch_shoot_auto_v2; + anim.initAnimSet[ "single" ] = array( %shotgun_crouch_fire ); + set_animarray_burst_and_semi_fire_crouch(); + + anim.initAnimSet[ "reload" ] = array( %shotgun_crouch_reload ); + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ); + + set_animarray_stance_change(); + set_animarray_crouching_turns(); + set_animarray_add_turn_aims_crouch(); + + assert( !isdefined( anim.animsets.shotgunCrouch ) ); + anim.animsets.shotgunCrouch = anim.initAnimSet; +} + + +init_animset_default_prone() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %prone_aim_8_add; + anim.initAnimSet[ "add_aim_down" ] = %prone_aim_2_add; + anim.initAnimSet[ "add_aim_left" ] = %prone_aim_4_add; + anim.initAnimSet[ "add_aim_right" ] = %prone_aim_6_add; + + anim.initAnimSet[ "straight_level" ] = %prone_aim_5; + anim.initAnimSet[ "fire" ] = %prone_fire_1; + + anim.initAnimSet[ "single" ] = array( %prone_fire_1 ); + anim.initAnimSet[ "reload" ] = array( %prone_reload ); + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "burst2" ] = %prone_fire_burst; + anim.initAnimSet[ "burst3" ] = %prone_fire_burst; + anim.initAnimSet[ "burst4" ] = %prone_fire_burst; + anim.initAnimSet[ "burst5" ] = %prone_fire_burst; + anim.initAnimSet[ "burst6" ] = %prone_fire_burst; + + anim.initAnimSet[ "semi2" ] = %prone_fire_burst; + anim.initAnimSet[ "semi3" ] = %prone_fire_burst; + anim.initAnimSet[ "semi4" ] = %prone_fire_burst; + anim.initAnimSet[ "semi5" ] = %prone_fire_burst; + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ); + + set_animarray_stance_change(); + + assert( !isdefined( anim.animsets.defaultProne ) ); + anim.animsets.defaultProne = anim.initAnimSet; +} + +init_animset_death() +{ + anim.initAnimSet = []; + + //special deaths + anim.initAnimSet[ "cover_right_stand" ] = array( %corner_standr_deathA, %corner_standr_deathB ); + anim.initAnimSet[ "cover_right_crouch_head_neck" ] = array( %CornerCrR_alert_death_slideout ); + anim.initAnimSet[ "cover_right_crouch" ] = array( %CornerCrR_alert_death_slideout, %CornerCrR_alert_death_back ); + anim.initAnimSet[ "cover_left_stand" ] = array( %corner_standl_deathA, %corner_standl_deathB ); + anim.initAnimSet[ "cover_left_crouch" ] = array( %CornerCrL_death_side, %CornerCrL_death_back ); + anim.initAnimSet[ "cover_stand" ] = array( %coverstand_death_left, %coverstand_death_right ); + anim.initAnimSet[ "cover_crouch_head" ] = array( %covercrouch_death_1, %covercrouch_death_2 ); + anim.initAnimSet[ "cover_crouch_back" ] = array( %covercrouch_death_3, %covercrouch_death_2 ); + anim.initAnimSet[ "cover_crouch" ] = array( %covercrouch_death_2 ); + anim.initAnimSet[ "saw_stand" ] = array( %saw_gunner_death ); + anim.initAnimSet[ "saw_crouch" ] = array( %saw_gunner_lowwall_death ); + anim.initAnimSet[ "saw_prone" ] = array( %saw_gunner_prone_death ); + anim.initAnimSet[ "crawl_crouch" ] = array( %dying_back_death_v2, %dying_back_death_v3, %dying_back_death_v4); + anim.initAnimSet[ "crawl_prone" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2 ); + + //stong bullet deaths + anim.initAnimSet[ "strong_leg_front" ] = array( %death_shotgun_legs, %death_stand_sniper_leg ); + anim.initAnimSet[ "strong_lower_torso_front" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2, %death_shotgun_back_v1, %exposed_death_blowback, %death_stand_sniper_chest1, %death_stand_sniper_chest2, %death_stand_sniper_spin1 ); + anim.initAnimSet[ "strong_torso_front" ] = array( %death_shotgun_back_v1, %exposed_death_blowback, %death_stand_sniper_chest1, %death_stand_sniper_chest2, %death_stand_sniper_spin1 ); + anim.initAnimSet[ "strong_left" ] = array( %death_shotgun_spinL, %death_stand_sniper_spin1, %death_stand_sniper_chest1, %death_stand_sniper_chest2 ); + anim.initAnimSet[ "strong_right" ] = array( %death_shotgun_spinR, %death_stand_sniper_spin2, %death_stand_sniper_chest1, %death_stand_sniper_chest2 ); + + //running forward deaths + anim.initAnimSet[ "running_forward" ] = array( %run_death_facedown, %run_death_roll, %run_death_fallonback, %run_death_flop ); + + //stand pistol deaths + anim.initAnimSet[ "stand_pistol_back" ] = %pistol_death_2; + anim.initAnimSet[ "stand_pistol_legs" ] = %pistol_death_3; + anim.initAnimSet[ "stand_pistol_chest" ] = %pistol_death_4; + anim.initAnimSet[ "stand_pistol_head" ] = %pistol_death_1; + + //stand deaths + anim.initAnimSet[ "stand_legs" ] = array( %exposed_death_groin, %stand_death_leg ); + anim.initAnimSet[ "stand_legs_extended" ] = array( %stand_death_crotch, %stand_death_guts ); + anim.initAnimSet[ "stand_head" ] = array( %exposed_death_headshot, %exposed_death_flop ); + anim.initAnimSet[ "stand_neck" ] = array( %exposed_death_neckgrab ); + anim.initAnimSet[ "stand_upper_torso" ] = array( %exposed_death_twist, %stand_death_shoulder_spin, %stand_death_shoulderback, %stand_death_tumbleforward, %stand_death_stumbleforward ); + anim.initAnimSet[ "stand_upper_torso_extended" ] = array( %stand_death_fallside ); + anim.initAnimSet[ "stand_upper_left" ] = array( %exposed_death_twist, %stand_death_shoulder_spin, %stand_death_shoulderback ); + anim.initAnimSet[ "stand_front_head" ] = array( %stand_death_face, %stand_death_headshot_slowfall ); + anim.initAnimSet[ "stand_front_head_extended" ] = array( %stand_death_head_straight_back ); + anim.initAnimSet[ "stand_front_torso" ] = array( %stand_death_tumbleback ); + anim.initAnimSet[ "stand_front_torso_extended" ] = array( %stand_death_chest_stunned ); + anim.initAnimSet[ "stand_back" ] = array( %exposed_death_falltoknees, %exposed_death_falltoknees_02 ); + anim.initAnimSet[ "stand_firing" ] = array( %exposed_death_firing_02, %exposed_death_firing ); + anim.initAnimSet[ "stand_generic" ] = array( %exposed_death_02, %exposed_death_nerve ); + anim.initAnimSet[ "stand_exposed" ] = array( %exposed_death ); + + //crouch deaths + anim.initAnimSet[ "crouch_head" ] = %exposed_crouch_death_fetal; + anim.initAnimSet[ "crouch_torso" ] = %exposed_crouch_death_flip; + anim.initAnimSet[ "crouch_twist" ] = %exposed_crouch_death_twist; + anim.initAnimSet[ "crouch_generic" ] = %exposed_crouch_death_flip; + + //prone + anim.initAnimSet[ "prone_aiming" ] = %prone_death_quickdeath; + anim.initAnimSet[ "prone" ] = %dying_crawl_death_v1; + + //back + anim.initAnimSet[ "back" ] = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3, %dying_back_death_v4 ); + + //explosions! + anim.initAnimSet[ "explode_stand_front" ] = array(%death_explosion_stand_B_v1, %death_explosion_stand_B_v2, %death_explosion_stand_B_v3, %death_explosion_stand_B_v4 ); + anim.initAnimSet[ "explode_stand_right" ] = array(%death_explosion_stand_L_v1, %death_explosion_stand_L_v2, %death_explosion_stand_L_v3 ); + anim.initAnimSet[ "explode_stand_left" ] = array(%death_explosion_stand_R_v1, %death_explosion_stand_R_v2 ); + anim.initAnimSet[ "explode_stand_back" ] = array(%death_explosion_stand_F_v1, %death_explosion_stand_F_v2, %death_explosion_stand_F_v3, %death_explosion_stand_F_v4 ); + anim.initAnimSet[ "explode_run_front" ] = array(%death_explosion_run_B_v1, %death_explosion_run_B_v2 ); + anim.initAnimSet[ "explode_run_right" ] = array(%death_explosion_run_L_v1, %death_explosion_run_L_v2 ); + anim.initAnimSet[ "explode_run_left" ] = array(%death_explosion_run_R_v1, %death_explosion_run_R_v2 ); + anim.initAnimSet[ "explode_run_back" ] = array(%death_explosion_run_F_v1, %death_explosion_run_F_v2, %death_explosion_run_F_v3, %death_explosion_run_F_v4 ); + + + assert( !isdefined( anim.animsets.deathAnimSet ) ); + anim.animsets.deathAnimSet = anim.initAnimSet; +} + +init_animset_pain() +{ + anim.initAnimSet = []; + + //special pain + anim.initAnimSet[ "shield" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "cover_left_stand" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "cover_left_crouch" ] = array( %CornerCrL_painB ); + anim.initAnimSet[ "cover_right_stand" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "cover_right_crouch" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "cover_right_stand_B" ] = array( %corner_standR_pain_B_2_alert ); + anim.initAnimSet[ "cover_left_stand_A" ] = array( %corner_standL_pain_A_2_alert ); + anim.initAnimSet[ "cover_left_stand_B" ] = array( %corner_standL_pain_B_2_alert ); + anim.initAnimSet[ "cover_crouch" ] = array( %covercrouch_pain_right, %covercrouch_pain_front, %covercrouch_pain_left_3 ); + anim.initAnimSet[ "cover_stand" ] = array( %coverstand_pain_groin, %coverstand_pain_leg ); + anim.initAnimSet[ "cover_stand_aim" ] = array( %coverstand_pain_aim_2_hide_01, %coverstand_pain_aim_2_hide_02 ); + anim.initAnimSet[ "cover_crouch_aim" ] = array( %covercrouch_pain_aim_2_hide_01 ); + anim.initAnimSet[ "saw_stand" ] = %saw_gunner_pain; + anim.initAnimSet[ "saw_crouch" ] = %saw_gunner_lowwall_pain_02; + anim.initAnimSet[ "saw_prone" ] = %saw_gunner_prone_pain; + + + //run pains + anim.initAnimSet[ "long_run" ] = array( %run_pain_leg, %run_pain_shoulder, %run_pain_stomach_stumble, %run_pain_head ); + anim.initAnimSet[ "med_run" ] = array( %run_pain_fallonknee_02, %run_pain_stomach, %run_pain_stumble, %run_pain_stomach_fast, %run_pain_leg_fast, %run_pain_fall ); + anim.initAnimSet[ "short_run" ] = array( %run_pain_fallonknee, %run_pain_fallonknee_03 ); + + //pistol pain + anim.initAnimSet[ "pistol_torso" ] = %pistol_stand_pain_chest; + anim.initAnimSet[ "pistol_legs" ] = %pistol_stand_pain_groin; + anim.initAnimSet[ "pistol_head" ] = %pistol_stand_pain_head; + anim.initAnimSet[ "pistol_left_arm" ] = %pistol_stand_pain_leftshoulder; + anim.initAnimSet[ "pistol_right_arm" ] = %pistol_stand_pain_rightshoulder; + + //stand pain + anim.initAnimSet[ "stand_torso_extended" ] = array( %stand_exposed_extendedpain_gut, %stand_exposed_extendedpain_stomach ); + anim.initAnimSet[ "stand_head" ] = array( %exposed_pain_face, %stand_exposed_extendedpain_neck ); + anim.initAnimSet[ "stand_head_extended" ] = array( %stand_exposed_extendedpain_head_2_crouch ); + anim.initAnimSet[ "stand_right_arm" ] = array( %exposed_pain_right_arm ); + anim.initAnimSet[ "stand_left_arm" ] = array( %stand_exposed_extendedpain_shoulderswing ); + anim.initAnimSet[ "stand_left_arm_extended" ] = array( %stand_exposed_extendedpain_shoulder_2_crouch ); + anim.initAnimSet[ "stand_legs" ] = array( %exposed_pain_groin, %stand_exposed_extendedpain_hip ); + anim.initAnimSet[ "stand_legs_extended" ] = array( %stand_exposed_extendedpain_hip_2_crouch, %stand_exposed_extendedpain_feet_2_crouch, %stand_exposed_extendedpain_stomach ); + anim.initAnimSet[ "stand_feet" ] = array( %stand_exposed_extendedpain_thigh ); + anim.initAnimSet[ "stand_feet_extended" ] = array( %stand_exposed_extendedpain_feet_2_crouch ); + anim.initAnimSet[ "stand_generic_long_death" ] = array( %exposed_pain_2_crouch, %stand_extendedpainB ); + anim.initAnimSet[ "stand_generic" ] = array( %exposed_pain_right_arm, %exposed_pain_face, %exposed_pain_groin ); + anim.initAnimSet[ "stand_generic_extended" ] = array( %stand_extendedpainC, %stand_exposed_extendedpain_chest ); + + //crouch pain + anim.initAnimSet[ "crouch_generic" ] = array( %exposed_crouch_extendedpainA ); + anim.initAnimSet[ "crouch_exposed" ] = array( %exposed_crouch_pain_chest, %exposed_crouch_pain_headsnap, %exposed_crouch_pain_flinch ); + anim.initAnimSet[ "crouch_left_arm" ] = array( %exposed_crouch_pain_left_arm ); + anim.initAnimSet[ "crouch_right_arm" ] = array( %exposed_crouch_pain_right_arm ); + + //prone pain + anim.initAnimSet[ "prone" ] = array( %prone_reaction_A, %prone_reaction_B ); + + anim.initAnimSet[ "crawl_trans_stand" ] = array( %dying_stand_2_back_v1, %dying_stand_2_back_v2 ); + anim.initAnimSet[ "crawl_trans_crouch" ] = array( %dying_crouch_2_back ); + anim.initAnimSet[ "crawl_trans_prone" ] = array( %dying_crawl_2_back ); + + //pistol crawl + anim.initAnimSet[ "stand_2_crawl" ] = array( %dying_stand_2_crawl_v1, %dying_stand_2_crawl_v2, %dying_stand_2_crawl_v3 ); + anim.initAnimSet[ "crouch_2_crawl" ] = array( %dying_crouch_2_crawl ); + anim.initAnimSet[ "crawl" ] = %dying_crawl; + anim.initAnimSet[ "death" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2 ); + anim.initAnimSet[ "back_idle" ] = %dying_back_idle; + anim.initAnimSet[ "back_idle_twitch" ] = array( %dying_back_twitch_A, %dying_back_twitch_B ); + anim.initAnimSet[ "back_crawl" ] = %dying_crawl_back; + anim.initAnimSet[ "back_fire" ] = %dying_back_fire; + anim.initAnimSet[ "back_death" ] = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3 ); + + //additive pain + anim.initAnimSet[ "add_generic" ] = array( %pain_add_standing_belly, %pain_add_standing_left_arm, %pain_add_standing_right_arm ); + anim.initAnimSet[ "add_left_arm" ] = array( %pain_add_standing_left_arm ); + anim.initAnimSet[ "add_right_arm" ] = array( %pain_add_standing_right_arm ); + anim.initAnimSet[ "add_left_leg" ] = array( %pain_add_standing_left_leg ); + anim.initAnimSet[ "add_right_leg" ] = array( %pain_add_standing_right_leg ); + + anim.initAnimSet[ "dying_back_aim_left" ] = %dying_back_aim_4; + anim.initAnimSet[ "dying_back_aim_right" ] = %dying_back_aim_6; + + //anim.initAnimSet[ "taser_stand" ] = array( %tp_moon_react_tased_stand_opfor_01 ); + //anim.initAnimSet[ "taser_crouch" ] = array( %tp_moon_react_tased_crouch_opfor_01 ); + //anim.initAnimSet[ "taser_stand_ally" ] = array( %tp_moon_react_tased_stand_ally_01 ); + //anim.initAnimSet[ "taser_crouch_ally" ] = array( %tp_moon_react_tased_crouch_ally_01 ); + + assert( !isdefined( anim.animsets.painAnimSet ) ); + anim.animsets.painAnimSet = anim.initAnimSet; +} + +init_animset_gib() +{ + anim.initAnimSet = []; + + //TEMP + anim.initAnimSet[ "gib_right_arm_front_start" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_arm_back_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_arm_front_loop" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_arm_back_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_arm_front_end" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_arm_back_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + + anim.initAnimSet[ "gib_left_arm_front_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_arm_back_start" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_arm_front_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_arm_back_loop" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_arm_front_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_arm_back_end" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + + anim.initAnimSet[ "gib_right_leg_front_start" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_leg_back_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_leg_front_loop" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_leg_back_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_leg_front_end" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_leg_back_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + + anim.initAnimSet[ "gib_left_leg_front_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_leg_back_start" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_leg_front_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_leg_back_loop" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_leg_front_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_leg_back_end" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + + anim.initAnimSet[ "gib_no_legs_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_no_legs_loop" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_no_legs_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + + anim.initAnimSet[ "gib_shoulder_back" ] = %stand_death_shoulderback; + anim.initAnimSet[ "gib_shoulder_spin" ] = %stand_death_shoulder_spin; + anim.initAnimSet[ "gib_shoulder_twist" ] = %exposed_death_twist; + + assert( !isdefined( anim.animsets.gibAnimSet ) ); + anim.animsets.gibAnimSet = anim.initAnimSet; +} + +init_animset_crouch_wall() +{ + anim.initAnimSet = []; + + anim.initAnimSet[ "hide_idle" ] = %covercrouch_hide_idle; + anim.initAnimSet[ "hide_idle_twitch" ] = array( + %covercrouch_twitch_1, + %covercrouch_twitch_2, + %covercrouch_twitch_3, + %covercrouch_twitch_4 + ); + + anim.initAnimSet[ "hide_idle_flinch" ] = array(); + + anim.initAnimSet[ "hide_2_crouch" ] = %covercrouch_hide_2_aim; + anim.initAnimSet[ "hide_2_stand" ] = %covercrouch_hide_2_stand; + anim.initAnimSet[ "hide_2_lean" ] = %covercrouch_hide_2_lean; + anim.initAnimSet[ "hide_2_right" ] = %covercrouch_hide_2_right; + anim.initAnimSet[ "hide_2_left" ] = %covercrouch_hide_2_left; + + anim.initAnimSet[ "crouch_2_hide" ] = %covercrouch_aim_2_hide; + anim.initAnimSet[ "stand_2_hide" ] = %covercrouch_stand_2_hide; + anim.initAnimSet[ "lean_2_hide" ] = %covercrouch_lean_2_hide; + anim.initAnimSet[ "right_2_hide" ] = %covercrouch_right_2_hide; + anim.initAnimSet[ "left_2_hide" ] = %covercrouch_left_2_hide; + + + anim.initAnimSet[ "crouch_aim" ] = %covercrouch_aim5; + anim.initAnimSet[ "stand_aim" ] = %exposed_aim_5; + anim.initAnimSet[ "lean_aim" ] = %covercrouch_lean_aim5; + + anim.initAnimSet[ "fire" ] = %exposed_shoot_auto_v2; + anim.initAnimSet[ "semi2" ] = %exposed_shoot_semi2; + anim.initAnimSet[ "semi3" ] = %exposed_shoot_semi3; + anim.initAnimSet[ "semi4" ] = %exposed_shoot_semi4; + anim.initAnimSet[ "semi5" ] = %exposed_shoot_semi5; + + anim.initAnimSet[ "burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + anim.initAnimSet[ "burst3" ] = %exposed_shoot_burst3; + anim.initAnimSet[ "burst4" ] = %exposed_shoot_burst4; + anim.initAnimSet[ "burst5" ] = %exposed_shoot_burst5; + anim.initAnimSet[ "burst6" ] = %exposed_shoot_burst6; + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "blind_fire" ] = array( %covercrouch_blindfire_1, %covercrouch_blindfire_2, %covercrouch_blindfire_3, %covercrouch_blindfire_4 ); + + anim.initAnimSet[ "reload" ] = %covercrouch_reload_hide; + + anim.initAnimSet[ "grenade_safe" ] = array( %covercrouch_grenadeA, %covercrouch_grenadeB ); + anim.initAnimSet[ "grenade_exposed" ] = array( %covercrouch_grenadeA, %covercrouch_grenadeB ); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "look" ] = array( %covercrouch_hide_look ); + + anim.initAnimSet[ "shotgun_lean_single" ] = array( %shotgun_stand_fire_1A ); + anim.initAnimSet[ "shotgun_over_single" ] = array( %shotgun_crouch_fire ); + anim.initAnimSet[ "normal_single" ] = array( %exposed_shoot_semi1 ); + + assert( !isdefined( anim.animsets.crouchWallAnimSet ) ); + anim.animsets.crouchWallAnimSet = anim.initAnimSet; +} + +init_animset_stand_wall() +{ + anim.initAnimSet = []; + + anim.initAnimSet[ "hide_idle" ] = %coverstand_hide_idle; + anim.initAnimSet[ "hide_idle_twitch" ] = array( + %coverstand_hide_idle_twitch01, + %coverstand_hide_idle_twitch02, + %coverstand_hide_idle_twitch03, + %coverstand_hide_idle_twitch04, + %coverstand_hide_idle_twitch05 + ); + + anim.initAnimSet[ "hide_idle_flinch" ] = array( + %coverstand_react01, + %coverstand_react02, + %coverstand_react03, + %coverstand_react04 + ); + + anim.initAnimSet[ "hide_2_stand" ] = %coverstand_hide_2_aim; + anim.initAnimSet[ "stand_2_hide" ] = %coverstand_aim_2_hide; + + anim.initAnimSet[ "hide_2_over" ] = %coverstand_2_coverstandaim; + anim.initAnimSet[ "over_2_hide" ] = %coverstandaim_2_coverstand; + + anim.initAnimSet[ "over_aim" ] = %coverstandaim_aim5; + + anim.initAnimSet[ "over_fire" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_semi2" ] = %coverstandaim_fire; + anim.initAnimSet[ "over_semi3" ] = %coverstandaim_fire; + anim.initAnimSet[ "over_semi4" ] = %coverstandaim_fire; + anim.initAnimSet[ "over_semi5" ] = %coverstandaim_fire; + + anim.initAnimSet[ "over_single" ] = array( %coverstandaim_fire ); + + anim.initAnimSet[ "over_burst2" ] = %coverstandaim_autofire;// ( will be limited to 2 shots ) + anim.initAnimSet[ "over_burst3" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_burst4" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_burst5" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_burst6" ] = %coverstandaim_autofire; + + anim.initAnimSet[ "over_continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "stand_aim" ] = %exposed_aim_5; + + anim.initAnimSet[ "stand_fire" ] = %exposed_shoot_auto_v2; + anim.initAnimSet[ "stand_semi2" ] = %exposed_shoot_semi2; + anim.initAnimSet[ "stand_semi3" ] = %exposed_shoot_semi3; + anim.initAnimSet[ "stand_semi4" ] = %exposed_shoot_semi4; + anim.initAnimSet[ "stand_semi5" ] = %exposed_shoot_semi5; + + anim.initAnimSet[ "stand_shotgun_single" ] = array( %shotgun_stand_fire_1A ); + anim.initAnimSet[ "stand_normal_single" ] = array( %exposed_shoot_semi1 ); + + anim.initAnimSet[ "stand_burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + anim.initAnimSet[ "stand_burst3" ] = %exposed_shoot_burst3; + anim.initAnimSet[ "stand_burst4" ] = %exposed_shoot_burst4; + anim.initAnimSet[ "stand_burst5" ] = %exposed_shoot_burst5; + anim.initAnimSet[ "stand_burst6" ] = %exposed_shoot_burst6; + + anim.initAnimSet[ "stand_continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet["blind_fire"] = array( %coverstand_blindfire_1, %coverstand_blindfire_2 ); // #3 looks silly + + anim.initAnimSet[ "reload" ] = %coverstand_reloadA; + + anim.initAnimSet[ "look" ] = array( %coverstand_look_quick, %coverstand_look_quick_v2 ); + + anim.initAnimSet[ "grenade_safe" ] = array( %coverstand_grenadeA, %coverstand_grenadeB ); + anim.initAnimSet[ "grenade_exposed" ] = array( %coverstand_grenadeA, %coverstand_grenadeB ); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "hide_to_look" ] = %coverstand_look_moveup; + anim.initAnimSet[ "look_idle" ] = %coverstand_look_idle; + anim.initAnimSet[ "look_to_hide" ] = %coverstand_look_movedown; + anim.initAnimSet[ "look_to_hide_fast" ] = %coverstand_look_movedown_fast; + + assert( !isdefined( anim.animsets.standWallAnimSet ) ); + anim.animsets.standWallAnimSet = anim.initAnimSet; +} + +init_animset_custom_crouch( fireAnim, idleAnim, reloadAnim ) +{ + assert( isdefined( anim.animsets ) && isdefined( anim.animsets.defaultCrouch ) ); + + anim.initAnimSet = anim.animsets.defaultCrouch; + + if ( isdefined( fireAnim ) ) + { + anim.initAnimSet[ "fire" ] = fireAnim; + anim.initAnimSet[ "single" ] = array( fireAnim ); + set_animarray_custom_burst_and_semi_fire_crouch( fireAnim ); + } + + if ( isdefined( idleAnim ) ) + anim.initAnimSet[ "exposed_idle" ] = array( idleAnim ); + + if ( isdefined( reloadAnim ) ) + anim.initAnimSet[ "reload" ] = array( reloadAnim ); + + self.combatCrouchAnims = anim.initAnimSet; +} + + +//////////////////////////////////////////// +// Helpers for the above init_* +//////////////////////////////////////////// + +set_animarray_standing_turns_pistol( animArray ) +{ + anim.initAnimSet[ "turn_left_45" ] = %pistol_stand_turn45L; + anim.initAnimSet[ "turn_left_90" ] = %pistol_stand_turn90L; + anim.initAnimSet[ "turn_left_135" ] = %pistol_stand_turn90L; + anim.initAnimSet[ "turn_left_180" ] = %pistol_stand_turn180L; + anim.initAnimSet[ "turn_right_45" ] = %pistol_stand_turn45R; + anim.initAnimSet[ "turn_right_90" ] = %pistol_stand_turn90R; + anim.initAnimSet[ "turn_right_135" ] = %pistol_stand_turn90R; + anim.initAnimSet[ "turn_right_180" ] = %pistol_stand_turn180L; +} + +set_animarray_standing_turns() +{ + anim.initAnimSet[ "turn_left_45" ] = %exposed_tracking_turn45L; + anim.initAnimSet[ "turn_left_90" ] = %exposed_tracking_turn90L; + anim.initAnimSet[ "turn_left_135" ] = %exposed_tracking_turn135L; + anim.initAnimSet[ "turn_left_180" ] = %exposed_tracking_turn180L; + anim.initAnimSet[ "turn_right_45" ] = %exposed_tracking_turn45R; + anim.initAnimSet[ "turn_right_90" ] = %exposed_tracking_turn90R; + anim.initAnimSet[ "turn_right_135" ] = %exposed_tracking_turn135R; + anim.initAnimSet[ "turn_right_180" ] = %exposed_tracking_turn180R; +} + +set_animarray_crouching_turns() +{ + anim.initAnimSet[ "turn_left_45" ] = %exposed_crouch_turn_90_left; + anim.initAnimSet[ "turn_left_90" ] = %exposed_crouch_turn_90_left; + anim.initAnimSet[ "turn_left_135" ] = %exposed_crouch_turn_180_left; + anim.initAnimSet[ "turn_left_180" ] = %exposed_crouch_turn_180_left; + anim.initAnimSet[ "turn_right_45" ] = %exposed_crouch_turn_90_right; + anim.initAnimSet[ "turn_right_90" ] = %exposed_crouch_turn_90_right; + anim.initAnimSet[ "turn_right_135" ] = %exposed_crouch_turn_180_right; + anim.initAnimSet[ "turn_right_180" ] = %exposed_crouch_turn_180_right; +} + + +set_animarray_stance_change() +{ + anim.initAnimSet[ "crouch_2_stand" ] = %exposed_crouch_2_stand; + anim.initAnimSet[ "crouch_2_prone" ] = %crouch_2_prone; + anim.initAnimSet[ "stand_2_crouch" ] = %exposed_stand_2_crouch; + anim.initAnimSet[ "stand_2_prone" ] = %stand_2_prone; + anim.initAnimSet[ "prone_2_crouch" ] = %prone_2_crouch; + anim.initAnimSet[ "prone_2_stand" ] = %prone_2_stand; +} + +set_animarray_burst_and_semi_fire_stand() +{ + anim.initAnimSet[ "burst2" ] = %exposed_shoot_burst3;// ( will be stopped after second bullet ) + anim.initAnimSet[ "burst3" ] = %exposed_shoot_burst3; + anim.initAnimSet[ "burst4" ] = %exposed_shoot_burst4; + anim.initAnimSet[ "burst5" ] = %exposed_shoot_burst5; + anim.initAnimSet[ "burst6" ] = %exposed_shoot_burst6; + + anim.initAnimSet[ "semi2" ] = %exposed_shoot_semi2; + anim.initAnimSet[ "semi3" ] = %exposed_shoot_semi3; + anim.initAnimSet[ "semi4" ] = %exposed_shoot_semi4; + anim.initAnimSet[ "semi5" ] = %exposed_shoot_semi5; + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); +} + + +set_animarray_burst_and_semi_fire_crouch() +{ + anim.initAnimSet[ "burst2" ] = %exposed_crouch_shoot_burst3; + anim.initAnimSet[ "burst3" ] = %exposed_crouch_shoot_burst3; + anim.initAnimSet[ "burst4" ] = %exposed_crouch_shoot_burst4; + anim.initAnimSet[ "burst5" ] = %exposed_crouch_shoot_burst5; + anim.initAnimSet[ "burst6" ] = %exposed_crouch_shoot_burst6; + + anim.initAnimSet[ "semi2" ] = %exposed_crouch_shoot_semi2; + anim.initAnimSet[ "semi3" ] = %exposed_crouch_shoot_semi3; + anim.initAnimSet[ "semi4" ] = %exposed_crouch_shoot_semi4; + anim.initAnimSet[ "semi5" ] = %exposed_crouch_shoot_semi5; + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); +} + +set_animarray_custom_burst_and_semi_fire_crouch( fireAnim ) +{ + anim.initAnimSet[ "burst2" ] = fireAnim; + anim.initAnimSet[ "burst3" ] = fireAnim; + anim.initAnimSet[ "burst4" ] = fireAnim; + anim.initAnimSet[ "burst5" ] = fireAnim; + anim.initAnimSet[ "burst6" ] = fireAnim; + + anim.initAnimSet[ "semi2" ] = fireAnim; + anim.initAnimSet[ "semi3" ] = fireAnim; + anim.initAnimSet[ "semi4" ] = fireAnim; + anim.initAnimSet[ "semi5" ] = fireAnim; +} + + +set_animarray_add_turn_aims_stand() +{ + anim.initAnimSet[ "add_turn_aim_up" ] = %exposed_turn_aim_8; + anim.initAnimSet[ "add_turn_aim_down" ] = %exposed_turn_aim_2; + anim.initAnimSet[ "add_turn_aim_left" ] = %exposed_turn_aim_4; + anim.initAnimSet[ "add_turn_aim_right" ] = %exposed_turn_aim_6; +} + +set_animarray_add_turn_aims_crouch() +{ + anim.initAnimSet[ "add_turn_aim_up" ] = %exposed_crouch_turn_aim_8; + anim.initAnimSet[ "add_turn_aim_down" ] = %exposed_crouch_turn_aim_2; + anim.initAnimSet[ "add_turn_aim_left" ] = %exposed_crouch_turn_aim_4; + anim.initAnimSet[ "add_turn_aim_right" ] = %exposed_crouch_turn_aim_6; +} + + +//////////////////////////////////////////// +// Moving turn +//////////////////////////////////////////// + +init_moving_turn_animations() +{ + anim.runTurnAnims[ "L90" ] = %run_turn_L90; + anim.runTurnAnims[ "R90" ] = %run_turn_R90; + anim.runTurnAnims[ "L45" ] = %run_turn_L45; + anim.runTurnAnims[ "R45" ] = %run_turn_R45; + anim.runTurnAnims[ "L135" ] = %run_turn_L135; + anim.runTurnAnims[ "R135" ] = %run_turn_R135; + anim.runTurnAnims[ "180" ] = %run_turn_180; + + anim.cqbTurnAnims[ "L90" ] = %CQB_walk_turn_4; + anim.cqbTurnAnims[ "R90" ] = %CQB_walk_turn_6; + anim.cqbTurnAnims[ "L45" ] = %CQB_walk_turn_7; + anim.cqbTurnAnims[ "R45" ] = %CQB_walk_turn_9; + anim.cqbTurnAnims[ "L135" ] = %CQB_walk_turn_1; + anim.cqbTurnAnims[ "R135" ] = %CQB_walk_turn_3; + anim.cqbTurnAnims[ "180" ] = %CQB_walk_turn_2; +} + + +//////////////////////////////////////////// +// Misc +//////////////////////////////////////////// + +init_animset_run_n_gun() +{ + anim.runNGunAnims[ "F" ] = %run_n_gun_F; + anim.runNGunAnims[ "L" ] = %run_n_gun_L; + anim.runNGunAnims[ "R" ] = %run_n_gun_R; + anim.runNGunAnims[ "LB" ] = %run_n_gun_L_120; + anim.runNGunAnims[ "RB" ] = %run_n_gun_R_120; +} + + +init_ambush_sidestep_anims() +{ + anim.moveAnimSet[ "move_l" ] = %combatwalk_L; + anim.moveAnimSet[ "move_r" ] = %combatwalk_R; + anim.moveAnimSet[ "move_b" ] = %combatwalk_B; +} + +init_heat_reload_function() +{ + anim.heat_reload_anim_func = ::heat_reload_anim; +} + +heat_reload_anim() +{ + if ( self.weapon != self.primaryweapon ) + return animArrayPickRandom( "reload" ); + + if ( isdefined( self.node ) ) + { + if ( self nearClaimNodeAndAngle() ) + { + coverReloadAnim = undefined; + if ( self.node.type == "Cover Left" ) + coverReloadAnim = %heat_cover_reload_R; + else if ( self.node.type == "Cover Right" ) + coverReloadAnim = %heat_cover_reload_L; + + if ( isdefined( coverReloadAnim ) ) + { + //self mayMoveToPoint( reloadAnimPos ); + return coverReloadAnim; + } + } + } + + return %heat_exposed_reload; +} + +init_animarray_standing_left() /* void */ +{ + array = []; + + array[ "alert_idle" ] = %corner_standL_alert_idle; + array[ "alert_idle_twitch" ] = [ + %corner_standL_alert_twitch01, + %corner_standL_alert_twitch02, + %corner_standL_alert_twitch03, + %corner_standL_alert_twitch04, + %corner_standL_alert_twitch05, + %corner_standL_alert_twitch06, + %corner_standL_alert_twitch07 + ]; + array[ "alert_idle_flinch" ] = [ %corner_standL_flinch ]; + + array[ "alert_to_A" ] = [ %corner_standL_trans_alert_2_A ]; + array[ "alert_to_B" ] = [ %corner_standL_trans_alert_2_B_v2 ]; + array[ "A_to_alert" ] = [ %corner_standL_trans_A_2_alert_v2 ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %corner_standL_trans_A_2_B_v2 ]; + array[ "B_to_alert" ] = [ %corner_standL_trans_B_2_alert_v2 ]; + array[ "B_to_alert_reload" ] = [ %corner_standL_reload_B_2_alert ]; + array[ "B_to_A" ] = [ %corner_standL_trans_B_2_A_v2 ]; + array[ "lean_to_alert" ] = [ %CornerStndL_lean_2_alert ]; + array[ "alert_to_lean" ] = [ %CornerStndL_alert_2_lean ]; + array[ "look" ] = %corner_standL_look; + array[ "reload" ] = [ %corner_standL_reload_v1 ];// , %corner_standL_reload_v2 ); + array[ "grenade_exposed" ] = %corner_standL_grenade_A; + array[ "grenade_safe" ] = %corner_standL_grenade_B; + + array[ "blind_fire" ] = [ %corner_standL_blindfire_v1, %corner_standL_blindfire_v2 ]; + + array[ "alert_to_look" ] = %corner_standL_alert_2_look; + array[ "look_to_alert" ] = %corner_standL_look_2_alert; + array[ "look_to_alert_fast" ] = %corner_standl_look_2_alert_fast_v1; + array[ "look_idle" ] = %corner_standL_look_idle; + array[ "stance_change" ] = %CornerCrL_stand_2_alert; + + array[ "lean_aim_down" ] = %CornerStndL_lean_aim_2; + array[ "lean_aim_left" ] = %CornerStndL_lean_aim_4; + array[ "lean_aim_straight" ] = %CornerStndL_lean_aim_5; + array[ "lean_aim_right" ] = %CornerStndL_lean_aim_6; + array[ "lean_aim_up" ] = %CornerStndL_lean_aim_8; + array[ "lean_reload" ] = %CornerStndL_lean_reload; + + array[ "lean_idle" ] = [ %CornerStndL_lean_idle ]; + + array[ "lean_single" ] = %CornerStndL_lean_fire; + //array["lean_burst"] = %CornerStndL_lean_autoburst; + array[ "lean_fire" ] = %CornerStndL_lean_auto; + + if ( isDefined( anim.ramboAnims ) ) + { + //array[ "rambo" ] = [ %corner_standL_rambo_set, %corner_standL_rambo_jam ]; + array[ "rambo90" ] = anim.ramboAnims.coverleft90; + array[ "rambo45" ] = anim.ramboAnims.coverleft45; + array[ "grenade_rambo" ] = anim.ramboAnims.coverleftgrenade; + } + + anim.coverLeftStand = array; +} + + +init_animarray_crouching_left() +{ + array = []; + + array[ "alert_idle" ] = %CornerCrL_alert_idle; + array[ "alert_idle_twitch" ] = []; + array[ "alert_idle_flinch" ] = []; + + //array["alert_to_C"] = %CornerCrL_trans_alert_2_C; + //array["B_to_C"] = %CornerCrL_trans_B_2_C; + //array["C_to_alert"] = %CornerCrL_trans_C_2_alert; + //array["C_to_B"] = %CornerCrL_trans_C_2_B; + array[ "alert_to_A" ] = [ %CornerCrL_trans_alert_2_A ]; + array[ "alert_to_B" ] = [ %CornerCrL_trans_alert_2_B ]; + array[ "A_to_alert" ] = [ %CornerCrL_trans_A_2_alert ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %CornerCrL_trans_A_2_B ]; + array[ "B_to_alert" ] = [ %CornerCrL_trans_B_2_alert ]; + array[ "B_to_alert_reload" ] = []; + array[ "B_to_A" ] = [ %CornerCrL_trans_B_2_A ]; + array[ "lean_to_alert" ] = [ %CornerCrL_lean_2_alert ]; + array[ "alert_to_lean" ] = [ %CornerCrL_alert_2_lean ]; + + array[ "look" ] = %CornerCrL_look_fast; + array[ "reload" ] = [ %CornerCrL_reloadA, %CornerCrL_reloadB ]; + array[ "grenade_safe" ] = %CornerCrL_grenadeA; + array[ "grenade_exposed" ] = %CornerCrL_grenadeB; + + array[ "alert_to_over" ] = [ %CornerCrL_alert_2_over ]; + array[ "over_to_alert" ] = [ %CornerCrL_over_2_alert ]; + array[ "over_to_alert_reload" ] = []; + array[ "blind_fire" ] = []; + + array[ "rambo90" ] = []; + array[ "rambo45" ] = []; + + //array["alert_to_look"] = %CornerCrL_alert_idle; // TODO + //array["look_to_alert"] = %CornerCrL_alert_idle; // TODO + //array["look_to_alert_fast"] = %CornerCrL_alert_idle; // TODO + //array["look_idle"] = %CornerCrL_alert_idle; // TODO + array[ "stance_change" ] = %CornerCrL_alert_2_stand; + + array[ "lean_aim_down" ] = %CornerCrL_lean_aim_2; + array[ "lean_aim_left" ] = %CornerCrL_lean_aim_4; + array[ "lean_aim_straight" ] = %CornerCrL_lean_aim_5; + array[ "lean_aim_right" ] = %CornerCrL_lean_aim_6; + array[ "lean_aim_up" ] = %CornerCrL_lean_aim_8; + + array[ "lean_idle" ] = [ %CornerCrL_lean_idle ]; + + array[ "lean_single" ] = %CornerCrL_lean_fire; + array[ "lean_fire" ] = %CornerCrL_lean_auto; + + anim.coverLeftCrouch = array; +} + +init_animarray_standing_right() /* void */ +{ + array = []; + + array[ "alert_idle" ] = %corner_standR_alert_idle; + array[ "alert_idle_twitch" ] = [ + %corner_standR_alert_twitch01, + %corner_standR_alert_twitch02, + %corner_standR_alert_twitch04, + %corner_standR_alert_twitch05, + %corner_standR_alert_twitch06, + %corner_standR_alert_twitch07 + ]; + array[ "alert_idle_flinch" ] = [ %corner_standR_flinch, %corner_standR_flinchB ]; + + array[ "alert_to_A" ] = [ %corner_standR_trans_alert_2_A, %corner_standR_trans_alert_2_A_v2 ]; + array[ "alert_to_B" ] = [ %corner_standR_trans_alert_2_B, %corner_standR_trans_alert_2_B_v2, %corner_standR_trans_alert_2_B_v3 ]; + array[ "A_to_alert" ] = [ %corner_standR_trans_A_2_alert_v2 ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %corner_standR_trans_A_2_B, %corner_standR_trans_A_2_B_v2 ]; + array[ "B_to_alert" ] = [ %corner_standR_trans_B_2_alert, %corner_standR_trans_B_2_alert_v2, %corner_standR_trans_B_2_alert_v3 ]; + array[ "B_to_alert_reload" ] = [ %corner_standR_reload_B_2_alert ]; + array[ "B_to_A" ] = [ %corner_standR_trans_B_2_A, %corner_standR_trans_B_2_A_v2 ]; + array[ "lean_to_alert" ] = [ %CornerStndR_lean_2_alert ]; + array[ "alert_to_lean" ] = [ %CornerStndR_alert_2_lean ]; + array[ "look" ] = %corner_standR_look; + array[ "reload" ] = [ %corner_standR_reload_v1 ];// , %corner_standR_reload_v2 );// v2 isn't finished, it seems + array[ "grenade_exposed" ] = %corner_standR_grenade_A; + array[ "grenade_safe" ] = %corner_standR_grenade_B; + + array[ "blind_fire" ] = [ %corner_standR_blindfire_v1, %corner_standR_blindfire_v2 ]; + + array[ "alert_to_look" ] = %corner_standR_alert_2_look; + array[ "look_to_alert" ] = %corner_standR_look_2_alert; + array[ "look_to_alert_fast" ] = %corner_standR_look_2_alert_fast; + array[ "look_idle" ] = %corner_standR_look_idle; + array[ "stance_change" ] = %CornerCrR_stand_2_alert; + + array[ "lean_aim_down" ] = %CornerStndR_lean_aim_2; + array[ "lean_aim_left" ] = %CornerStndR_lean_aim_4; + array[ "lean_aim_straight" ] = %CornerStndR_lean_aim_5; + array[ "lean_aim_right" ] = %CornerStndR_lean_aim_6; + array[ "lean_aim_up" ] = %CornerStndR_lean_aim_8; + array[ "lean_reload" ] = %CornerStndR_lean_reload; + + array[ "lean_idle" ] = [ %CornerStndR_lean_idle ]; + + array[ "lean_single" ] = %CornerStndR_lean_fire; + array[ "lean_fire" ] = %CornerStndR_lean_auto; + + if ( isDefined( anim.ramboAnims ) ) + { + array[ "rambo90" ] = anim.ramboAnims.coverright90; + array[ "rambo45" ] = anim.ramboAnims.coverright45; + array[ "grenade_rambo" ] = anim.ramboAnims.coverrightgrenade; + } + + anim.coverRightStand = array; +} + +init_animarray_crouching_right() +{ + array = []; + + array[ "alert_idle" ] = %CornerCrR_alert_idle; + array[ "alert_idle_twitch" ] = [ + %CornerCrR_alert_twitch_v1, + %CornerCrR_alert_twitch_v2, + %CornerCrR_alert_twitch_v3 + ]; + array[ "alert_idle_flinch" ] = []; + + array[ "alert_to_A" ] = [ %CornerCrR_trans_alert_2_A ]; + array[ "alert_to_B" ] = [ %CornerCrR_trans_alert_2_B ]; + array[ "A_to_alert" ] = [ %CornerCrR_trans_A_2_alert ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %CornerCrR_trans_A_2_B ]; + array[ "B_to_alert" ] = [ %CornerCrR_trans_B_2_alert ]; + array[ "B_to_alert_reload" ] = []; + array[ "B_to_A" ] = [ %CornerCrR_trans_B_2_A ]; + array[ "lean_to_alert" ] = [ %CornerCrR_lean_2_alert ]; + array[ "alert_to_lean" ] = [ %CornerCrR_alert_2_lean ]; + array[ "reload" ] = [ %CornerCrR_reloadA, %CornerCrR_reloadB ]; + array[ "grenade_exposed" ] = %CornerCrR_grenadeA; + array[ "grenade_safe" ] = %CornerCrR_grenadeA;// TODO: need a unique animation for this; use the exposed throw because not having it limits the options of the AI too much + + array[ "alert_to_over" ] = [ %CornerCrR_alert_2_over ]; + array[ "over_to_alert" ] = [ %CornerCrR_over_2_alert ]; + array[ "over_to_alert_reload" ] = []; + + array[ "blind_fire" ] = []; + + array[ "rambo90" ] = []; + array[ "rambo45" ] = []; + + array[ "alert_to_look" ] = %CornerCrR_alert_2_look; + array[ "look_to_alert" ] = %CornerCrR_look_2_alert; + array[ "look_to_alert_fast" ] = %CornerCrR_look_2_alert_fast;// there's a v2 we could use for this also if we want + array[ "look_idle" ] = %CornerCrR_look_idle; + array[ "stance_change" ] = %CornerCrR_alert_2_stand; + + array[ "lean_aim_down" ] = %CornerCrR_lean_aim_2; + array[ "lean_aim_left" ] = %CornerCrR_lean_aim_4; + array[ "lean_aim_straight" ] = %CornerCrR_lean_aim_5; + array[ "lean_aim_right" ] = %CornerCrR_lean_aim_6; + array[ "lean_aim_up" ] = %CornerCrR_lean_aim_8; + + array[ "lean_idle" ] = [ %CornerCrR_lean_idle ]; + + array[ "lean_single" ] = %CornerCrR_lean_fire; + array[ "lean_fire" ] = %CornerCrR_lean_auto; + + anim.coverRightCrouch = array; +} + +initGrenadeThrowAnims() +{ + // generated with scr_testgrenadethrows in combat.gsc + animscripts\init_common::addGrenadeThrowAnimOffset( %exposed_grenadethrowb, ( 41.5391, 7.28883, 72.2128 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %exposed_grenadethrowc, ( 34.8849, -4.77048, 74.0488 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %corner_standl_grenade_a, ( 41.605, 6.80107, 81.4785 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %corner_standl_grenade_b, ( 24.1585, -14.7221, 29.2992 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %cornercrl_grenadea, ( 25.8988, -10.2811, 30.4813 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %cornercrl_grenadeb, ( 24.688, 45.0702, 64.377 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %corner_standr_grenade_a, ( 37.1254, -32.7053, 76.5745 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %corner_standr_grenade_b, ( 19.356, 15.5341, 16.5036 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %cornercrr_grenadea, ( 39.8857, 5.92472, 24.5878 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %covercrouch_grenadea, ( -1.6363, -0.693674, 60.1009 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %covercrouch_grenadeb, ( -1.6363, -0.693674, 60.1009 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %coverstand_grenadea, ( 10.8573, 7.12614, 77.2356 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %coverstand_grenadeb, ( 19.1804, 5.68214, 73.2278 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %prone_grenade_a, ( 12.2859, -1.3019, 33.4307 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %CQB_stand_grenade_throw, ( 35.7494, 26.6052, 37.7086 ) ); +} + +init_noder_anims() +{ + array = []; + array[ "node_cover_left" ][ 0 ] = %CornerCrL_reloadA; + array[ "node_cover_left" ][ 1 ] = %CornerCrL_look_fast; + array[ "node_cover_left" ][ 2 ] = %corner_standL_grenade_B; + array[ "node_cover_left" ][ 3 ] = %corner_standL_flinch; + array[ "node_cover_left" ][ 4 ] = %corner_standL_look_idle; + array[ "node_cover_left" ][ 5 ] = %corner_standL_look_2_alert; + + array[ "node_cover_right" ][ 0 ] = %CornerCrR_reloadA; + array[ "node_cover_right" ][ 1 ] = %corner_standR_grenade_B; + array[ "node_cover_right" ][ 2 ] = %corner_standR_flinch; + array[ "node_cover_right" ][ 3 ] = %corner_standR_look_idle; + array[ "node_cover_right" ][ 4 ] = %corner_standR_look_2_alert; + + array[ "node_cover_crouch" ][ 0 ] = %covercrouch_hide_idle; + array[ "node_cover_crouch" ][ 1 ] = %covercrouch_twitch_1; + array[ "node_cover_crouch" ][ 2 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch" ][ 3 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch" ][ 4 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch" ][ 5 ] = %covercrouch_hide_look; + + array[ "node_cover_crouch_window" ][ 0 ] = %covercrouch_hide_idle; + array[ "node_cover_crouch_window" ][ 1 ] = %covercrouch_twitch_1; + array[ "node_cover_crouch_window" ][ 2 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch_window" ][ 3 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch_window" ][ 4 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch_window" ][ 5 ] = %covercrouch_hide_look; + + array[ "node_cover_prone" ][ 0 ] = %crouch_2_prone_firing; + array[ "node_cover_prone" ][ 1 ] = %prone_2_crouch; + array[ "node_cover_prone" ][ 2 ] = %prone_reload; + + array[ "node_cover_stand" ][ 0 ] = %coverstand_reloadA; + + array[ "node_concealment_crouch" ][ 0 ] = %covercrouch_hide_idle; + array[ "node_concealment_crouch" ][ 1 ] = %covercrouch_twitch_1; + array[ "node_concealment_crouch" ][ 2 ] = %covercrouch_hide_2_aim; + array[ "node_concealment_crouch" ][ 3 ] = %covercrouch_hide_2_aim; + array[ "node_concealment_crouch" ][ 4 ] = %covercrouch_hide_2_aim; + array[ "node_concealment_crouch" ][ 5 ] = %covercrouch_hide_look; + + array[ "node_concealment_prone" ][ 0 ] = %crouch_2_prone_firing; + array[ "node_concealment_prone" ][ 1 ] = %prone_2_crouch; + array[ "node_concealment_prone" ][ 2 ] = %prone_reload; + + array[ "node_concealment_stand" ][ 0 ] = %coverstand_reloadA; + + anim.noderAnims = array; +} + +init_standing_animarray_aiming() +{ + array = []; + array[ "add_aim_up" ] = %exposed_aim_8; + array[ "add_aim_down" ] = %exposed_aim_2; + array[ "add_aim_left" ] = %exposed_aim_4; + array[ "add_aim_right" ] = %exposed_aim_6; + array[ "add_turn_aim_up" ] = %exposed_turn_aim_8; + array[ "add_turn_aim_down" ] = %exposed_turn_aim_2; + array[ "add_turn_aim_left" ] = %exposed_turn_aim_4; + array[ "add_turn_aim_right" ] = %exposed_turn_aim_6; + array[ "straight_level" ] = %exposed_aim_5; + + array[ "fire" ] = %exposed_shoot_auto_v2; + array[ "semi2" ] = %exposed_shoot_semi2; + array[ "semi3" ] = %exposed_shoot_semi3; + array[ "semi4" ] = %exposed_shoot_semi4; + array[ "semi5" ] = %exposed_shoot_semi5; + + array[ "shotgun_single" ] = [ %shotgun_stand_fire_1A ]; //used to replace single with this one when pump action. + array[ "single" ] = [ %exposed_shoot_semi1 ]; + + array[ "burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + array[ "burst3" ] = %exposed_shoot_burst3; + array[ "burst4" ] = %exposed_shoot_burst4; + array[ "burst5" ] = %exposed_shoot_burst5; + array[ "burst6" ] = %exposed_shoot_burst6; + + array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + + array[ "exposed_idle" ] = [ %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ]; + + anim.standingAiming = array; +} + +init_crouching_animarray_aiming() +{ + array = []; + array[ "add_aim_up" ] = %covercrouch_aim8_add; + array[ "add_aim_down" ] = %covercrouch_aim2_add; + array[ "add_aim_left" ] = %covercrouch_aim4_add; + array[ "add_aim_right" ] = %covercrouch_aim6_add; + array[ "straight_level" ] = %covercrouch_aim5; + + array[ "fire" ] = %exposed_shoot_auto_v2; + array[ "semi2" ] = %exposed_shoot_semi2; + array[ "semi3" ] = %exposed_shoot_semi3; + array[ "semi4" ] = %exposed_shoot_semi4; + array[ "semi5" ] = %exposed_shoot_semi5; + + array[ "burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + array[ "burst3" ] = %exposed_shoot_burst3; + array[ "burst4" ] = %exposed_shoot_burst4; + array[ "burst5" ] = %exposed_shoot_burst5; + array[ "burst6" ] = %exposed_shoot_burst6; + + array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + + array[ "shotgun_single" ] = [ %shotgun_crouch_fire ]; + array[ "single" ] = [ %exposed_shoot_semi1 ]; + array[ "exposed_idle" ] = [ %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ]; + + anim.crouchingAimingOver = array; + + + + array = []; + array[ "fire" ] = %exposed_crouch_shoot_auto_v2; + array[ "semi2" ] = %exposed_crouch_shoot_semi2; + array[ "semi3" ] = %exposed_crouch_shoot_semi3; + array[ "semi4" ] = %exposed_crouch_shoot_semi4; + array[ "semi5" ] = %exposed_crouch_shoot_semi5; + + array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + + array[ "shotgun_single" ] = [ %shotgun_crouch_fire ]; + array[ "single" ] = [ %exposed_crouch_shoot_semi1 ]; + + array[ "burst2" ] = %exposed_crouch_shoot_burst3;// ( will be limited to 2 shots ) + array[ "burst3" ] = %exposed_crouch_shoot_burst3; + array[ "burst4" ] = %exposed_crouch_shoot_burst4; + array[ "burst5" ] = %exposed_crouch_shoot_burst5; + array[ "burst6" ] = %exposed_crouch_shoot_burst6; + + array[ "add_aim_up" ] = %exposed_crouch_aim_8; + array[ "add_aim_down" ] = %exposed_crouch_aim_2; + array[ "add_aim_left" ] = %exposed_crouch_aim_4; + array[ "add_aim_right" ] = %exposed_crouch_aim_6; + array[ "add_turn_aim_up" ] = %exposed_crouch_turn_aim_8; + array[ "add_turn_aim_down" ] = %exposed_crouch_turn_aim_2; + array[ "add_turn_aim_left" ] = %exposed_crouch_turn_aim_4; + array[ "add_turn_aim_right" ] = %exposed_crouch_turn_aim_6; + array[ "straight_level" ] = %exposed_crouch_aim_5; + + array[ "exposed_idle" ] = [ %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ]; + + anim.crouchingAiming = array; +} + +init_reaction_anims() +{ + anim.runningReactToBullets = []; + anim.runningReactToBullets = array( %run_react_duck, %run_react_flinch, %run_react_stumble ); + + anim.lastRunningReactAnim = 0; + + anim.reactionAnimArray = []; + anim.reactionAnimArray[ "cover_stand" ] = array( %stand_cover_reaction_A, %stand_cover_reaction_B ); + anim.reactionAnimArray[ "cover_crouch" ] = array( %crouch_cover_reaction_A, %crouch_cover_reaction_B ); + anim.reactionAnimArray[ "cover_left" ] = array( %CornerStndL_react_A ); + anim.reactionAnimArray[ "cover_right" ] = array( %CornerStndR_react_A ); + anim.reactionAnimArray[ "wizby_idle" ] = array( %exposed_idle_reactA, %exposed_idle_reactB, %exposed_idle_twitch, %exposed_idle_twitch_v4); + anim.reactionAnimArray[ "wizby_dive" ] = array( %exposed_dive_grenade_B, %exposed_dive_grenade_F ); + anim.reactionAnimArray[ "wizby_twitch" ] = array( %exposed_crouch_idle_twitch_v2, %exposed_crouch_idle_twitch_v3 ); + anim.reactionAnimArray[ "wizby_crouch" ] = %exposed_stand_2_crouch; + anim.reactionAnimArray[ "wizby_turn" ] = array( %exposed_crouch_turn_180_left, %exposed_crouch_turn_180_right ); + + anim.reactionAnimArray[ "stand" ] = array( %exposed_backpedal, %exposed_idle_reactB ); + anim.reactionAnimArray[ "crouch" ] = array( %crouch_cover_reaction_A, %crouch_cover_reaction_B ); + anim.reactionAnimArray[ "stealth" ] = %exposed_idle_reactB; + anim.reactionAnimArray[ "stealth_backpedal" ] = %exposed_backpedal; + anim.reactionAnimArray[ "stealth_backpedal2" ] = %exposed_backpedal_v2; + anim.reactionAnimArray[ "surprise" ] = %surprise_stop_v1; +} \ No newline at end of file diff --git a/animscripts/battlechatter.gsc b/animscripts/battlechatter.gsc new file mode 100644 index 0000000..0a10c28 --- /dev/null +++ b/animscripts/battlechatter.gsc @@ -0,0 +1,4630 @@ +/**************************************************************************** + + battleChatter.gsc + + Basic concepts: Battle chatter events work on a queue system. Events are + added the AI's queue, and script calls to playBattleChatter(), scattered + throughout the animscripts, give the AI oppurtunities to play the events. + Events have an expiration time; if there are no calls to playBattleChatter + before an event expires, it will not play. + Script calls, usually within animscripts or battleChatter_ai::*Waiter() + functions, call the add*Event(); functions to add a voice event to the + AI's queue. + Since an AI can have multiple events in it's queue at a give time, there + is a priority system in place to help the AI choose which events get added + to the queue and which events it will play. Events with a priority of 1 + will always be added to the queue (unless battlechatter is disabled on the + AI) + +*****************************************************************************/ + +#include common_scripts\utility; +#include maps\_utility; +#include animscripts\utility; +#include animscripts\battlechatter_ai; + +/**************************************************************************** + initialization +*****************************************************************************/ + +// Initializes the battle chatter system +init_battleChatter() +{ + if ( IsDefined( anim.chatInitialized ) && anim.chatInitialized ) + return; + + SetDvarIfUninitialized( "bcs_enable", "on" ); + + if ( GetDvar( "bcs_enable", "on" ) == "off" ) + { + anim.chatInitialized = false; + anim.player.chatInitialized = false; + return; + } + + anim.chatInitialized = true; + anim.player.chatInitialized = false; + + SetDvarIfUninitialized( "bcs_filterThreat", "off" ); + SetDvarIfUninitialized( "bcs_filterInform", "off" ); + SetDvarIfUninitialized( "bcs_filterOrder", "off" ); + SetDvarIfUninitialized( "bcs_filterReaction", "off" ); + SetDvarIfUninitialized( "bcs_filterResponse", "off" ); + + SetDvarIfUninitialized( "bcs_forceEnglish", "0" ); + // useful if you only have one voice actor's set of aliases and need to test responses + SetDvarIfUninitialized( "bcs_allowsamevoiceresponse", "off" ); + + SetDvarIfUninitialized( "debug_bcprint", "off" ); + SetDvarIfUninitialized( "debug_bcprintdump", "off" ); + SetDvarIfUninitialized( "debug_bcprintdumptype", "csv" ); + SetDvarIfUninitialized( "debug_bcshowqueue", "off" ); + + /# + SetDvarIfUninitialized( "debug_bcthreat", "off" ); + SetDvarIfUninitialized( "debug_bcresponse", "off" ); + SetDvarIfUninitialized( "debug_bcorder", "off" ); + SetDvarIfUninitialized( "debug_bcinform", "off" ); + SetDvarIfUninitialized( "debug_bcdrawobjects", "off" ); + SetDvarIfUninitialized( "debug_bcinteraction", "off" ); + #/ + + anim.bcPrintFailPrefix = "^3***** BCS FAILURE: "; + anim.bcPrintWarnPrefix = "^3***** BCS WARNING: "; + + bcs_setup_teams_array(); + bcs_setup_countryIDs(); + + // Player Name IDs: + // - these IDs map to whatever the sound dept. uses for the player name alias + // (ex. "US_name_player_1" would make our id = 1) + anim.playerNameIDs[ "american" ] = "1"; + anim.playerNameIDs[ "seal" ] = "1"; + anim.playerNameIDs[ "taskforce" ] = "1"; + anim.playerNameIDs[ "secretservice" ] = "1"; + + thread setPlayerBcNameID(); + + // AI Name IDs + // - update these when the number or ID(s) of voice actors for any nationality changes! + anim.usedIDs = []; + + anim.usedIDs[ "russian" ] = []; + anim.usedIDs[ "russian" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "russian" ][ 0 ].count = 0; + anim.usedIDs[ "russian" ][ 0 ].npcID = "0"; + anim.usedIDs[ "russian" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "russian" ][ 1 ].count = 0; + anim.usedIDs[ "russian" ][ 1 ].npcID = "1"; + anim.usedIDs[ "russian" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "russian" ][ 2 ].count = 0; + anim.usedIDs[ "russian" ][ 2 ].npcID = "2"; + anim.usedIDs[ "russian" ][ 3 ] = SpawnStruct(); + anim.usedIDs[ "russian" ][ 3 ].count = 0; + anim.usedIDs[ "russian" ][ 3 ].npcID = "3"; + anim.usedIDs[ "russian" ][ 4 ] = SpawnStruct(); + anim.usedIDs[ "russian" ][ 4 ].count = 0; + anim.usedIDs[ "russian" ][ 4 ].npcID = "4"; + + anim.usedIDs[ "portuguese" ] = []; + anim.usedIDs[ "portuguese" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "portuguese" ][ 0 ].count = 0; + anim.usedIDs[ "portuguese" ][ 0 ].npcID = "0"; + anim.usedIDs[ "portuguese" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "portuguese" ][ 1 ].count = 0; + anim.usedIDs[ "portuguese" ][ 1 ].npcID = "1"; + anim.usedIDs[ "portuguese" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "portuguese" ][ 2 ].count = 0; + anim.usedIDs[ "portuguese" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "shadowcompany" ] = []; + anim.usedIDs[ "shadowcompany" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "shadowcompany" ][ 0 ].count = 0; + anim.usedIDs[ "shadowcompany" ][ 0 ].npcID = "0"; + anim.usedIDs[ "shadowcompany" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "shadowcompany" ][ 1 ].count = 0; + anim.usedIDs[ "shadowcompany" ][ 1 ].npcID = "1"; + anim.usedIDs[ "shadowcompany" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "shadowcompany" ][ 2 ].count = 0; + anim.usedIDs[ "shadowcompany" ][ 2 ].npcID = "2"; + anim.usedIDs[ "shadowcompany" ][ 3 ] = SpawnStruct(); + anim.usedIDs[ "shadowcompany" ][ 3 ].count = 0; + anim.usedIDs[ "shadowcompany" ][ 3 ].npcID = "3"; + + anim.usedIDs[ "british" ] = []; + anim.usedIDs[ "british" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "british" ][ 0 ].count = 0; + anim.usedIDs[ "british" ][ 0 ].npcID = "0"; + anim.usedIDs[ "british" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "british" ][ 1 ].count = 0; + anim.usedIDs[ "british" ][ 1 ].npcID = "1"; + anim.usedIDs[ "british" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "british" ][ 2 ].count = 0; + anim.usedIDs[ "british" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "american" ] = []; + anim.usedIDs[ "american" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "american" ][ 0 ].count = 0; + anim.usedIDs[ "american" ][ 0 ].npcID = "0"; + anim.usedIDs[ "american" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "american" ][ 1 ].count = 0; + anim.usedIDs[ "american" ][ 1 ].npcID = "1"; + anim.usedIDs[ "american" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "american" ][ 2 ].count = 0; + anim.usedIDs[ "american" ][ 2 ].npcID = "2"; + anim.usedIDs[ "american" ][ 3 ] = SpawnStruct(); + anim.usedIDs[ "american" ][ 3 ].count = 0; + anim.usedIDs[ "american" ][ 3 ].npcID = "3"; + anim.usedIDs[ "american" ][ 4 ] = SpawnStruct(); + anim.usedIDs[ "american" ][ 4 ].count = 0; + anim.usedIDs[ "american" ][ 4 ].npcID = "4"; + + anim.usedIDs[ "seal" ] = []; + anim.usedIDs[ "seal" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "seal" ][ 0 ].count = 0; + anim.usedIDs[ "seal" ][ 0 ].npcID = "0"; + anim.usedIDs[ "seal" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "seal" ][ 1 ].count = 0; + anim.usedIDs[ "seal" ][ 1 ].npcID = "1"; + anim.usedIDs[ "seal" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "seal" ][ 2 ].count = 0; + anim.usedIDs[ "seal" ][ 2 ].npcID = "2"; + anim.usedIDs[ "seal" ][ 3 ] = SpawnStruct(); + anim.usedIDs[ "seal" ][ 3 ].count = 0; + anim.usedIDs[ "seal" ][ 3 ].npcID = "3"; + + anim.usedIDs[ "taskforce" ] = []; + anim.usedIDs[ "taskforce" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "taskforce" ][ 0 ].count = 0; + anim.usedIDs[ "taskforce" ][ 0 ].npcID = "0"; + anim.usedIDs[ "taskforce" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "taskforce" ][ 1 ].count = 0; + anim.usedIDs[ "taskforce" ][ 1 ].npcID = "1"; + anim.usedIDs[ "taskforce" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "taskforce" ][ 2 ].count = 0; + anim.usedIDs[ "taskforce" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "secretservice" ] = []; + anim.usedIDs[ "secretservice" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "secretservice" ][ 0 ].count = 0; + anim.usedIDs[ "secretservice" ][ 0 ].npcID = "0"; + anim.usedIDs[ "secretservice" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "secretservice" ][ 1 ].count = 0; + anim.usedIDs[ "secretservice" ][ 1 ].npcID = "1"; + anim.usedIDs[ "secretservice" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "secretservice" ][ 2 ].count = 0; + anim.usedIDs[ "secretservice" ][ 2 ].npcID = "2"; + anim.usedIDs[ "secretservice" ][ 3 ] = SpawnStruct(); + anim.usedIDs[ "secretservice" ][ 3 ].count = 0; + anim.usedIDs[ "secretservice" ][ 3 ].npcID = "3"; + + anim.usedIDs[ "arab" ] = []; + anim.usedIDs[ "arab" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "arab" ][ 0 ].count = 0; + anim.usedIDs[ "arab" ][ 0 ].npcID = "0"; + anim.usedIDs[ "arab" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "arab" ][ 1 ].count = 0; + anim.usedIDs[ "arab" ][ 1 ].npcID = "1"; + anim.usedIDs[ "arab" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "arab" ][ 2 ].count = 0; + anim.usedIDs[ "arab" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "german" ] = []; + anim.usedIDs[ "german" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "german" ][ 0 ].count = 0; + anim.usedIDs[ "german" ][ 0 ].npcID = "0"; + anim.usedIDs[ "german" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "german" ][ 1 ].count = 0; + anim.usedIDs[ "german" ][ 1 ].npcID = "1"; + anim.usedIDs[ "german" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "german" ][ 2 ].count = 0; + anim.usedIDs[ "german" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "italian" ] = []; + anim.usedIDs[ "italian" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "italian" ][ 0 ].count = 0; + anim.usedIDs[ "italian" ][ 0 ].npcID = "0"; + anim.usedIDs[ "italian" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "italian" ][ 1 ].count = 0; + anim.usedIDs[ "italian" ][ 1 ].npcID = "1"; + anim.usedIDs[ "italian" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "italian" ][ 2 ].count = 0; + anim.usedIDs[ "italian" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "spanish" ] = []; + anim.usedIDs[ "spanish" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "spanish" ][ 0 ].count = 0; + anim.usedIDs[ "spanish" ][ 0 ].npcID = "0"; + anim.usedIDs[ "spanish" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "spanish" ][ 1 ].count = 0; + anim.usedIDs[ "spanish" ][ 1 ].npcID = "1"; + anim.usedIDs[ "spanish" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "spanish" ][ 2 ].count = 0; + anim.usedIDs[ "spanish" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "indonesian" ] = []; + anim.usedIDs[ "indonesian" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "indonesian" ][ 0 ].count = 0; + anim.usedIDs[ "indonesian" ][ 0 ].npcID = "0"; + anim.usedIDs[ "indonesian" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "indonesian" ][ 1 ].count = 0; + anim.usedIDs[ "indonesian" ][ 1 ].npcID = "1"; + anim.usedIDs[ "indonesian" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "indonesian" ][ 2 ].count = 0; + anim.usedIDs[ "indonesian" ][ 2 ].npcID = "2"; + + anim.usedIDs[ "mexican" ] = []; + anim.usedIDs[ "mexican" ][ 0 ] = SpawnStruct(); + anim.usedIDs[ "mexican" ][ 0 ].count = 0; + anim.usedIDs[ "mexican" ][ 0 ].npcID = "0"; + anim.usedIDs[ "mexican" ][ 1 ] = SpawnStruct(); + anim.usedIDs[ "mexican" ][ 1 ].count = 0; + anim.usedIDs[ "mexican" ][ 1 ].npcID = "1"; + anim.usedIDs[ "mexican" ][ 2 ] = SpawnStruct(); + anim.usedIDs[ "mexican" ][ 2 ].count = 0; + anim.usedIDs[ "mexican" ][ 2 ].npcID = "2"; + + + init_flavorbursts(); + + // doesn't impact friendlyfire warnings normally played when battlechatter is on, + // just whether it plays when battlechatter is otherwise turned off + if ( !IsDefined( level._friendlyfire_warnings ) ) + { + level._friendlyfire_warnings = false; + } + + anim.eventTypeMinWait = []; + anim.eventTypeMinWait[ "threat" ] = []; + anim.eventTypeMinWait[ "response" ] = []; + anim.eventTypeMinWait[ "reaction" ] = []; + anim.eventTypeMinWait[ "order" ] = []; + anim.eventTypeMinWait[ "inform" ] = []; + anim.eventTypeMinWait[ "custom" ] = []; + anim.eventTypeMinWait[ "direction" ] = []; + + // If you want to tweak how often battlechatter messages happen, + // this is place to do it. + // A priority of 1 will force an event to be added to the queue, and + // will make it override pre-existing events of the same type. + + // times are in milliseconds + if ( IsDefined( level._stealth ) ) + { + anim.eventActionMinWait[ "threat" ][ "self" ] = 20000; + anim.eventActionMinWait[ "threat" ][ "squad" ] = 30000; + } + else + { + anim.eventActionMinWait[ "threat" ][ "self" ] = 12500; + anim.eventActionMinWait[ "threat" ][ "squad" ] = 7500; + } + anim.eventActionMinWait[ "threat" ][ "location_repeat" ] = 5000; + anim.eventActionMinWait[ "response" ][ "self" ] = 1000; + anim.eventActionMinWait[ "response" ][ "squad" ] = 1000; + anim.eventActionMinWait[ "reaction" ][ "self" ] = 1000; + anim.eventActionMinWait[ "reaction" ][ "squad" ] = 1000; + anim.eventActionMinWait[ "order" ][ "self" ] = 8000; + anim.eventActionMinWait[ "order" ][ "squad" ] = 10000; + anim.eventActionMinWait[ "inform" ][ "self" ] = 6000; + anim.eventActionMinWait[ "inform" ][ "squad" ] = 8000; + anim.eventActionMinWait[ "custom" ][ "self" ] = 0; + anim.eventActionMinWait[ "custom" ][ "squad" ] = 0; + + anim.eventTypeMinWait[ "playername" ] = 15000; + anim.eventTypeMinWait[ "reaction" ][ "casualty" ] = 14000; + anim.eventTypeMinWait[ "reaction" ][ "friendlyfire" ] = 5000; + anim.eventTypeMinWait[ "reaction" ][ "taunt" ] = 30000; + anim.eventTypeMinWait[ "inform" ][ "reloading" ] = 20000; + anim.eventTypeMinWait[ "inform" ][ "killfirm" ] = 15000; + + anim.eventPriority[ "threat" ][ "infantry" ] = 0.5; + anim.eventPriority[ "threat" ][ "vehicle" ] = 0.7; + anim.eventPriority[ "response" ][ "ack" ] = 0.9; + anim.eventPriority[ "response" ][ "exposed" ] = 0.8; + anim.eventPriority[ "response" ][ "callout" ] = 0.9; + anim.eventPriority[ "response" ][ "echo" ] = 0.9; + anim.eventPriority[ "reaction" ][ "casualty" ] = 0.5; + anim.eventPriority[ "reaction" ][ "friendlyfire" ] = 1.0; + anim.eventPriority[ "reaction" ][ "taunt" ] = 0.9; + anim.eventPriority[ "order" ][ "action" ] = 0.3; + anim.eventPriority[ "order" ][ "move" ] = 0.3; + anim.eventPriority[ "order" ][ "displace" ] = 0.5; + anim.eventPriority[ "inform" ][ "attack" ] = 0.9; + anim.eventPriority[ "inform" ][ "incoming" ] = 0.9; + anim.eventPriority[ "inform" ][ "reloading" ] = 0.2; + anim.eventPriority[ "inform" ][ "suppressed" ] = 0.2; + anim.eventPriority[ "inform" ][ "killfirm" ] = 0.7; + anim.eventPriority[ "custom" ][ "generic" ] = 1.0; + + anim.eventDuration[ "threat" ][ "infantry" ] = 1000; + anim.eventDuration[ "threat" ][ "vehicle" ] = 1000; + anim.eventDuration[ "response" ][ "exposed" ] = 2000; + anim.eventDuration[ "response" ][ "callout" ] = 2000; + anim.eventDuration[ "response" ][ "echo" ] = 2000; + anim.eventDuration[ "response" ][ "ack" ] = 1750; + anim.eventDuration[ "reaction" ][ "casualty" ] = 2000; + anim.eventDuration[ "reaction" ][ "friendlyfire" ] = 1000; + anim.eventDuration[ "reaction" ][ "taunt" ] = 2000; + anim.eventDuration[ "order" ][ "action" ] = 3000; + anim.eventDuration[ "order" ][ "move" ] = 3000; + anim.eventDuration[ "order" ][ "displace" ] = 3000; + anim.eventDuration[ "inform" ][ "attack" ] = 1000; + anim.eventDuration[ "inform" ][ "incoming" ] = 1500; + anim.eventDuration[ "inform" ][ "reloading" ] = 1000; + anim.eventDuration[ "inform" ][ "suppressed" ] = 2000; + anim.eventDuration[ "inform" ][ "killfirm" ] = 2000; + anim.eventDuration[ "custom" ][ "generic" ] = 1000; + + // event chances are in % out of 100 + anim.eventChance[ "response" ][ "exposed" ] = 75; + anim.eventChance[ "response" ][ "reload" ] = 65; + anim.eventChance[ "response" ][ "callout" ] = 75; + anim.eventChance[ "response" ][ "callout_negative" ] = 20; + anim.eventChance[ "response" ][ "order" ] = 40; + anim.eventChance[ "moveEvent" ][ "coverme" ] = 70; + anim.eventChance[ "moveEvent" ][ "ordertoplayer" ] = 10; + + // flavor burst transmission tweakables + anim.fbt_desiredDistMax = 620;// try to keep fbts within this range from the player + anim.fbt_waitMin = 12;// time to wait between transmissions + anim.fbt_waitMax = 24; + anim.fbt_lineBreakMin = 2;// time to wait between lines + anim.fbt_lineBreakMax = 5; + + anim.moveOrigin = Spawn( "script_origin", ( 0, 0, 0 ) ); + + // how many units from the player dudes can be and still chatter or be chattered about + if ( !IsDefined( level._bcs_maxTalkingDistFromPlayer ) ) + { + level._bcs_maxTalkingDistFromPlayer = 1500; + } + if ( !IsDefined( level._bcs_maxThreatDistFromPlayerSqr ) ) + { + level._bcs_maxThreatDistFromPlayerSqr = 6250000; + } + + // set up location triggers + maps\_bcs_location_trigs::bcs_location_trigs_init(); + Assert( IsDefined( anim.bcs_locations ) ); + anim.locationLastCalloutTimes = []; + + // how long after starting some scripted dialogue will we wait before chattering? (milliseconds) + anim.scriptedDialogueBufferTime = 4000; + + // how long before we can chatter about a threat again? + anim.bcs_threatResetTime = 3000; + + /# + if ( GetDvar( "debug_bcdrawobjects" ) == "on" ) + thread bcDrawObjects(); + #/ + + anim.squadCreateFuncs[ anim.squadCreateFuncs.size ] = ::init_squadBattleChatter; + anim.squadCreateStrings[ anim.squadCreateStrings.size ] = "::init_squadBattleChatter"; + + foreach ( team in anim.teams ) + { + anim.isTeamSpeaking[ team ] = false; + anim.isTeamSaying[ team ][ "threat" ] = false; + anim.isTeamSaying[ team ][ "order" ] = false; + anim.isTeamSaying[ team ][ "reaction" ] = false; + anim.isTeamSaying[ team ][ "response" ] = false; + anim.isTeamSaying[ team ][ "inform" ] = false; + anim.isTeamSaying[ team ][ "custom" ] = false; + } + + bcs_setup_chatter_toggle_array(); + + // which nationalities can do flavor burst transmissions? + if ( !IsDefined( anim.flavorburstVoices ) ) + { + anim.flavorburstVoices = []; + anim.flavorburstVoices[ "american" ] = true; + anim.flavorburstVoices[ "shadowcompany" ] = true; + anim.flavorburstVoices[ "seal" ] = false; + anim.flavorburstVoices[ "taskforce" ] = false; + anim.flavorburstVoices[ "secretservice" ] = false; + anim.flavorburstVoices[ "british" ] = false; + } + + bcs_setup_flavorburst_toggle_array(); + + anim.lastTeamSpeakTime = []; + anim.lastNameSaid = []; + anim.lastNameSaidTime = []; + foreach ( team in anim.teams ) + { + anim.lastTeamSpeakTime[ team ] = -50000;// so it doesnt pause if nobody has ever spoken + anim.lastNameSaid[ team ] = "none"; + anim.lastNameSaidTime[ team ] = -100000; + } + + // how long we'll wait before allowing use of a certain AI name again + anim.lastNameSaidTimeout = 10000; + + for ( index = 0; index < anim.squadIndex.size; index++ ) + { + if ( IsDefined( anim.squadIndex[ index ].chatInitialized ) && anim.squadIndex[ index ].chatInitialized ) + continue; + + anim.squadIndex[ index ] init_squadBattleChatter(); + } + + /*----------- THREAT CALLOUT CHANCES ----------- + - anim.threatCallouts[] is indexed by the types of possible threat callouts for this AI, + and holds %chance weights that help determine if that the AI will + try to use that type of threat callout to alert players to a threat. + + - These are matched against the values of self.allowedCallouts[] for each AI, to determine + whether the AI can use a particular kind of callout - not all nationalities get all callouts. + self.allowedCallouts gets set up for each AI in battlechatter_ai::init_aiBattleChatter. + + - higher numbers mean a higher chance! + - zero values do not get considered. + - 100+ values are prioritized above everything except other 100+ values. + - chances are dicerolled against one another to find a winner, like this: + if( RandomInt( threatCallouts[ "player_yourclock" ] ) > RandomInt( threatCallouts[ "ai_yourclock ] ) ) + + rough priorities: + 1. RPG + 2. exposed + 3. player_obvious + 3. "catch-all": + 1. player_your_clock + 2. player_contact_clock + 3. player_target_clock + 4. ai_your_clock + 5. player_cardinal + 4. object (aka landmark): + 1. player_object_yourclock + 1. player_object_clock + 2. ai_object_yourclock + 5. location + 1. player_location + 2. ai_location + 3. generic_location + -------------------------------------------------*/ + + anim.threatCallouts = []; + + // RPG/exposed + anim.threatCallouts[ "rpg" ] = 100;// "RPG!" + anim.threatCallouts[ "exposed" ] = 25;// "Got a tango in the open!" + + // "obvious" callouts + anim.threatCallouts[ "player_obvious" ] = 40;// "Player! Light 'em up!"( for 12 o'clock threats ) + + // player-relative callouts + anim.threatCallouts[ "player_yourclock" ] = 30;// "Player! Contact at your 10 o'clock!" + anim.threatCallouts[ "player_contact_clock" ] = 25;// "Player! Contact at 10 o'clock!" + anim.threatCallouts[ "player_target_clock" ] = 25;// "Player! Target, 10 o'clock!" + anim.threatCallouts[ "player_cardinal" ] = 20;// "Player! Contact, northwest!" + + // "catch-all" callouts + anim.threatCallouts[ "ai_yourclock" ] = 25;// "Peas! Contact at your 10 o'clock!" + anim.threatCallouts[ "ai_contact_clock" ] = 20; + anim.threatCallouts[ "ai_target_clock" ] = 20; + anim.threatCallouts[ "ai_cardinal" ] = 10; + + /* DEPRECATED + // object (aka landmark) callouts + anim.threatCallouts[ "player_object_yourclock" ] = 100;// "Player! Movement by the dumpster at your 10 o'clock!" + anim.threatCallouts[ "player_object_clock" ] = 100;// "Player! Movement by the dumpster at 10 o'clock!" + anim.threatCallouts[ "ai_object_yourclock" ] = 95;// "Peas! Movement by the dumpster at your 10 o'clock!" + */ + + // location callouts + anim.threatCallouts[ "player_location" ] = 95;// ( Player - relative ) "Contact! 2nd floor window on the left!" + anim.threatCallouts[ "ai_location" ] = 100;// ( AI - relative ) "Contact! 2nd floor window on the left!" + anim.threatCallouts[ "generic_location" ] = 90; + + + anim.lastTeamThreatCallout = []; + anim.lastTeamThreatCalloutTime = []; + foreach ( team in anim.teams ) + { + anim.lastTeamThreatCallout[ team ] = undefined; + anim.lastTeamThreatCalloutTime[ team ] = undefined; + } + anim.teamThreatCalloutLimitTimeout = 20000; + + + level notify( "battlechatter initialized" ); + anim notify( "battlechatter initialized" ); +} + +bcs_setup_teams_array() +{ + if ( !IsDefined( anim.teams ) ) + { + anim.teams = []; + anim.teams[ anim.teams.size ] = "axis"; + anim.teams[ anim.teams.size ] = "allies"; + anim.teams[ anim.teams.size ] = "team3"; + anim.teams[ anim.teams.size ] = "neutral"; + } +} + +bcs_setup_countryIDs() +{ + if ( !IsDefined( anim.countryIDs ) ) + { + anim.countryIDs[ "british" ] = "UK"; + anim.countryIDs[ "american" ] = "US"; + anim.countryIDs[ "seal" ] = "NS"; + anim.countryIDs[ "taskforce" ] = "TF"; + anim.countryIDs[ "secretservice" ] = "SS"; + anim.countryIDs[ "russian" ] = "RU"; + anim.countryIDs[ "arab" ] = "AB"; + anim.countryIDs[ "german" ] = "GE"; + anim.countryIDs[ "spanish" ] = "SP"; + anim.countryIDs[ "italian" ] = "IT"; + anim.countryIDs[ "portuguese" ] = "PG"; + anim.countryIDs[ "shadowcompany" ] = "SC"; + anim.countryIDs[ "indonesian" ] = "IN"; + anim.countryIDs[ "mexican" ] = "MX"; + } +} + +bcs_setup_chatter_toggle_array() +{ + bcs_setup_teams_array(); + + if ( !IsDefined( level._battlechatter ) ) + { + level._battlechatter = []; + foreach ( team in anim.teams ) + { + set_battlechatter_variable( team, true ); + } + } +} + +bcs_setup_flavorburst_toggle_array() +{ + bcs_setup_teams_array(); + + if ( !IsDefined( level._flavorbursts ) ) + { + level._flavorbursts = []; + foreach ( team in anim.teams ) + { + level._flavorbursts[ team ] = true; + } + } +} + +init_flavorbursts() +{ + // flavor burst transmission aliases + // update these as new transmissions are recorded + // (format of the fbt aliases is "FB_US_7_11", + // where the first number is what you put below) + anim.flavorbursts[ "american" ] = []; + numBursts = 41; + + us = []; + if ( level._script == "roadkill" || level._script == "trainer" ) + { + // we don't want to hear chatter about US locales when we're operating in another country + // (these correspond to sequence numbers in the aliases) + us[ us.size ] = 13; + us[ us.size ] = 15; + us[ us.size ] = 16; + us[ us.size ] = 19; + us[ us.size ] = 20; + us[ us.size ] = 30; + us[ us.size ] = 31; + us[ us.size ] = 33; + us[ us.size ] = 38; + } + + for ( i = 0; i < numBursts; i++ ) + { + // don't include US-specific aliases, if applicable + if ( us.size ) + { + foundOne = false; + foreach ( sequenceNum in us ) + { + if ( sequenceNum == i ) + { + foundOne = true; + break; + } + } + + if ( foundOne ) + { + continue; + } + } + + anim.flavorbursts[ "american" ][ i ] = string( i + 1 ); + } + + anim.flavorbursts[ "shadowcompany" ] = []; + numBursts = 9; + + for ( i = 1; i <= numBursts; i++ ) + { + anim.flavorbursts[ "shadowcompany" ][ i ] = string( i + 1 ); + } + + anim.flavorburstsUsed = []; +} + +shutdown_battleChatter() +{ + anim.countryIDs = undefined; + anim.eventTypeMinWait = undefined; + anim.eventActionMinWait = undefined; + anim.eventTypeMinWait = undefined; + anim.eventPriority = undefined; + anim.eventDuration = undefined; + + anim.moveOrigin = undefined; + + anim.scriptedDialogueBufferTime = undefined; + anim.bcs_threatResetTime = undefined; + + anim.locationLastCalloutTimes = undefined; + + anim.usedIDs = undefined; + + anim.flavorburstsUsed = undefined; + + anim.lastTeamThreatCallout = undefined; + anim.lastTeamThreatCalloutTime = undefined; + + anim.lastNameSaidTimeout = undefined; + anim.lastNameSaid = undefined; + anim.lastNameSaidTime = undefined; + + anim.chatInitialized = false; + anim.player.chatInitialized = false; + + level._battlechatter = undefined; + + for ( i = 0; i < anim.squadCreateFuncs.size; i++ ) + { + if ( anim.squadCreateStrings[ i ] != "::init_squadBattleChatter" ) + continue; + + if ( i != ( anim.squadCreateFuncs.size - 1 ) ) + { + anim.squadCreateFuncs[ i ] = anim.squadCreateFuncs[ anim.squadCreateFuncs.size - 1 ]; + anim.squadCreateStrings[ i ] = anim.squadCreateStrings[ anim.squadCreateStrings.size - 1 ]; + } + + anim.squadCreateFuncs[ anim.squadCreateFuncs.size - 1 ] = undefined; + anim.squadCreateStrings[ anim.squadCreateStrings.size - 1 ] = undefined; + } + + level notify( "battlechatter disabled" ); + anim notify( "battlechatter disabled" ); +} + +// initializes battlechatter data that resides in the squad manager +// this is done to keep the squad management system free from clutter +init_squadBattleChatter() +{ + squad = self; + + // tweakables + squad.numSpeakers = 0; + squad.maxSpeakers = 1; + + // non tweakables + squad.nextSayTime = GetTime() + 50; + squad.nextSayTimes[ "threat" ] = GetTime() + 50; + squad.nextSayTimes[ "order" ] = GetTime() + 50; + squad.nextSayTimes[ "reaction" ] = GetTime() + 50; + squad.nextSayTimes[ "response" ] = GetTime() + 50; + squad.nextSayTimes[ "inform" ] = GetTime() + 50; + squad.nextSayTimes[ "custom" ] = GetTime() + 50; + + squad.nextTypeSayTimes[ "threat" ] = []; + squad.nextTypeSayTimes[ "order" ] = []; + squad.nextTypeSayTimes[ "reaction" ] = []; + squad.nextTypeSayTimes[ "response" ] = []; + squad.nextTypeSayTimes[ "inform" ] = []; + squad.nextTypeSayTimes[ "custom" ] = []; + + squad.isMemberSaying[ "threat" ] = false; + squad.isMemberSaying[ "order" ] = false; + squad.isMemberSaying[ "reaction" ] = false; + squad.isMemberSaying[ "response" ] = false; + squad.isMemberSaying[ "inform" ] = false; + squad.isMemberSaying[ "custom" ] = false; + squad.lastDirection = ""; + + squad.memberAddFuncs[ squad.memberAddFuncs.size ] = ::addToSystem; + squad.memberAddStrings[ squad.memberAddStrings.size ] = "::addToSystem"; + squad.memberRemoveFuncs[ squad.memberRemoveFuncs.size ] = ::removeFromSystem; + squad.memberRemoveStrings[ squad.memberRemoveStrings.size ] = "::removeFromSystem"; + squad.squadUpdateFuncs[ squad.squadUpdateFuncs.size ] = ::initContact; + squad.squadUpdateStrings[ squad.squadUpdateStrings.size ] = "::initContact"; + + squad.fbt_firstBurst = true; + squad.fbt_lastBursterID = undefined; + + for ( i = 0; i < anim.squadIndex.size; i++ ) + squad thread initContact( anim.squadIndex[ i ].squadName ); + + squad thread squadThreatWaiter(); + squad thread squadOfficerWaiter(); + + squad thread squadFlavorBurstTransmissions(); + + squad.chatInitialized = true; + squad notify( "squad chat initialized" ); +} + +// initializes battlechatter data that resides in the squad manager +// this is done to keep the squad management system free from clutter +shutdown_squadBattleChatter() +{ + squad = self; + + // tweakables + squad.numSpeakers = undefined; + squad.maxSpeakers = undefined; + + // non tweakables + squad.nextSayTime = undefined; + squad.nextSayTimes = undefined; + + squad.nextTypeSayTimes = undefined; + + squad.isMemberSaying = undefined; + + squad.fbt_firstBurst = undefined; + squad.fbt_lastBursterID = undefined; + + for ( i = 0; i < squad.memberAddFuncs.size; i++ ) + { + if ( squad.memberAddStrings[ i ] != "::addToSystem" ) + continue; + + if ( i != ( squad.memberAddFuncs.size - 1 ) ) + { + squad.memberAddFuncs[ i ] = squad.memberAddFuncs[ squad.memberAddFuncs.size - 1 ]; + squad.memberAddStrings[ i ] = squad.memberAddStrings[ squad.memberAddStrings.size - 1 ]; + } + + squad.memberAddFuncs[ squad.memberAddFuncs.size - 1 ] = undefined; + squad.memberAddStrings[ squad.memberAddStrings.size - 1 ] = undefined; + } + + for ( i = 0; i < squad.memberRemoveFuncs.size; i++ ) + { + if ( squad.memberRemoveStrings[ i ] != "::removeFromSystem" ) + continue; + + if ( i != ( squad.memberRemoveFuncs.size - 1 ) ) + { + squad.memberRemoveFuncs[ i ] = squad.memberRemoveFuncs[ squad.memberRemoveFuncs.size - 1 ]; + squad.memberRemoveStrings[ i ] = squad.memberRemoveStrings[ squad.memberRemoveStrings.size - 1 ]; + } + + squad.memberRemoveFuncs[ squad.memberRemoveFuncs.size - 1 ] = undefined; + squad.memberRemoveStrings[ squad.memberRemoveStrings.size - 1 ] = undefined; + } + + for ( i = 0; i < squad.squadUpdateFuncs.size; i++ ) + { + if ( squad.squadUpdateStrings[ i ] != "::initContact" ) + continue; + + if ( i != ( squad.squadUpdateFuncs.size - 1 ) ) + { + squad.squadUpdateFuncs[ i ] = squad.squadUpdateFuncs[ squad.squadUpdateFuncs.size - 1 ]; + squad.squadUpdateStrings[ i ] = squad.squadUpdateStrings[ squad.squadUpdateStrings.size - 1 ]; + } + + squad.squadUpdateFuncs[ squad.squadUpdateFuncs.size - 1 ] = undefined; + squad.squadUpdateStrings[ squad.squadUpdateStrings.size - 1 ] = undefined; + } + + for ( i = 0; i < anim.squadIndex.size; i++ ) + squad shutdownContact( anim.squadIndex[ i ].squadName ); + + squad.chatInitialized = false; +} + +bcsEnabled() +{ + return anim.chatInitialized; +} + +bcsDebugWaiter() +{ + lastState = GetDvar( "bcs_enable", "on" ); + + while ( 1 ) + { + state = GetDvar( "bcs_enable", "on" ); + + if ( state != lastState ) + { + switch( state ) + { + case "on": + if ( !anim.chatInitialized ) + enableBattleChatter(); + break; + case "off": + if ( anim.chatInitialized ) + disableBattleChatter(); + break; + } + lastState = state; + } + + wait( 1.0 ); + } +} + +enableBattleChatter() +{ + init_battleChatter(); + + anim.player thread animscripts\battleChatter_ai::addToSystem(); + + ai = GetAIArray(); + for ( i = 0; i < ai.size; i++ ) + { + ai[ i ] addToSystem(); + } +} + +disableBattleChatter() +{ + shutdown_battleChatter(); + + ai = GetAIArray(); + for ( i = 0; i < ai.size; i++ ) + { + if ( IsDefined( ai[ i ].squad ) && ai[ i ].squad.chatInitialized ) + ai[ i ].squad shutdown_squadBattleChatter(); + + ai[ i ] removeFromSystem(); + } +} + +// sets the player name to use when playing player-relative phrases +// these are used to make an alias that looks like: US_1_name_player_US_1 +setPlayerBcNameID( overrideNameID, overrideCountryID ) +{ + if ( IsDefined( overrideNameID ) && IsDefined( overrideCountryID ) ) + { + level._player.bcNameID = overrideNameID; + level._player.bcCountryID = overrideCountryID; + return; + } + + while ( !IsDefined( level._campaign ) ) + { + wait( 0.1 ); + } + + nationality = level._campaign; + + nameID = anim.playerNameIDs[ nationality ]; + countryID = anim.countryIDs[ nationality ]; + + if ( IsDefined( nameID ) ) + { + level._player.bcNameID = nameID; + } + + if ( IsDefined( countryID ) ) + { + level._player.bcCountryID = countryID; + } +} + +/**************************************************************************** + processing +*****************************************************************************/ + +playBattleChatter() +{ + if ( !IsAlive( self ) ) + { + return; + } + + // battlechatter system is totally turned off (as opposed to dormant) + if ( !bcsEnabled() ) + { + return; + } + + // he's doing a scripted animation + // tagJW: Allow override to let actors play battlechatter during vignette anims if desired + if ( self._animActive > 0 && !isdefined( self.bc_during_animscripted ) ) + { + return; + } + + // he's already saying a battlechatter line + if ( IsDefined( self.isSpeaking ) && self.isSpeaking ) + { + return; + } + + // an ally is doing scripted dialogue + if ( self.team == "allies" && IsDefined( anim.scriptedDialogueStartTime ) ) + { + if ( ( anim.scriptedDialogueStartTime + anim.scriptedDialogueBufferTime ) > GetTime() ) + { + return; + } + } + + // hacky! friendlyfire warnings have greatly reduced requirements for whether they can play + if ( self friendlyfire_warning() ) + { + return; + } + + if ( !isdefined( self.battleChatter ) || !self.battleChatter ) + { + return; + } + + if ( self.team == "allies" && GetDvarInt( "bcs_forceEnglish", 0 ) ) + { + return; + } + + if ( anim.isTeamSpeaking[ self.team ] ) + { + return; + } + + self endon( "death" ); + +// self thread debugQueueEvents(); +// self thread debugPrintEvents(); + + event = self getHighestPriorityEvent(); + + if ( !isdefined( event ) ) + { + return; + } + + switch( event ) + { + case "custom": + self thread playCustomEvent(); + break; + case "response": + self thread playResponseEvent(); + break; + case "order": + self thread playOrderEvent(); + break; + case "threat": + self thread playThreatEvent(); + break; + case "reaction": + self thread playReactionEvent(); + break; + case "inform": + self thread playInformEvent(); + break; + } +} + +//// threat events functions +playThreatEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + self endon( "cancel speaking" ); + + self.curEvent = self.chatQueue[ "threat" ]; + + threat = self.chatQueue[ "threat" ].threat; + + if ( !IsAlive( threat ) ) + { + return; + } + + if ( threatWasAlreadyCalledOut( threat ) && !IsPlayer( threat ) ) + { + return; + } + + anim thread lockAction( self, "threat" ); + + /# + if ( GetDvar( "debug_bcinteraction" ) == "on" ) + animscripts\utility::showDebugLine( self.origin + ( 0, 0, 50 ), threat.origin + ( 0, 0, 50 ), ( 1, 0, 0 ), 1.5 ); + #/ + + success = false; + + switch( self.chatQueue[ "threat" ].eventType ) + { + case "infantry": + if ( IsPlayer( threat ) || !isdefined( threat GetTurret() ) ) + { + success = self threatInfantry( threat ); + } + else + { + // if we ever want emplacement callouts again, put one here + } + break; + case "dog": + success = self threatDog( threat ); + break; + case "vehicle": + break; + } + + self notify( "done speaking" ); + + if ( !success ) + { + return; + } + + if ( !IsAlive( threat ) ) + { + return; + } + + threat.calledOut[ self.squad.squadName ] = SpawnStruct(); + threat.calledOut[ self.squad.squadName ].spotter = self; + threat.calledOut[ self.squad.squadName ].threatType = self.chatQueue[ "threat" ].eventType; + threat.calledOut[ self.squad.squadName ].expireTime = GetTime() + anim.bcs_threatResetTime; + + if ( IsDefined( threat.squad ) ) + { + self.squad.squadList[ threat.squad.squadName ].calledOut = true; + } +} + +threatWasAlreadyCalledOut( threat ) +{ + if ( IsDefined( threat.calledOut ) && IsDefined( threat.calledOut[ self.squad.squadName ] ) ) + { + // maybe we can talk about him again if he was previously called out + if ( threat.calledOut[ self.squad.squadName ].expireTime < GetTime() ) + { + return true; + } + } + + return false; +} + +threatInfantry( threat, forceDetail ) +{ + self endon( "cancel speaking" ); + + chatPhrase = self createChatPhrase(); + chatPhrase.master = true; + chatPhrase.threatEnt = threat; + + // figure out what kind of callout we want to do + callout = self getThreatInfantryCalloutType( threat ); + + if ( !IsDefined( callout ) || ( IsDefined( callout ) && !IsDefined( callout.type ) ) ) + { + /* + printStr = anim.bcPrintFailPrefix + "Couldn't find a threat callout type using getThreatInfantryCalloutType. "; + + if( IsDefined( threat ) && IsDefined( threat.classname ) ) + { + printStr += "Threat classname was: " + threat.classname; + } + else if( IsDefined( threat ) && !IsDefined( threat.classname ) ) + { + printStr += "Threat didn't have a classname!"; + } + else if( !IsDefined( threat ) ) + { + printStr += "Threat wasn't defined!"; + } + + PrintLn( printStr ); + */ + return false; + } + + switch( callout.type ) + { + case "rpg": + + chatPhrase threatInfantryRPG( threat ); + break; + + case "exposed": + + // not getting enough variety when we count on AIs to see the targets themselves + // before responding, so do a simpler diceroll check that we can control better + // + // - check for callout.responder because it's not required + // for this callout, just nice to have + doResponse = self doExposedCalloutResponse( callout.responder ); + + // if we can say their name, do it + if ( doResponse && self canSayName( callout.responder ) ) + { + chatPhrase addNameAlias( callout.responder.bcName ); + chatPhrase.lookTarget = callout.responder; + } + + // set up the exposed line to play + chatPhrase threatInfantryExposed( threat ); + + // add a response event, if we decided to do it + if ( doResponse ) + { + if ( RandomInt( 100 ) < anim.eventChance[ "response" ][ "callout_negative" ] ) + { + callout.responder addResponseEvent( "callout", "neg", self, 0.9 ); + } + else + { + callout.responder addResponseEvent( "exposed", "acquired", self, 0.9 ); + } + } + break; + + case "player_obvious": + + chatPhrase addPlayerNameAlias(); + chatPhrase addThreatObviousAlias(); + break; + + case "player_yourclock": + + chatPhrase addPlayerNameAlias(); + + chatPhrase addThreatCalloutAlias( "yourclock", callout.playerClockDirection ); + break; + + case "player_contact_clock": + + chatPhrase addPlayerNameAlias(); + + chatPhrase addThreatCalloutAlias( "contactclock", callout.playerClockDirection ); + break; + + case "player_target_clock": + + chatPhrase addPlayerNameAlias(); + + chatPhrase addThreatCalloutAlias( "targetclock", callout.playerClockDirection ); + break; + + case "player_cardinal": + + chatPhrase addPlayerNameAlias(); + + cardinalDirection = getDirectionCompass( level._player.origin, threat.origin ); + normalizedDirection = normalizeCompassDirection( cardinalDirection ); + + if ( normalizedDirection == "impossible" ) + { + return false; + } + + chatPhrase addThreatCalloutAlias( "cardinal", normalizedDirection ); + break; + + case "ai_yourclock": + + AssertEx( IsDefined( callout.responder ), "we should have found a valid responder in order to do an ai_yourclock callout!" ); + + angles = getRelativeAngles( callout.responder ); + + if ( self canSayName( callout.responder ) ) + { + chatPhrase addNameAlias( callout.responder.bcName ); + chatPhrase.lookTarget = callout.responder; + } + + chatPhrase addThreatCalloutAlias( "yourclock", callout.responderClockDirection ); + + chatPhrase addCalloutResponseEvent( self, callout, threat ); + + break; + + case "ai_contact_clock": + + relativeGuy = self; + + if ( self.team == "allies" ) + { + // make it player relative for allied callouts + relativeGuy = level._player; + } + // for axis, make it relative to a responder if we have one + else if ( IsDefined( callout.responder ) + && RandomInt( 100 ) < anim.eventChance[ "response" ][ "callout" ] ) + { + relativeGuy = callout.responder; + } + + angles = getRelativeAngles( relativeGuy ); + clockDirection = getDirectionFacingClock( angles, relativeGuy.origin, threat.origin ); + + if ( IsDefined( callout.responder ) && self canSayName( callout.responder ) ) + { + chatPhrase addNameAlias( callout.responder.bcName ); + chatPhrase.lookTarget = callout.responder; + } + + chatPhrase addThreatCalloutAlias( "contactclock", clockDirection ); + + chatPhrase addCalloutResponseEvent( self, callout, threat ); + + break; + + case "ai_target_clock": + + relativeGuy = self; + + if ( self.team == "allies" ) + { + // make it player relative for allied callouts + relativeGuy = level._player; + } + // for axis, make it relative to a responder if we have one + else if ( IsDefined( callout.responder ) + && RandomInt( 100 ) < anim.eventChance[ "response" ][ "callout" ] ) + { + relativeGuy = callout.responder; + } + + angles = getRelativeAngles( relativeGuy ); + clockDirection = getDirectionFacingClock( angles, relativeGuy.origin, threat.origin ); + + if ( IsDefined( callout.responder ) && self canSayName( callout.responder ) ) + { + chatPhrase addNameAlias( callout.responder.bcName ); + chatPhrase.lookTarget = callout.responder; + } + + chatPhrase addThreatCalloutAlias( "targetclock", clockDirection ); + + chatPhrase addCalloutResponseEvent( self, callout, threat ); + + break; + + case "ai_cardinal": + + relativeGuy = self; + + if ( self.team == "allies" ) + { + relativeGuy = level._player; + } + + cardinalDirection = getDirectionCompass( relativeGuy.origin, threat.origin ); + normalizedDirection = normalizeCompassDirection( cardinalDirection ); + + if ( normalizedDirection == "impossible" ) + { + return false; + } + + chatPhrase addThreatCalloutAlias( "cardinal", normalizedDirection ); + + break; + + /* DEPRECATED + case "player_object_yourclock": + + chatPhrase addPlayerNameAlias(); + + success = chatPhrase addThreatCalloutLandmarkAlias( callout.landmark, callout.playerClockDirection, true ); + if ( !success ) + { + return false; + } + + break; + + case "player_object_clock": + + chatPhrase addPlayerNameAlias(); + + success = chatPhrase addThreatCalloutLandmarkAlias( callout.landmark, callout.playerClockDirection ); + if ( !success ) + { + return false; + } + + break; + + case "ai_object_yourclock": + + // SRS TODO add when we have obj_your_[clocknum] aliases + break; + */ + + case "generic_location": + + Assert( IsDefined( callout.location ) ); + + success = chatPhrase threatInfantry_doCalloutLocation( callout, level._player, threat ); + if ( !success ) + { + return false; + } + + break; + + case "player_location": + + Assert( IsDefined( callout.location ) ); + + chatPhrase addPlayerNameAlias(); + + success = chatPhrase threatInfantry_doCalloutLocation( callout, level._player, threat ); + if ( !success ) + { + return false; + } + + break; + + case "ai_location": + + Assert( IsDefined( callout.location ) ); + AssertEx( IsDefined( callout.responder ), "we should have found a valid responder in order to do an ai_location callout!" ); + + if ( self canSayName( callout.responder ) ) + { + chatPhrase addNameAlias( callout.responder.bcName ); + chatPhrase.lookTarget = callout.responder; + } + + success = chatPhrase threatInfantry_doCalloutLocation( callout, self, threat ); + if ( !success ) + { + return false; + } + + // the last alias in the soundaliases array is always the one with the actual location info + index = chatPhrase.soundaliases.size - 1; + alias = chatPhrase.soundaliases[ index ]; + + // if the location alias is a "report" we'll have an "echo" to go with it + if ( IsCalloutTypeReport( alias ) ) + { + callout.responder addResponseEvent( "callout", "echo", self, 0.9, alias ); + } + else if ( IsCalloutTypeQA( alias, self ) ) + { + callout.responder addResponseEvent( "callout", "QA", self, 0.9, alias, callout.location ); + } + // otherwise do a generic response + else + { + if ( RandomInt( 100 ) < anim.eventChance[ "response" ][ "callout_negative" ] ) + { + callout.responder addResponseEvent( "callout", "neg", self, 0.9 ); + } + else + { + callout.responder addResponseEvent( "exposed", "acquired", self, 0.9 ); + } + } + + break; + } + + setLastCalloutType( callout.type ); + + self playPhrase( chatPhrase ); + + return true; +} + +doExposedCalloutResponse( responder ) +{ + if ( !IsDefined( responder ) ) + { + return false; + } + + if ( responder.countryID != "US" && responder.countryID != "NS" && responder.countryID != "TF" ) + { + return false; + } + + if ( RandomInt( 100 ) > anim.eventChance[ "response" ][ "exposed" ] ) + { + return false; + } + + return true; +} + +// self = a chatPhrase +// refEnt = the player or an AI, they will determine where the threat is in worldspace relative to themselves +threatInfantry_doCalloutLocation( callout, refEnt, threat ) +{ + success = self addThreatCalloutLocationAlias( callout.location ); + + return success; +} + +// self = a chatPhrase +addCalloutResponseEvent( respondTo, callout, threat ) +{ + if ( !IsDefined( callout.responder ) ) + { + return; + } + + if ( RandomInt( 100 ) > anim.eventChance[ "response" ][ "callout" ] ) + { + return; + } + + modifier = "affirm"; + + // make sure that the guy can't actually see the enemy while we do the diceroll + if ( !callout.responder CanSee( threat ) && RandomInt( 100 ) < anim.eventChance[ "response" ][ "callout_negative" ] ) + { + modifier = "neg"; + } + + callout.responder addResponseEvent( "callout", modifier, respondTo, 0.9 ); +} + +// figures out what kind of callout to do for infantry threats +// - uses chances from anim.threatCallouts[], matched against callout types in self.allowedCallouts[] +getThreatInfantryCalloutType( threat ) +{ + // get info about the AI + location = threat GetLocation(); + selfClockDirection = getDirectionFacingClock( self.angles, self.origin, threat.origin ); + + // try to find a responder + responder = self getResponder( 64, 1024, "response" ); + + responderClockDirection = undefined; + if ( IsDefined( responder ) ) + { + responderClockDirection = getDirectionFacingClock( responder.angles, responder.origin, threat.origin ); + } + + // get relative info + + threatInPlayerFOV = level._player pointInFov( threat.origin ); + threatInFrontArc = level._player entInFrontArc( threat ); + playerClockDirection = getDirectionFacingClock( level._player.angles, level._player.origin, threat.origin ); + + // now, figure out all possible kinds of callouts that this AI can say + self.possibleThreatCallouts = []; + + // is it an RPG? + if ( !IsPlayer( threat ) && threat usingRocketLauncher() ) + { + self addPossibleThreatCallout( "rpg" ); + } + + // is the threat exposed? + if ( threat IsExposed() ) + { + self addPossibleThreatCallout( "exposed" ); + } + + // player-relatives: can the player see the threat? + if ( threatInFrontArc && self canSayPlayerName() ) + { + // guys right in front of you either get "obvious" callouts or nothing + if ( playerClockDirection == "11" + || playerClockDirection == "12" + || playerClockDirection == "1" ) + { + playerCanSeeThreat = false; + if ( self.team == level._player.team ) + { + playerCanSeeThreat = player_can_see_ai( threat ); + } + + if ( playerCanSeeThreat ) + { + self addPossibleThreatCallout( "player_obvious" ); + } + } + else + { + self addPossibleThreatCallout( "player_yourclock" ); + self addPossibleThreatCallout( "player_contact_clock" ); + self addPossibleThreatCallout( "player_target_clock" ); + self addPossibleThreatCallout( "player_cardinal" ); + } + } + + // can another AI whose name we can say see the threat? + if ( IsDefined( responder ) && self canSayName( responder ) ) + { + self addPossibleThreatCallout( "ai_yourclock" ); + } + + // catch-alls + // don't want allies to do 12 o'clock callouts if it's not a "your" callout + if ( enemy_team_name() || ( selfClockDirection != "12" ) ) + { + self addPossibleThreatCallout( "ai_contact_clock" ); + self addPossibleThreatCallout( "ai_target_clock" ); + } + + self addPossibleThreatCallout( "ai_cardinal" ); + + /* DEPRECATED + // is the threat in a landmark trigger? + if ( IsDefined( landmark ) ) + { + // only call out landmarks at 10 or 2 + if ( playerClockDirection == "10" || playerClockDirection == "2" ) + { + + if ( self canSayPlayerName() ) + { + self addPossibleThreatCallout( "player_object_yourclock" ); + } + + self addPossibleThreatCallout( "player_object_clock" ); + } + + if ( IsDefined( responder ) && self canSayName( responder ) ) + { + if ( responderClockDirection == "10" || responderClockDirection == "2" ) + { + self addPossibleThreatCallout( "ai_object_yourclock" ); + } + } + } + */ + + // is the threat in a location trigger? + if ( IsDefined( location ) ) + { + cannedResponse = location GetCannedResponse( self ); + + // locations with responses are the best + if ( IsDefined( cannedResponse ) ) + { + // if we have an accompanying response line and a responder, don't tell the player because it's cool to have the AI respond + if ( IsDefined( responder ) ) + { + self addPossibleThreatCallout( "ai_location" ); + } + else + { + /# + debugstring = anim.bcPrintWarnPrefix + "Calling out a location at origin " + location.origin + " with a canned response, but there are no AIs able to respond."; + #/ + + if ( self canSayPlayerName() ) + { + self addPossibleThreatCallout( "player_location" ); + } + + self addPossibleThreatCallout( "generic_location" ); + } + } + // otherwise do whichever + else + { + if ( IsDefined( responder ) ) + { + self addPossibleThreatCallout( "ai_location" ); + } + + if ( self canSayPlayerName() ) + { + self addPossibleThreatCallout( "player_location" ); + } + + // last ditch effort! + self addPossibleThreatCallout( "generic_location" ); + } + } + + if ( !self.possibleThreatCallouts.size ) + { + return undefined; + } + + // now figure out which of the possible threat callouts we're actually going to use + best = getWeightedChanceRoll( self.possibleThreatCallouts, anim.threatCallouts ); + + callout = SpawnStruct(); + callout.type = best; + callout.responder = responder; + callout.responderClockDirection = responderClockDirection; + //callout.landmark = landmark; // DEPRECATED + callout.playerClockDirection = playerClockDirection; + + if ( IsDefined( location ) ) + { + callout.location = location; + } + + //println( "CALLOUT: " + callout.type ); + + return callout; +} + +// determines whether this kind of location has an alias that could do a canned response +GetCannedResponse( speaker ) +{ + cannedResponseAlias = undefined; + + aliases = self.locationAliases; + foreach ( alias in aliases ) + { + // always do a "QA" type callout if we can, since it's cooler + if ( IsCalloutTypeQA( alias, speaker ) && !IsDefined( self.qaFinished ) ) + { + cannedResponseAlias = alias; + break; + } + + // it's ok that we always choose the last one because we randomize them earlier + if ( IsCalloutTypeReport( alias ) ) + { + cannedResponseAlias = alias; + } + } + + return cannedResponseAlias; +} + +IsCalloutTypeReport( alias ) +{ + return IsSubStr( alias, "_report" ); +} + +// tells us whether a given alias can start a back-and-forth conversation about the location +IsCalloutTypeQA( alias, speaker ) +{ + // first try to see if it's fully constructed + if ( IsSubStr( alias, "_qa" ) && SoundExists( alias ) ) + { + return true; + } + + // otherwise, maybe we have to add prefix/suffix info + tryQA = speaker GetQACalloutAlias( alias, 0 ); + + if ( SoundExists( tryQA ) ) + { + return true; + } + + return false; +} + +GetQACalloutAlias( basealias, lineIndex ) +{ + alias = self.countryID + "_" + self.npcID + "_co_"; + alias += basealias; + alias += "_qa" + lineIndex; + + return alias; +} + +addAllowedThreatCallout( threatType ) +{ + self.allowedCallouts[ self.allowedCallouts.size ] = threatType; +} + +addPossibleThreatCallout( threatType ) +{ + allowed = false; + foreach ( calloutType in self.allowedCallouts ) + { + if ( calloutType == threatType ) + { + if ( !self calloutTypeWillRepeat( threatType ) ) + { + allowed = true; + } + break; + } + } + + if ( !allowed ) + { + return; + } + + self.possibleThreatCallouts[ self.possibleThreatCallouts.size ] = threatType; +} + +calloutTypeWillRepeat( threatType ) +{ + if ( !IsDefined( anim.lastTeamThreatCallout[ self.team ] ) ) + { + return false; + } + + if ( !IsDefined( anim.lastTeamThreatCalloutTime[ self.team ] ) ) + { + return false; + } + + lastThreat = anim.lastTeamThreatCallout[ self.team ]; + lastCalloutTime = anim.lastTeamThreatCalloutTime[ self.team ]; + timeout = anim.teamThreatCalloutLimitTimeout; + + if ( ( threatType == lastThreat ) && ( GetTime() - lastCalloutTime < timeout ) ) + { + return true; + } + + return false; +} + +setLastCalloutType( type ) +{ + anim.lastTeamThreatCallout[ self.team ] = type; + anim.lastTeamThreatCalloutTime[ self.team ] = GetTime(); +} + +// returns a member of possibleValues[], determined by dicerolling it against all the other +// members of possibleValues[]. +// - chances are provided for each possible value by the values +// in chancesForValues[], which is indexed by possibleValues, so we can match them up +getWeightedChanceRoll( possibleValues, chancesForValues ) +{ + best = undefined; + bestRoll = -1;// only want to roll once per value so store this off + foreach ( value in possibleValues ) + { + // don't consider it if the chance is 0 + if ( chancesForValues[ value ] <= 0 ) + { + continue; + } + + thisRoll = RandomInt( chancesForValues[ value ] ); + + // if the best is 100+... + if ( IsDefined( best ) && ( chancesForValues[ best ] >= 100 ) ) + { + // ...and the new challenger isn't at that level, keep going + if ( chancesForValues[ value ] < 100 ) + { + continue; + } + } + // otherwise, if the new challenger is 100+... + else if ( ( chancesForValues[ value ] >= 100 ) ) + { + // he wins automatically + best = value; + bestRoll = thisRoll; + } + // otherwise, everyone else rolls against each other, or 100+'s roll against each other + else if ( thisRoll > bestRoll ) + { + best = value; + bestRoll = thisRoll; + } + } + + return best; +} + + +threatDog( threat, forceDetail ) +{ + self endon( "cancel speaking" ); + chatPhrase = self createChatPhrase(); + + chatPhrase.master = true; + chatPhrase.threatEnt = threat; + + // SRS 10/27/08: updated to be more generic until we have actual dog aliases + chatPhrase addThreatAlias( "dog", "generic" ); + + self playPhrase( chatPhrase ); + return true; +} + +threatInfantryExposed( threat ) +{ + exposedVariants = []; + exposedVariants = array_add( exposedVariants, "open" ); + exposedVariants = array_add( exposedVariants, "breaking" ); + + // only allies get these variants - except Russians, who are usually enemies so we didn't record extras for them + if ( self.owner.team == "allies" && self.owner.countryID != "RU" ) + { + exposedVariants = array_add( exposedVariants, "oscarmike" ); + exposedVariants = array_add( exposedVariants, "movement" ); + } + + exposedVariant = exposedVariants[ RandomInt( exposedVariants.size ) ]; + + self addThreatExposedAlias( exposedVariant ); +} + +threatInfantryRPG( threat ) +{ + self addThreatAlias( "rpg" ); +} + +//// reaction events functions +playReactionEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self.curEvent = self.chatQueue[ "reaction" ]; + + reactTo = self.chatQueue[ "reaction" ].reactTo; + modifier = self.chatQueue[ "reaction" ].modifier; + + anim thread lockAction( self, "reaction" ); + + switch( self.chatQueue[ "reaction" ].eventType ) + { + case "casualty": + self reactionCasualty( reactTo, modifier ); + break; + case "taunt": + self reactionTaunt( reactTo, modifier ); + break; + case "friendlyfire": + self reactionFriendlyFire( reactTo, modifier ); + break; + } + + self notify( "done speaking" ); +} + +reactionCasualty( reactTo, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addReactionAlias( "casualty", "generic" ); + + self playPhrase( chatPhrase ); +} + +reactionTaunt( reactTo, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + + if ( IsDefined( modifier ) && modifier == "hostileburst" ) + { + chatPhrase addHostileBurstAlias(); + } + else + { + chatPhrase addTauntAlias( "taunt", "generic" ); + } + + self playPhrase( chatPhrase ); +} + +reactionFriendlyFire( reactTo, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addCheckFireAlias(); + + self playPhrase( chatPhrase ); +} + +playResponseEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self.curEvent = self.chatQueue[ "response" ]; + + modifier = self.chatQueue[ "response" ].modifier; + respondTo = self.chatQueue[ "response" ].respondTo; + + if ( !IsAlive( respondTo ) ) + { + return; + } + + // if he's responding to a "follow" order, make sure that he's actually moving + if ( self.chatQueue[ "response" ].modifier == "follow" && self.a.state != "move" ) + { + return; + } + + anim thread lockAction( self, "response" ); + + /# + if ( GetDvar( "debug_bcinteraction" ) == "on" ) + animscripts\utility::showDebugLine( self.origin + ( 0, 0, 50 ), respondTo.origin + ( 0, 0, 50 ), ( 1, 1, 0 ), 1.5 ); + #/ + + switch( self.chatQueue[ "response" ].eventType ) + { + case "exposed": + self responseThreatExposed( respondTo, modifier ); + break; + + case "callout": + self responseThreatCallout( respondTo, modifier ); + break; + + case "ack": + self responseGeneric( respondTo, modifier ); + break; + } + + self notify( "done speaking" ); +} + +responseThreatExposed( respondTo, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !IsAlive( respondTo ) ) + { + return; + } + + chatPhrase = self createChatPhrase(); + // these aliases look different from regular responses, + // so construct them using addThreatExposedAlias() + chatPhrase addThreatExposedAlias( modifier ); + chatPhrase.lookTarget = respondTo; + chatPhrase.master = true; + + self playPhrase( chatPhrase ); +} + +responseThreatCallout( respondTo, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !IsAlive( respondTo ) ) + { + return; + } + + chatPhrase = self createChatPhrase(); + + success = false; + if ( modifier == "echo" ) + { + success = chatPhrase addThreatCalloutEcho( self.curEvent.reportAlias, respondTo ); + } + else if ( modifier == "QA" ) + { + success = chatPhrase addThreatCalloutQA_NextLine( respondTo, self.curEvent.reportAlias, self.curEvent.location ); + } + else + { + success = chatPhrase addThreatCalloutResponseAlias( modifier ); + } + + if ( !success ) + { + return; + } + + chatPhrase.lookTarget = respondTo; + chatPhrase.master = true; + + self playPhrase( chatPhrase ); +} + +responseGeneric( respondTo, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !IsAlive( respondTo ) ) + { + return; + } + + type = self.chatQueue[ "response" ].eventType; + + chatPhrase = self createChatPhrase(); + chatPhrase addResponseAlias( type, modifier ); + chatPhrase.lookTarget = respondTo; + chatPhrase.master = true; + + self playPhrase( chatPhrase ); +} + +//// order events functions +playOrderEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self.curEvent = self.chatQueue[ "order" ]; + + modifier = self.chatQueue[ "order" ].modifier; + orderTo = self.chatQueue[ "order" ].orderTo; + + anim thread lockAction( self, "order" ); + + switch( self.chatQueue[ "order" ].eventType ) + { + case "action": + self orderAction( modifier, orderTo ); + break; + case "move": + self orderMove( modifier, orderTo ); + break; + case "displace": + self orderDisplace( modifier ); + break; + } + + self notify( "done speaking" ); +} + +orderAction( modifier, orderTo ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + + self tryOrderTo( chatPhrase, orderTo ); + + chatPhrase addOrderAlias( "action", modifier ); + + self playPhrase( chatPhrase ); +} + +orderMove( modifier, orderTo ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + + /# + if ( GetDvar( "debug_bcinteraction" ) == "on" && IsDefined( orderTo ) ) + animscripts\utility::showDebugLine( self.origin + ( 0, 0, 50 ), orderTo.origin + ( 0, 0, 50 ), ( 0, 1, 0 ), 1.5 ); + #/ + + self tryOrderTo( chatPhrase, orderTo ); + + chatPhrase addOrderAlias( "move", modifier ); + + self playPhrase( chatPhrase ); +} + +orderDisplace( modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addOrderAlias( "displace", modifier ); + + self playPhrase( chatPhrase, true ); +} + +tryOrderTo( chatPhrase, orderTo ) +{ + if ( RandomInt( 100 ) > anim.eventChance[ "response" ][ "order" ] ) + { + // only return if the orderTo guy isn't the player + if ( !IsDefined( orderTo ) || ( IsDefined( orderTo ) && !IsPlayer( orderTo ) ) ) + { + return; + } + } + + if ( IsDefined( orderTo ) && IsPlayer( orderTo ) && IsDefined( level._player.bcNameID ) ) + { + chatPhrase addPlayerNameAlias(); + chatPhrase.lookTarget = level._player; + } + else if ( IsDefined( orderTo ) && self canSayName( orderTo ) ) + { + chatPhrase addNameAlias( orderTo.bcName ); + chatPhrase.lookTarget = orderTo; + + orderTo addResponseEvent( "ack", "yes", self, 0.9 ); + } + else + { + // if we can't specifically respond to someone, throw a notify out there + // and hope that someone is around to catch it + level notify( "follow order", self ); + } +} + +//// inform events functions +playInformEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self.curEvent = self.chatQueue[ "inform" ]; + + modifier = self.chatQueue[ "inform" ].modifier; + + anim thread lockAction( self, "inform" ); + + switch( self.chatQueue[ "inform" ].eventType ) + { + case "incoming": + self informIncoming( modifier ); + break; + case "attack": + self informAttacking( modifier ); + break; + case "reloading": + self informReloading( modifier ); + break; + case "suppressed": + self informSuppressed( modifier ); + break; + case "killfirm": + self informKillfirm( modifier ); + break; + } + + self notify( "done speaking" ); +} + +informReloading( modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addInformAlias( "reloading", modifier ); + + self playPhrase( chatPhrase ); +} + +informSuppressed( modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addInformAlias( "suppressed", modifier ); + + self playPhrase( chatPhrase ); +} + +informIncoming( modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + if ( modifier == "grenade" ) + chatPhrase.master = true; + + chatPhrase addInformAlias( "incoming", modifier ); + + self playPhrase( chatPhrase ); +} + +informAttacking( modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addInformAlias( "attack", modifier ); + + self playPhrase( chatPhrase ); +} + +informKillfirm( modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatPhrase = self createChatPhrase(); + chatPhrase addInformAlias( "killfirm", modifier ); + + self playPhrase( chatPhrase ); +} + +//// custom events functions +playCustomEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self.curEvent = self.chatQueue[ "custom" ]; + + anim thread lockAction( self, self.curEvent.type, true ); + + self playPhrase( self.customChatPhrase ); + + self notify( "done speaking" ); + self.customChatEvent = undefined; + self.customChatPhrase = undefined; +} + +/**************************************************************************** + utility +*****************************************************************************/ + +playPhrase( chatPhrase, noSound ) +{ + anim endon( "battlechatter disabled" ); + self endon( "death" ); + + if ( IsDefined( noSound ) ) + { + return; + } + +// if ( GetDvar( "bcs_stealth" ) != "" && self.voice == "british" ) + if ( IsDefined( level._stealth ) && ( self voice_is_british_based() ) ) + { + for ( i = 0; i < chatPhrase.soundAliases.size; i++ ) + chatPhrase.soundAliases[ i ] = chatPhrase.soundAliases[ i ] + "_s"; + } + + if ( self battleChatter_canPrint() || self battleChatter_canPrintDump() ) + { + bcAliases = []; + foreach ( alias in chatPhrase.soundAliases ) + { + bcAliases[ bcAliases.size ] = alias; + } + + if ( self battleChatter_canPrint() ) + { + self battleChatter_print( bcAliases ); + } + + if ( self battleChatter_canPrintDump() ) + { + bcDescriptor = self.curEvent.eventAction + "_" + self.curEvent.eventType; + + if ( IsDefined( self.curEvent.modifier ) ) + { + bcDescriptor += ( "_" + self.curEvent.modifier ); + } + + self thread battleChatter_printDump( bcAliases, bcDescriptor ); + } + } + + for ( i = 0; i < chatPhrase.soundAliases.size; i++ ) + { + // if battlechatter is turned off and this isn't a friendly fire event, don't keep talking + if ( !self.battleChatter ) + { + if ( !is_friendlyfire_event( self.curEvent ) ) + { + continue; + } + // hacky! passing false here - don't check the typelimit since we set it early for friendlyfire + else if ( !self can_say_friendlyfire( false ) ) + { + continue; + } + } + + if ( self._animActive > 0 && !isdefined( self.bc_during_animscripted ) ) + { + continue; + } + + if ( isFiltered( self.curEvent.eventAction ) ) + { + wait( 0.85 ); + continue; + } + + if ( !SoundExists( chatPhrase.soundAliases[ i ] ) ) + { + /# + PrintLn( anim.bcPrintFailPrefix + "Tried to play an alias that doesn't exist: '" + chatPhrase.soundAliases[ i ] + "'." ); + #/ + + continue; + } + + startTime = GetTime(); + + if ( chatPhrase.master && self.team == "allies" ) + { + self thread maps\_anim::anim_facialFiller( chatPhrase.soundAliases[ i ], chatPhrase.lookTarget ); + self PlaySoundAsMaster( chatPhrase.soundAliases[ i ], chatPhrase.soundAliases[ i ], true ); + self waittill( chatPhrase.soundAliases[ i ] ); + } + else + { + self thread maps\_anim::anim_facialFiller( chatPhrase.soundAliases[ i ], chatPhrase.lookTarget ); + + if ( GetDvarInt( "bcs_forceEnglish", 0 ) ) + { + self PlaySoundAsMaster( chatPhrase.soundAliases[ i ], chatPhrase.soundAliases[ i ], true ); + } + else + { + self PlaySound( chatPhrase.soundAliases[ i ], chatPhrase.soundAliases[ i ], true ); + } + self waittill( chatPhrase.soundAliases[ i ] ); + } + + if ( GetTime() < startTime + 250 ) + { + // This could mean the alias points to a 'null.wav', or that PlaySound() failed for some other reason. + //println( anim.bcPrintFailPrefix + "alias exists but sound didn't play: " + chatPhrase.soundAliases[i] ); + } + } +// animscripts\shared::LookAtStop(); + + self notify( "playPhrase_done" ); + + self doTypeLimit( self.curEvent.eventAction, self.curEvent.eventType ); +} + +is_friendlyfire_event( curEvent ) +{ + if ( !IsDefined( curEvent.eventAction ) || !IsDefined( curEvent.eventType ) ) + { + return false; + } + + if ( curEvent.eventAction == "reaction" && curEvent.eventType == "friendlyfire" ) + { + return true; + } + + return false; +} + +isSpeakingFailSafe( eventAction ) +{ + self endon( "death" ); + wait( 25 ); + self clearIsSpeaking( eventAction ); +} + +clearIsSpeaking( eventAction ) +{ + self.isSpeaking = false; + self.chatQueue[ eventAction ].expireTime = 0; + self.chatQueue[ eventAction ].priority = 0.0; + self.nextSayTimes[ eventAction ] = GetTime() + anim.eventActionMinWait[ eventAction ][ "self" ]; +} + +lockAction( speaker, eventAction, customEvent ) +{ + anim endon( "battlechatter disabled" ); + + Assert( !speaker.isSpeaking ); + + squad = speaker.squad; + team = speaker.team; + + speaker.isSpeaking = true; + speaker thread isSpeakingFailSafe( eventAction ); + + squad.isMemberSaying[ eventAction ] = true; + squad.numSpeakers++; + anim.isTeamSpeaking[ team ] = true; + anim.isTeamSaying[ team ][ eventAction ] = true; + + message = speaker waittill_any_return( "death", "done speaking", "cancel speaking" ); + + squad.isMemberSaying[ eventAction ] = false; + squad.numSpeakers--; + anim.isTeamSpeaking[ team ] = false; + anim.isTeamSaying[ team ][ eventAction ] = false; + + if ( message == "cancel speaking" ) + { + return; + } + + anim.lastTeamSpeakTime[ team ] = GetTime(); + + if ( IsAlive( speaker ) ) + { + speaker clearIsSpeaking( eventAction ); + } + squad.nextSayTimes[ eventAction ] = GetTime() + anim.eventActionMinWait[ eventAction ][ "squad" ]; +} + +updateContact( squadName, member ) +{ + if ( GetTime() - self.squadList[ squadName ].lastContact > 10000 ) + { + isInContact = false; + for ( i = 0; i < self.members.size; i++ ) + { + if ( self.members[ i ] != member && IsAlive( self.members[ i ].enemy ) && IsDefined( self.members[ i ].enemy.squad ) && self.members[ i ].enemy.squad.squadName == squadName ) + isInContact = true; + } + + if ( !isInContact ) + { + self.squadList[ squadName ].firstContact = GetTime(); + self.squadList[ squadName ].calledOut = false; + } + } + + self.squadList[ squadName ].lastContact = GetTime(); +} + +canSay( eventAction, eventType, priority, modifier ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( IsPlayer( self ) ) + { + return false; + } + + if ( Distance( level._player.origin, self.origin ) > level._bcs_maxTalkingDistFromPlayer ) + { + return false; + } + + // our battlechatter is disabled + if ( !isdefined( self.battlechatter ) || !self.battlechatter ) + return( false ); + + if ( IsDefined( priority ) && priority >= 1 ) + return( true ); + + // we're not allowed to call out a threat now, and won't be able to before it expires + if ( ( GetTime() + anim.eventActionMinWait[ eventAction ][ "self" ] ) < self.nextSayTimes[ eventAction ] ) + return( false ); + + // the squad is not allowed to call out a threat yet and won't be able to before it expires + if ( ( GetTime() + anim.eventActionMinWait[ eventAction ][ "squad" ] ) < self.squad.nextSayTimes[ eventAction ] ) + return( false ); + + if ( IsDefined( eventType ) && typeLimited( eventAction, eventType ) ) + return( false ); + + if ( IsDefined( eventType ) && anim.eventPriority[ eventAction ][ eventType ] < self.bcs_minPriority ) + return( false ); + + if ( self voice_is_british_based() ) + return quietFilter( eventAction, eventType, modifier ); + + return( true ); +} + + +quietFilter( action, type, modifier ) +{ + if ( !isDefined( modifier ) ) + modifier = ""; + + if ( !isDefined( type ) ) + return false; + + switch( action ) + { + case "order": + if ( type == "action" && modifier == "coverme" ) + return true; + break; + case "threat": + if ( type == "infantry" || type == "dog" || type == "rpg" ) + return true; + break; + case "inform": + if ( type == "attack" && modifier == "grenade" ) + return true; + else if ( type == "incoming" && modifier == "grenade" ) + return true; + else if ( type == "reloading" && modifier == "generic" ) + return true; + break; + case "reaction": + if ( type == "casualty" && modifier == "generic" ) + return true; + break; + default: + return false; + } + + return false; +} + +getHighestPriorityEvent() +{ + best = undefined; + bestpriority = -999999999; + + if ( self isValidEvent( "custom" ) ) + { + // don't have to check priority because this is the first if + best = "custom"; + bestpriority = self.chatQueue[ "custom" ].priority; + } + if ( self isValidEvent( "response" ) ) + { + if ( self.chatQueue[ "response" ].priority > bestpriority ) + { + best = "response"; + bestpriority = self.chatQueue[ "response" ].priority; + } + } + if ( self isValidEvent( "order" ) ) + { + if ( self.chatQueue[ "order" ].priority > bestpriority ) + { + best = "order"; + bestpriority = self.chatQueue[ "order" ].priority; + } + } + if ( self isValidEvent( "threat" ) ) + { + if ( self.chatQueue[ "threat" ].priority > bestpriority ) + { + best = "threat"; + bestpriority = self.chatQueue[ "threat" ].priority; + } + } + if ( self isValidEvent( "inform" ) ) + { + if ( self.chatQueue[ "inform" ].priority > bestpriority ) + { + best = "inform"; + bestpriority = self.chatQueue[ "inform" ].priority; + } + } + if ( self isValidEvent( "reaction" ) ) + { + if ( self.chatQueue[ "reaction" ].priority > bestpriority ) + { + best = "reaction"; + bestpriority = self.chatQueue[ "reaction" ].priority; + } + } + + return best; +} + +getTargettingAI( threat ) +{ + squad = self.squad; + targettingAI = []; + for ( index = 0; index < squad.members.size; index++ ) + { + if ( IsDefined( squad.members[ index ].enemy ) && squad.members[ index ].enemy == threat ) + targettingAI[ targettingAI.size ] = squad.members[ index ]; + } + + if ( !isdefined( targettingAI[ 0 ] ) ) + return( undefined ); + + targettingSpeaker = undefined; + for ( index = 0; index < targettingAI.size; index++ ) + { + if ( targettingAI[ index ] canSay( "response" ) ) + return( targettingSpeaker ); + } + return( getClosest( self.origin, targettingAI ) ); +} + +getQueueEvents() +{ + queueEvents = []; + queueEventStates = []; + + queueEvents[ 0 ] = "custom"; + queueEvents[ 1 ] = "response"; + queueEvents[ 2 ] = "order"; + queueEvents[ 3 ] = "threat"; + queueEvents[ 4 ] = "inform"; + + for ( i = queueEvents.size - 1; i >= 0; i-- ) + { + for ( j = 1; j <= i; j++ ) + { + if ( self.chatQueue[ queueEvents[ j - 1 ] ].priority < self.chatQueue[ queueEvents[ j ] ].priority ) + { + strTemp = queueEvents[ j - 1 ]; + queueEvents[ j - 1 ] = queueEvents[ j ]; + queueEvents[ j ] = strTemp; + } + } + } + + validEventFound = false; + for ( i = 0; i < queueEvents.size; i++ ) + { + eventState = self getEventState( queueEvents[ i ] ); + + if ( eventState == " valid" && !validEventFound ) + { + validEventFound = true; + queueEventStates[ i ] = "g " + queueEvents[ i ] + eventState + " " + self.chatQueue[ queueEvents[ i ] ].priority; + } + else if ( eventState == " valid" ) + { + queueEventStates[ i ] = "y " + queueEvents[ i ] + eventState + " " + self.chatQueue[ queueEvents[ i ] ].priority; + } + else + { + if ( self.chatQueue[ queueEvents[ i ] ].expireTime == 0 ) + queueEventStates[ i ] = "b " + queueEvents[ i ] + eventState + " " + self.chatQueue[ queueEvents[ i ] ].priority; + else + queueEventStates[ i ] = "r " + queueEvents[ i ] + eventState + " " + self.chatQueue[ queueEvents[ i ] ].priority; + } + } + + return queueEventStates; +} + +getEventState( strAction ) +{ + strState = ""; + if ( self.squad.isMemberSaying[ strAction ] ) + strState += " playing"; + if ( GetTime() > self.chatQueue[ strAction ].expireTime ) + strState += " expired"; + if ( GetTime() < self.squad.nextSayTimes[ strAction ] ) + strState += " cantspeak"; + + if ( strState == "" ) + strState = " valid"; + + return( strState ); +} + +isFiltered( strAction ) +{ + if ( GetDvar( "bcs_filter" + strAction, "off" ) == "on" || GetDvar( "bcs_filter" + strAction, "off" ) == "1" ) + return( true ); + + return( false ); +} + +isValidEvent( strAction ) +{ + if ( !self.squad.isMemberSaying[ strAction ] && + !anim.isTeamSaying[ self.team ][ strAction ] && + GetTime() < self.chatQueue[ strAction ].expireTime && + GetTime() > self.squad.nextSayTimes[ strAction ] ) + { + // redundant? + if ( !typeLimited( strAction, self.chatQueue[ strAction ].eventType ) ) + return( true ); + } + + return( false ); +} + +typeLimited( strAction, strType ) +{ + if ( !isdefined( anim.eventTypeMinWait[ strAction ][ strType ] ) ) + return( false ); + + if ( !isdefined( self.squad.nextTypeSayTimes[ strAction ][ strType ] ) ) + return( false ); + + if ( GetTime() > self.squad.nextTypeSayTimes[ strAction ][ strType ] ) + return( false ); + + return( true ); +} + +doTypeLimit( strAction, strType ) +{ + if ( !isdefined( anim.eventTypeMinWait[ strAction ][ strType ] ) ) + return; + + self.squad.nextTypeSayTimes[ strAction ][ strType ] = GetTime() + anim.eventTypeMinWait[ strAction ][ strType ]; +} + +bcIsSniper() +{ + if ( IsPlayer( self ) ) + return false; + + if ( self isExposed() ) + return false; + + return IsSniperRifle( self.weapon ); +} + +isExposed() +{ + // if he's too far away, the disadvantage of his exposed state + // is negated by contact distance + if ( Distance( self.origin, level._player.origin ) > 1500 ) + { + return false; + } + + // if he's in a "location" that'll be a better way to find him + if ( IsDefined( self GetLocation() ) ) + { + return false; + } + + node = self bcGetClaimedNode(); + + // if he doesn't have a claimed node, he's not in cover + if ( !IsDefined( node ) ) + { + return true; + } + + // if the node is cover or conceal, he's not exposed + if ( !self isNodeCoverOrConceal() ) + { + return false; + } + + return true; +} + +isNodeCoverOrConceal() +{ + node = self.node; + + if ( !IsDefined( node ) ) + { + return false; + } + + if ( IsSubStr( node.type, "Cover" ) || IsSubStr( node.type, "Conceal" ) ) + { + return true; + } + + return false; +} + +squadHasOfficer( squad ) +{ + if ( squad.officerCount > 0 ) + { + return true; + } + else + { + return false; + } +} + +isOfficer() +{ + fullRank = self getRank(); + + if ( !isdefined( fullRank ) ) + return false; + + if ( fullRank == "sergeant" + || fullRank == "lieutenant" + || fullRank == "captain" + || fullRank == "sergeant" ) + { + return true; + } + + return false; +} + +bcGetClaimedNode() +{ + if ( IsPlayer( self ) ) + return self.node; + else + return self GetClaimedNode(); +} + +enemy_team_name() +{ + if ( self IsBadGuy() ) + return true; + else + return false; +} + +getName() +{ + if ( enemy_team_name() ) + { + name = self.ainame; + } + else if ( self.team == "allies" ) + { + name = self.name; + } + else + { + name = undefined; + } + + if ( !isdefined( name ) || self voice_is_british_based() ) + { + return( undefined ); + } + + // check to see if this is a name with two parts, like "Sgt. Peas" or "Agent Smith" + tokens = StrTok( name, " " ); + if ( tokens.size < 2 ) + { + return( name ); + } + + Assert( tokens.size > 1 ); + return( tokens[ 1 ] ); +} + +getRank() +{ + return self.airank; +} + +getClosestFriendlySpeaker( strAction ) +{ + speakers = self getSpeakers( strAction, self.team ); + + speaker = getClosest( self.origin, speakers ); + return( speaker ); +} + +getSpeakers( strAction, team ) +{ + speakers = []; + + soldiers = GetAIArray( team ); + + for ( i = 0; i < soldiers.size; i++ ) + { + if ( soldiers[ i ] == self ) + { + continue; + } + + if ( !soldiers[ i ] canSay( strAction ) ) + { + continue; + } + + speakers[ speakers.size ] = soldiers[ i ]; + } + + return( speakers ); +} + +// see if self can find someone to respond to him +getResponder( distMin, distMax, eventType ) +{ + responder = undefined; + + if ( !IsDefined( eventType ) ) + { + eventType = "response"; + } + + soldiers = array_randomize( self.squad.members ); + + for ( i = 0; i < soldiers.size; i++ ) + { + if ( soldiers[ i ] == self ) + { + continue; + } + + if ( !IsAlive( soldiers[ i ] ) ) + { + continue; + } + + if ( Distance( self.origin, soldiers[ i ].origin ) > distMin + && Distance( self.origin, soldiers[ i ].origin ) < distMax + && !self isUsingSameVoice( soldiers[ i ] ) + && soldiers[ i ] canSay( eventType ) ) + { + responder = soldiers[ i ]; + + // prioritize for guys whose names we know how to say + if ( self canSayName( responder ) ) + { + break; + } + } + } + + return responder; +} + +getLocation() +{ + //prof_begin( "getLocation" ); + myLocations = self get_all_my_locations(); + myLocations = array_randomize( myLocations ); + + if ( myLocations.size ) + { + // give us new ones first + foreach ( location in myLocations ) + { + if ( !location_called_out_ever( location ) ) + { + //prof_end( "getLocation" ); + return location; + } + } + + // otherwise just get a valid one + foreach ( location in myLocations ) + { + if ( !location_called_out_recently( location ) ) + { + //prof_end( "getLocation" ); + return location; + } + } + } + + //prof_end( "getLocation" ); + return undefined; +} + +get_all_my_locations() +{ + //prof_begin( "get_all_my_locations" ); + + // Check for cached locations + if ( isdefined( self.bc_last_get_location_time ) && self.bc_last_get_location_time + self.bc_cache_time > GetTime() ) + { + //prof_end( "get_all_my_locations" ); + return self.bc_locations; + } + + allLocations = anim.bcs_locations; + myLocations = []; + foreach ( location in allLocations ) + { + if ( self IsTouching( location ) && IsDefined( location.locationAliases ) ) + { + myLocations[ myLocations.size ] = location; + } + } + self.bc_locations = myLocations; + self.bc_last_get_location_time = GetTime(); + self.bc_cache_time = RandomIntRange( 2000, 4000 ); // Cache for 2-4 seconds + //prof_end( "get_all_my_locations" ); + return myLocations; +} + +is_in_callable_location() +{ + myLocations = self get_all_my_locations(); + + foreach ( location in myLocations ) + { + if ( !location_called_out_recently( location ) ) + { + return true; + } + } + + return false; +} + +location_called_out_ever( location ) +{ + lastCalloutTime = location_get_last_callout_time( location ); + if ( !IsDefined( lastCalloutTime ) ) + { + return false; + } + + return true; +} + +location_called_out_recently( location ) +{ + lastCalloutTime = location_get_last_callout_time( location ); + if ( !IsDefined( lastCalloutTime ) ) + { + return false; + } + + nextCalloutTime = lastCalloutTime + anim.eventActionMinWait[ "threat" ][ "location_repeat" ]; + if ( GetTime() < nextCalloutTime ) + { + return true; + } + + return false; +} + +location_add_last_callout_time( location ) +{ + anim.locationLastCalloutTimes[ location.classname ] = GetTime(); +} + +location_get_last_callout_time( location ) +{ + if ( IsDefined( anim.locationLastCalloutTimes[ location.classname ] ) ) + { + return anim.locationLastCalloutTimes[ location.classname ]; + } + + return undefined; +} + +// if AI is on a cover node, we want to use the node angles to determine threats relative +// to him; this eliminates false callouts in case the AI's cover animation turns him +// away from the threats +getRelativeAngles( ent ) +{ + Assert( IsDefined( ent ) ); + + angles = ent.angles; + + if ( !IsPlayer( ent ) ) + { + node = ent bcGetClaimedNode(); + if ( IsDefined( node ) ) + { + angles = node.angles; + } + } + + return angles; +} + +sideIsLeftRight( side ) +{ + if ( side == "left" || side == "right" ) + { + return true; + } + + return false; +} + +/* DEPRECATED but this is a cool function, we should keep it around somewhere +getDirectionReferenceSide( vOrigin, vPoint, vReference ) +{ + anglesToReference = VectorToAngles( vReference - vOrigin ); + anglesToPoint = VectorToAngles( vPoint - vOrigin ); + + angle = anglesToReference[ 1 ] - anglesToPoint[ 1 ]; + angle += 360; + angle = Int( angle ) % 360; + if ( angle > 180 ) + angle -= 360; + + if ( angle > 2 && angle < 45 ) + side = "right"; + else if ( angle < - 2 && angle > - 45 ) + side = "left"; + else + { + if ( Distance( vOrigin, vPoint ) < Distance( vOrigin, vReference ) ) + side = "front"; + else + side = "rear"; + } + + return( side ); +} +*/ + +getDirectionFacingFlank( vOrigin, vPoint, vFacing ) +{ + anglesToFacing = VectorToAngles( vFacing ); + anglesToPoint = VectorToAngles( vPoint - vOrigin ); + + angle = anglesToFacing[ 1 ] - anglesToPoint[ 1 ]; + angle += 360; + angle = Int( angle ) % 360; + + if ( angle > 315 || angle < 45 ) + direction = "front"; + else if ( angle < 135 ) + direction = "right"; + else if ( angle < 225 ) + direction = "rear"; + else + direction = "left"; + + return( direction ); +} + +// takes output from getDirectionCompass and normalizes it to the convention +// used by the soundaliases +normalizeCompassDirection( direction ) +{ + Assert( IsDefined( direction ) ); + + new = undefined; + + switch( direction ) + { + case "north": + new = "n"; + break; + case "northwest": + new = "nw"; + break; + case "west": + new = "w"; + break; + case "southwest": + new = "sw"; + break; + case "south": + new = "s"; + break; + case "southeast": + new = "se"; + break; + case "east": + new = "e"; + break; + case "northeast": + new = "ne"; + break; + case "impossible": + new = "impossible"; + break; + default: + AssertMsg( "Can't normalize compass direction " + direction ); + return; + } + + Assert( IsDefined( new ) ); + + return new; +} + +getDirectionCompass( vOrigin, vPoint ) +{ + angles = VectorToAngles( vPoint - vOrigin ); + angle = angles[ 1 ]; + + northYaw = GetNorthYaw(); + angle -= northYaw; + + if ( angle < 0 ) + angle += 360; + else if ( angle > 360 ) + angle -= 360; + + if ( angle < 22.5 || angle > 337.5 ) + direction = "north"; + else if ( angle < 67.5 ) + direction = "northwest"; + else if ( angle < 112.5 ) + direction = "west"; + else if ( angle < 157.5 ) + direction = "southwest"; + else if ( angle < 202.5 ) + direction = "south"; + else if ( angle < 247.5 ) + direction = "southeast"; + else if ( angle < 292.5 ) + direction = "east"; + else if ( angle < 337.5 ) + direction = "northeast"; + else + direction = "impossible"; + + return( direction ); +} + +// takes a getDirectionFacingClock value and, if it's in the "front arc" (10-2 on the clock face), +// will return it normalized to 10, 12, or 2. Otherwise, returns undefined. +getFrontArcClockDirection( direction ) +{ + AssertEx( IsDefined( direction ) ); + + faDirection = "undefined"; + + if ( direction == "10" || direction == "11" ) + { + faDirection = "10"; + } + else if ( direction == "12" ) + { + faDirection = direction; + } + else if ( direction == "1" || direction == "2" ) + { + faDirection = "2"; + } + + return faDirection; +} + +// gets a clock direction from a "viewer" to a "target" +getDirectionFacingClock( viewerAngles, viewerOrigin, targetOrigin ) +{ + forward = AnglesToForward( viewerAngles ); + vFacing = VectorNormalize( forward ); + anglesToFacing = VectorToAngles( vFacing ); + anglesToPoint = VectorToAngles( targetOrigin - viewerOrigin ); + + angle = anglesToFacing[ 1 ] - anglesToPoint[ 1 ]; + angle += 360; + angle = Int( angle ) % 360; + + if ( angle > 345 || angle < 15 ) + { + direction = "12"; + } + else if ( angle < 45 ) + { + direction = "1"; + } + else if ( angle < 75 ) + { + direction = "2"; + } + else if ( angle < 105 ) + { + direction = "3"; + } + else if ( angle < 135 ) + { + direction = "4"; + } + else if ( angle < 165 ) + { + direction = "5"; + } + else if ( angle < 195 ) + { + direction = "6"; + } + else if ( angle < 225 ) + { + direction = "7"; + } + else if ( angle < 255 ) + { + direction = "8"; + } + else if ( angle < 285 ) + { + direction = "9"; + } + else if ( angle < 315 ) + { + direction = "10"; + } + else + { + direction = "11"; + } + + return( direction ); +} + +getVectorRightAngle( vDir ) +{ + return( vDir[ 1 ], 0 - vDir[ 0 ], vDir[ 2 ] ); +} + +getVectorArrayAverage( avAngles ) +{ + vDominantDir = ( 0, 0, 0 ); + + for ( i = 0; i < avAngles.size; i++ ) + vDominantDir += avAngles[ i ]; + + return( vDominantDir[ 0 ] / avAngles.size, vDominantDir[ 1 ] / avAngles.size, vDominantDir[ 2 ] / avAngles.size ); +} + +addNameAlias( name ) +{ + self.soundAliases[ self.soundAliases.size ] = + self.owner.countryID + "_" + self.owner.npcID + "_name_" + name; + + anim.lastNameSaid[ self.owner.team ] = name; + anim.lastNameSaidTime[ self.owner.team ] = GetTime(); +} + +addPlayerNameAlias() +{ + if ( !self.owner canSayPlayerName() ) + { + return; + } + + anim.lastPlayerNameCallTime = GetTime(); + + nameAlias = self.owner.countryID + "_" + self.owner.npcID + "_name_player_" + level._player.bcCountryID + "_" + level._player.bcNameID; + + self.soundAliases[ self.soundAliases.size ] = nameAlias; + + self.lookTarget = level._player; +} + +addRankAlias( name ) +{ + self.soundAliases[ self.soundAliases.size ] = self.owner.countryID + "_" + self.owner.npcID + "_rank_" + name; +} + +canSayName( ai ) +{ + // axis don't use names + if ( enemy_team_name() ) + { + return false; + } + + if ( !IsDefined( ai.bcName ) ) + { + return false; + } + + // SRE'd so added this defensive mechanism + if ( !IsDefined( ai.countryID ) ) + { + return false; + } + + // don't want to cross the streams for AI names in mixed-nationality squads + if ( self.countryID != ai.countryID ) + { + return false; + } + + // make sure we don't say this guy's name too frequently + if ( self nameSaidRecently( ai ) ) + { + return false; + } + + nameAlias = self.countryID + "_" + self.npcID + "_name_" + ai.bcName; + + if ( SoundExists( nameAlias ) ) + { + return true; + } + + return false; +} + +nameSaidRecently( ai ) +{ + if ( ( anim.lastNameSaid[ self.team ] == ai.bcName ) && ( ( GetTime() - anim.lastNameSaidTime[ self.team ] ) < anim.lastNameSaidTimeout ) ) + { + return true; + } + + return false; +} + +canSayPlayerName() +{ + if ( enemy_team_name() ) + { + return false; + } + + if ( !IsDefined( level._player.bcNameID ) || !IsDefined( level._player.bcCountryID ) ) + { + return false; + } + + if ( player_name_called_recently() ) + { + return false; + } + + nameAlias = self.countryID + "_" + self.npcID + "_name_player_" + level._player.bcCountryID + "_" + level._player.bcNameID; + + if ( SoundExists( nameAlias ) ) + { + return true; + } + + return false; +} + +player_name_called_recently() +{ + if ( !IsDefined( anim.lastPlayerNameCallTime ) ) + { + return false; + } + + if ( GetTime() - anim.lastPlayerNameCallTime >= anim.eventTypeMinWait[ "playername" ] ) + { + return false; + } + + return true; +} + +isUsingSameVoice( otherguy ) +{ + /# + if ( GetDvar( "bcs_allowsamevoiceresponse" ) == "on" ) + { + return false; + } + #/ + + if ( ( IsString( self.npcID ) && IsString( otherguy.npcID ) ) && ( self.npcID == otherguy.npcID ) ) + { + return true; + } + else if ( ( !isString( self.npcID ) && !isString( otherguy.npcID ) ) && ( self.npcID == otherguy.npcID ) ) + { + return true; + } + else + { + return false; + } +} + +// format: US_1_threat_[type], with optional _[modifier] +addThreatAlias( type, modifier ) +{ + Assert( IsDefined( type ) ); + + threat = self.owner.countryID + "_" + self.owner.npcID + "_threat_" + type; + + // not all threat aliases use modifiers anymore + if ( IsDefined( modifier ) ) + { + threat += ( "_" + modifier ); + } + + self.soundAliases = array_add( self.soundAliases, threat ); + return true; +} + +// format: US_1_exposed_[type] +addThreatExposedAlias( type ) +{ + Assert( IsDefined( type ) ); + + alias = self.owner.countryID + "_" + self.owner.npcID + "_exposed_" + type; + + self.soundAliases[ self.soundAliases.size ] = alias; + return true; +} + +// format: US_1_order_action_suppress +addThreatObviousAlias() +{ + // just using the order_action_suppress aliases + alias = self.owner.countryID + "_" + self.owner.npcID + "_order_action_suppress"; + + self.soundAliases[ self.soundAliases.size ] = alias; + + return true; +} + +// format: [reportAlias]_echo ("_echo" replaces "_report" in the reportAlias) +addThreatCalloutEcho( reportAlias, respondTo ) +{ + Assert( IsDefined( reportAlias ) ); + + alias = self createEchoAlias( reportAlias, respondTo ); + + if ( !SoundExists( alias ) ) + { + /# + PrintLn( anim.bcPrintFailPrefix + "Can't find echo alias '" + alias + "'." ); + #/ + // TODO maybe output to data csv/txt file later + return false; + } + + self.soundAliases[ self.soundAliases.size ] = alias; + return true; +} + +// format: US_1_resp_ack_co_gnrc_[affirm/neg] +addThreatCalloutResponseAlias( modifier ) +{ + Assert( IsDefined( modifier ) ); + + alias = self.owner.countryID + "_" + self.owner.npcID + "_resp_ack_co_gnrc_" + modifier; + + if ( !SoundExists( alias ) ) + { + /# + PrintLn( anim.bcPrintFailPrefix + "Can't find callout response alias '" + alias + "'." ); + #/ + // TODO maybe output to data csv/txt file later + return false; + } + + self.soundAliases[ self.soundAliases.size ] = alias; + return true; +} + +addThreatCalloutQA_NextLine( respondTo, prevLine, location ) +{ + Assert( IsDefined( respondTo ) && IsDefined( prevLine ) ); + + // figure out the partial alias so we can reconstruct it later + // this is easier than parsing out the prevLine to just get the meat + partialAlias = undefined; + foreach ( str in location.locationAliases ) + { + if ( IsSubStr( prevLine, str ) ) + { + partialAlias = str; + break; + } + } + Assert( IsDefined( partialAlias ) ); + + // now try to construct the new string + prefix = self.owner.countryID + "_" + self.owner.npcID + "_co_"; + lastChar = GetSubStr( prevLine, prevLine.size - 1, prevLine.size ); + Assert( string_is_single_digit_integer( lastChar ) ); + nextIndex = Int( lastChar ) + 1; + + qaAlias = prefix + partialAlias + "_qa" + nextIndex; + + if ( !SoundExists( qaAlias ) ) + { + // finish up the conversation with a yes/no response + if ( RandomInt( 100 ) < anim.eventChance[ "response" ][ "callout_negative" ] ) + { + respondTo addResponseEvent( "callout", "neg", self.owner, 0.9 ); + } + else + { + respondTo addResponseEvent( "exposed", "acquired", self.owner, 0.9 ); + } + + // from now on we'll use the base alias to refer to it since we talked about it already + location.qaFinished = true; + + return false; + } + + // keep the QA conversation going, potentially + respondTo addResponseEvent( "callout", "QA", self.owner, 0.9, qaAlias, location ); + + self.soundAliases[ self.soundAliases.size ] = qaAlias; + return true; +} + + +// takes a soundalias that ends with "_report" and returns a variant of it that ends with "_echo" +createEchoAlias( reportAlias, respondTo ) +{ + reportSuffix = "_report"; + echoSuffix = "_echo"; + + // make sure that we're responding in the responder's countryID and voice + echoPrefix = self.owner.countryID + "_" + self.owner.npcID + "_"; + + AssertEx( IsSubStr( reportAlias, reportSuffix ), "reportAlias doesn't have substring '" + reportSuffix + "', so it doesn't look like an eligible report alias." ); + + reportSuffixStartIndex = reportAlias.size - reportSuffix.size;// figure out where the end of this baseAlias is + + // some guys have longer npcIDs than others, so we have to allow the base prefix to be variable length + oldPrefix = self.owner.countryID + "_" + respondTo.npcID + "_"; + oldPrefixLength = oldPrefix.size; + + baseAlias = GetSubStr( reportAlias, oldPrefixLength, reportSuffixStartIndex );// start 5 spots in to eliminate the original countryID and npcID info + + // construct the final alias + echoAlias = echoPrefix + baseAlias + echoSuffix; + + return echoAlias; +} + +// format: US_1_co_[contactclock/targetclock/yourclock/cardinal]_[modifier] +addThreatCalloutAlias( type, modifier ) +{ + Assert( IsDefined( type ) && IsDefined( modifier ) ); + + alias = self.owner.countryID + "_" + self.owner.npcID + "_co_" + type + "_" + modifier; + + self.soundAliases[ self.soundAliases.size ] = alias; + return true; +} + +// "landmarks" are aka "objects" in the soundaliases +// format: US_1_co_obj_[landmark]_your(optional)_[frontArcDirection] +// - isRelative dictates if we will add the "your" to the string +addThreatCalloutLandmarkAlias( landmark, frontArcDirection, isRelative ) +{ + Assert( IsDefined( landmark ) && IsDefined( frontArcDirection ) ); + + landmarkStr = landmark.script_landmark; + + if ( !IsDefined( isRelative ) ) + { + isRelative = false; + } + + alias = self.owner.countryID + "_" + self.owner.npcID + "_co_obj_" + landmarkStr; + if ( isRelative ) + { + alias += "_y"; + } + alias += "_" + frontArcDirection; + + if ( !SoundExists( alias ) ) + { + /# + PrintLn( anim.bcPrintFailPrefix + "Can't find sound alias '" + alias + "'. Does landmark '" + landmarkStr + "' have callout references in the battlechatter csv for nationality '" + self.owner.countryID + "'?" ); + #/ + // TODO maybe output to data csv/txt file later + return false; + } + + self.soundAliases[ self.soundAliases.size ] = alias; + return true; +} + +// format: US_1_co_[location.locationAliases[idx]] +// -- oldstyle format: US_1_callout_loc_[location](optional if floor exists)_[floor](optional if location exists)_[left/right](optional)_report(optional) +addThreatCalloutLocationAlias( location ) +{ + Assert( IsDefined( location ) && IsDefined( location.locationAliases ) ); + + finalAlias = undefined; + + // some triggers have more than one alias set up + locationAliases = location.locationAliases; + Assert( locationAliases.size ); + + locAlias = locationAliases[ 0 ]; + + if ( locationAliases.size > 1 ) + { + // prefer aliases that make the AIs talk more + responseAlias = undefined; + responseAlias = location GetCannedResponse( self.owner ); + if ( IsDefined( responseAlias ) ) + { + locAlias = responseAlias; + } + else + { + // otherwise just randomize it + locAlias = random( locationAliases ); + } + } + + alias = undefined; + + // see if this is a QA conversation that hasn't been finished + if ( !IsDefined( location.qaFinished ) && IsCalloutTypeQA( locAlias, self.owner ) ) + { + alias = self.owner GetQACalloutAlias( locAlias, 0 ); + } + else + { + // standard prefix + prefix = self.owner.countryID + "_" + self.owner.npcID + "_"; + + // this separates oldstyle location callouts so we can use some older assets + // - in the future we'll just add the rest of the prefix string above + if ( !IsSubStr( locAlias, "callout" ) ) + { + prefix += "co_";// the newstyle standard + } + + alias = prefix + locAlias; + } + + if ( SoundExists( alias ) ) + { + finalAlias = alias; + } + + if ( !IsDefined( finalAlias ) ) + { + /# + printStr = anim.bcPrintFailPrefix + "Couldn't find a location callout alias for data:"; + if ( IsDefined( location ) ) + { + printStr += " location=" + locAlias; + } + if ( IsDefined( alias ) ) + { + printStr += " finalAlias=" + alias; + } + printStr += ". Are you sure that there is an alias to support it?"; + + PrintLn( printStr ); + // TODO maybe output to data csv/txt file later + #/ + + return false; + } + + location_add_last_callout_time( location ); + self.soundAliases[ self.soundAliases.size ] = finalAlias; + return true; +} + +addInformAlias( type, modifier ) +{ + Assert( IsDefined( type ) && IsDefined( modifier ) ); + + alias = self.owner.countryID + "_" + self.owner.npcID + "_inform_" + type + "_" + modifier; + + self.soundAliases[ self.soundAliases.size ] = alias; +} + +addResponseAlias( type, modifier ) +{ + Assert( IsDefined( type ) && IsDefined( modifier ) ); + + alias = self.owner.countryID + "_" + self.owner.npcID + "_response_" + type + "_" + modifier; + self.soundAliases[ self.soundAliases.size ] = alias; + + return( true ); +} + +addReactionAlias( type, modifier ) +{ + Assert( IsDefined( type ) && IsDefined( modifier ) ); + reaction = self.owner.countryID + "_" + self.owner.npcID + "_reaction_" + type + "_" + modifier; + self.soundAliases[ self.soundAliases.size ] = reaction; + + return( true ); +} + +addCheckFireAlias() +{ + reaction = self.owner.countryID + "_" + self.owner.npcID + "_check_fire"; + self.soundAliases[ self.soundAliases.size ] = reaction; + + return true; +} + +addTauntAlias( type, modifier ) +{ + Assert( IsDefined( type ) && IsDefined( modifier ) ); + reaction = self.owner.countryID + "_" + self.owner.npcID + "_taunt"; + self.soundAliases[ self.soundAliases.size ] = reaction; + + return( true ); +} + +// format: GE_1_hostile_burst +addHostileBurstAlias() +{ + burst = self.owner.countryID + "_" + self.owner.npcID + "_hostile_burst"; + self.soundAliases[ self.soundAliases.size ] = burst; + + return true; +} + +// format: US_1_order_move_follow (etc.) +addOrderAlias( type, modifier ) +{ + Assert( IsDefined( type ) && IsDefined( modifier ) ); + + order = self.owner.countryID + "_" + self.owner.npcID + "_order_" + type + "_" + modifier; + self.soundAliases[ self.soundAliases.size ] = order; + + return true; +} + +initContact( squadName ) +{ + if ( !isdefined( self.squadList[ squadName ].calledOut ) ) + self.squadList[ squadName ].calledOut = false; + + if ( !isdefined( self.squadList[ squadName ].firstContact ) ) + self.squadList[ squadName ].firstContact = 2000000000; + + if ( !isdefined( self.squadList[ squadName ].lastContact ) ) + self.squadList[ squadName ].lastContact = 0; +} + +shutdownContact( squadName ) +{ + self.squadList[ squadName ].calledOut = undefined; + self.squadList[ squadName ].firstContact = undefined; + self.squadList[ squadName ].lastContact = undefined; +} + +createChatEvent( eventAction, eventType, priority ) +{ + chatEvent = SpawnStruct(); + chatEvent.owner = self; + chatEvent.eventType = eventType; + chatEvent.eventAction = eventAction; + + if ( IsDefined( priority ) ) + chatEvent.priority = priority; + else + chatEvent.priority = anim.eventPriority[ eventAction ][ eventType ]; + + chatEvent.expireTime = GetTime() + anim.eventDuration[ eventAction ][ eventType ]; + + return chatEvent; +} + +createChatPhrase() +{ + chatPhrase = SpawnStruct(); + chatPhrase.owner = self; + chatPhrase.soundAliases = []; + chatPhrase.master = false; + + return chatPhrase; +} + +pointInFov( origin ) +{ + forward = AnglesToForward( self.angles ); + normalVec = VectorNormalize( origin - self.origin ); + + dot = VectorDot( forward, normalVec ); + return dot > 0.766;// fov = 80 +} + +// the "front arc" goes from 9 to 3 on a clock face - the front 180 degrees +entInFrontArc( ent ) +{ + direction = getDirectionFacingClock( self.angles, self.origin, ent.origin ); + + if ( direction == "9" + || direction == "10" + || direction == "11" + || direction == "12" + || direction == "1" + || direction == "2" + || direction == "3" ) + { + return true; + } + + return false; +} + +/**************************************************************************** + flavor burst transmissions +*****************************************************************************/ + +// self = the squad +squadFlavorBurstTransmissions() +{ + anim endon( "battlechatter disabled" ); + self endon( "squad_deleting" ); + + if ( self.team != "allies" ) + { + // hackish, don't need it to be more complicated for now though + if ( level._script != "af_caves" ) + { + return; + } + } + + // wait until an AI gets put in the squad + while ( self.memberCount <= 0 ) + { + wait( 0.5 ); + } + + // don't do regular waits if we're coming back from flavorbursts being disabled + burstingWasPaused = false; + + while ( IsDefined( self ) ) + { + // make sure at least one of the guys in the squad can burst + if ( !squadCanBurst( self ) ) + { + burstingWasPaused = true; + + wait( 1 ); + continue; + } + else if ( self.fbt_firstBurst ) + { + if ( !burstingWasPaused ) + { + wait( RandomFloat( anim.fbt_waitMin ) ); + } + + if ( burstingWasPaused ) + { + burstingWasPaused = false; + } + + self.fbt_firstBurst = false; + } + else + { + if ( !burstingWasPaused ) + { + wait( RandomFloatRange( anim.fbt_waitMin, anim.fbt_waitMax ) ); + } + + if ( burstingWasPaused ) + { + burstingWasPaused = false; + } + } + + burster = getBurster( self ); + + if ( !IsDefined( burster ) ) + { + continue; + } + + nationality = burster.voice; + burstID = getFlavorBurstID( self, nationality ); + aliases = getFlavorBurstAliases( nationality, burstID ); + + foreach ( i, alias in aliases ) + { + // see if we need to migrate our burster + if ( !burster canDoFlavorBurst() || Distance( level._player.origin, burster.origin ) > anim.fbt_desiredDistMax ) + { + for ( j = 0; j < self.members.size; j++ ) + { + burster = getBurster( self ); + + if ( !IsDefined( burster ) ) + { + continue; + } + + // to continue the burst transmission, we want to stick with the same + // nationality - this is in case we have a squad of mixed nationalities + if ( burster.voice == nationality ) + { + break; + } + } + + // if we can't find a new burster of the same nationality, + // quit this transmission + if ( !IsDefined( burster ) || burster.voice != nationality ) + { + break; + } + } + + // play the burst + self thread playFlavorBurstLine( burster, alias ); + self waittill( "burst_line_done" ); + + if ( i != ( aliases.size - 1 ) ) + { + wait( RandomFloatRange( anim.fbt_lineBreakMin, anim.fbt_lineBreakMax ) ); + } + } + } +} + +squadCanBurst( squad ) +{ + foundOne = false; + foreach ( member in squad.members ) + { + if ( member canDoFlavorBurst() ) + { + foundOne = true; + break; + } + } + + return foundOne; +} + +canDoFlavorBurst() +{ + canDo = false; + + if ( !IsPlayer( self ) + && IsAlive( self ) + && self.classname != "actor_enemy_dog" + && level._flavorbursts[ self.team ] + && self voiceCanBurst() + && self.flavorbursts ) + { + canDo = true; + } + + return canDo; +} + +voiceCanBurst() +{ + if ( IsDefined( anim.flavorburstVoices[ self.voice ] ) && anim.flavorburstVoices[ self.voice ] ) + { + return true; + } + + return false; +} + +getBurster( squad ) +{ + burster = undefined; + // prioritize by player proximity + // for some reason, get_array_of_farthest returns the closest at index 0 + squadMembers = get_array_of_farthest( level._player.origin, squad.members ); + + foreach ( guy in squadMembers ) + { + if ( guy canDoFlavorBurst() ) + { + burster = guy; + + if ( !IsDefined( squad.fbt_lastBursterID ) ) + { + break; + } + + // try not to play it off the last guy we played it off of + if ( IsDefined( squad.fbt_lastBursterID ) && squad.fbt_lastBursterID == burster.unique_id ) + { + continue; + } + } + } + + if ( IsDefined( burster ) ) + { + // store the ent's unique ID because the ent could be gone by the time we check again + squad.fbt_lastBursterID = burster.unique_id; + } + + return burster; +} + +getFlavorBurstID( squad, nationality ) +{ + bursts = array_randomize( anim.flavorbursts[ nationality ] ); + + // if we used all of the flavor bursts already, reset + if ( anim.flavorburstsUsed.size >= bursts.size ) + { + anim.flavorburstsUsed = []; + } + + burstID = undefined; + foreach ( burst in bursts ) + { + burstID = burst; + + if ( !flavorBurstWouldRepeat( burstID ) ) + { + break; + } + } + + anim.flavorburstsUsed[ anim.flavorburstsUsed.size ] = burstID; + return burstID; +} + +flavorBurstWouldRepeat( burstID ) +{ + if ( !anim.flavorburstsUsed.size ) + { + return false; + } + + foundIt = false; + foreach ( usedBurst in anim.flavorburstsUsed ) + { + if ( usedBurst == burstID ) + { + foundIt = true; + break; + } + } + + return foundIt; +} + +getFlavorBurstAliases( nationality, burstID, startingLine ) +{ + if ( !IsDefined( startingLine ) ) + { + startingLine = 1; + } + + burstLine = startingLine; + aliases = []; + + while ( 1 ) + { + alias = "FB_" + anim.countryIDs[ nationality ] + "_" + burstID + "_" + burstLine; + + burstLine++; + + if ( SoundExists( alias ) ) + { + aliases[ aliases.size ] = alias; + } + else + { + break; + } + } + + return aliases; +} + +playFlavorBurstLine( burster, alias ) +{ + anim endon( "battlechatter disabled" ); + + /# + if ( GetDvar( "bcs_fbt_debug" ) == "on" ) + { + self thread flavorBurstLineDebug( burster, alias ); + } + #/ + + // make a separate origin to play the sound off of so that mission dialogue doesn't get cut off when played on this guy at the same time + soundOrg = Spawn( "sound_emitter", burster.origin ); + soundOrg LinkTo( burster ); + + soundOrg PlaySound( alias, alias, true ); + soundOrg waittill( alias ); + + soundOrg Delete(); + + if ( IsDefined( self ) ) + self notify( "burst_line_done" ); +} + +flavorBurstLineDebug( burster, alias ) +{ + self endon( "burst_line_done" ); + + while ( 1 ) + { + Print3d( burster GetTagOrigin( "j_spinelower" ), alias, ( 1, 1, 1 ), 1, 0.5 ); + wait( 0.05 ); + } +} + +/**************************************************************************** + debugging functions +*****************************************************************************/ + + /* +debugPrintEvents() +{ + if ( !isalive( self ) ) + return; + + if ( GetDvar( "debug_bcshowqueue" ) != self.team && GetDvar( "debug_bcshowqueue" ) != "all" ) + return; + + self endon( "death" ); + self notify( "debugPrintEvents" ); + self endon( "debugPrintEvents" ); + + queueEvents = self getQueueEvents(); + colors[ "g" ] = ( 0, 1, 0 ); + colors[ "y" ] = ( 1, 1, 0 ); + colors[ "r" ] = ( 1, 0, 0 ); + colors[ "b" ] = ( 0, 0, 0 ); + + while ( 1 ) + { + aboveHead = self GetShootAtPos() + ( 0, 0, 10 ); + for ( i = 0; i < queueEvents.size; i++ ) + { + Print3d( aboveHead, queueEvents[ i ], colors[ queueEvents[ i ][ 0 ] ], 1, 0.5 ); // origin, text, RGB, alpha, scale + aboveHead += ( 0, 0, 5 ); + } + wait 0.05; + } +} + +debugQueueEvents() +{ + if ( GetDvar( "debug_bcresponse" ) == "on" ) + self thread printQueueEvent( "response" ); + if ( GetDvar( "debug_bcthreat" ) == "on" ) + self thread printQueueEvent( "threat" ); + if ( GetDvar( "debug_bcinform" ) == "on" ) + self thread printQueueEvent( "inform" ); + if ( GetDvar( "debug_bcorder" ) == "on" ) + self thread printQueueEvent( "order" ); +} + +printAboveHead( string, duration, offset ) +{ + self endon( "death" ); + + if ( !isdefined( offset ) ) + offset = ( 0, 0, 0 ); + + for ( i = 0; i < ( duration * 2 ); i++ ) + { + if ( !isalive( self ) ) + return; + + aboveHead = self GetShootAtPos() + ( 0, 0, 10 ) + offset; + Print3d( aboveHead, string, ( 1, 0, 0 ), 1, 0.5 ); // origin, text, RGB, alpha, scale + wait 0.05; + } +} + +printQueueEvent( eventAction ) +{ + time = GetTime(); + + if ( self.chatQueue[ eventAction ].expireTime > 0 && !isdefined( self.chatQueue[ eventAction ].printed ) ) + { + Print( "QUEUE EVENT " + eventAction + "_" + self.chatQueue[ eventAction ].eventType + ": " ); + if ( time > self.chatQueue[ eventAction ].expireTime ) + PrintLn( "^2 missed by " + ( time - self.chatQueue[ eventAction ].expireTime ) + "ms" ); + else + PrintLn( "slack of " + ( self.chatQueue[ eventAction ].expireTime - time ) + "ms" ); + + self.chatQueue[ eventAction ].printed = true; + } +} +*/ + +battleChatter_canPrint() +{ +/# + if ( GetDebugDvar( "debug_bcprint" ) == self.team || GetDebugDvar( "debug_bcprint" ) == "all" ) + return( true ); +#/ + return( false ); +} + +battleChatter_canPrintDump() +{ +/# + if ( GetDebugDvar( "debug_bcprintdump" ) == self.team || GetDebugDvar( "debug_bcprintdump" ) == "all" ) + { + return true; + } +#/ + return false; +} + +// SRS 10/16/08: this used to be unnecessarily covered with two functions +battleChatter_print( aliases ) +{ + if ( aliases.size <= 0 ) + { + AssertMsg( "battleChatter_print(): the aliases array is empty." ); + return; + } + + if ( !self battleChatter_canPrint() ) + { + return; + } + + colorPrefix = "^5 ";// allies + if ( enemy_team_name() ) + { + colorPrefix = "^6 "; + } + + // print to the console + Print( colorPrefix ); + + foreach ( alias in aliases ) + { + Print( alias ); + } + + PrintLn( "" ); +} + +// optionally dumps info out to files for examination later +battleChatter_printDump( aliases, descriptor ) +{ +/# + if ( !self battleChatter_canPrintDump() ) + { + return; + } + + if ( aliases.size <= 0 ) + { + AssertMsg( "battleChatter_printDump(): the aliases array is empty." ); + return; + } + + dumpType = GetDvar( "debug_bcprintdumptype" ); + if ( dumpType != "csv" && dumpType != "txt" ) + { + return; + } + + // do this early, in case the file writing hangs for a bit of time + secsSinceLastDump = -1; + if ( IsDefined( level._lastDumpTime ) ) + { + secsSinceLastDump = ( GetTime() - level._lastDumpTime ) / 1000; + } + + level._lastDumpTime = GetTime();// reset + + // -- CSV dumps help the audio dept optimize where they spend their time -- + if ( dumpType == "csv" ) + { + // only 1 write at a time + if ( !flag_exist( "bcs_csv_dumpFileWriting" ) ) + { + flag_init( "bcs_csv_dumpFileWriting" ); + } + + // open the file, if it's not already open + if ( !IsDefined( level._bcs_csv_dumpFile ) ) + { + filePath = "scriptgen/battlechatter/bcsDump_" + level._script + ".csv"; + level._bcs_csv_dumpFile = OpenFile( filePath, "write" ); + } + + // dump a new line for each sound + // format: levelname,countryID,npcID,aliasType + foreach ( alias in aliases ) + { + aliasType = getAliasTypeFromSoundalias( alias ); + + dumpString = level._script + "," + + self.countryID + "," + + self.npcID + "," + + aliasType; + + battleChatter_printDumpLine( level._bcs_csv_dumpFile, dumpString, "bcs_csv_dumpFileWriting" ); + } + } + + // -- TXT dumps help the design dept tweak distributions and timing -- + else if ( dumpType == "txt" ) + { + AssertEx( IsDefined( descriptor ), "battlechatter print dumps of type 'txt' require a descriptor!" ); + + if ( !flag_exist( "bcs_txt_dumpFileWriting" ) ) + { + flag_init( "bcs_txt_dumpFileWriting" ); + } + + if ( !IsDefined( level._bcs_txt_dumpFile ) ) + { + filePath = "scriptgen/battlechatter/bcsDump_" + level._script + ".txt"; + level._bcs_txt_dumpFile = OpenFile( filePath, "write" ); + } + + name = self.name; + if ( enemy_team_name() ) + { + name = self.ainame; + } + + // format: (2.3 secs) US_1 order_move_follow: US_1_threat_rpg_generic, US_1_landmark_near_cargocontainer, US_1_direction_relative_north + dumpString = "(" + secsSinceLastDump + " secs) "; + dumpString += name + " " + descriptor + ": "; + foreach ( i, alias in aliases ) + { + dumpString += alias; + if ( i != ( aliases.size - 1 ) ) + { + dumpString += ", "; + } + } + + battleChatter_printDumpLine( level._bcs_txt_dumpFile, dumpString, "bcs_txt_dumpFileWriting" ); + } +#/ +} + +getAliasTypeFromSoundalias( alias ) +{ + // get the prefix and make sure it matches as we'd expect + prefix = self.countryID + "_" + self.npcID + "_"; + AssertEx( IsSubStr( alias, prefix ), "didn't find expected prefix info in alias '" + alias + "' with substr test of '" + prefix + "'." ); + + // figure out the alias type by removing the prefix + aliasType = GetSubStr( alias, prefix.size, alias.size ); + + return aliasType; +} + +battleChatter_printDumpLine( file, str, controlFlag ) +{ + if ( flag( controlFlag ) ) + { + flag_wait( controlFlag ); + } + flag_set( controlFlag ); + + FPrintLn( file, str ); + + flag_clear( controlFlag ); +} + +bcDrawObjects() +{ + for ( i = 0; i < anim.bcs_locations.size; i++ ) + { + locationAliases = anim.bcs_locations[ i ].locationAliases; + + if ( !IsDefined( locationAliases ) ) + { + continue; + } + + locationStr = ""; + foreach ( alias in locationAliases ) + { + locationStr += alias; + } + thread drawBCObject( "Location: " + locationStr, anim.bcs_locations[ i ] GetOrigin(), ( 0, 0, 8 ), ( 1, 1, 1 ) ); + } +} + +drawBCObject( string, origin, offset, color ) +{ + while ( true ) + { + if ( Distance( level._player.origin, origin ) > 2048 ) + { + wait( 0.1 ); + continue; + } + + Print3d( origin + offset, string, color, 1, 0.75 ); // origin, text, RGB, alpha, scale + wait 0.05; + } +} + +drawBCDirections( landmark, offset, color ) +{ + landmarkOrigin = landmark GetOrigin(); + + while ( true ) + { + if ( Distance( level._player.origin, landmarkOrigin ) > 2048 ) + { + wait( 0.1 ); + continue; + } + + compass = getDirectionCompass( level._player.origin, landmarkOrigin ); + compass = normalizeCompassDirection( compass ); + + clock = getDirectionFacingClock( level._player.angles, level._player.origin, landmarkOrigin ); + + string = compass + ", " + clock + ":00"; + + Print3d( landmarkOrigin + offset, string, color, 1, 0.75 ); // origin, text, RGB, alpha, scale + wait 0.05; + } +} + + +resetNextSayTimes( team, action ) +{ + soldiers = GetAIArray( team ); + + for ( index = 0; index < soldiers.size; index++ ) + { + soldier = soldiers[ index ]; + + if ( !isAlive( soldier ) ) + continue; + + if ( !isDefined( soldier.battlechatter ) ) + continue; + + soldier.nextSayTimes[ action ] = GetTime() + 350; + soldier.squad.nextSayTimes[ action ] = GetTime() + 350; + } +} + +voice_is_british_based() +{ + self endon( "death" ); + if ( self.voice == "british" || self.voice == "spanish" || self.voice == "italian" || self.voice == "german" ) + return true; + else + return false; +} + +friendlyfire_warning() +{ + if ( !self can_say_friendlyfire() ) + { + return false; + } + + // since we're skipping a lot of the normal bcs checks, multiple guys can potentially say this at the same time, so do the typelimit earlier than usual + self doTypeLimit( "reaction", "friendlyfire" ); + + self thread playReactionEvent(); + return true; +} + +can_say_friendlyfire( checkTypeLimit ) +{ + if ( IsDefined( self.friendlyfire_warnings_disable ) ) + { + return false; + } + + if ( !IsDefined( self.chatQueue ) ) + { + return false; + } + + // do we have a reaction event in our queue? + if ( !IsDefined( self.chatQueue[ "reaction" ] ) || !IsDefined( self.chatQueue[ "reaction" ].eventType ) ) + { + return false; + } + + // is it a friendlyfire reaction? + if ( self.chatQueue[ "reaction" ].eventType != "friendlyfire" ) + { + return false; + } + + // has it expired? + if ( GetTime() > self.chatQueue[ "reaction" ].expireTime ) + { + return false; + } + + if ( !IsDefined( checkTypeLimit ) ) + { + checkTypeLimit = true; + } + + if ( checkTypeLimit ) + { + // is it too early to do another one yet? + if ( IsDefined( self.squad.nextTypeSayTimes[ "reaction" ][ "friendlyfire" ] ) ) + { + if ( GetTime() < self.squad.nextTypeSayTimes[ "reaction" ][ "friendlyfire" ] ) + { + return false; + } + } + } + + return true; +} \ No newline at end of file diff --git a/animscripts/battlechatter_ai.gsc b/animscripts/battlechatter_ai.gsc new file mode 100644 index 0000000..44674cb --- /dev/null +++ b/animscripts/battlechatter_ai.gsc @@ -0,0 +1,1544 @@ +/**************************************************************************** + + battleChatter_ai.gsc + +*****************************************************************************/ + +#include common_scripts\utility; +#include maps\_utility; +#include animscripts\utility; +#include animscripts\battlechatter; + +/**************************************************************************** + initialization +*****************************************************************************/ + +addToSystem( squadName ) +{ + self endon( "death" ); + + //prof_begin("addToSystem"); + + if ( !bcsEnabled() ) + return; + + if ( self.chatInitialized ) + return; + + assert( isdefined( self.squad ) ); + + // initialize battlechatter data for this AI's squad if it hasn't been already + if ( !isdefined( self.squad.chatInitialized ) || !self.squad.chatInitialized ) + self.squad init_squadBattleChatter(); + + self.enemyClass = "infantry"; + self.calledOut = []; + + if ( isPlayer( self ) ) + { + self.battleChatter = false; + self.flavorbursts = false; + self.type = "human"; + return; + } + + if ( self.type == "dog" ) + { + self.enemyClass = undefined; + self.battlechatter = false; + self.flavorbursts = false; + return; + } + + // don't want civilians doing battlechatter + if ( self.team == "neutral" ) + { + self.enemyClass = undefined; + self.battlechatter = false; + self.flavorbursts = false; + return; + } + + if ( forceEnglish() ) + { + if ( self.team == "allies" ) + self.script_battlechatter = false; + else + self.voice = "american"; + } + + // SRS 1/31/09: turning off multilingual voices to avoid a bunch of errors that don't really + // make sense right now since we're not sure if we even want multilingual functionality anymore + if ( self.voice == "multilingual" ) + { + ASSERTMSG( "Actor with classname '" + self.code_classname + "' has their character asset marked as 'multilingual' in the character GDT. This is no longer supported, please change it!" ); + //sLanguage = get_random_nationality(); + //self.countryID = anim.countryIDs[ sLanguage ]; + sLanguage = "russian"; + self.countryID = anim.countryIDs[ sLanguage ]; + self.voice = sLanguage; + } + else + { + self.countryID = anim.countryIDs[ self.voice ]; + } + + if ( isdefined( self.script_friendname ) ) + { + friendname = ToLower( self.script_friendname ); + + if ( IsSubStr( friendname, "price" ) ) + { + self.npcID = "pri"; + } + else if ( IsSubStr( friendname, "mactavish" ) || IsSubStr( friendname, "soap" ) ) + { + self.npcID = "mct"; + } + else if ( IsSubStr( friendname, "ghost" ) ) + { + self.npcID = "gst"; + } + else if ( IsSubStr( friendname, "dunn" ) ) + { + self.npcID = "cpd"; + } + else if ( IsSubStr( friendname, "foley" ) ) + { + self.npcID = "mcy"; + } + // tagJW: Setting special npc IDs this way makes me sad. + else if ( IsSubStr( friendname, "eagle" ) ) + { + self.npcID = "eag"; + } + else if ( IsSubStr( friendname, "falcon" ) ) + { + self.npcID = "fal"; + } + else if ( IsSubStr( friendname, "baker" ) ) + { + self.npcID = "bak"; + } + else if ( IsSubStr( friendname, "duke" ) ) + { + self.npcID = "du"; + } + + /* DEPRECATED + if ( IsSubStr( friendname, "grigsby" ) || IsSubStr( friendname, "griggs" ) ) + { + self.npcID = "grg"; + } + else if ( IsSubStr( friendname, "gaz" ) ) + { + self.npcID = "gaz"; + } + */ + else + { + self setNPCID(); + } + } + else + { + self setNPCID(); + } + + self thread aiNameAndRankWaiter(); + + self init_aiBattleChatter(); + self thread aiThreadThreader(); + + //prof_end("addToSystem"); +} + + +/* SRS 1/31/09: DEPRECATED +get_random_nationality() +{ + //used for multilingual PMC enemies + //determine what language the multilingual PMC will speak + sMultiLang = ""; + iRand = RandomIntrange( 1, 4 ); + if ( iRand == 1 ) + sMultiLang = "german"; + else if ( iRand == 2 ) + sMultiLang = "italian"; + else + sMultiLang = "spanish"; + + return sMultiLang; +} +*/ + +forceEnglish() +{ + if ( !getDvarInt( "bcs_forceEnglish", 0 ) ) + return false; + + switch( level._script ) + { + case "airlift": + case "armada": + case "bog_a": + case "bog_b": + case "launchfacility_a": + case "launchfacility_b": + case "scoutsniper": + case "sniperescape": + case "co_armada": + case "co_break": + case "co_crossfire": + case "co_hunted": + case "co_launchfacility_a": + case "co_scoutsniper": + case "co_strike": + case "pmc_strike": + case "so_ac130_co_hunted": + return true; + } + return false; +} + +// semi hackish way to make large numbers of ai spawning take less time +aiThreadThreader() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + waitTime = 0.5; + + wait( waitTime ); + self thread aiGrenadeDangerWaiter(); + self thread aiFollowOrderWaiter(); + + if ( self.team == "allies" ) + { + wait( waitTime ); + + self thread aiDisplaceWaiter(); + } + else if( ( self.team == "axis" || self.team == "team3" ) && !isAlliedCountryID( self.countryID ) ) + { + self thread aiHostileBurstLoop(); + } + + if( self.team == level._player.team ) + { + self thread player_friendlyfire_waiter(); + } + + wait( waitTime ); + self thread aiBattleChatterLoop(); +} + +isAlliedCountryID( id ) +{ + if( id == "UK" || id == "US" || id == "NS" || id == "TF" || id == "SS" ) + { + return true; + } + + return false; +} + +setNPCID() +{ + //prof_begin("setNPCID"); + assert( !isDefined( self.npcID ) ); + + usedIDs = anim.usedIDs[ self.voice ]; + numIDs = usedIDs.size; + + startIndex = randomIntRange( 0, numIDs ); + + lowestID = startIndex; + for ( index = 0; index <= numIDs; index++ ) + { + if ( usedIDs[ ( startIndex + index )%numIDs ].count < usedIDs[ lowestID ].count ) + lowestID = ( startIndex + index ) % numIDs; + } + + self thread npcIDTracker( lowestID ); + self.npcID = usedIDs[ lowestID ].npcID; + //prof_end("setNPCID"); +} + + +npcIDTracker( lowestID ) +{ +// self endon ("removed from battleChatter"); + + anim.usedIDs[ self.voice ][ lowestID ].count++ ; + self waittill( "death" ); + if ( !bcsEnabled() ) + return; + + anim.usedIDs[ self.voice ][ lowestID ].count-- ; +} + +aiHostileBurstLoop() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while( 1 ) + { + if( Distance( self.origin, level._player.origin ) < 1024 ) + { + // don't burst unless there's at least one other guy to hear you + if( IsDefined( self.squad.memberCount ) && self.squad.memberCount > 1 ) + { + self addReactionEvent( "taunt", "hostileburst" ); + } + } + + wait( RandomFloatRange( 2, 5 ) ); + } +} + +aiBattleChatterLoop() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while ( true ) + { + //prof_begin( "aiBattleChatterLoop" ); + self playBattleChatter(); + //prof_end( "aiBattleChatterLoop" ); + + wait( 0.3 + randomfloat( 0.2 ) ); + } +} + +aiNameAndRankWaiter() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while ( 1 ) + { + self.bcName = self animscripts\battlechatter::getName(); + self.bcRank = self animscripts\battlechatter::getRank(); + self waittill( "set name and rank" ); + } + +} + +removeFromSystem( squadName ) +{ + if ( !IsAlive( self ) && bcsEnabled() ) + { + self aiDeathFriendly(); + self aiDeathEnemy(); + } + + if ( IsDefined( self ) ) + { + self.battleChatter = false; + self.chatInitialized = false; + } + + self notify( "removed from battleChatter" ); + + if ( IsDefined( self ) ) + { + self.chatQueue = undefined; + self.nextSayTime = undefined; + self.nextSayTimes = undefined; + self.isSpeaking = undefined; + self.enemyClass = undefined; + self.calledOut = undefined; + self.countryID = undefined; + self.npcID = undefined; + } +} + +init_aiBattleChatter() +{ + //prof_begin("init_aiBattleChatter"); + self.chatQueue = []; + self.chatQueue[ "threat" ] = spawnstruct(); + self.chatQueue[ "threat" ].expireTime = 0; + self.chatQueue[ "threat" ].priority = 0.0; + self.chatQueue[ "response" ] = spawnstruct(); + self.chatQueue[ "response" ].expireTime = 0; + self.chatQueue[ "response" ].priority = 0.0; + self.chatQueue[ "reaction" ] = spawnstruct(); + self.chatQueue[ "reaction" ].expireTime = 0; + self.chatQueue[ "reaction" ].priority = 0.0; + self.chatQueue[ "inform" ] = spawnstruct(); + self.chatQueue[ "inform" ].expireTime = 0; + self.chatQueue[ "inform" ].priority = 0.0; + self.chatQueue[ "order" ] = spawnstruct(); + self.chatQueue[ "order" ].expireTime = 0; + self.chatQueue[ "order" ].priority = 0.0; + self.chatQueue[ "custom" ] = spawnstruct(); + self.chatQueue[ "custom" ].expireTime = 0; + self.chatQueue[ "custom" ].priority = 0.0; + + self.nextSayTime = getTime() + 50; + self.nextSayTimes[ "threat" ] = 0; + self.nextSayTimes[ "reaction" ] = 0; + self.nextSayTimes[ "response" ] = 0; + self.nextSayTimes[ "inform" ] = 0; + self.nextSayTimes[ "order" ] = 0; + self.nextSayTimes[ "custom" ] = 0; + + self.isSpeaking = false; + self.bcs_minPriority = 0.0; + + /*-------- ALLOWED THREAT CALLOUTS -------- + Here we set up the types of threat callouts that this AI is allowed to use. + - these should always match the values that index the anim.threatCallouts[] array, + which is set up in battlechatter::init_battleChatter() + ------------------------------------------*/ + self.allowedCallouts = []; + + // global + self addAllowedThreatCallout( "rpg" ); + self addAllowedThreatCallout( "exposed" ); + + // shadow company doesn't do these kinds of callouts + if( self.voice != "shadowcompany" ) + { + self addAllowedThreatCallout( "ai_contact_clock" ); + self addAllowedThreatCallout( "ai_target_clock" ); + self addAllowedThreatCallout( "ai_cardinal" ); + } + + // allies only + if( self.team == "allies" ) + { + self addAllowedThreatCallout( "ai_yourclock" ); + self addAllowedThreatCallout( "player_yourclock" ); + self addAllowedThreatCallout( "player_contact_clock" ); + self addAllowedThreatCallout( "player_target_clock" ); + self addAllowedThreatCallout( "player_cardinal" ); + self addAllowedThreatCallout( "player_obvious" ); + self addAllowedThreatCallout( "player_object_yourclock" ); + self addAllowedThreatCallout( "ai_object_yourclock" ); + self addAllowedThreatCallout( "player_object_clock" ); + self addAllowedThreatCallout( "player_location" ); + self addAllowedThreatCallout( "ai_location" ); + } + + if ( IsDefined( self.script_battlechatter ) && !self.script_battlechatter ) + { + self.battleChatter = false; + } + else + { + self.battleChatter = level._battlechatter[ self.team ]; + } + + if( self voiceCanBurst() ) + { + self.flavorbursts = true; + } + else + { + self.flavorbursts = false; + } + + // doesn't impact friendlyfire warnings normally played when battlechatter is on, + // just whether it plays when battlechatter is otherwise turned off + if( level._friendlyfire_warnings ) + { + self set_friendlyfire_warnings( true ); + } + else + { + self set_friendlyfire_warnings( false ); + } + + self.chatInitialized = true; + //prof_end("init_aiBattleChatter"); +} + +/**************************************************************************** + ai event queue +*****************************************************************************/ + +// checks if it's safe to add a threat callout to this AIs queue +canAddThreatEvent( eventType, threat, priority ) +{ + ASSERTEX( IsDefined( eventType ), "addThreatEvent called with undefined eventType" ); + + if ( !self canSay( "threat", eventType, priority ) ) + { + return false; + } + + // check if the threat has already been called out by someone in our squad + if( threatWasAlreadyCalledOut( threat ) && !IsPlayer( threat ) ) + { + return false; + } + + return true; +} + +// adds a threat callout to this AIs queue without checking its validity +addThreatEventFast( eventType, threat, priority ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + chatEvent = self createChatEvent( "threat", eventType, priority ); + + switch( eventType ) + { + case "infantry": + chatEvent.threat = threat; + break; + /* + case "emplacement": + chatEvent.threat = threat; + break; + case "vehicle": + chatEvent.threat = threat; + break; + */ + } + + if( IsDefined( threat.squad ) ) + { + self.squad updateContact( threat.squad.squadName, self ); + } + + self.chatQueue[ "threat" ] = undefined; + self.chatQueue[ "threat" ] = chatEvent; +} + +// safely adds a threat callout to this AIs queue +addThreatEvent( eventType, threat, priority ) +{ + if ( self canAddThreatEvent( eventType, threat, priority )) + { + self addThreatEventFast( eventType, threat, priority ); + } +} + +// adds a response to this AIs queue +// reportAlias = in the case of a report/echo situation, this is the alias +// that the reporter used, and will have a specifically corresponding "echo" alias +// location = for QA situations, so we have the location trigger object +addResponseEvent( eventType, modifier, respondTo, priority, reportAlias, location ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self thread addResponseEvent_internal( eventType, modifier, respondTo, priority, reportAlias, location ); +} + +addResponseEvent_internal( eventType, modifier, respondTo, priority, reportAlias, location ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + self endon( "responseEvent_failsafe" ); + + // wait until respondTo is done talking + self thread responseEvent_failSafe( respondTo ); + message = respondTo waittill_any_return( "death", "done speaking", "cancel speaking" ); + + if ( message == "cancel speaking" ) + { + return; + } + + if ( !IsAlive( respondTo ) ) + { + return; + } + + if ( !self canSay( "response", eventType, priority, modifier ) ) + { + return; + } + + if ( !IsPlayer( respondTo ) ) + { + // make sure that we don't respond in the same voice + if( self isUsingSameVoice( respondTo ) ) + { + return; + } + } + + chatEvent = self createChatEvent( "response", eventType, priority ); + + if( IsDefined( reportAlias ) ) + { + chatEvent.reportAlias = reportAlias; + } + + if( IsDefined( location ) ) + { + chatEvent.location = location; + } + + chatEvent.respondTo = respondTo; + chatEvent.modifier = modifier; + + self.chatQueue[ "response" ] = undefined; + self.chatQueue[ "response" ] = chatEvent; +} + +responseEvent_failSafe( respondTo ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + respondTo endon( "death" ); + respondTo endon( "done speaking" ); + respondTo endon( "cancel speaking" ); + + wait( 25 ); + self notify( "responseEvent_failsafe" ); +} + +// adds a informative callout to this AIs queue +addInformEvent( eventType, modifier, informTo, priority ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !self canSay( "inform", eventType, priority, modifier ) ) + { + return; + } + + chatEvent = self createChatEvent( "inform", eventType, priority ); + + switch( eventType ) + { + case "reloading": + chatEvent.modifier = modifier; + chatEvent.informTo = informTo; + break; + default: + chatEvent.modifier = modifier; + } + + self.chatQueue[ "inform" ] = undefined; + self.chatQueue[ "inform" ] = chatEvent; +} + +// adds a response to this AIs queue +addReactionEvent( eventType, modifier, reactTo, priority ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + /* + if ( !self canSay( "reaction", eventType, priority, modifier ) ) + { + return; + } + */ + if ( !isdefined( self.chatQueue ) ) + return; + + chatEvent = self createChatEvent( "reaction", eventType, priority ); + + chatEvent.reactTo = reactTo; + chatEvent.modifier = modifier; + + + self.chatQueue[ "reaction" ] = undefined; + self.chatQueue[ "reaction" ] = chatEvent; +} + +// adds an order to this AIs queue +addOrderEvent( eventType, modifier, orderTo, priority ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !self canSay( "order", eventType, priority, modifier ) ) + { + return; + } + + if ( IsDefined( orderTo ) && orderTo.type == "dog" ) + { + return; + } + + chatEvent = self createChatEvent( "order", eventType, priority ); + + chatEvent.modifier = modifier; + chatEvent.orderTo = orderTo; + + self.chatQueue[ "order" ] = undefined; + self.chatQueue[ "order" ] = chatEvent; +} + +/**************************************************************************** + ai trackers/waiters +*****************************************************************************/ + +squadOfficerWaiter() +{ + anim endon( "battlechatter disabled" ); + anim endon( "squad deleted " + self.squadName ); + + while ( 1 ) + { + officer = undefined; + + if ( self.officers.size ) + members = self.officers; + else + members = self.members; + + officers = []; + for ( index = 0; index < members.size; index++ ) + { + if ( isalive( members[ index ] ) ) + officers[ officers.size ] = members[ index ]; + } + + if ( officers.size ) + { + officer = getClosest( level._player.origin, officers ); + officer aiOfficerOrders(); + officer waittill( "death" ); + } + + wait( 3.0 ); + } +} + + +getThreats( potentialThreats ) +{ + //prof_begin( "getThreats" ); + threats = []; + for ( i = 0; i < potentialThreats.size; i++ ) + { + if ( !IsDefined( potentialThreats[ i ].enemyClass ) ) + { + continue; + } + + if( !threatIsViable( potentialThreats[i] ) ) + { + continue; + } + + potentialThreats[ i ].threatID = threats.size; + threats[ threats.size ] = potentialThreats[ i ]; + } + + // sort by distance from the player + threats = SortByDistance( threats, level._player.origin ); + + // deliver guys in locational triggers first + haveLocs = []; + noLocs = []; + foreach( threat in threats ) + { + location = threat GetLocation(); + if( IsDefined( location ) && !location_called_out_recently( location ) ) + { + haveLocs[ haveLocs.size ] = threat; + } + else + { + noLocs[ noLocs.size ] = threat; + } + } + + // array_combine adds the first argument to the returned array first + threats = array_combine( haveLocs, noLocs ); + + return( threats ); +} + +threatIsViable( threat ) +{ + if( !level._player entInFrontArc( threat ) ) + { + return false; + } + + if( DistanceSquared( level._player.origin, threat.origin ) > level._bcs_maxThreatDistFromPlayerSqr ) + { + return false; + } + + return true; +} + +squadThreatWaiter() +{ + anim endon( "battlechatter disabled" ); + anim endon( "squad deleted " + self.squadName ); + + while ( 1 ) + { + wait( RandomFloatRange( 0.25, 0.75 )); + + //prof_begin("squadThreatWaiter"); + + if ( self.team == "allies" ) + { + validEnemies = getThreats( GetAIArray( "axis", "team3" )); + } + else if ( self.team == "team3" ) + { + validEnemies = getThreats( GetAIArray( "allies", "axis" )); + } + else + { + validEnemies = GetAIArray( "allies", "team3" ); + validEnemies[ validEnemies.size ] = level._player; + } + + if ( !validEnemies.size ) + { + continue; + } + + addedEnemies = []; + trace_count = 0; + foreach( i, member in self.members ) + { + if ( !IsAlive( member )) + { + continue; + } + + if ( !validEnemies.size ) + { + validEnemies = addedEnemies; + addedEnemies = []; + } + + foreach( j, enemy in validEnemies ) + { + + if ( trace_count > 0 ) + { + trace_count = 0; + //prof_end("squadThreatWaiter"); + wait 0.05; + //prof_begin("squadThreatWaiter"); + } + + if ( !isAlive( member ) ) + { + break; + } + + if ( !IsDefined( enemy )) + { + if ( j == 0 ) + { + validEnemies = []; + } + + continue; + } + + if ( !IsAlive( enemy )) + { + continue; + } + + // Check if this event is even safe to be queued up before + // any expensive trace checks. + if ( !member canAddThreatEvent( enemy.enemyClass, enemy ) ) + { + continue; + } + + trace_count++; // CanSee does 0-1 SightTraces. Might be cached. Count as 1. + if( !member CanSee( enemy ) ) + { + if ( IsPlayer( enemy )) + { + continue; + } + + if ( enemy.team == level._player.team ) + { + continue; + } + + // we want enemies that the player can see to get called out even if other team members can't see them + trace_count = trace_count + 2; // player_can_see_ai does 0-2 SightTraces, count as two + if ( !player_can_see_ai( enemy )) + { + continue; + } + } + + // We can call the "unsafe" fast version of "addThreatEvent()" here since + // we check the validity of the request above. + member addThreatEventFast( enemy.enemyClass, enemy ); + + addedEnemies[ addedEnemies.size ] = enemy; + validEnemies = array_remove( validEnemies, enemy ); + + break; + } + + trace_count = 0; + wait( 0.05 ); + } + //prof_end("squadThreatWaiter"); + } +} + +aiDeathFriendly() +{ + attacker = self.attacker; + + // reaction event + array_thread( self.squad.members, ::aiDeathEventThread ); + + // if the guy who killed him is a regular AI, call him out if we can + if ( IsAlive( attacker ) && IsSentient( attacker ) && IsDefined( attacker.squad ) && attacker.battleChatter ) + { + // reset this guy's calledOut status since he's dangerous again + if ( IsDefined( attacker.calledOut[ attacker.squad.squadName ] ) ) + { + attacker.calledOut[ attacker.squad.squadName ] = undefined; + } + + // only infantry do this + if ( !IsDefined( attacker.enemyClass ) ) + { + return; + } + + // only if the attacker is in a location we can talk about + if ( !attacker is_in_callable_location() ) + { + return; + } + + foreach( member in self.squad.members ) + { + // make sure we've seen someone lately + if ( GetTime() > ( member.lastEnemySightTime + 2000 ) ) + { + continue; + } + + // re-add this attacker as a threat + member addThreatEvent( attacker.enemyClass, attacker ); + } + } +} + +aiDeathEventThread() +{ + if( !IsAlive( self ) ) + { + return; + } + + self endon( "death" ); + self endon( "removed from battleChatter" ); + + wait( 1.5 ); + self addReactionEvent( "casualty", "generic", self, 0.9 ); +} + +aiDeathEnemy() +{ + attacker = self.attacker; + + if ( !IsAlive( attacker ) || !IsSentient( attacker ) || !IsDefined( attacker.squad ) ) + { + return; + } + + // only SEALs get to do killfirms + if( !IsDefined( attacker.countryID ) || attacker.countryID != "NS" ) + { + return; + } + + if ( !IsPlayer( attacker ) ) + { + // attacker says "got one" or something similar + attacker thread aiKillEventThread(); + } +} + +aiKillEventThread() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + wait( 1.5 ); + self addInformEvent( "killfirm", "generic" ); +} + +aiOfficerOrders() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !isdefined( self.squad.chatInitialized ) ) + self.squad waittill( "squad chat initialized" ); + + while ( 1 ) + { + if ( getdvar( "bcs_enable", "on" ) == "off" ) + { + wait( 1.0 ); + continue; + } + + self addSituationalOrder(); + + wait( RandomFloatRange( 3.0, 6.0 ) ); + } +} + +aiGrenadeDangerWaiter() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while ( 1 ) + { + self waittill( "grenade danger", grenade ); + + if ( getdvar( "bcs_enable", "on" ) == "off" ) + continue; + + if ( !isdefined( grenade ) || grenade.model != "projectile_m67fraggrenade" ) + continue; + + if ( distance( grenade.origin, level._player.origin ) < 512 )// grenade radius is 220 + self addInformEvent( "incoming", "grenade" ); + } +} + +aiDisplaceWaiter() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while( true ) + { + self waittill( "trigger" ); + + if ( getdvar( "bcs_enable", "on" ) == "off" ) + continue; + + // no acknowledgement if you just took pain, looks dumb + if ( GetTime() < self.a.painTime + 4000 ) + { + continue; + } + + self addResponseEvent( "ack", "yes", level._player, 1.0 ); + } +} + +evaluateMoveEvent( wasInCover ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !bcsEnabled() ) + { + return; + } + + if ( !IsDefined( self.node ) ) + { + return; + } + + dist = Distance( self.origin, self.node.origin ); + + // it looks silly to have an order for a short distance + if ( dist < 512 ) + { + return; + } + + if ( !self isNodeCoverOrConceal() ) + { + return; + } + + if( !self nationalityOkForMoveOrder() ) + { + return; + } + + // figure out who to talk to + responder = self getResponder( 24, 1024, "response" ); + + if( self.team != "axis" && self.team != "team3" ) + { + if( !IsDefined( responder ) ) + { + responder = level._player; + } + // if we do have a responder, sometimes we want to pick the player anyway, for variety + else + { + if( RandomInt( 100 ) < anim.eventChance[ "moveEvent" ][ "ordertoplayer" ] ) + { + responder = level._player; + } + } + } + + // if we're in combat... + if( self.combatTime > 0.0 ) + { + if( RandomInt( 100 ) < anim.eventChance[ "moveEvent" ][ "coverme" ] ) + { + self addOrderEvent( "action", "coverme", responder ); + } + // sometimes we do a different kind of order + else + { + self addOrderEvent( "move", "combat", responder ); + } + } + else + { + if( self nationalityOkForMoveOrderNoncombat() ) + { + self addOrderEvent( "move", "noncombat", responder ); + } + } +} + +nationalityOkForMoveOrder() +{ + // secretservice do not talk about move events + if( self.countryID == "SS" ) + { + return false; + } + + return true; +} + +nationalityOkForMoveOrderNoncombat() +{ + // only Marines do noncombat move orders + if( self.countryID == "US" ) + { + return true; + } + + return false; +} + +aiFollowOrderWaiter() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while ( true ) + { + level waittill( "follow order", speaker ); + + if ( !bcsEnabled() ) + return; + + if ( speaker.team != self.team ) + continue; + + if ( distance( self.origin, speaker.origin ) < 600 ) + { + self addResponseEvent( "ack", "yes", speaker, 0.9 ); + } + } +} + +// waits/reacts to the player shooting near the friendlies +player_friendlyfire_waiter() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + self thread player_friendlyfire_waiter_damage(); + + while( 1 ) + { + self waittill( "bulletwhizby", shooter, whizByDist ); + + if( !bcsEnabled() ) + { + continue; + } + + if( !IsPlayer( shooter ) ) + { + continue; + } + + if( self friendlyfire_whizby_distances_valid( shooter, whizbyDist ) ) + { + self player_friendlyfire_addReactionEvent(); + wait( 3 ); + } + } +} + +player_friendlyfire_addReactionEvent() +{ + self addReactionEvent( "friendlyfire", undefined, level._player, 1.0 ); +} + +// player damaging friendly should always get noticed +player_friendlyfire_waiter_damage() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + while( 1 ) + { + self waittill( "damage", amount, attacker, direction_vec, point, type ); + + if( IsDefined( attacker ) && IsPlayer( attacker ) ) + { + if( damage_is_valid_for_friendlyfire_warning( type ) ) + { + self player_friendlyfire_addReactionEvent(); + } + } + } +} + +damage_is_valid_for_friendlyfire_warning( type ) +{ + if( !IsDefined( type ) ) + { + return false; + } + + switch( type ) + { + case "MOD_MELEE": + case "MOD_GRENADE": + case "MOD_GRENADE_SPLASH": + case "MOD_CRUSH": + case "MOD_IMPACT": + return false; + } + + return true; +} + +friendlyfire_whizby_distances_valid( shooter, whizbyDist ) +{ + minDistFromAI = 256 * 256; + maxWhizbyDist = 42; + + if( DistanceSquared( shooter.origin, self.origin ) < minDistFromAI ) + { + return false; + } + + if( whizbyDist > maxWhizbyDist ) + { + return false; + } + + return true; +} + +evaluateReloadEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !bcsEnabled() ) + { + return; + } + + self addInformEvent( "reloading", "generic" ); +} + +// doesn't do anything atm, it's a good hook for melee events though +evaluateMeleeEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !bcsEnabled() ) + return( false ); + + if ( !isdefined( self.enemy ) ) + return( false ); + +// self addReactionEvent("taunt", "generic", self.enemy); + +// return (true); + return( false ); +} + +evaluateFiringEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !bcsEnabled() ) + return; + + if ( !isdefined( self.enemy ) ) + return; + +// if (distance(self.origin, self.enemy.origin) > 384) +// self addReactionEvent("taunt", "generic", self.enemy, 0.4); +} + +evaluateSuppressionEvent() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !bcsEnabled() ) + return; + + if ( !self.suppressed ) + return; + + self addInformEvent( "suppressed", "generic" ); +} + +evaluateAttackEvent( type ) +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( !bcsEnabled() ) + { + return; + } + + ASSERTEX( IsDefined( type ), "Grenade type [self.grenadeWeapon] thrown is undefined!" ); + + // just do frag callouts for all kinds of grenades + self addInformEvent( "attack", "grenade" ); + + /* + switch( type ) + { + case "flash_grenade": + self addInformEvent( "attack", "flash" ); + break; + default: + self addInformEvent( "attack", "grenade" ); + return; + } + */ +} + +addSituationalOrder() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + if ( self.squad.squadStates[ "combat" ].isActive ) + { + self addSituationalCombatOrder(); + } +} + +addSituationalCombatOrder() +{ + self endon( "death" ); + self endon( "removed from battleChatter" ); + + squad = self.squad; + squad animscripts\squadmanager::updateStates(); + + if ( squad.squadStates[ "suppressed" ].isActive ) + { + if ( squad.squadStates[ "cover" ].isActive ) + { + responder = self getResponder( 96, 512, "response" ); + self addOrderEvent( "action", "grenade", responder ); + } + else + { + self addOrderEvent( "displace", "generic" ); + } + } + else if ( squad.squadStates[ "combat" ].isActive ) + { + // secretservice don't do suppress orders + if( self.countryID != "SS" ) + { + responder = self getResponder( 24, 1024, "response" ); + self addOrderEvent( "action", "suppress", responder ); + } + } +} + + +/**************************************************************************** + custom battlechatter event functions +*****************************************************************************/ + +custom_battlechatter_init_valid_phrases() +{ + // when this list changes, update the documentation in + // _utility::custom_battlechatter to reflect it! + phrases = []; + phrases[ phrases.size ] = "order_move_combat"; // "Move move move!" + phrases[ phrases.size ] = "order_move_noncombat"; // "Move out." + phrases[ phrases.size ] = "order_action_coverme"; // "Covering fire!" + phrases[ phrases.size ] = "inform_reloading"; // "Reloading!" + + level._customBCS_validPhrases = phrases; +} + +custom_battlechatter_validate_phrase( string ) +{ + foundIt = false; + + foreach( phrase in level._customBCS_validPhrases ) + { + if( phrase == string ) + { + foundIt = true; + break; + } + } + + return foundIt; +} + +custom_battlechatter_internal( string ) +{ + if ( !IsDefined( level._customBcs_validPhrases ) ) + { + custom_battlechatter_init_valid_phrases(); + } + + string = tolower( string ); + + phraseInvalidStr = anim.bcPrintFailPrefix + "custom battlechatter phrase '" + string + "' isn't valid. look at _utility::custom_battlechatter_init_valid_phrases(), or the util script documentation for custom_battlechatter(), for a list of valid phrases."; + badCountryIdStr = anim.bcPrintFailPrefix + "AI at origin " + self.origin + "wasn't able to play custom battlechatter because his nationality is '" + self.countryID + "'."; + + if( !custom_battlechatter_validate_phrase( string ) ) + { + ASSERTMSG( phraseInvalidStr ); + return false; + } + + responder = self getResponder( 24, 512, "response" ); + + self beginCustomEvent(); + + switch( string ) + { + case "order_move_combat": + if( !self nationalityOkForMoveOrder() ) + { + /# + println( badCountryIdStr ); + #/ + return false; + } + + self tryOrderTo( self.customChatPhrase, responder ); + self addMoveCombatAliasEx(); + break; + + case "order_move_noncombat": + if( !self nationalityOkForMoveOrderNoncombat() ) + { + /# + println( badCountryIdStr ); + #/ + return false; + } + + self addMoveNoncombatAliasEx(); + break; + + case "order_action_coverme": + self tryOrderTo( self.customChatPhrase, responder ); + self addActionCovermeAliasEx(); + break; + + case "inform_reloading": + self addInformReloadingAliasEx(); + break; + + default: + // we validated this already, so we shouldn't ever get here + ASSERTMSG( phraseInvalidStr ); + return false; + } + + self endCustomEvent( 2000 ); + + return true; +} + +beginCustomEvent() +{ + if ( !bcsEnabled() ) + return; + + self.customChatPhrase = createChatPhrase(); +} + +addActionCovermeAliasEx() +{ + self.customChatPhrase addOrderAlias( "action", "coverme" ); +} + +addMoveCombatAliasEx() +{ + self.customChatPhrase addOrderAlias( "move", "combat" ); +} + +addMoveNoncombatAliasEx() +{ + self.customChatPhrase addOrderAlias( "move", "noncombat" ); +} + +addInformReloadingAliasEx() +{ + self.customChatPhrase addInformAlias( "reloading", "generic" ); +} + +addNameAliasEx( name ) +{ + if ( !bcsEnabled() ) + return; + + self.customChatPhrase addNameAlias( name ); +} + +endCustomEvent( eventDuration, typeOverride ) +{ + if ( !bcsEnabled() ) + return; + + chatEvent = self createChatEvent( "custom", "generic", 1.0 ); + if ( isdefined( eventDuration ) ) + chatEvent.expireTime = gettime() + eventDuration; + + if ( isDefined( typeOverride ) ) + chatEvent.type = typeOverride; + else + chatEvent.type = "custom"; + + self.chatQueue[ "custom" ] = undefined; + self.chatQueue[ "custom" ] = chatEvent; +} diff --git a/animscripts/civilian.gsc b/animscripts/civilian.gsc new file mode 100644 index 0000000..070d939 --- /dev/null +++ b/animscripts/civilian.gsc @@ -0,0 +1,58 @@ +#include common_scripts\utility; +#include maps\_utility; +#include maps\_anim; +#include animscripts\shared; + +#using_animtree( "generic_human" ); + +cover() +{ + self endon( "killanimscript" ); + + self clearanim( %root, 0.2 ); + + if ( self animscripts\utility::IsInCombat() ) + situation = "idle_combat"; + else + situation = "idle_noncombat"; + + idle_array = undefined; + if ( isdefined( self.animname ) && isdefined( level._scr_anim[ self.animname ] ) ) + idle_array = level._scr_anim[ self.animname ][ situation ]; + + if ( !isdefined( idle_array ) ) + { + if ( !isdefined( level._scr_anim[ "default_civilian" ] ) ) + return; + + idle_array = level._scr_anim[ "default_civilian" ][ situation ]; + } + + thread move_check(); + + for ( ;; ) + { + self setflaggedanimknoball( "idle", random( idle_array ), %root, 1, 0.2, 1 ); + self waittillmatch( "idle", "end" ); + } +} + +move_check() +{ + self endon( "killanimscript" ); + + while ( !isdefined( self.champion ) ) + { + wait( 1 ); + } +} + +stop() +{ + cover(); +} + +get_flashed_anim() +{ + return anim.civilianFlashedArray[ randomint( anim.civilianFlashedArray.size ) ]; +} diff --git a/animscripts/civilian/civilian_flashed.gsc b/animscripts/civilian/civilian_flashed.gsc new file mode 100644 index 0000000..98e1a9f --- /dev/null +++ b/animscripts/civilian/civilian_flashed.gsc @@ -0,0 +1,19 @@ +#include common_scripts\utility; +#include maps\_utility; +#include maps\_anim; +#include animscripts\shared; + + +get_flashed_anim() +{ + return anim.civilianFlashedArray[ randomint( anim.civilianFlashedArray.size ) ]; +} + +main() +{ + flashDuration = self flashBangGetTimeLeftSec(); + if ( flashDuration <= 0 ) + return; + + animscripts\flashed::flashBangedLoop( get_flashed_anim(), flashDuration ); +} \ No newline at end of file diff --git a/animscripts/civilian/civilian_init.gsc b/animscripts/civilian/civilian_init.gsc new file mode 100644 index 0000000..946980a --- /dev/null +++ b/animscripts/civilian/civilian_init.gsc @@ -0,0 +1,74 @@ +#include common_scripts\utility; +#include maps\_utility; +#include maps\_anim; +#include animscripts\shared; + + +#using_animtree( "generic_human" ); + +main() +{ + animscripts\init::main(); + + if ( !isdefined( level._initialized_civilian_animations ) ) + { + initCivilianAnims(); + } + + animscripts\civilian\civilian_init_common::civilian_init(); +} + +initCivilianAnims() +{ + level._initialized_civilian_animations = true; + + + //put the default ones here: + level._scr_anim[ "default_civilian" ][ "run_combat" ] = [ %civilian_run_upright ]; + level._scr_anim[ "default_civilian" ][ "run_hunched_combat" ] = [ %civilian_run_hunched_A, %civilian_run_hunched_C, %civilian_run_hunched_flinch ]; + + level._scr_anim[ "default_civilian" ][ "run_noncombat" ] = [ %civilian_walk_cool ]; + + level._scr_anim[ "default_civilian" ][ "run_hunched_weights" ] = get_cumulative_weights( [3, 3, 1] ); + + level._scr_anim[ "default_civilian" ][ "run_weights" ] = get_cumulative_weights( [ 1 ] ); + level._scr_anim[ "default_civilian" ][ "idle_noncombat" ] = [ %unarmed_cowerstand_idle ]; + level._scr_anim[ "default_civilian" ][ "idle_combat" ] = [ %casual_crouch_v2_idle, %unarmed_cowercrouch_idle_duck ]; + + level._scr_anim[ "default_civilian" ][ "dodge_left" ] = %civilian_briefcase_walk_dodge_L; + level._scr_anim[ "default_civilian" ][ "dodge_right" ] = %civilian_briefcase_walk_dodge_R; + + level._scr_anim[ "default_civilian" ][ "react_stumble" ] = [ %run_react_stumble ]; + + //7 9 <- 8 is invalid, it's straight. + //4 6 <- 5 is invalid, it's not a turn. + //1 2 3 + level._scr_anim[ "default_civilian"]["run_combat_turn"][1] = %civilian_run_upright_turnL135; + level._scr_anim[ "default_civilian"]["run_combat_turn"][2] = %civilian_run_upright_turn180; + level._scr_anim[ "default_civilian"]["run_combat_turn"][3] = %civilian_run_upright_turnR135; + level._scr_anim[ "default_civilian"]["run_combat_turn"][4] = %civilian_run_upright_turnL90; + level._scr_anim[ "default_civilian"]["run_combat_turn"][6] = %civilian_run_upright_turnR90; + level._scr_anim[ "default_civilian"]["run_combat_turn"][7] = %civilian_run_upright_turnL45; + level._scr_anim[ "default_civilian"]["run_combat_turn"][9] = %civilian_run_upright_turnR45; + + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][1] = %civilian_run_upright_turnL135; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][2] = %civilian_run_upright_turn180; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][3] = %civilian_run_upright_turnR135; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][4] = %civilian_run_hunched_turnL90; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][6] = %civilian_run_hunched_turnR90; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][7] = %civilian_run_hunched_turnL45; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][9] = %civilian_run_hunched_turnR45; + + //a more dramatic version. Randomly chosen from. + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][1] = []; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][2] = []; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][3] = []; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][4] = [ %civilian_run_hunched_turnL90_slide, %civilian_run_hunched_turnL90_stumble ]; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][6] = [ %civilian_run_hunched_turnR90_slide, %civilian_run_hunched_turnR90_stumble ]; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][7] = []; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][9] = []; + + //TagCC: not happy with this, but we don't have flashes around civilians on the moon, + //so I'm hesistent to mess with this just yet. + anim.civilianFlashedArray = [ %unarmed_cowerstand_react, %unarmed_cowercrouch_react_A, %unarmed_cowercrouch_react_B ]; +} \ No newline at end of file diff --git a/animscripts/civilian/civilian_init_common.gsc b/animscripts/civilian/civilian_init_common.gsc new file mode 100644 index 0000000..82b2d34 --- /dev/null +++ b/animscripts/civilian/civilian_init_common.gsc @@ -0,0 +1,229 @@ +#include common_scripts\utility; +#include maps\_utility; +#include maps\_anim; +#include animscripts\shared; + + +#using_animtree( "generic_human" ); + +civilian_init() +{ + self.allowdeath = 1; + self.disablearrivals = true; + self.disableexits = true; + self.neverEnableCQB = true; + self.alwaysRunForward = true; + self OrientMode( "face default" ); + self.combatmode = "no_cover"; + self.pushplayer = false; + self pushplayer( false ); + self.a.reactToBulletChance = 1; + + + + // set the civilians animname to use defaults, or specific group if specified in radiant + animName = undefined; + if ( isdefined( self.civilian_walk_animation ) ) + { + self.animname = self.civilian_walk_animation; + self attachProps( self.civilian_walk_animation ); + self.alertLevel = "noncombat"; + startNonCombat(); + } + else + { + self.animname = "default_civilian"; + self.alertLevel = "alert"; + startCombat(); + } + + self thread checkCombatState(); + + // Make sure all required anims exist for this civilian, or set some defaults if they weren't specified + assert( isdefined( level._scr_anim[ self.animname ][ "run_noncombat" ] ) ); + + self.dropWeapon = false; + self DropAIWeapon(); + self.saved = false; +} + +attachProps( anime ) +{ + if ( isdefined( self.hasAttachedProps ) ) + return; + + initCivilianProps(); + + prop_model = anim.civilianProps[ anime ]; + + if ( isdefined( prop_model ) ) + { + self attach( prop_model, "tag_inhand", true ); + self.hasAttachedProps = true; + } +} + +detachProps( anime ) +{ + if ( isdefined( self.hasAttachedProps ) ) + { + self.hasAttachedProps = undefined; + self detach( anim.civilianProps[ anime ], "tag_inhand" ); + } +} + +initCivilianProps() +{ + if ( isdefined( anim.civilianProps ) ) + return; + + anim.civilianProps = []; + anim.civilianProps[ "civilian_briefcase_walk" ] = "com_metal_briefcase"; + anim.civilianProps[ "civilian_crazy_walk" ] = "electronics_pda"; + anim.civilianProps[ "civilian_cellphone_walk" ] = "com_cellphone_on"; + anim.civilianProps[ "sit_lunch_A" ] = "com_cellphone_on"; + anim.civilianProps[ "civilian_soda_walk" ] = "ma_cup_single_closed"; + anim.civilianProps[ "civilian_paper_walk" ] = "paper_memo"; + anim.civilianProps[ "civilian_coffee_walk" ] = "cs_coffeemug02"; + anim.civilianProps[ "civilian_pda_walk" ] = "electronics_pda"; + anim.civilianProps[ "reading1" ] = "open_book"; + anim.civilianProps[ "reading2" ] = "open_book"; + anim.civilianProps[ "texting_stand" ] = "electronics_pda"; + anim.civilianProps[ "texting_sit" ] = "electronics_pda"; + anim.civilianProps[ "smoking1" ] = "prop_cigarette"; + anim.civilianProps[ "smoking2" ] = "prop_cigarette"; +} + + + + +startNonCombat() +{ + self.turnRate = 0.2; + + // dodge animations + if ( isdefined( self.civilian_walk_animation ) ) + { + dodgeLeft = level._scr_anim[ "default_civilian" ][ "dodge_left" ]; + dodgeRight = level._scr_anim[ "default_civilian" ][ "dodge_right" ]; + + if ( isdefined( level._scr_anim[ self.animname ][ "dodge_left" ] ) ) + dodgeLeft = level._scr_anim[ self.animname ][ "dodge_left" ]; + + if ( isdefined( level._scr_anim[ self.animname ][ "dodge_right" ] ) ) + dodgeRight = level._scr_anim[ self.animname ][ "dodge_right" ]; + + self animscripts\move::setDodgeAnims( dodgeLeft, dodgeRight ); + } + + // move turn animations + if ( isdefined( level._scr_anim[ self.animname ][ "turn_left_90" ] ) ) + { + assert( isdefined( level._scr_anim[ self.animname ][ "turn_right_90" ] ) ); + self.pathTurnAnimOverrideFunc = animscripts\civilian\civilian_move::civilian_nonCombatMoveTurn; + self.pathTurnAnimBlendTime = 0.1; + self enable_turnAnims(); + } + else + { + self disable_turnAnims(); + } + + self.run_overrideanim = level._scr_anim[ self.animname ][ "run_noncombat" ]; + self.walk_overrideanim = self.run_overrideanim; + + self.run_overrideBulletReact = undefined; + + if ( self.animname == "default_civilian" ) + { + self.run_override_weights = level._scr_anim[ self.animname ][ "run_weights_noncombat" ]; + self.walk_override_weights = self.run_override_weights; + } +} + +startCombat() +{ + self notify( "combat" ); + + self animscripts\move::clearDodgeAnims(); + + self.pathTurnAnimBlendTime = undefined; + self enable_turnAnims(); + + self.turnRate = 0.3; + + standing_run = randomint( 3 ) < 1; + if ( isdefined( self.force_civilian_stand_run ) ) + { + standing_run = true; + } + else + if ( isdefined( self.force_civilian_hunched_run ) ) + { + standing_run = false; + } + + if ( standing_run ) + { + self.pathTurnAnimOverrideFunc = animscripts\civilian\civilian_move::civilian_combatMoveTurn; + + self.run_overrideanim = level._scr_anim[ "default_civilian" ][ "run_combat" ]; + self.run_override_weights = level._scr_anim[ "default_civilian" ][ "run_weights" ]; + + //now check if this animset defines it's own anims + if ( IsDefined( level._scr_anim[ self.animname ][ "run_combat" ] ) ) + { + self.run_overrideanim = level._scr_anim[ self.animname ][ "run_combat" ]; + self.run_override_weights = level._scr_anim[ self.animname ][ "run_weights" ]; + } + } + else + { + self.pathTurnAnimOverrideFunc = animscripts\civilian\civilian_move::civilian_combatHunchedMoveTurn; + + self.run_overrideanim = level._scr_anim[ "default_civilian" ][ "run_hunched_combat" ]; + self.run_override_weights = level._scr_anim[ "default_civilian" ][ "run_hunched_weights" ]; + + //now check if this animset defines it's own anims + if ( IsDefined( level._scr_anim[ self.animname ][ "run_hunched_combat" ] ) ) + { + self.run_overrideanim = level._scr_anim[ self.animname ][ "run_hunched_combat" ]; + self.run_override_weights = level._scr_anim[ self.animname ][ "run_hunched_weights" ]; + } + } + + self.run_overrideBulletReact = level._scr_anim[ "default_civilian" ][ "react_stumble" ]; + + //now check if this animset defines it's own anims + if ( IsDefined( level._scr_anim[ self.animname ][ "react_stumble" ] ) ) + { + self.run_overrideBulletReact = level._scr_anim[ self.animname ][ "react_stumble" ]; + } + + self.walk_overrideanim = self.run_overrideanim; + self.walk_override_weights = self.run_override_weights; + + detachProps( self.civilian_walk_animation ); +} + + +checkCombatState() +{ + self endon( "death" ); + + wasInCombat = ( self.alertLevelInt > 1 ); + + while ( 1 ) + { + isInCombat = ( self.alertLevelInt > 1 ); + + if ( wasInCombat && !isInCombat ) + startNonCombat(); + else if ( !wasInCombat && isInCombat ) + startCombat(); + + wasInCombat = isInCombat; + + wait 0.05; // TEMP make this an alert level change wait when code is in. + } +} \ No newline at end of file diff --git a/animscripts/civilian/civilian_move.gsc b/animscripts/civilian/civilian_move.gsc new file mode 100644 index 0000000..a8d046a --- /dev/null +++ b/animscripts/civilian/civilian_move.gsc @@ -0,0 +1,133 @@ +#include animscripts\Utility; + +#using_animtree( "generic_human" ); + +main() +{ + animscripts\move::main(); +} + + +civilian_nonCombatMoveTurn( angleDiff ) +{ + assert( isdefined( level._scr_anim[ self.animname ][ "turn_left_90" ] ) ); + assert( isdefined( level._scr_anim[ self.animname ][ "turn_right_90" ] ) ); + + turnAnim = undefined; + + if ( angleDiff < -60 && angleDiff > -120 ) + turnAnim = level._scr_anim[ self.animname ][ "turn_left_90" ]; + + if ( angleDiff > 60 && angleDiff < 120 ) + turnAnim = level._scr_anim[ self.animname ][ "turn_right_90" ]; + + if ( isdefined( turnAnim ) && animscripts\move::pathChange_canDoTurnAnim( turnAnim ) ) + return turnAnim; + else + return undefined; +} + +//7 9 <- 8 is invalid, it's straight. +//4 6 <- 5 is invalid, it's not a turn. +//1 2 3 +civilian_combatMoveTurn( angleDiff ) +{ + turnDir = -1; + turnAnim = undefined; + + if ( angleDiff < -22.5 ) + { + if ( angleDiff > -45 ) + turnDir = 7; + else if ( angleDiff > -112.5 ) + turnDir = 4; + else if ( angleDiff > -157.5 ) + turnDir = 1; + else + turnDir = 2; + } + else if ( angleDiff > 22.5 ) + { + if ( angleDiff < 45 ) + turnDir = 9; + else if ( angleDiff < 112.5 ) + turnDir = 6; + else if ( angleDiff < 157.5 ) + turnDir = 3; + else + turnDir = 2; + } + + if ( IsDefined(level._scr_anim[ self.animname ]) + && IsDefined(level._scr_anim[ self.animname ][ "run_combat_turn" ]) + && IsDefined(level._scr_anim[ self.animname ][ "run_combat_turn" ][turnDir]) ) + { + turnAnim = level._scr_anim[ self.animname ][ "run_combat_turn" ][turnDir]; + } + + if ( IsDefined( turnAnim ) && animscripts\move::pathChange_canDoTurnAnim( turnAnim ) ) + return turnAnim; + else + return undefined; +} + + +civilian_combatHunchedMoveTurn( angleDiff ) +{ + turnDir = -1; + turnAnim = undefined; + largeTurnAnim = undefined; + + if ( angleDiff < -22.5 ) + { + if ( angleDiff > -45 ) + turnDir = 7; + else if ( angleDiff > -112.5 ) + turnDir = 4; + else if ( angleDiff > -157.5 ) + turnDir = 1; + else + turnDir = 2; + } + else if ( angleDiff > 22.5 ) + { + if ( angleDiff < 45 ) + turnDir = 9; + else if ( angleDiff < 112.5 ) + turnDir = 6; + else if ( angleDiff < 157.5 ) + turnDir = 3; + else + turnDir = 2; + } + + if ( IsDefined(level._scr_anim[ self.animname ]) + && IsDefined(level._scr_anim[ self.animname ][ "run_combat_hunched_large_turn" ]) + && IsDefined(level._scr_anim[ self.animname ][ "run_combat_hunched_large_turn" ][turnDir]) ) + { + largeTurns = level._scr_anim[ self.animname ][ "run_combat_hunched_large_turn" ][turnDir]; + if ( largeTurns.size > 0 ) + { + largeTurnAnim = largeTurns[RandomInt(largeTurns.size)]; + } + else + { + largeTurnAnim = undefined; + } + } + + if ( IsDefined( largeTurnAnim ) && ( RandomInt( 3 ) < 2 ) && animscripts\move::pathChange_canDoTurnAnim( largeTurnAnim ) ) + return largeTurnAnim; + + if ( IsDefined(level._scr_anim[ self.animname ]) + && IsDefined(level._scr_anim[ self.animname ][ "run_combat_hunched_turn" ]) + && IsDefined(level._scr_anim[ self.animname ][ "run_combat_hunched_turn" ][turnDir]) ) + { + turnAnim = level._scr_anim[ self.animname ][ "run_combat_hunched_turn" ][turnDir]; + } + + if ( isdefined( turnAnim ) && animscripts\move::pathChange_canDoTurnAnim( turnAnim ) ) + return turnAnim; + else + return undefined; +} \ No newline at end of file diff --git a/animscripts/civilian/civilian_scripted.gsc b/animscripts/civilian/civilian_scripted.gsc new file mode 100644 index 0000000..b2fec6b --- /dev/null +++ b/animscripts/civilian/civilian_scripted.gsc @@ -0,0 +1,4 @@ +main() +{ + animscripts\scripted::main(); +} \ No newline at end of file diff --git a/animscripts/civilian/lunar/civilian_init.gsc b/animscripts/civilian/lunar/civilian_init.gsc new file mode 100644 index 0000000..e899646 --- /dev/null +++ b/animscripts/civilian/lunar/civilian_init.gsc @@ -0,0 +1,76 @@ +#include common_scripts\utility; +#include maps\_utility; +#include maps\_anim; +#include animscripts\shared; + + +#using_animtree( "generic_human" ); + +main() +{ + animscripts\lunar\init::main(); + + if ( !isdefined( level._initialized_civilian_animations ) ) + { + initCivilianAnims(); + } + + animscripts\civilian\civilian_init_common::civilian_init(); + +} + +initCivilianAnims() +{ + level._initialized_civilian_animations = true; + + + level._scr_anim[ "default_civilian" ][ "run_combat" ] = [ %tp_moon_civ_run_combat ]; + level._scr_anim[ "default_civilian" ][ "run_weights" ] = common_scripts\utility::get_cumulative_weights( [ 1 ] ); + + level._scr_anim[ "default_civilian" ][ "run_noncombat" ] = [ %tp_moon_civ_run_noncombat ]; + + level._scr_anim[ "default_civilian" ][ "run_hunched_combat" ] = [ %tp_moon_civ_run_hunched_combat_A, %tp_moon_civ_run_hunched_combat_B, %tp_moon_civ_run_hunched_combat_C ]; + level._scr_anim[ "default_civilian" ][ "run_hunched_weights" ] = common_scripts\utility::get_cumulative_weights( [ 1, 1, 1 ] ); + + level._scr_anim[ "default_civilian" ][ "idle_noncombat" ] = [ %tp_moon_civ_idle_noncombat ]; + level._scr_anim[ "default_civilian" ][ "idle_combat" ] = [ %tp_moon_civ_idle_combat_A, %tp_moon_civ_idle_combat_B ]; + level._scr_anim[ "default_civilian" ][ "dodge_left" ] = %tp_moon_civ_dodge_L; + level._scr_anim[ "default_civilian" ][ "dodge_right" ] = %tp_moon_civ_dodge_R; + level._scr_anim[ "default_civilian" ][ "react_stumble" ] = [ %tp_moon_civ_react_stumble ]; + + level._scr_anim[ "default_civilian" ][ "turn_left_90" ] = %tp_moon_civ_run_noncombat_turn_L90; + level._scr_anim[ "default_civilian" ][ "turn_right_90" ] = %tp_moon_civ_run_noncombat_turn_R90; + + //7 9 <- 8 is invalid, it's straight. + //4 6 <- 5 is invalid, it's not a turn. + //1 2 3 + level._scr_anim[ "default_civilian"]["run_combat_turn"][1] = %tp_moon_civ_run_combat_turn_L135; + level._scr_anim[ "default_civilian"]["run_combat_turn"][2] = %tp_moon_civ_run_combat_turn_180; + level._scr_anim[ "default_civilian"]["run_combat_turn"][3] = %tp_moon_civ_run_combat_turn_R135; + level._scr_anim[ "default_civilian"]["run_combat_turn"][4] = %tp_moon_civ_run_combat_turn_L90; + level._scr_anim[ "default_civilian"]["run_combat_turn"][6] = %tp_moon_civ_run_combat_turn_R90; + level._scr_anim[ "default_civilian"]["run_combat_turn"][7] = %tp_moon_civ_run_combat_turn_L45; + level._scr_anim[ "default_civilian"]["run_combat_turn"][9] = %tp_moon_civ_run_combat_turn_R45; + + + + //define these if we want hunched turns, they aren't required though. + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][1] = %tp_moon_civ_run_combat_turn_L135; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][2] = %tp_moon_civ_run_combat_turn_180; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][3] = %tp_moon_civ_run_combat_turn_R135; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][4] = %tp_moon_civ_run_combat_hunched_turnL90; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][6] = %tp_moon_civ_run_combat_hunched_turnR90; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][7] = %tp_moon_civ_run_combat_hunched_turnL45; + level._scr_anim[ "default_civilian"]["run_combat_hunched_turn"][9] = %tp_moon_civ_run_combat_hunched_turnR45; + /* + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][1] = undefined; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][2] = undefined; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][3] = undefined; + + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][4] = [ %tp_moon_civ_run_combat_hunched_large_turnL90_slide, %tp_moon_civ_run_combat_hunched_large_turnL90_stumble ]; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][6] = [ %tp_moon_civ_run_combat_hunched_large_turnR90_slide, %tp_moon_civ_run_combat_hunched_large_turnR90_stumble ]; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][7] = undefined; + level._scr_anim[ "default_civilian"]["run_combat_hunched_large_turn"][9] = undefined; + */ +} + diff --git a/animscripts/combat.gsc b/animscripts/combat.gsc new file mode 100644 index 0000000..b5c3ff9 --- /dev/null +++ b/animscripts/combat.gsc @@ -0,0 +1,1532 @@ +#include animscripts\Utility; +#include animscripts\SetPoseMovement; +#include animscripts\Combat_utility; +#include animscripts\shared; +#include common_scripts\Utility; + +#using_animtree( "generic_human" ); + +sqr512 = 512 * 512; +sqr285 = 285 * 285; +sqr100 = 100 * 100; + +pistolPullOutDistSq = sqr512 * 0.64; +pistolPutBackDistSq = sqr512; + + +main() +{ + if ( isdefined( self.no_ai ) ) + return; + + if ( isdefined( self.onSnowMobile ) ) + { + animscripts\snowmobile::main(); + return; + } + + if ( isdefined( self.locked_combat ) ) + { + animscripts\locked_combat::locked_combat(); + return; + } + + //prof_begin("combat_init"); + + self endon( "killanimscript" ); + + [[ self.exception[ "exposed" ] ]](); + + animscripts\utility::initialize( "combat" ); + self.a.arrivalType = undefined; + + if ( isdefined( self.node ) && self.node.type == "Ambush" && self nearNode( self.node ) ) + self.ambushNode = self.node; + + /# + if ( getdvar( "scr_testgrenadethrows" ) == "on" ) + testGrenadeThrowAnimOffsets(); + #/ + + self transitionToCombat(); + + self do_friendly_fire_reaction(); + + animscripts\stop::specialIdleLoop(); + + self setup(); + + //prof_end("combat_init"); + + self exposedCombatMainLoop(); + + self notify( "stop_deciding_how_to_shoot" ); +} + + +end_script() +{ + self.ambushNode = undefined; +} + + +do_friendly_fire_reaction() +{ + if ( self.team != "allies" ) + return; + + if ( self IsMoveSuppressed() && self.prevScript == "move" && self.a.pose == "stand" && !isdefined( self.disableFriendlyFireReaction ) ) + { + if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < squared( 128 ) ) + return; + + self animmode( "zonly_physics" ); + self setFlaggedAnimKnobAllRestart( "react", animscripts\reactions::getReactionAnim( "surprise" ), %root, 1, 0.2, self.animplaybackrate ); + self animscripts\shared::DoNoteTracks( "react" ); + } +} + +transitionToCombat() +{ + if ( isdefined( self.specialIdleAnim ) || isdefined( self.customIdleAnimSet ) ) + return; + + if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < 512 * 512 ) + return; + + if ( self.prevScript == "stop" && !self isCQBWalking() && self.a.pose == "stand" ) + { + self animmode( "zonly_physics" ); + self setFlaggedAnimKnobAllRestart( "transition", %casual_stand_idle_trans_out, %root, 1, 0.2, 1.2 * self.animplaybackrate ); + self animscripts\shared::DoNoteTracks( "transition" ); + } +} + + +/# +testGrenadeThrowAnimOffsets() +{ + model = getGrenadeModel(); + + self animmode( "zonly_physics" ); + self OrientMode( "face angle", self.angles[ 1 ] ); + self.keepClaimedNodeIfValid = true; + foreach ( throwAnim in anim.grenadeThrowAnims ) + { + forward = anglestoforward( self.angles ); + right = anglestoright( self.angles ); + startpos = self.origin; + + tag = "TAG_INHAND"; + + self setFlaggedAnimKnobAllRestart( "grenadetest", throwAnim, %root, 1, 0, 1 ); + for ( ;; ) + { + self waittill( "grenadetest", notetrack ); + if ( notetrack == "grenade_left" || notetrack == "grenade_right" ) + self attach( model, tag ); + if ( notetrack == "grenade_throw" || notetrack == "grenade throw" ) + break; + assert( notetrack != "end" );// we shouldn't hit "end" until after we've hit "grenade_throw"! + if ( notetrack == "end" )// failsafe + break; + } + + pos = self getTagOrigin( tag ); + baseoffset = pos - startpos; + + offset = ( vectordot( baseoffset, forward ), -1 * vectordot( baseoffset, right ), baseoffset[ 2 ] ); + + // check our answer =) + endpos = startpos + forward * offset[ 0 ] - right * offset[ 1 ] + ( 0, 0, 1 ) * offset[ 2 ]; + thread debugLine( startpos, endpos, ( 1, 1, 1 ), 20 ); + + println( "addGrenadeThrowAnimOffset( %", throwAnim, ", ", offset, " );" ); + + self detach( model, tag ); + + wait 1; + } + self.keepClaimedNodeIfValid = false; +} +#/ + +setup_anim_array() +{ + if ( self.a.pose == "stand" ) + { + self animscripts\init_common::set_animarray_standing(); + } + else if ( self.a.pose == "crouch" ) + { + self animscripts\init_common::set_animarray_crouching(); + } + else if ( self.a.pose == "prone" ) + { + self animscripts\init_common::set_animarray_prone(); + } + else + { + assertMsg( "Unsupported self.a.pose: " + self.a.pose ); + } +} + +// SETUP FUNCTIONS +setup() +{ + if ( usingSidearm() && self isStanceAllowed( "stand" ) ) + transitionTo( "stand" ); + + setup_anim_array(); + + set_aim_and_turn_limits(); + + self thread stopShortly(); + self.previousPitchDelta = 0.0; + + self clearAnim( %root, .2 ); + + setupAim( .2 ); + + self thread aimIdleThread(); + + self.a.meleeState = "aim"; + + self delayStandardMelee(); +} + +stopShortly() +{ + self endon( "killanimscript" ); + + // we want to stop at about the time we blend out of whatever we were just doing. + wait .2; + self.a.movement = "stop"; +} + + +set_aim_and_turn_limits() +{ + //We have a slightly greater pitch reach when stand_exposed + self setDefaultAimLimits(); + + if ( self.a.pose == "stand" ) + { + self.upAimLimit = 60; + self.downAimLimit = -60; + } + + self.turnThreshold = self.defaultTurnThreshold; +} + + +setupExposedCombatLoop() +{ + self thread trackShootEntOrPos(); + self thread ReacquireWhenNecessary(); + self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" ); + self thread watchShootEntVelocity(); + + self resetGiveUpOnEnemyTime(); + + if ( isdefined( self.a.magicReloadWhenReachEnemy ) ) + { + self animscripts\weaponList::RefillClip(); + self.a.magicReloadWhenReachEnemy = undefined; + } + + // Hesitate to crouch. Crouching too early can look stupid because we'll tend to stand right back up in a lot of cases. + self.a.dontCrouchTime = gettime() + randomintrange( 500, 1500 ); +} + + +exposedCombatStopUsingRPGCheck( distSqToShootPos ) +{ + // too close for RPG or out of ammo + if ( usingRocketLauncher() && ( distSqToShootPos < sqr512 || self.a.rockets < 1 ) ) + { + if ( self.a.pose != "stand" && self.a.pose != "crouch" ) + transitionTo( "crouch" ); + + if ( self.a.pose == "stand" ) + animscripts\shared::throwDownWeapon( %RPG_stand_throw ); + else + animscripts\shared::throwDownWeapon( %RPG_crouch_throw ); + + self clearAnim( %root, 0.2 ); + + self endFireAndAnimIdleThread(); + self setup_anim_array(); + self startFireAndAimIdleThread(); + return true; + } + + return false; +} + + +exposedCombatCheckStance( distSqToShootPos ) +{ + if ( self.a.pose != "stand" && self isStanceAllowed( "stand" ) ) + { + if ( distSqToShootPos < sqr285 ) + { + transitionTo( "stand" ); + return true; + } + if ( standIfMakesEnemyVisible() ) + return true; + } + + if ( distSqToShootPos > sqr512 && + self.a.pose != "crouch" && + self isStanceAllowed( "crouch" ) && + !usingSidearm() && + !isdefined( self.heat ) && + gettime() >= self.a.dontCrouchTime && + lengthSquared( self.shootEntVelocity ) < sqr100 ) + { + if ( !isdefined( self.shootPos ) || sightTracePassed( self.origin + ( 0, 0, 36 ), self.shootPos, false, undefined ) ) + { + transitionTo( "crouch" ); + return true; + } + } + + return false; +} + + +exposedCombatCheckReloadOrUsePistol( distSqToShootPos ) +{ + if ( !usingSidearm() ) + { + if ( isdefined( self.forceSideArm ) && self.a.pose == "stand" ) + { + if ( self tryUsingSidearm() ) + return true; + } + + if ( isSniper() && distSqToShootPos < pistolPullOutDistSq ) + { + if ( self tryUsingSidearm() ) + return true; + } + } + + if ( NeedToReload( 0 ) ) + { + // TODO: tweak prone exposed reloading to be considered safer + // requiring self.weapon == self.primaryweapon because we dont want him to drop his shotgun and then, if wantshotgun = false, decide to pick up his rifle when he's done + if ( !usingSidearm() && cointoss() && !usingRocketLauncher() && usingPrimary() && + distSqToShootPos < pistolPullOutDistSq && self isStanceAllowed( "stand" ) ) + { + // we need to be standing to switch weapons + if ( self.a.pose != "stand" ) + { + transitionTo( "stand" ); + return true; + } + + if ( self tryUsingSidearm() ) + return true; + } + + if ( self exposedReload( 0 ) ) + return true; + } + + return false; +} + + +exposedCombatCheckPutAwayPistol( distSqToShootPos ) +{ + if ( usingSidearm() && self.a.pose == "stand" && !isdefined( self.forceSideArm ) ) + if ( ( distSqToShootPos > pistolPutBackDistSq ) || ( self.combatMode == "ambush_nodes_only" && ( !isdefined( self.enemy ) || !self cansee( self.enemy ) ) ) ) + switchToLastWeapon( %pistol_stand_switch ); +} + +exposedCombatPositionAdjust() +{ + if ( isdefined( self.heat ) && self nearClaimNodeAndAngle() ) + { + assert( isdefined( self.node ) ); + self safeTeleport( self.nodeoffsetpos, self.node.angles ); + } +} + +exposedCombatNeedToTurn() +{ + if ( needToTurn() ) + { + predictTime = 0.25; + if ( isdefined( self.shootEnt ) && !isSentient( self.shootEnt ) ) + predictTime = 1.5; + yawToShootEntOrPos = getPredictedAimYawToShootEntOrPos( predictTime );// yaw to where we think our enemy will be in x seconds + if ( TurnToFaceRelativeYaw( yawToShootEntOrPos ) ) + return true; + } + + return false; +} + +exposedCombatMainLoop() +{ + self endon( "killanimscript" ); + self endon( "combat_restart" ); + + self setupExposedCombatLoop(); + + self animMode( "zonly_physics", false ); + self OrientMode( "face angle", self.angles[ 1 ] ); // // just face current immediately and rely on turning. + + for ( ;; ) + { + if ( usingRocketLauncher() ) + self.deathFunction = undefined; + + self IsInCombat();// reset our in - combat state + + // it is important for this to be *after* the set_animarray calls! + if ( WaitForStanceChange() ) + continue; + + tryMelee(); + + exposedCombatPositionAdjust(); + + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + cantSeeEnemyBehavior(); + + if ( !isdefined( self.enemy ) ) + justWaited = true; + continue; + } + + // we can use self.shootPos after this point. + assert( isdefined( self.shootPos ) ); + self resetGiveUpOnEnemyTime(); + + distSqToShootPos = lengthsquared( self.origin - self.shootPos ); + + if ( exposedCombatStopUsingRPGCheck( distSqToShootPos ) ) + continue; + + if ( exposedCombatNeedToTurn() ) + continue; + + if ( considerThrowGrenade() )// TODO: make considerThrowGrenade work with shootPos rather than only self.enemy + continue; + + if ( exposedCombatCheckReloadOrUsePistol( distSqToShootPos ) ) + continue; + + if ( usingRocketLauncher() && self.a.pose != "crouch" && randomFloat( 1 ) > 0.65 ) + self.deathFunction = ::rpgDeath; + + exposedCombatCheckPutAwayPistol( distSqToShootPos ); + + if ( exposedCombatCheckStance( distSqToShootPos ) ) + continue; + + if ( aimedAtShootEntOrPos() ) + { + self shootUntilNeedToTurn(); + self hideFireShowAimIdle(); + continue; + } + + exposedWait(); + } +} + + +exposedWait() +{ + if ( !isdefined( self.enemy ) || !self cansee( self.enemy ) ) + { + self endon( "enemy" ); + self endon( "shoot_behavior_change" ); + + wait 0.2 + randomfloat( 0.1 ); + self waittill( "do_slow_things" ); + } + else + { + wait 0.05; + } +} + +standIfMakesEnemyVisible() +{ + assert( self.a.pose != "stand" ); + assert( self isStanceAllowed( "stand" ) ); + if ( isdefined( self.enemy ) && ( !self cansee( self.enemy ) || !self canShootEnemy() ) && sightTracePassed( self.origin + ( 0, 0, 64 ), self.enemy getShootAtPos(), false, undefined ) ) + { + self.a.dontCrouchTime = gettime() + 3000; + transitionTo( "stand" ); + return true; + } + return false; +} + +needToTurn() +{ + // Old way, slower + /* + yawToShootEntOrPos = getAimYawToShootEntOrPos(); // yaw to where we think our enemy will be in x seconds + + return (abs( yawToShootEntOrPos ) > self.turnThreshold); + */ + + // New way + point = self.shootPos; + if ( !isdefined( point ) ) + return false; + + yaw = self.angles[ 1 ] - VectorToYaw( point - self.origin ); + + // need to have fudge factor because the gun's origin is different than our origin, + // the closer our distance, the more we need to fudge. + distsq = distanceSquared( self.origin, point ); + if ( distsq < 256 * 256 ) + { + dist = sqrt( distsq ); + if ( dist > 3 ) + yaw += asin( -3 / dist ); + } + return AbsAngleClamp180( yaw ) > self.turnThreshold; +} + +WaitForStanceChange() +{ + curstance = self.a.pose; + + if ( isdefined( self.a.onback ) ) + { + wait 0.1; // something else should correct the stance from "back", or this AI will die on its back, so wait. + return true; + } + + if ( curstance == "stand" && isdefined( self.heat ) ) + return false; + + if ( !self isStanceAllowed( curstance ) ) + { + assert( curstance == "stand" || curstance == "crouch" || curstance == "prone" ); + + otherstance = "crouch"; + if ( curstance == "crouch" ) + otherstance = "stand"; + + if ( self isStanceAllowed( otherstance ) ) + { + if ( curstance == "stand" && usingSidearm() ) + return false; + + transitionTo( otherstance ); + return true; + } + } + return false; +} + +cantSeeEnemyBehavior() +{ + //prof_begin("combat_cantSeeEnemyBehavior"); + + if ( self.a.pose != "stand" && self isStanceAllowed( "stand" ) && standIfMakesEnemyVisible() ) + return true; + + time = gettime(); + + self.a.dontCrouchTime = time + 1500; + + if ( isdefined( self.group ) && isdefined( self.group.forward ) ) + { + relYaw = AngleClamp180( self.angles[ 1 ] - vectorToYaw( self.group.forward ) ); + if ( self TurnToFaceRelativeYaw( relYaw ) ) + return true; + } + + if ( isdefined( self.node ) && isdefined( anim.isCombatScriptNode[ self.node.type ] ) ) + { + relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); + if ( self TurnToFaceRelativeYaw( relYaw ) ) + return true; + } + else if ( (isdefined( self.enemy ) && self seeRecently( self.enemy, 2 )) || time > self.a.scriptStartTime + 1200 ) + { + relYaw = undefined; + likelyEnemyDir = self getAnglesToLikelyEnemyPath(); + if ( isdefined( likelyEnemyDir ) ) + { + relYaw = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] ); + } + else if ( isdefined( self.node ) ) + { + relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); + } + else if ( isdefined( self.enemy ) ) + { + likelyEnemyDir = vectorToAngles( self lastKnownPos( self.enemy ) - self.origin ); + relYaw = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] ); + } + + if ( isdefined( relYaw ) && self TurnToFaceRelativeYaw( relYaw ) ) + return true; + } + // the above likely enemy path is more important than node angles + else if ( isdefined( self.heat ) && self nearClaimNode() ) + { + relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); + if ( self TurnToFaceRelativeYaw( relYaw ) ) + return true; + } + + if ( considerThrowGrenade() ) + return true; + + givenUpOnEnemy = ( self.a.nextGiveUpOnEnemyTime < time ); + + threshold = 0; + if ( givenUpOnEnemy ) + threshold = 0.99999; + + if ( self exposedReload( threshold ) ) + return true; + + if ( givenUpOnEnemy && usingSidearm() ) + { + // switch back to main weapon so we can reload it too before another enemy appears + switchToLastWeapon( %pistol_stand_switch ); + return true; + } + + /*if ( shouldSwapShotgun() ) + { + self swapShotgun(); + return true; + }*/ + + cantSeeEnemyWait(); + return true; +} + +cantSeeEnemyWait() +{ + self endon( "shoot_behavior_change" ); + + wait 0.4 + randomfloat( 0.4 ); + self waittill( "do_slow_things" ); +} + +resetGiveUpOnEnemyTime() +{ + self.a.nextGiveUpOnEnemyTime = gettime() + randomintrange( 2000, 4000 ); +} + +TurnToFaceRelativeYaw( faceYaw ) +{ + if ( faceYaw < 0 - self.turnThreshold ) + { + if ( self.a.pose == "prone" ) + { + self animscripts\cover_prone::proneTo( "crouch" ); + self animscripts\init_common::set_animarray_crouching(); + } + + self doTurn( "left", 0 - faceYaw ); + self maps\_gameskill::didSomethingOtherThanShooting(); + return true; + } + if ( faceYaw > self.turnThreshold ) + { + if ( self.a.pose == "prone" ) + { + self animscripts\cover_prone::proneTo( "crouch" ); + self animscripts\init_common::set_animarray_crouching(); + } + + self doTurn( "right", faceYaw ); + self maps\_gameskill::didSomethingOtherThanShooting(); + return true; + } + return false; +} + +watchShootEntVelocity() +{ + self endon( "killanimscript" ); + + self.shootEntVelocity = ( 0, 0, 0 ); + + prevshootent = undefined; + prevpos = self.origin; + + interval = .15; + + while ( 1 ) + { + if ( isdefined( self.shootEnt ) && isdefined( prevshootent ) && self.shootEnt == prevshootent ) + { + curpos = self.shootEnt.origin; + self.shootEntVelocity = vector_multiply( curpos - prevpos, 1 / interval ); + prevpos = curpos; + } + else + { + if ( isdefined( self.shootEnt ) ) + prevpos = self.shootEnt.origin; + else + prevpos = self.origin; + prevshootent = self.shootEnt; + + self.shootEntVelocity = ( 0, 0, 0 ); + } + + wait interval; + } +} + +shouldSwapShotgun() +{ + return false;// anims aren't set up yet + + /* + if ( self.a.pose != "stand" ) + return false; + + if ( self usingSidearm() ) + return false; + + usingShotgun = isShotgun( self.primaryweapon ); + wantShotgun = isdefined( self.wantShotgun ) && self.wantShotgun ); + + if ( wantShotgun == usingShotgun ) + return false; + + if ( !wantShotgun ) // there is no standing shotgun putaway animation + return false; + + return true;*/ +} + +/*swapShotgun() +{ + assert( self shouldSwapShotgun() ); + assert( isdefined( self.wantShotgun ) ); + + if ( self.wantShotgun ) + { + self setFlaggedAnimKnobAllRestart( "weapon_swap", %shotgun_stand_pullout, %body, 1, .2, 1 ); + + self thread DoNoteTracksWithEndon( "weapon_swap" ); + + self waittill( "weapon_swap", "" ); + self waittill( "weapon_swap", "end" ); + } + else + { + assert(false); // we don't have a standing shotgun putaway animation + } +}*/ + +DoNoteTracksWithEndon( animname ) +{ + self endon( "killanimscript" ); + self animscripts\shared::DoNoteTracks( animname ); +} + +// does turntable movement to face the enemy; +// should be used sparingly because turn animations look better. +faceEnemyImmediately() +{ + self endon( "killanimscript" ); + self notify( "facing_enemy_immediately" ); + self endon( "facing_enemy_immediately" ); + + maxYawChange = 5;// degrees per frame + + while ( 1 ) + { + yawChange = 0 - GetYawToEnemy(); + + if ( abs( yawChange ) < 2 ) + break; + + if ( abs( yawChange ) > maxYawChange ) + yawChange = maxYawChange * sign( yawChange ); + + self OrientMode( "face angle", self.angles[ 1 ] + yawChange ); + + wait .05; + } + + self OrientMode( "face current" ); + + self notify( "can_stop_turning" ); +} + +isDeltaAllowed( theanim ) +{ + delta = getMoveDelta( theanim, 0, 1 ); + endPoint = self localToWorldCoords( delta ); + + return self isInGoal( endPoint ) && self mayMoveToPoint( endPoint ); +} + +isAnimDeltaInGoal( theanim ) +{ + delta = getMoveDelta( theanim, 0, 1 ); + endPoint = self localToWorldCoords( delta ); + + return self isInGoal( endPoint ); +} + +doTurn( direction, amount ) +{ + knowWhereToShoot = isdefined( self.shootPos ); + rate = 1; + transTime = 0.2; + mustFaceEnemy = ( isdefined( self.enemy ) && !isdefined( self.turnToMatchNode ) && self seeRecently( self.enemy, 2 ) && distanceSquared( self.enemy.origin, self.origin ) < sqr512 ); + if ( self.a.scriptStartTime + 500 > gettime() ) + { + transTime = 0.25;// if it's the first thing we're doing, always blend slowly + if ( mustFaceEnemy ) + self thread faceEnemyImmediately(); + } + else if ( mustFaceEnemy ) + { + urgency = 1.0 - ( distance( self.enemy.origin, self.origin ) / 512 ); + rate = 1 + urgency * 1; + + // ( ensure transTime <= 0.2 / rate ) + if ( rate > 2 ) + transTime = .05; + else if ( rate > 1.3 ) + transTime = .1; + else + transTime = .15; + } + + angle = 0; + if ( amount > 157.5 ) + angle = 180; + else if ( amount > 112.5 ) + angle = 135; + else if ( amount > 67.5 ) + angle = 90; + else + angle = 45; + + animname = "turn_" + direction + "_" + angle; + turnanim = animarray( animname ); + + if ( isdefined( self.turnToMatchNode ) ) + self animmode( "angle deltas", false ); + else if ( isdefined( self.node ) && isdefined( anim.isCombatPathNode[ self.node.type ] ) && distanceSquared( self.origin, self.node.origin ) < 16 * 16 ) + self animmode( "angle deltas", false ); + else if ( isAnimDeltaInGoal( turnanim ) ) + self animMode( "zonly_physics", false ); + else + self animmode( "angle deltas", false ); + + self setAnimKnobAll( %exposed_aiming, %body, 1, transTime ); + + if ( !isdefined( self.turnToMatchNode ) ) + self TurningAimingOn( transTime ); + + self setAnimLimited( %turn, 1, transTime ); + + if ( isdefined( self.heat ) ) + rate = min( 1.0, rate ); // TEMP 1.0, adjust animations first + else if ( isdefined( self.turnToMatchNode ) ) + rate = max( 1.5, rate ); + + self setFlaggedAnimKnobLimitedRestart( "turn", turnanim, 1, transTime, rate ); + self notify( "turning" ); + + if ( knowWhereToShoot && !isdefined( self.turnToMatchNode ) && !isdefined( self.heat ) ) + self thread shootWhileTurning(); + + doTurnNotetracks(); + + self setanimlimited( %turn, 0, .2 ); + + if ( !isdefined( self.turnToMatchNode ) ) + self TurningAimingOff( .2 ); + + if ( !isdefined( self.turnToMatchNode ) ) + { + self clearanim( %turn, .2 ); + self setanimknob( %exposed_aiming, 1, .2, 1 ); + } + else + { + self clearanim( %exposed_modern, .3 ); + } + + // if we didn't actually turn, code prevented us from doing so. + // give up and turntable. + if ( isdefined( self.turnLastResort ) ) + { + self.turnLastResort = undefined; + self thread faceEnemyImmediately(); + } + + self animMode( "zonly_physics", false ); + + self notify( "done turning" ); +} + +doTurnNotetracks() +{ + //self endon( "turning_isnt_working" ); + self endon( "can_stop_turning" ); + + //self thread makeSureTurnWorks(); + self animscripts\shared::DoNoteTracks( "turn" ); +} + +makeSureTurnWorks() +{ + self endon( "killanimscript" ); + self endon( "done turning" ); + + startAngle = self.angles[ 1 ]; + + wait .3; + + if ( self.angles[ 1 ] == startAngle ) + { + self notify( "turning_isnt_working" ); + self.turnLastResort = true; + } +} + +TurningAimingOn( transTime ) +{ + self setAnimLimited( animarray( "straight_level" ), 0, transTime ); + self setAnim( %add_idle, 0, transTime ); + + if ( !weapon_pump_action_shotgun() ) + self clearAnim( %add_fire, .2 ); +} + +TurningAimingOff( transTime ) +{ + self setAnimLimited( animarray( "straight_level" ), 1, transTime ); + self setAnim( %add_idle, 1, transTime ); +} + +shootWhileTurning() +{ + self endon( "killanimscript" ); + self endon( "done turning" ); + + if ( usingRocketLauncher() ) + return; + + shootUntilShootBehaviorChange(); + + self clearAnim( %add_fire, .2 ); +} + +shootUntilNeedToTurn() +{ + self thread watchForNeedToTurnOrTimeout(); + self endon( "need_to_turn" ); + + self thread keepTryingToMelee(); + + shootUntilShootBehaviorChange(); + + self notify( "stop_watching_for_need_to_turn" ); + self notify( "stop_trying_to_melee" ); +} + +watchForNeedToTurnOrTimeout() +{ + self endon( "killanimscript" ); + self endon( "stop_watching_for_need_to_turn" ); + + endtime = gettime() + 4000 + randomint( 2000 ); + + while ( 1 ) + { + if ( gettime() > endtime || needToTurn() ) + { + self notify( "need_to_turn" ); + break; + } + wait .1; + } +} + +considerThrowGrenade() +{ + if ( !myGrenadeCoolDownElapsed() )// early out for efficiency + return false; + + if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level._player ) ) + { + if ( tryExposedThrowGrenade( level._player, 200 ) ) + return true; + } + + if ( isdefined( self.enemy ) && tryExposedThrowGrenade( self.enemy, self.minExposedGrenadeDist ) ) + return true; + + self.a.nextGrenadeTryTime = gettime() + 500;// don't try this too often + + return false; +} + +tryExposedThrowGrenade( throwAt, minDist ) +{ + threw = false; + + if ( isdefined( self.dontEverShoot ) || isdefined( throwAt.dontAttackMe ) ) + return false; + + if ( !isdefined( self.a.array[ "exposed_grenade" ] ) ) + return false; + + throwSpot = throwAt.origin; + if ( !self canSee( throwAt ) ) + { + if ( isdefined( self.enemy ) && throwAt == self.enemy && isdefined( self.shootPos ) ) + throwSpot = self.shootPos; + } + + if ( !self canSee( throwAt ) ) + minDist = 100; + + if ( distanceSquared( self.origin, throwSpot ) > minDist * minDist && self.a.pose == self.a.grenadeThrowPose ) + { + self setActiveGrenadeTimer( throwAt ); + + if ( !grenadeCoolDownElapsed( throwAt ) ) + return false; + + yaw = GetYawToSpot( throwSpot ); + if ( abs( yaw ) < 60 ) + { + throwAnims = []; + + foreach( throwAnim in ( self.a.array[ "exposed_grenade" ] ) ) + { + if ( isDeltaAllowed( throwAnim ) ) + throwAnims[ throwAnims.size ] = throwAnim; + } + + if ( throwAnims.size > 0 ) + { + self setanim( %exposed_aiming, 0, .1 ); + self animMode( "zonly_physics" ); + + setAnimAimWeight( 0, 0 ); + + threw = TryGrenade( throwAt, throwAnims[ randomint( throwAnims.size ) ] ); + + self setanim( %exposed_aiming, 1, .1 ); + + if ( threw ) + setAnimAimWeight( 1, .5 );// ease into aiming + else + setAnimAimWeight( 1, 0 ); + } + } + } + + if ( threw ) + self maps\_gameskill::didSomethingOtherThanShooting(); + + return threw; +} + +transitionTo( newPose ) +{ + if ( newPose == self.a.pose ) + return; + + // allow using pistol but it might look bad, put a new animation if so + // assert( !usingSidearm() ); + + transAnimName = self.a.pose + "_2_" + newPose; + + if ( !isdefined( self.a.array ) ) + return; + + transAnim = self.a.array[ transAnimName ]; + + if ( !isdefined( transAnim ) ) + return; + + self clearanim( %root, .3 ); + + self endFireAndAnimIdleThread(); + + if ( newPose == "stand" ) + rate = 2;// gotta stand up fast! + else + rate = 1.5; + + if ( !animHasNoteTrack( transAnim, "anim_pose = \"" + newPose + "\"" ) ) + { + println( "error: " + self.a.pose + "_2_" + newPose + " missing notetrack to set pose!" ); + } + + self setFlaggedAnimKnobAllRestart( "trans", transanim, %body, 1, .2, rate ); + transTime = getAnimLength( transanim ) / rate; + playTime = transTime - 0.3; + if ( playTime < 0.2 ) + playTime = 0.2; + self animscripts\shared::DoNoteTracksForTime( playTime, "trans" ); + + self.a.pose = newPose; + + setup_anim_array(); + + self startFireAndAimIdleThread(); + + self maps\_gameskill::didSomethingOtherThanShooting(); +} + +keepTryingToMelee() +{ + self endon( "killanimscript" ); + self endon( "stop_trying_to_melee" ); + self endon( "done turning" ); + self endon( "need_to_turn" ); + self endon( "shoot_behavior_change" ); + + while ( 1 ) + { + wait .2 + randomfloat( .3 ); + + // this function is running when we're doing something like shooting or reloading. + // we only want to melee if we would look really stupid by continuing to do what we're trying to get done. + // only melee if our enemy is very close. + if ( isdefined( self.enemy ) ) + { + if ( isPlayer( self.enemy ) ) + checkDistSq = 200 * 200; + else + checkDistSq = 100 * 100; + + if ( distanceSquared( self.enemy.origin, self.origin ) < checkDistSq ) + tryMelee(); + } + } +} + +tryMelee() +{ + animscripts\melee::Melee_TryExecuting(); // will start a new anim script and stop combat when successful +} + +delayStandardMelee() +{ + if ( isdefined( self.noMeleeChargeDelay ) ) + return; + + if ( isPlayer( self.enemy ) ) + return; + + // give the AI a chance to charge the player if he forced him out of cover + //if ( isPlayer( self.enemy ) && isDefined( self.meleeCoverChargeGraceEndTime ) && (self.meleeCoverChargeGraceEndTime > getTime()) ) + // return; + + animscripts\melee::Melee_Standard_DelayStandardCharge( self.enemy ); +} + +exposedReload( threshold ) +{ + if ( NeedToReload( threshold ) ) + { + self.a.exposedReloading = true; + self endFireAndAnimIdleThread(); + + reloadAnim = undefined; + if ( isdefined( self.specialReloadAnimFunc ) ) + { + reloadAnim = self [[ self.specialReloadAnimFunc ]](); + self.keepClaimedNode = true; + } + else + { + reloadAnim = animArrayPickRandom( "reload" ); + + if ( self.a.pose == "stand" && animArrayAnyExist( "reload_crouchhide" ) && cointoss() ) + reloadAnim = animArrayPickRandom( "reload_crouchhide" ); + } + + self thread keepTryingToMelee(); + + self.finishedReload = false; + + // pistol reload looks weird pointing at fixed current angle + if ( weaponClass( self.weapon ) == "pistol" ) + self orientmode( "face default" ); + + self doReloadAnim( reloadAnim, threshold > .05 );// this will return at the time when we should start aiming + self notify( "abort_reload" );// make sure threads that doReloadAnim() started finish + self orientmode( "face current" ); + + if ( self.finishedReload ) + self animscripts\weaponList::RefillClip(); + + self clearanim( %reload, .2 ); + self.keepClaimedNode = false; + + self notify( "stop_trying_to_melee" ); + + self.a.exposedReloading = false; + + self maps\_gameskill::didSomethingOtherThanShooting(); + + self startFireAndAimIdleThread(); + + return true; + } + + return false; +} + +doReloadAnim( reloadAnim, stopWhenCanShoot ) +{ + self endon( "abort_reload" ); + if ( stopWhenCanShoot ) + self thread abortReloadWhenCanShoot(); + + animRate = 1; + + if ( !self usingSidearm() && !isShotgun( self.weapon ) && isdefined( self.enemy ) && self canSee( self.enemy ) && distanceSquared( self.enemy.origin, self.origin ) < 1024*1024 ) + animRate = 1.2; + + flagName = "reload_" + getUniqueFlagNameIndex(); + + self clearanim( %root, 0.2 ); + self setflaggedanimrestart( flagName, reloadAnim, 1, .2, animRate ); + self thread notifyOnStartAim( "abort_reload", flagName ); + self endon( "start_aim" ); + self animscripts\shared::DoNoteTracks( flagName ); + + self.finishedReload = true; +} + +abortReloadWhenCanShoot() +{ + self endon( "abort_reload" ); + self endon( "killanimscript" ); + while ( 1 ) + { + if ( isdefined( self.shootEnt ) && self canSee( self.shootEnt ) ) + break; + wait .05; + } + self notify( "abort_reload" ); +} + +notifyOnStartAim( endonStr, flagName ) +{ + self endon( endonStr ); + self waittillmatch( flagName, "start_aim" ); + self.finishedReload = true; + self notify( "start_aim" ); +} + +finishNoteTracks( animname ) +{ + self endon( "killanimscript" ); + animscripts\shared::DoNoteTracks( animname ); +} + +drop_turret() +{ + maps\_mgturret::dropTurret(); +// level.theturret = turret; +// throwVel = 75 + randomInt(50); + +// self animscripts\shared::PutGunInHand("right"); + self animscripts\weaponList::RefillClip(); + self.a.needsToRechamber = 0; + self notify( "dropped_gun" ); + maps\_mgturret::restoreDefaults(); +} + +exception_exposed_mg42_portable() +{ + drop_turret(); +} + + +tryUsingSidearm() +{ + // temp fix! we run out of bones on a particular friendly model with a shotgun who tries to pull out his pistol. + if ( isdefined( self.secondaryWeapon ) && isShotgun( self.secondaryweapon ) ) + return false; + + if ( isdefined( self.no_pistol_switch ) ) + return false; + + // TEMP no pistol crouch pullout yet + self.a.pose = "stand"; + + switchToSidearm( %pistol_stand_pullout ); + + return true; +} + + +switchToSidearm( swapAnim ) +{ + self endon( "killanimscript" ); + assert( self.sidearm != "" ); + + self thread putGunBackInHandOnKillAnimScript(); + self endFireAndAnimIdleThread(); + + self.swapAnim = swapAnim; + self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .2, fasterAnimSpeed() ); + self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handlePickup, "end_weapon_swap" ); + self clearAnim( self.swapAnim, 0.2 ); + + self notify( "facing_enemy_immediately" ); + + // notify for level script + self notify( "switched_to_sidearm" ); + self maps\_gameskill::didSomethingOtherThanShooting(); +} + +DoNoteTracksPostCallbackWithEndon( flagName, interceptFunction, endonMsg ) +{ + self endon( endonMsg ); + self animscripts\shared::DoNoteTracksPostCallback( flagName, interceptFunction ); +} + +faceEnemyDelay( delay ) +{ + self endon( "killanimscript" ); + wait delay; + self faceEnemyImmediately(); +} + +handlePickup( notetrack ) +{ + if ( notetrack == "pistol_pickup" ) + { + self clearAnim( animarray( "straight_level" ), 0 ); + self animscripts\init_common::set_animarray_standing(); + + self thread faceEnemyDelay( 0.25 ); + } + else if ( notetrack == "start_aim" ) + { + startFireAndAimIdleThread(); + + if ( self needToTurn() ) + self notify( "end_weapon_swap" ); + } +} + + +// %pistol_stand_switch +switchToLastWeapon( swapAnim, cleanUp ) +{ + self endon( "killanimscript" ); + + assertex( self.lastWeapon != getAISidearmWeapon() || self.lastWeapon == "none", "AI \"" + self.classname + "\" using sidearm trying to switch back to sidearm. lastweapon = \"" + self.lastWeapon + "\", primaryweapon = \"" + self.primaryweapon + "\"" ); + assertex( self.lastWeapon == getAIPrimaryWeapon() || self.lastWeapon == getAISecondaryWeapon() ); + + self endFireAndAnimIdleThread(); + + self.swapAnim = swapAnim; + self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .1, 1 ); + + if ( isdefined( cleanUp ) ) + self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handleCleanUpPutaway, "end_weapon_swap" ); + else + self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handlePutaway, "end_weapon_swap" ); + self clearanim( self.swapAnim, 0.2 ); + + self notify( "switched_to_lastweapon" ); + self maps\_gameskill::didSomethingOtherThanShooting(); +} + +handlePutaway( notetrack ) +{ + if ( notetrack == "pistol_putaway" ) + { + self clearAnim( animarray( "straight_level" ), 0 ); + self animscripts\init_common::set_animarray_standing(); + + self thread putGunBackInHandOnKillAnimScript(); + } + else if ( notetrack == "start_aim" ) + { + startFireAndAimIdleThread(); + + if ( self needToTurn() ) + self notify( "end_weapon_swap" ); + } +} + +handleCleanUpPutaway( notetrack ) +{ + if ( notetrack == "pistol_putaway" ) + self thread putGunBackInHandOnKillAnimScript(); + else if ( issubstr( notetrack, "anim_gunhand" ) ) + self notify( "end_weapon_swap" ); +} + + +rpgDeath() +{ + if ( !usingRocketLauncher() || self.bulletsInClip == 0 ) + return false; + + if ( randomFloat( 1 ) > 0.5 ) + self SetFlaggedAnimKnobAll( "deathanim", %RPG_stand_death, %root, 1, .05, 1 ); + else + self SetFlaggedAnimKnobAll( "deathanim", %RPG_stand_death_stagger, %root, 1, .05, 1 ); + + self animscripts\shared::DoNoteTracks( "deathanim" ); + self animscripts\shared::DropAllAIWeapons(); + return; +} + +ReacquireWhenNecessary() +{ + self endon( "killanimscript" ); + + self.a.exposedReloading = false; + + while ( 1 ) + { + wait .2; + + if ( isdefined( self.enemy ) && !self seeRecently( self.enemy, 2 ) ) + { + if ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" ) + continue; + } + + TryExposedReacquire(); + } +} + +// this function is meant to be called many times in succession. +// each time it tries another option, until eventually it finds something it can do. +TryExposedReacquire() +{ + if ( self.fixedNode ) + return; + + //prof_begin( "TryExposedReacquire" ); + if ( !isdefined( self.enemy ) ) + { + self.reacquire_state = 0; + return; + } + + // don't do reacquire move when temporarily blocked by teammate + if ( gettime() < self.teamMoveWaitTime ) + { + self.reacquire_state = 0; + return; + } + + if ( isdefined( self.prevEnemy ) && self.prevEnemy != self.enemy ) + { + self.reacquire_state = 0; + self.prevEnemy = undefined; + return; + } + self.prevEnemy = self.enemy; + + if ( self canSee( self.enemy ) && self canShootEnemy() ) + { + self.reacquire_state = 0; + return; + } + + if ( isdefined( self.finishedReload ) && !self.finishedReload ) + { + self.reacquire_state = 0; + return; + } + + // don't do reacquire unless facing enemy + dirToEnemy = vectornormalize( self.enemy.origin - self.origin ); + forward = anglesToForward( self.angles ); + + if ( vectordot( dirToEnemy, forward ) < 0.5 ) // (0.5 = cos60) + { + self.reacquire_state = 0; + return; + } + + if ( self.a.exposedReloading && NeedToReload( .25 ) && self.enemy.health > self.enemy.maxhealth * .5 ) + { + self.reacquire_state = 0; + return; + } + + if ( shouldHelpAdvancingTeammate() && self.reacquire_state < 3 ) + self.reacquire_state = 3; + + switch( self.reacquire_state ) + { + case 0: + if ( self ReacquireStep( 32 ) ) + return; + break; + + case 1: + if ( self ReacquireStep( 64 ) ) + { + self.reacquire_state = 0; + return; + } + break; + + case 2: + if ( self ReacquireStep( 96 ) ) + { + self.reacquire_state = 0; + return; + } + break; + + case 3: + if ( tryRunningToEnemy( false ) ) + { + self.reacquire_state = 0; + return; + } + break; + + case 4: + if ( !( self canSee( self.enemy ) ) || !( self canShootEnemy() ) ) + self FlagEnemyUnattackable(); + break; + + default: + // don't do anything for a while + if ( self.reacquire_state > 15 ) + { + self.reacquire_state = 0; + return; + } + break; + } + + self.reacquire_state++; +} + diff --git a/animscripts/combat_say.gsc b/animscripts/combat_say.gsc new file mode 100644 index 0000000..4e6172c --- /dev/null +++ b/animscripts/combat_say.gsc @@ -0,0 +1,15 @@ +// combat_say.gsc +// Determines when to talk during combat. + +// Plays miscellaneous combat lines sometimes during combat +generic_combat() +{ + self animscripts\battlechatter::playBattleChatter(); +} + +// Plays vaguely context-sensitive combat lines sometimes during combat +specific_combat( dialogueLine ) +{ + self animscripts\battlechatter::playBattleChatter(); +} + diff --git a/animscripts/combat_utility.gsc b/animscripts/combat_utility.gsc new file mode 100644 index 0000000..4200170 --- /dev/null +++ b/animscripts/combat_utility.gsc @@ -0,0 +1,2258 @@ +#include animscripts\Utility; +#include maps\_gameskill; +#include maps\_utility; +#include common_scripts\utility; +#include animscripts\SetPoseMovement; +#using_animtree( "generic_human" ); + + +getTargetAngleOffset( target ) +{ + pos = self getshootatpos() + ( 0, 0, -3 );// compensate for eye being higher than gun + dir = ( pos[ 0 ] - target[ 0 ], pos[ 1 ] - target[ 1 ], pos[ 2 ] - target[ 2 ] ); + dir = VectorNormalize( dir ); + fact = dir[ 2 ] * - 1; +// println ("offset " + fact); + return fact; +} + +getSniperBurstDelayTime() +{ + if ( isPlayer( self.enemy ) ) + return randomFloatRange( self.enemy.gs.min_sniper_burst_delay_time, self.enemy.gs.max_sniper_burst_delay_time ); + else + return randomFloatRange( anim.min_sniper_burst_delay_time, anim.max_sniper_burst_delay_time ); +} + +getRemainingBurstDelayTime() +{ + timeSoFar = ( gettime() - self.a.lastShootTime ) / 1000; + delayTime = getBurstDelayTime(); + if ( delayTime > timeSoFar ) + return delayTime - timeSoFar; + return 0; +} + +getBurstDelayTime() +{ + if ( self usingSidearm() ) + return randomFloatRange( .15, .55 ); + else if ( weapon_pump_action_shotgun() ) + return randomFloatRange( 1.0, 1.7 ); + else if ( self isSniper() ) + return getSniperBurstDelayTime(); + else if ( self.fastBurst ) + return randomFloatRange( .1, .35 ); + else + return randomFloatRange( .4, .9 ); +} + +burstDelay() +{ + if ( self.bulletsInClip ) + { + if ( self.shootStyle == "full" && !self.fastBurst ) + { + if ( self.a.lastShootTime == gettime() ) + wait .05; + return; + } + + delayTime = getRemainingBurstDelayTime(); + if ( delayTime ) + wait delayTime; + } +} + +cheatAmmoIfNecessary() +{ + assert( !self.bulletsInClip ); + + if ( !isdefined( self.enemy ) ) + return false; + + if ( self.team != "allies" ) + { + // cheat and finish off the player if we can. + if ( !isPlayer( self.enemy ) ) + return false; + //if ( self.enemy.health > self.enemy.maxHealth * level.healthOverlayCutoff ) + // return false; + + if ( self.enemy ent_flag( "player_is_invulnerable" ) ) + return false; + } + + if ( usingSidearm() || usingRocketLauncher() ) + return false; + + if ( gettime() - self.ammoCheatTime < self.ammoCheatInterval ) + return false; + + if ( !self canSee( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) > 256*256 ) + return false; + + self.bulletsInClip = int( weaponClipSize( self.weapon ) / 2 ); + + if ( self.bulletsInClip > weaponClipSize( self.weapon ) ) + self.bulletsInClip = weaponClipSize( self.weapon ); + + self.ammoCheatTime = gettime(); + + return true; +} + + +dontShoot_totalTime = 3; +dontShoot_interval = 0.05; +dontShoot_loopCount = dontShoot_totalTime / dontShoot_interval; + +aimButDontShoot() +{ + loopCount = int( dontShoot_loopCount ); + + while ( loopCount > 0 ) + { + assert( !isdefined( self.dontEverShoot ) || self.dontEverShoot != 0 ); + + if ( isdefined( self.dontEverShoot ) || ( isdefined( self.enemy ) && isdefined( self.enemy.dontAttackMe ) ) ) + wait dontShoot_interval; + else + return false; + + loopCount--; + } + + return true; +} + +areAlliesInCone() +{ + // Do a forward cone check to make sure we aren't going to blast the crap out of allies + shoot_from_pos = animscripts\shared::getShootFromPos(); + enemy_sight_pos = self GetEnemyEyePos(); + aim_dir = vectornormalize( enemy_sight_pos - shoot_from_pos ); + + // 0.866 halfcone = cos( 30 ) = 60 degree wide cone + // 0.966 halfcone = cos( 15 ) = 30 degree wide cone + cos_halfcone = 0.966; + if ( self isAllyInCone( shoot_from_pos, aim_dir, cos_halfcone, 512.0 ) ) + { + return true; + } + + return false; +} + + +shootUntilShootBehaviorChange() +{ + self endon( "shoot_behavior_change" ); + self endon( "stopShooting" ); + + if ( self isLongRangeAI() ) + { + if ( isDefined( self.enemy ) && isAI( self.enemy ) && distanceSquared( level._player.origin, self.enemy.origin ) < 384 * 384 ) + self.enemy animscripts\battlechatter_ai::addThreatEvent( "infantry", self, 1.0 ); + + if ( usingRocketLauncher() && isSentient( self.enemy ) ) + wait( randomFloat( 2.0 ) ); + } + + if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) > squared( 400 ) ) + burstCount = randomintrange( 1, 5 ); + else + burstCount = 10; + + while ( 1 ) + { + burstDelay();// waits only if necessary + + // TODO: This sort of logic should really be in shoot_behavior. This thread is meant to be slave to shootent, shootpos, and shootstyle. + if ( aimButDontShoot() ) + break; + + if ( self.shootStyle == "continuous" ) + { + if ( areAlliesInCone() ) + { + // Ally in front of us, don't fire! + wait 1.0; + continue; + } + + self ContinuousFire( animArrayPickRandom( "continuous" ), true, 1.0 ); + self notify( "stop_continuous_fire" ); + } + else if ( self.shootStyle == "full" ) + { + // TODO: get rid of 'stopOnAnimationEnd', makes autofire not work if not enough fire notetracks + self FireUntilOutOfAmmo( animArray( "fire" ), true, animscripts\shared::decideNumShotsForFull() ); + } + else if ( self.shootStyle == "burst" || self.shootStyle == "semi" ) + { + numShots = animscripts\shared::decideNumShotsForBurst(); + + if ( numShots == 1 ) + self FireUntilOutOfAmmo( animArrayPickRandom( "single" ), true, numShots ); + else + self FireUntilOutOfAmmo( animArray( self.shootStyle + numShots ), true, numShots ); + } + else if ( self.shootStyle == "single" ) + { + self FireUntilOutOfAmmo( animArrayPickRandom( "single" ), true, 1 ); + } + else + { + assert( self.shootStyle == "none" ); + self waittill( "hell freezes over" );// waits for the endons to happen + } + + if ( !self.bulletsInClip ) + break; + + burstCount--; + if ( burstCount < 0 ) + { + self.shouldReturnToCover = true; + break; + } + } +} + +getUniqueFlagNameIndex() +{ + anim.animFlagNameIndex++; + return anim.animFlagNameIndex; +} + +setupAim( transTime ) +{ + assert( isDefined( transTime ) ); + + self setAnim( %exposed_aiming, 1, .2 ); + self setAnimKnobLimited( animarray( "straight_level" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_up" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_down" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_left" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_right" ), 1, transTime ); +} + +startFireAndAimIdleThread() +{ + if ( !isdefined( self.a.aimIdleThread ) ) + { + setupAim( 0.2 ); + self thread aimIdleThread(); + self thread animscripts\shared::trackShootEntOrPos(); + } +} + +endFireAndAnimIdleThread() +{ + endAimIdleThread(); + self clearAnim( %add_fire, .1 ); + self notify( "stop tracking" ); +} + +showFireHideAimIdle() +{ + if ( isdefined( self.a.aimIdleThread ) ) + self setAnim( %add_idle, 0, .2 ); + + self setAnim( %add_fire, 1, .1 ); +} + +hideFireShowAimIdle() +{ + if ( isdefined( self.a.aimIdleThread ) ) + self setAnim( %add_idle, 1, .2 ); + + self setAnim( %add_fire, 0, .1 ); +} + +aimIdleThread( lean ) +{ + self endon( "killanimscript" ); + self endon( "end_aim_idle_thread" ); + + if ( isdefined( self.a.aimIdleThread ) ) + return; + self.a.aimIdleThread = true; + + // wait a bit before starting idle since firing will end the idle thread + wait 0.1; + + // this used to be setAnim, but it caused problems with turning on its parent nodes when they were supposed to be off (like during pistol pullout). + self setAnimLimited( %add_idle, 1, .2 ); + + for ( i = 0; ; i++ ) + { + flagname = "idle" + i; + + if ( isdefined( self.a.leanAim ) ) + idleanim = animArrayPickRandom( "lean_idle" ); + else + idleanim = animArrayPickRandom( "exposed_idle" ); + + self setFlaggedAnimKnobLimitedRestart( flagname, idleanim, 1, 0.2 ); + + self waittillmatch( flagname, "end" ); + } + + self clearAnim( %add_idle, .1 ); +} + +endAimIdleThread() +{ + self notify( "end_aim_idle_thread" ); + self.a.aimIdleThread = undefined; + self clearAnim( %add_idle, .1 ); +} + +shotgunFireRate() +{ + if ( weapon_pump_action_shotgun() ) + return 1.0; + + if ( animscripts\weaponList::usingAutomaticWeapon() ) + return animscripts\weaponList::autoShootAnimRate() * 0.7; + + return 0.4; +} + +ContinuousFire( fireAnim, stopOnAnimationEnd, duration ) +{ + animName = "fireAnim_" + getUniqueFlagNameIndex(); + + //prof_begin("FireUntilOutOfAmmo"); + + //self thread maintain_continuous_fire(); + + // reset our accuracy as we aim + maps\_gameskill::resetMissTime(); + + // first, wait until we're aimed right + while ( !aimedAtShootEntOrPos() ) + wait .05; + + //prof_begin("FireUntilOutOfAmmo"); + self showFireHideAimIdle(); + + self setFlaggedAnimKnobRestart( animName, fireAnim, 1, .2, 1.0 ); + + // Update the sight accuracy against the player. Should be called before the volley starts. + self updatePlayerSightAccuracy(); + + //prof_end("FireUntilOutOfAmmo"); + + //FireUntilOutOfAmmoInternal( animName, fireAnim, stopOnAnimationEnd, maxshots ); + + self endon( "enemy" );// stop shooting if our enemy changes, because we have to reset our accuracy and stuff + // stop shooting if the player becomes invulnerable, so we will call resetAccuracyAndPause again + if ( isPlayer( self.enemy ) && ( self.shootStyle == "full" || self.shootStyle == "semi" ) ) + level endon( "player_becoming_invulnerable" ); + + self thread NotifyOnAnimEnd( animName, "fireAnimEnd" ); + self endon( "fireAnimEnd" ); + + + if ( cointoss() ) + { + // Miss! Set new pos + //self.shootPos = self.shootPos + randomvector( 50 ); + } + + self.shootEnt = undefined; + self StartContinuousFire(); + self thread StopContinuousFireOnExit(); + + if ( animHasNoteTrack( fireAnim, "fire_continuous_start" ) && animHasNoteTrack( fireAnim, "fire_continuous_end" ) ) + { + self waittillmatch( animName, "fire_continuous_start" ); + self thread StopContinuousFireOnNoteTrack( animName ); + } + + while ( 1 ) + { + if ( !self.bulletsInClip ) + { + if ( !cheatAmmoIfNecessary() ) + { + break; + } + } + if ( !aimedAtShootEntOrPos()) + { + break; + } + + //self shootAtShootEntOrPos(); + + self.a.lastShootTime = gettime(); + + // set accuracy at time of shoot rather than in a separate thread that is vulnerable to timing issues + maps\_gameskill::set_accuracy_based_on_situation(); + + self animscripts\shared::fire_straight(); + + wait 0.05; + } + + self hideFireShowAimIdle(); +} + +StopContinuousFireOnExit() +{ + waittill_any( "stop_continuous_fire", "shoot_behavior_change", "stopShooting", "killanimscript", "stop_deciding_how_to_shoot" ); + self StopContinuousFire(); +} + +StopContinuousFireOnNoteTrack( animName ) +{ + self waittillmatch( animName, "fire_continuous_end" ); + self notify( "stop_continuous_fire" ); +} + + +FireUntilOutOfAmmo( fireAnim, stopOnAnimationEnd, maxshots ) +{ + animName = "fireAnim_" + getUniqueFlagNameIndex(); + + //prof_begin("FireUntilOutOfAmmo"); + + // reset our accuracy as we aim + maps\_gameskill::resetMissTime(); + + // first, wait until we're aimed right + while ( !aimedAtShootEntOrPos() ) + wait .05; + + //prof_begin("FireUntilOutOfAmmo"); + self showFireHideAimIdle(); + + rate = 1.0; + if ( isdefined( self.shootRateOverride ) ) + rate = self.shootRateOverride; + else if ( self.shootStyle == "full" ) + rate = animscripts\weaponList::autoShootAnimRate() * randomfloatrange( 0.5, 1.0 ); + else if ( self.shootStyle == "burst" ) + rate = animscripts\weaponList::burstShootAnimRate(); + else if ( usingSidearm() ) + rate = 3.0; + else if ( usingShotgun() ) + rate = shotgunFireRate(); + + self setFlaggedAnimKnobRestart( animName, fireAnim, 1, .2, rate ); + + // Update the sight accuracy against the player. Should be called before the volley starts. + self updatePlayerSightAccuracy(); + + //prof_end("FireUntilOutOfAmmo"); + + FireUntilOutOfAmmoInternal( animName, fireAnim, stopOnAnimationEnd, maxshots ); + + self hideFireShowAimIdle(); +} + +FireUntilOutOfAmmoInternal( animName, fireAnim, stopOnAnimationEnd, maxshots ) +{ + self endon( "enemy" );// stop shooting if our enemy changes, because we have to reset our accuracy and stuff + // stop shooting if the player becomes invulnerable, so we will call resetAccuracyAndPause again + if ( isPlayer( self.enemy ) && ( self.shootStyle == "full" || self.shootStyle == "semi" ) ) + level endon( "player_becoming_invulnerable" ); + + if ( stopOnAnimationEnd ) + { + self thread NotifyOnAnimEnd( animName, "fireAnimEnd" ); + self endon( "fireAnimEnd" ); + } + + if ( !isdefined( maxshots ) ) + maxshots = -1; + + numshots = 0; + + hasFireNotetrack = animHasNoteTrack( fireAnim, "fire" ); + + usingRocketLauncher = ( weaponClass( self.weapon ) == "rocketlauncher" ); + + while ( numshots < maxshots && maxshots > 0 ) // note: maxshots == -1 if no limit + { +// prof_begin("FireUntilOutOfAmmoInternal"); + + if ( hasFireNotetrack ) + self waittillmatch( animName, "fire" ); + + if ( !self.bulletsInClip ) + { + if ( !cheatAmmoIfNecessary() ) + break; + } + + if ( !aimedAtShootEntOrPos() ) + break; + + self shootAtShootEntOrPos(); + + assertex( self.bulletsInClip >= 0, self.bulletsInClip ); + if ( isPlayer( self.enemy ) && self.enemy ent_flag( "player_is_invulnerable" ) ) + { + if ( randomint( 3 ) == 0 ) + self.bulletsInClip -- ; + } + else + { + self.bulletsInClip -- ; + } + + if ( usingRocketLauncher ) + { + self.a.rockets -- ; + if ( self.weapon == "rpg" ) + { + self hidepart( "tag_rocket" ); + self.a.rocketVisible = false; + } + } + + numshots++; + + self thread shotgunPumpSound( animName ); + + if ( self.fastBurst && numshots == maxshots ) + break; + +// prof_end("FireUntilOutOfAmmoInternal"); + + if ( !hasFireNotetrack || (maxShots == 1 && self.shootStyle == "single") ) + self waittillmatch( animName, "end" ); + } + + if ( stopOnAnimationEnd ) + self notify( "fireAnimEnd" );// stops NotifyOnAnimEnd() +} + +streamgun_check_cone_for_allies() +{ + // Check + + + //prof_begin( "aimedAtShootEntOrPos" ); + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + + //prof_end( "aimedAtShootEntOrPos" ); + return true; + } + + weaponAngles = self getMuzzleAngle(); + shootFromPos = animscripts\shared::getShootFromPos(); + + anglesToShootPos = vectorToAngles( self.shootPos - shootFromPos ); + + absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] ); + if ( absyawdiff > anim.aimYawDiffFarTolerance ) + { + if ( distanceSquared( self getEye(), self.shootPos ) > anim.aimYawDiffCloseDistSQ || absyawdiff > anim.aimYawDiffCloseTolerance ) + { + //prof_end( "aimedAtShootEntOrPos" ); + return false; + } + } + + //prof_end( "aimedAtShootEntOrPos" ); + return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= anim.aimPitchDiffTolerance; +} + +aimed_outside_of_yaw_tolerance() +{ + //prof_begin( "aimedAtShootEntOrPos" ); + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + + //prof_end( "aimedAtShootEntOrPos" ); + return true; + } + + weaponAngles = self getMuzzleAngle(); + shootFromPos = animscripts\shared::getShootFromPos(); + + anglesToShootPos = vectorToAngles( self.shootPos - shootFromPos ); + + absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] ); + if ( absyawdiff > anim.aimYawDiffFarTolerance ) + { + if ( distanceSquared( self getEye(), self.shootPos ) > anim.aimYawDiffCloseDistSQ || absyawdiff > anim.aimYawDiffCloseTolerance ) + { + //prof_end( "aimedAtShootEntOrPos" ); + return false; + } + } + + //prof_end( "aimedAtShootEntOrPos" ); + return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= anim.aimPitchDiffTolerance; +} + +aimedAtShootEntOrPos() +{ + //prof_begin( "aimedAtShootEntOrPos" ); + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + + //prof_end( "aimedAtShootEntOrPos" ); + return true; + } + + weaponAngles = self getMuzzleAngle(); + shootFromPos = animscripts\shared::getShootFromPos(); + + anglesToShootPos = vectorToAngles( self.shootPos - shootFromPos ); + + absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] ); + if ( absyawdiff > anim.aimYawDiffFarTolerance ) + { + if ( distanceSquared( self getEye(), self.shootPos ) > anim.aimYawDiffCloseDistSQ || absyawdiff > anim.aimYawDiffCloseTolerance ) + { + //prof_end( "aimedAtShootEntOrPos" ); + return false; + } + } + + //prof_end( "aimedAtShootEntOrPos" ); + return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= anim.aimPitchDiffTolerance; +} + +NotifyOnAnimEnd( animNotify, endNotify ) +{ + self endon( "killanimscript" ); + self endon( endNotify ); + self waittillmatch( animNotify, "end" ); + self notify( endNotify ); +} + +shootAtShootEntOrPos() +{ + //prof_begin("shootAtShootEntOrPos"); + + if ( isdefined( self.shootEnt ) ) + { + if ( isDefined( self.enemy ) && self.shootEnt == self.enemy ) + self shootEnemyWrapper(); + + // it's possible that shootEnt isn't our enemy, which was probably caused by our enemy changing but shootEnt not being updated yet. + // we don't want to shoot directly at shootEnt because if our accuracy is 0 we shouldn't hit it perfectly. + // In retrospect, the existance of self.shootEnt was a bad idea and self.enemy should probably have just been used. + //else + // self shootPosWrapper( self.shootEnt getShootAtPos() ); + } + else + { + // if self.shootPos isn't defined, "shoot_behavior_change" should + // have been notified and we shouldn't be firing anymore + assert( isdefined( self.shootPos ) ); + + self shootPosWrapper( self.shootPos ); + } + + //prof_end("shootAtShootEntOrPos"); +} + +showRocket() +{ + if ( self.weapon != "rpg" ) + return; + + self.a.rocketVisible = true; + self showpart("tag_rocket"); + self notify( "showing_rocket" ); +} + +showRocketWhenReloadIsDone() +{ + if ( self.weapon != "rpg" ) + return; + + self endon( "death" ); + self endon( "showing_rocket" ); + self waittill( "killanimscript" ); + + self showRocket(); +} + +decrementBulletsInClip() +{ + // we allow this to happen even when bulletsinclip is zero, + // because sometimes we want to shoot even if we're out of ammo, + // like when we've already started a blind fire animation. + if ( self.bulletsInClip ) + self.bulletsInClip -- ; +} + +shotgunPumpSound( animName ) +{ + if ( !weapon_pump_action_shotgun() ) + return; + + self endon( "killanimscript" ); + + self notify( "shotgun_pump_sound_end" ); + self endon( "shotgun_pump_sound_end" ); + + self thread stopShotgunPumpAfterTime( 2.0 ); + + self waittillmatch( animName, "rechamber" ); + + self playSound( "ai_shotgun_pump" ); + + self notify( "shotgun_pump_sound_end" ); +} + +stopShotgunPumpAfterTime( timer ) +{ + self endon( "killanimscript" ); + self endon( "shotgun_pump_sound_end" ); + wait timer; + self notify( "shotgun_pump_sound_end" ); +} + +// Rechambers the weapon if appropriate +Rechamber( isExposed ) +{ + // obsolete... +} + +// Returns true if character has less than thresholdFraction of his total bullets in his clip. Thus, a value +// of 1 would always reload, 0 would only reload on an empty clip. +NeedToReload( thresholdFraction ) +{ + if ( self.weapon == "none" ) + return false; + + if ( isdefined( self.noreload ) || self.weaponInfo[ self.weapon ].recharges ) + { + if ( self.bulletsinclip < weaponClipSize( self.weapon ) * 0.5 ) + self.bulletsinclip = int( weaponClipSize( self.weapon ) * 0.5 ); + if ( self.bulletsinclip <= 0 ) + self.bulletsinclip = 0; + return false; + } + + if ( self.bulletsInClip <= weaponClipSize( self.weapon ) * thresholdFraction ) + { + if ( thresholdFraction == 0 ) + { + if ( cheatAmmoIfNecessary() ) + return false; + } + + return true; + } + return false; +} + +// Put the gun back in the AI's hand if he cuts off his weapon throw down animation +putGunBackInHandOnKillAnimScript() +{ + self endon( "weapon_switch_done" ); + self endon( "death" ); + + self waittill( "killanimscript" ); + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); +} + +Reload( thresholdFraction, optionalAnimation ) +{ + self endon( "killanimscript" ); + + if ( !NeedToReload( thresholdFraction ) ) + return false; + + //prof_begin( "Reload" ); + + self animscripts\battleChatter_ai::evaluateReloadEvent(); + self animscripts\battleChatter::playBattleChatter(); + + if ( isDefined( optionalAnimation ) ) + { + self setFlaggedAnimKnobAll( "reloadanim", optionalAnimation, %body, 1, .1, 1 ); + animscripts\shared::DoNoteTracks( "reloadanim" ); + self animscripts\weaponList::RefillClip(); // This should be in the animation as a notetrack in theory. + self.a.needsToRechamber = 0; + } + else + { + if ( self.a.pose == "prone" ) + { + self setFlaggedAnimKnobAll( "reloadanim", %prone_reload, %body, 1, .1, 1 ); + self UpdateProne( %prone_legs_up, %prone_legs_down, 1, 0.1, 1 ); + } + else + { + println( "Bad anim_pose in combat::Reload" ); + //prof_end( "Reload" ); + wait 2; + return; + } + animscripts\shared::DoNoteTracks( "reloadanim" ); + animscripts\weaponList::RefillClip(); // This should be in the animation as a notetrack in most instances. + self.a.needsToRechamber = 0; + } + + //prof_end( "Reload" ); + return true; +} + +getGrenadeThrowOffset( throwAnim ) +{ + //prof_begin( "getGrenadeThrowOffset" ); + offset = ( 0, 0, 64 ); + + if ( isdefined( throwAnim ) ) + { + foreach( index, grenadeThrowAnim in anim.grenadeThrowAnims ) + { + if ( throwAnim == grenadeThrowAnim ) + { + offset = anim.grenadeThrowOffsets[ index ]; + break; + } + } + } + + if ( offset[ 2 ] == 64 ) + { + if ( isdefined( throwAnim ) ) + println( "^1Warning: undefined grenade throw animation used; hand offset unknown" ); + else + println( "^1Warning: grenade throw animation ", throwAnim, " has no recorded hand offset" ); + } + + //prof_end( "getGrenadeThrowOffset" ); + return offset; +} + +// this function is called from maps\_utility::ThrowGrenadeAtPlayerASAP +ThrowGrenadeAtPlayerASAP_combat_utility() +{ + assert( self isBadGuy() ); + + for ( i = 0; i < level._players.size; i++ ) + { + if ( level._players[ i ].numGrenadesInProgressTowardsPlayer == 0 ) + { + level._players[ i ].grenadeTimers[ "fraggrenade" ] = 0; + level._players[ i ].grenadeTimers[ "flash_grenade" ] = 0; + } + } + anim.throwGrenadeAtPlayerASAP = true; + + /# + enemies = getaiarray( "bad_guys" ); + if ( enemies.size == 0 ) + return; + numwithgrenades = 0; + for ( i = 0; i < enemies.size; i++ ) + { + if ( enemies[ i ].grenadeammo > 0 ) + return; + } + println( "^1Warning: called ThrowGrenadeAtPlayerASAP, but no enemies have any grenadeammo!" ); + #/ +} + +setActiveGrenadeTimer( throwingAt ) +{ + self.activeGrenadeTimer = spawnstruct(); + if ( isPlayer( throwingAt ) ) + { + self.activeGrenadeTimer.isPlayerTimer = true; + self.activeGrenadeTimer.player = throwingAt; + self.activeGrenadeTimer.timerName = self.grenadeWeapon; + assertex( isdefined( throwingAt.grenadeTimers[ self.activeGrenadeTimer.timerName ] ), "No grenade timer for " + self.activeGrenadeTimer.timerName ); + } + else + { + self.activeGrenadeTimer.isPlayerTimer = false; + self.activeGrenadeTimer.timerName = "AI_" + self.grenadeWeapon; + assertex( isdefined( anim.grenadeTimers[ self.activeGrenadeTimer.timerName ] ), "No grenade timer for " + self.activeGrenadeTimer.timerName ); + } +} + +usingPlayerGrenadeTimer() +{ + assert( isDefined( self.activeGrenadeTimer ) ); + return self.activeGrenadeTimer.isPlayerTimer; +} + +setGrenadeTimer( grenadeTimer, newValue ) +{ + if ( grenadeTimer.isPlayerTimer ) + { + player = grenadeTimer.player; + oldValue = player.grenadeTimers[ grenadeTimer.timerName ]; + player.grenadeTimers[ grenadeTimer.timerName ] = max( newValue, oldValue ); + } + else + { + oldValue = anim.grenadeTimers[ grenadeTimer.timerName ]; + anim.grenadeTimers[ grenadeTimer.timerName ] = max( newValue, oldValue ); + } +} + +getDesiredGrenadeTimerValue() +{ + nextGrenadeTimeToUse = undefined; + if ( self usingPlayerGrenadeTimer() ) + { + player = self.activeGrenadeTimer.player; + nextGrenadeTimeToUse = gettime() + player.gs.playerGrenadeBaseTime + randomint( player.gs.playerGrenadeRangeTime ); + } + else + { + nextGrenadeTimeToUse = gettime() + 30000 + randomint( 30000 ); + } + return nextGrenadeTimeToUse; +} + +getGrenadeTimerTime( grenadeTimer ) +{ + if ( grenadeTimer.isPlayerTimer ) + { + return grenadeTimer.player.grenadeTimers[ grenadeTimer.timerName ]; + } + else + { + return anim.grenadeTimers[ grenadeTimer.timerName ]; + } +} + +considerChangingTarget( throwingAt ) +{ + //prof_begin( "considerChangingTarget" ); + + if ( !isPlayer( throwingAt ) && self isBadGuy() ) + { + if ( gettime() < getGrenadeTimerTime( self.activeGrenadeTimer ) ) + { + if ( level._player.ignoreme ) + { + //prof_end( "considerChangingTarget" ); + return throwingAt; + } + + // check if player threatbias is set to be ignored by self + myGroup = self getthreatbiasgroup(); + playerGroup = level._player getthreatbiasgroup(); + + if ( myGroup != "" && playerGroup != "" && getThreatBias( playerGroup, myGroup ) < - 10000 ) + { + //prof_end( "considerChangingTarget" ); + return throwingAt; + } + + + // can't throw at an AI right now anyway. + // check if the player is an acceptable target (be careful not to be aware of him when we wouldn't know about him) + if ( self canSee( level._player ) || ( isAI( throwingAt ) && throwingAt canSee( level._player ) ) ) + { + if ( isdefined( self.covernode ) ) + { + angles = VectorToAngles( level._player.origin - self.origin ); + yawDiff = AngleClamp180( self.covernode.angles[ 1 ] - angles[ 1 ] ); + } + else + { + yawDiff = self GetYawToSpot( level._player.origin ); + } + + if ( abs( yawDiff ) < 60 ) + { + throwingAt = level._player; + self setActiveGrenadeTimer( throwingAt ); + } + } + } + } + + //prof_end( "considerChangingTarget" ); + return throwingAt; +} + +// a "double" grenade is when 2 grenades land at the player's feet at once. +// we do this sometimes on harder difficulty modes. +mayThrowDoubleGrenade( throwingAt ) +{ + assert( self.activeGrenadeTimer.isPlayerTimer ); + assert( self.activeGrenadeTimer.timerName == "fraggrenade" ); + assert( isPlayer( throwingAt ) ); + + if ( player_died_recently() ) + return false; + + if ( !throwingAt.gs.double_grenades_allowed ) + return false; + + time = gettime(); + + // if it hasn't been long enough since the last double grenade, don't do it + if ( time < throwingAt.grenadeTimers[ "double_grenade" ] ) + return false; + + // if no one's started throwing a grenade recently, we can't do it + if ( time > throwingAt.lastFragGrenadeToPlayerStart + 3000 ) + return false; + + // stagger double grenades by 0.5 sec + if ( time < throwingAt.lastFragGrenadeToPlayerStart + 500 ) + return false; + + return throwingAt.numGrenadesInProgressTowardsPlayer < 2; +} + +myGrenadeCoolDownElapsed() +{ + // this should be as fast as possible; put slow checks in grenadeCoolDownElapsed + return( gettime() >= self.a.nextGrenadeTryTime ); +} + +grenadeCoolDownElapsed( throwingAt ) +{ + if ( player_died_recently() ) + return false; + + if ( self.script_forcegrenade == 1 ) + return true; + + // If we are at a node with script_forcegrenade defined, there is no grenade cooldown + if ( isdefined( self.node ) && isdefined( self.node.script_forcegrenade ) && self.node.script_forcegrenade == 1 ) + return true; + + if ( !myGrenadeCoolDownElapsed() ) + return false; + + if ( gettime() >= getGrenadeTimerTime( self.activeGrenadeTimer ) ) + return true; + + if ( self.activeGrenadeTimer.isPlayerTimer && self.activeGrenadeTimer.timerName == "fraggrenade" ) + return mayThrowDoubleGrenade( throwingAt ); + + return false; +} + + /# +getGrenadeTimerDebugName( grenadeTimer ) +{ + if ( grenadeTimer.isPlayerTimer ) + { + for ( i = 0; i < level._players.size; i++ ) + { + if ( level._players[ i ] == grenadeTimer.player ) + break; + } + return "Player " + ( i + 1 ) + " " + grenadeTimer.timerName; + } + else + { + return "AI " + grenadeTimer.timerName; + } +} + +printGrenadeTimers() +{ + level notify( "stop_printing_grenade_timers" ); + level endon( "stop_printing_grenade_timers" ); + + x = 40; + y = 40; + + level._grenadeTimerHudElem = []; + + level._grenadeDebugTimers = []; + keys = getArrayKeys( anim.grenadeTimers ); + for ( i = 0; i < keys.size; i++ ) + { + timer = spawnstruct(); + timer.isPlayerTimer = false; + timer.timerName = keys[ i ]; + level._grenadeDebugTimers[ level._grenadeDebugTimers.size ] = timer; + } + for ( i = 0; i < level._players.size; i++ ) + { + player = level._players[ i ]; + keys = getArrayKeys( player.grenadeTimers ); + for ( j = 0; j < keys.size; j++ ) + { + timer = spawnstruct(); + timer.isPlayerTimer = true; + timer.player = player; + timer.timerName = keys[ j ]; + level._grenadeDebugTimers[ level._grenadeDebugTimers.size ] = timer; + } + } + + for ( i = 0; i < level._grenadeDebugTimers.size; i++ ) + { + textelem = newHudElem(); + textelem.x = x; + textelem.y = y; + textelem.alignX = "left"; + textelem.alignY = "top"; + textelem.horzAlign = "fullscreen"; + textelem.vertAlign = "fullscreen"; + textelem setText( getGrenadeTimerDebugName( level._grenadeDebugTimers[ i ] ) ); + + bar = newHudElem(); + bar.x = x + 110; + bar.y = y + 2; + bar.alignX = "left"; + bar.alignY = "top"; + bar.horzAlign = "fullscreen"; + bar.vertAlign = "fullscreen"; + bar setshader( "black", 1, 8 ); + + textelem.bar = bar; + + y += 10; + + level._grenadeDebugTimers[ i ].textelem = textelem; + } + + while ( 1 ) + { + wait .05; + + for ( i = 0; i < level._grenadeDebugTimers.size; i++ ) + { + timeleft = ( getGrenadeTimerTime( level._grenadeDebugTimers[ i ] ) - gettime() ) / 1000; + + width = max( timeleft * 4, 1 ); + width = int( width ); + + bar = level._grenadeDebugTimers[ i ].textelem.bar; + bar setShader( "black", width, 8 ); + } + } +} + +destroyGrenadeTimers() +{ + if ( !isdefined( level._grenadeDebugTimers ) ) + return; + for ( i = 0; i < level._grenadeDebugTimers.size; i++ ) + { + level._grenadeDebugTimers[ i ].textelem.bar destroy(); + level._grenadeDebugTimers[ i ].textelem destroy(); + } + level._grenadeDebugTimers = undefined; +} + +grenadeTimerDebug() +{ + setDvarIfUninitialized( "scr_grenade_debug", "0" ); + + while ( 1 ) + { + while ( 1 ) + { + if ( getdebugdvar( "scr_grenade_debug" ) == "1" ) + break; + wait .5; + } + thread printGrenadeTimers(); + while ( 1 ) + { + if ( getdebugdvar( "scr_grenade_debug" ) != "1" ) + break; + wait .5; + } + level notify( "stop_printing_grenade_timers" ); + destroyGrenadeTimers(); + } +} + +grenadeDebug( state, duration, showMissReason ) +{ + if ( getdebugdvar( "scr_grenade_debug" ) != "1" ) + return; + + self notify( "grenade_debug" ); + self endon( "grenade_debug" ); + self endon( "killanimscript" ); + self endon( "death" ); + endtime = gettime() + 1000 * duration; + + while ( gettime() < endtime ) + { + if ( !isDefined( self ) ) + { + return; + } + print3d( self getShootAtPos() + ( 0, 0, 10 ), state ); + if ( isdefined( showMissReason ) && isdefined( self.grenadeMissReason ) ) + print3d( self getShootAtPos() + ( 0, 0, 0 ), "Failed: " + self.grenadeMissReason ); + else if ( isdefined( self.activeGrenadeTimer ) ) + print3d( self getShootAtPos() + ( 0, 0, 0 ), "Timer: " + getGrenadeTimerDebugName( self.activeGrenadeTimer ) ); + wait .05; + } +} + +setGrenadeMissReason( reason ) +{ + if ( getdebugdvar( "scr_grenade_debug" ) != "1" ) + return; + self.grenadeMissReason = reason; +} +#/ + +TryGrenadePosProc( throwingAt, destination, optionalAnimation, armOffset ) +{ + // Dont throw a grenade right near you or your buddies + if ( !( self isGrenadePosSafe( throwingAt, destination ) ) ) + return false; + else if ( distanceSquared( self.origin, destination ) < 200 * 200 ) + return false; + + //prof_begin( "TryGrenadePosProc" ); + + trace = physicsTrace( destination + ( 0, 0, 1 ), destination + ( 0, 0, -500 ) ); + if ( trace == destination + ( 0, 0, -500 ) ) + return false; + trace += ( 0, 0, .1 );// ensure just above ground + + //prof_end( "TryGrenadePosProc" ); + + return TryGrenadeThrow( throwingAt, trace, optionalAnimation, armOffset ); +} + +TryGrenade( throwingAt, optionalAnimation ) +{ + if ( self.weapon == "mg42" || self.grenadeammo <= 0 ) + return false; + + self setActiveGrenadeTimer( throwingAt ); + + throwingAt = considerChangingTarget( throwingAt ); + + if ( !grenadeCoolDownElapsed( throwingAt ) ) + return false; + + // tagJW: Do not throw grenades at enemy vehicles + if ( !isAI( throwingAt ) && !isPlayer( throwingAt ) ) + return false; + + /# + self thread grenadeDebug( "Tried grenade throw", 4, true ); + #/ + + armOffset = getGrenadeThrowOffset( optionalAnimation ); + + if ( isdefined( self.enemy ) && throwingAt == self.enemy ) + { + if ( !checkGrenadeThrowDist() ) + { + /# self setGrenadeMissReason( "Too close or too far" ); #/ + return false; + } + + if ( isPlayer( self.enemy ) && self.enemy isPlayerDown() ) + { + /# self setGrenadeMissReason( "Enemy is downed player" ); #/ + return false; + } + + if ( self canSeeEnemyFromExposed() ) + { + if ( !( self isGrenadePosSafe( throwingAt, throwingAt.origin ) ) ) + { + /# self setGrenadeMissReason( "Teammates near target" ); #/ + return false; + } + return TryGrenadeThrow( throwingAt, undefined, optionalAnimation, armOffset ); + } + else if ( self canSuppressEnemyFromExposed() ) + { + return TryGrenadePosProc( throwingAt, self getEnemySightPos(), optionalAnimation, armOffset ); + } + else + { + // hopefully we can get through a grenade hint or something + if ( !( self isGrenadePosSafe( throwingAt, throwingAt.origin ) ) ) + { + /# self setGrenadeMissReason( "Teammates near target" ); #/ + return false; + } + return TryGrenadeThrow( throwingAt, undefined, optionalAnimation, armOffset ); + } + + /# self setGrenadeMissReason( "Don't know where to throw" ); #/ + return false;// didn't know where to throw! + } + else + { + return TryGrenadePosProc( throwingAt, throwingAt.origin, optionalAnimation, armOffset ); + } +} + +TryGrenadeThrow( throwingAt, destination, optionalAnimation, armOffset, fastThrow, withBounce, throwInThread ) +{ + // no AI grenade throws in the first 10 seconds, bad during black screen + if ( gettime() < 10000 && !isdefined( level._ignoreGrenadeSafeTime ) ) + { + /# self setGrenadeMissReason( "First 10 seconds of game" ); #/ + return false; + } + + if ( !isdefined( withBounce ) ) + withBounce = true; + + //prof_begin( "TryGrenadeThrow" ); + + if ( isDefined( optionalAnimation ) ) + { + throw_anim = optionalAnimation; + // Assume armOffset and gunHand are defined whenever optionalAnimation is. + gunHand = self.a.gunHand; // Actually we don't want gunhand in this case. We rely on notetracks. + } + else + { + switch( self.a.special ) + { + case "cover_crouch": + case "none": + if ( self.a.pose == "stand" ) + { + armOffset = ( 0, 0, 80 ); + throw_anim = %stand_grenade_throw; + } + else// if ( self.a.pose == "crouch" ) + { + armOffset = ( 0, 0, 65 ); + throw_anim = %crouch_grenade_throw; + } + gunHand = "left"; + break; + default:// Do nothing - we don't have an appropriate throw animation. + throw_anim = undefined; + gunHand = undefined; + break; + } + } + + // If we don't have an animation, we can't throw the grenade. + if ( !isDefined( throw_anim ) ) + { + //prof_end( "TryGrenadeThrow" ); + return( false ); + } + + if ( isdefined( destination ) )// Now try to throw it. + { + if ( !isdefined( fastThrow ) ) + throwvel = self checkGrenadeThrowPos( armOffset, destination, withBounce, "min energy", "min time", "max time" ); + else + throwvel = self checkGrenadeThrowPos( armOffset, destination, withBounce, "min time", "min energy" ); + } + else + { + randomRange = self.randomGrenadeRange; + // scale down random range as target gets closer to avoid crazy sideways throws + dist = distance( throwingAt.origin, self.origin ); + if ( dist < 800 ) + { + if ( dist < 256 ) + randomRange = 0; + else + randomRange *= (dist - 256) / (800 - 256); + } + + assert( self.enemy == throwingAt ); + if ( !isdefined( fastThrow ) ) + throwvel = self checkGrenadeThrow( armOffset, randomRange, "min energy", "min time", "max time" ); + else + throwvel = self checkGrenadeThrow( armOffset, randomRange, "min time", "min energy" ); + } + + // the grenade checks are slow. don't do it too often. + self.a.nextGrenadeTryTime = gettime() + randomintrange( 1000, 2000 ); + + if ( isdefined( throwvel ) ) + { + if ( !isdefined( self.oldGrenAwareness ) ) + self.oldGrenAwareness = self.grenadeawareness; + self.grenadeawareness = 0;// so we dont respond to nearby grenades while throwing one + + /# + if ( getdebugdvar( "anim_debug" ) == "1" ) + thread animscripts\utility::debugPos( destination, "O" ); + #/ + + // remember the time we want to delay any future grenade throws to, to avoid throwing too many. + // however, for now, only set the timer far enough in the future that it will expire when we throw the grenade. + // that way, if the throw fails (maybe due to killanimscript), we'll try again soon. + nextGrenadeTimeToUse = self getDesiredGrenadeTimerValue(); + setGrenadeTimer( self.activeGrenadeTimer, min( gettime() + 3000, nextGrenadeTimeToUse ) ); + + secondGrenadeOfDouble = false; + if ( self usingPlayerGrenadeTimer() ) + { + assert( throwingAt == self.activeGrenadeTimer.player ); + throwingAt.numGrenadesInProgressTowardsPlayer++ ; + self thread reduceGIPTPOnKillanimscript( throwingAt ); + if ( throwingAt.numGrenadesInProgressTowardsPlayer > 1 ) + secondGrenadeOfDouble = true; + + if ( self.activeGrenadeTimer.timerName == "fraggrenade" ) + { + if ( throwingAt.numGrenadesInProgressTowardsPlayer <= 1 ) + throwingAt.lastFragGrenadeToPlayerStart = gettime(); + } + } + + /# + if ( getdvar( "grenade_spam" ) == "on" ) + nextGrenadeTimeToUse = 0; + #/ + + //prof_end( "TryGrenadeThrow" ); + if ( isdefined( throwInThread ) ) + thread DoGrenadeThrow( throw_anim, throwVel, nextGrenadeTimeToUse, secondGrenadeOfDouble, throwingAt ); + else + DoGrenadeThrow( throw_anim, throwVel, nextGrenadeTimeToUse, secondGrenadeOfDouble, throwingAt ); + + return true; + } + else + { + /# self setGrenadeMissReason( "Couldn't find trajectory" ); #/ + /# + if ( getdebugdvar( "debug_grenademiss" ) == "on" && isdefined( destination ) ) + thread grenadeLine( armoffset, destination ); + #/ + } + + //prof_end( "TryGrenadeThrow" ); + return false; +} + +reduceGIPTPOnKillanimscript( throwingAt ) +{ + self endon( "dont_reduce_giptp_on_killanimscript" ); + self waittill( "killanimscript" ); + throwingAt.numGrenadesInProgressTowardsPlayer -- ; +} + +DoGrenadeThrow( throw_anim, throwVel, nextGrenadeTimeToUse, secondGrenadeOfDouble, throwingAt ) +{ + self endon( "killanimscript" ); + /# + self thread grenadeDebug( "Starting throw", 3 ); + #/ + + //prof_begin( "DoGrenadeThrow" ); + + if ( self.script == "combat" || self.script == "move" ) + self orientmode( "face direction", throwVel ); + + self animscripts\battleChatter_ai::evaluateAttackEvent( self.grenadeWeapon ); + self notify( "stop_aiming_at_enemy" ); + self SetFlaggedAnimKnobAllRestart( "throwanim", throw_anim, %body, fasterAnimSpeed(), 0.1, 1 ); + + self thread animscripts\shared::DoNoteTracksForever( "throwanim", "killanimscript" ); + + //prof_begin( "DoGrenadeThrow" ); + + model = getGrenadeModel(); + + attachside = "none"; + for ( ;; ) + { + self waittill( "throwanim", notetrack ); + //prof_begin( "DoGrenadeThrow" ); + if ( notetrack == "grenade_left" || notetrack == "grenade_right" ) + { + attachside = attachGrenadeModel( model, "TAG_INHAND" ); + self.isHoldingGrenade = true; + } + if ( notetrack == "grenade_throw" || notetrack == "grenade throw" ) + break; + assert( notetrack != "end" );// we shouldn't hit "end" until after we've hit "grenade_throw"! + if ( notetrack == "end" )// failsafe + { + self.activeGrenadeTimer.player.numGrenadesInProgressTowardsPlayer -- ; + self notify( "dont_reduce_giptp_on_killanimscript" ); + //prof_end( "DoGrenadeThrow" ); + return false; + } + } + + /# + if ( getdebugdvar( "debug_grenadehand" ) == "on" ) + { + tags = []; + numTags = self getAttachSize(); + emptySlot = []; + for ( i = 0;i < numTags;i++ ) + { + name = self getAttachModelName( i ); + if ( issubstr( name, "weapon" ) ) + { + tagName = self getAttachTagname( i ); + emptySlot[ tagname ] = 0; + tags[ tags.size ] = tagName; + } + } + + for ( i = 0;i < tags.size;i++ ) + { + emptySlot[ tags[ i ] ]++ ; + if ( emptySlot[ tags[ i ] ] < 2 ) + continue; + iprintlnbold( "Grenade throw needs fixing (check console)" ); + println( "Grenade throw animation ", throw_anim, " has multiple weapons attached to ", tags[ i ] ); + break; + } + } + #/ + + /# + self thread grenadeDebug( "Threw", 5 ); + #/ + + self notify( "dont_reduce_giptp_on_killanimscript" ); + + if ( self usingPlayerGrenadeTimer() ) + { + // give the grenade some time to get to the player. + // if it gets there, we'll reset the timer so we don't throw any more in a while. + self thread watchGrenadeTowardsPlayer( self.activeGrenadeTimer.player, nextGrenadeTimeToUse ); + } + + self throwGrenade( throwingAt ); + + if ( !self usingPlayerGrenadeTimer() ) + { + setGrenadeTimer( self.activeGrenadeTimer, nextGrenadeTimeToUse ); + } + + if ( secondGrenadeOfDouble ) + { + assert( self.activeGrenadeTimer.isPlayerTimer ); + player = self.activeGrenadeTimer.player; + assert( isPlayer( player ) ); + if ( player.numGrenadesInProgressTowardsPlayer > 1 || gettime() - player.lastGrenadeLandedNearPlayerTime < 2000 ) + { + // two grenades in progress toward player. give them time to arrive. + player.grenadeTimers[ "double_grenade" ] = gettime() + min( 5000, player.gs.playerDoubleGrenadeTime ); + } + } + + self notify( "stop grenade check" ); + +// assert (attachSide != "none"); + if ( attachSide != "none" ) + self detach( model, attachside ); + else + { + print( "No grenade hand set: " ); + println( throw_anim ); + println( "animation in console does not specify grenade hand" ); + } + self.isHoldingGrenade = undefined; + + self.grenadeawareness = self.oldGrenAwareness; + self.oldGrenAwareness = undefined; + + //prof_end( "DoGrenadeThrow" ); + + self waittillmatch( "throwanim", "end" ); + self notify( "done_grenade_throw" ); + self notify( "weapon_switch_done" ); + // modern + + // TODO: why is this here? why are we assuming that the calling function wants these particular animnodes turned on? + self setanim( %exposed_modern, 1, .2 ); + self setanim( %exposed_aiming, 1 ); + self clearanim( throw_anim, .2 ); +} + +watchGrenadeTowardsPlayer( player, nextGrenadeTimeToUse ) +{ + player endon( "death" ); + + watchGrenadeTowardsPlayerInternal( nextGrenadeTimeToUse ); + player.numGrenadesInProgressTowardsPlayer -- ; +} + +watchGrenadeTowardsPlayerInternal( nextGrenadeTimeToUse ) +{ + // give the grenade at least 5 seconds to land + activeGrenadeTimer = self.activeGrenadeTimer; + timeoutObj = spawnstruct(); + timeoutObj thread watchGrenadeTowardsPlayerTimeout( 5 ); + timeoutObj endon( "watchGrenadeTowardsPlayerTimeout" ); + + type = self.grenadeWeapon; + + grenade = self getGrenadeIThrew(); + if ( !isdefined( grenade ) ) + { + // the throw failed. maybe we died. =( + return; + } + + setGrenadeTimer( activeGrenadeTimer, min( gettime() + 5000, nextGrenadeTimeToUse ) ); + + /# + grenade thread grenadeDebug( "Incoming", 5 ); + #/ + + goodRadiusSqrd = 250 * 250; + giveUpRadiusSqrd = 400 * 400; + if ( type == "flash_grenade" ) + { + goodRadiusSqrd = 900 * 900; + giveUpRadiusSqrd = 1300 * 1300; + } + + playersToCheck = level._players; + + // wait for grenade to settle + prevorigin = grenade.origin; + while ( 1 ) + { + wait .1; + + if ( !isdefined( grenade ) ) + break; + + if ( distanceSquared( grenade.origin, prevorigin ) < 400 ) // sqr(20) + { + /# + grenade thread grenadeDebug( "Landed", 5 ); + #/ + // grenade is stationary. check if it's near any players + newPlayersToCheck = []; + for ( i = 0; i < playersToCheck.size; i++ ) + { + player = playersToCheck[ i ]; + distSqrd = distanceSquared( grenade.origin, player.origin ); + if ( distSqrd < goodRadiusSqrd ) + { + /# + grenade thread grenadeDebug( "Landed near player", 5 ); + #/ + + player grenadeLandedNearPlayer( activeGrenadeTimer, nextGrenadeTimeToUse ); + } + else if ( distSqrd < giveUpRadiusSqrd ) + { + newPlayersToCheck[ newPlayersToCheck.size ] = player; + } + } + playersToCheck = newPlayersToCheck; + if ( playersToCheck.size == 0 ) + break; + } + prevorigin = grenade.origin; + } +} + +grenadeLandedNearPlayer( activeGrenadeTimer, nextGrenadeTimeToUse ) +{ + player = self; + + // the grenade landed near the player! =D + anim.throwGrenadeAtPlayerASAP = undefined; + + if ( gettime() - player.lastGrenadeLandedNearPlayerTime < 3000 ) + { + // double grenade happened + player.grenadeTimers[ "double_grenade" ] = gettime() + player.gs.playerDoubleGrenadeTime; + } + + player.lastGrenadeLandedNearPlayerTime = gettime(); + + oldValue = player.grenadeTimers[ activeGrenadeTimer.timerName ]; + player.grenadeTimers[ activeGrenadeTimer.timerName ] = max( nextGrenadeTimeToUse, oldValue ); +} + +getGrenadeIThrew() +{ + self endon( "killanimscript" ); + self waittill( "grenade_fire", grenade ); + return grenade; +} + +watchGrenadeTowardsPlayerTimeout( timerlength ) +{ + wait timerlength; + self notify( "watchGrenadeTowardsPlayerTimeout" ); +} + + +attachGrenadeModel( model, tag ) +{ + self attach( model, tag ); + thread detachGrenadeOnScriptChange( model, tag ); + return tag; +} + + +detachGrenadeOnScriptChange( model, tag ) +{ + //self endon ("death"); // don't end on death or it will hover when we die! + self endon( "stop grenade check" ); + self waittill( "killanimscript" ); + + if ( !isdefined( self ) )// we may be dead but still defined. if we're not defined, we were probably deleted. + return; + + if ( isdefined( self.oldGrenAwareness ) ) + { + self.grenadeawareness = self.oldGrenAwareness; + self.oldGrenAwareness = undefined; + } + + self detach( model, tag ); +} + +offsetToOrigin( start ) +{ + forward = anglestoforward( self.angles ); + right = anglestoright( self.angles ); + up = anglestoup( self.angles ); + forward = vector_multiply( forward, start[ 0 ] ); + right = vector_multiply( right, start[ 1 ] ); + up = vector_multiply( up, start[ 2 ] ); + return( forward + right + up ); +} + +grenadeLine( start, end ) +{ + level notify( "armoffset" ); + level endon( "armoffset" ); + + start = self.origin + offsetToOrigin( start ); + for ( ;; ) + { + line( start, end, ( 1, 0, 1 ) ); + print3d( start, start, ( 0.2, 0.5, 1.0 ), 1, 1 ); // origin, text, RGB, alpha, scale + print3d( end, end, ( 0.2, 0.5, 1.0 ), 1, 1 ); // origin, text, RGB, alpha, scale + wait( 0.05 ); + } +} + +getGrenadeDropVelocity() +{ + yaw = randomFloat( 360 ); + pitch = randomFloatRange( 30, 75 ); + + amntz = sin( pitch ); + cospitch = cos( pitch ); + + amntx = cos( yaw ) * cospitch; + amnty = sin( yaw ) * cospitch; + + speed = randomFloatRange( 100, 200 ); + + velocity = ( amntx, amnty, amntz ) * speed; + return velocity; +} + +dropGrenade() +{ + grenadeOrigin = self GetTagOrigin( "tag_inhand" ); + velocity = getGrenadeDropVelocity(); + self MagicGrenadeManual( grenadeOrigin, velocity, 3 ); +} + +lookForBetterCover() +{ + // don't do cover searches if we don't have an enemy. + if ( !isdefined( self.enemy ) ) + return false; + + if ( self.fixedNode || self.doingAmbush ) + return false; + + //prof_begin( "lookForBetterCover" ); + + node = self getBestCoverNodeIfAvailable(); + + if ( isdefined( node ) ) + { + //prof_end( "lookForBetterCover" ); + return useCoverNodeIfPossible( node ); + } + + //prof_end( "lookForBetterCover" ); + return false; +} + +getBestCoverNodeIfAvailable() +{ + //prof_begin( "getBestCoverNodeIfAvailable" ); + node = self FindBestCoverNode(); + + if ( !isdefined( node ) ) + { + //prof_end( "getBestCoverNodeIfAvailable" ); + return undefined; + } + + currentNode = self GetClaimedNode(); + if ( isdefined( currentNode ) && node == currentNode ) + { + //prof_end( "getBestCoverNodeIfAvailable" ); + return undefined; + } + + // work around FindBestCoverNode() resetting my .node in rare cases involving overlapping nodes + // This prevents us from thinking we've found a new node somewhere when in reality it's the one we're already at, so we won't abort our script. + if ( isdefined( self.coverNode ) && node == self.coverNode ) + { + //prof_end( "getBestCoverNodeIfAvailable" ); + return undefined; + } + + //prof_end( "getBestCoverNodeIfAvailable" ); + return node; +} + +useCoverNodeIfPossible( node ) +{ + oldKeepNodeInGoal = self.keepClaimedNodeIfValid; + oldKeepNode = self.keepClaimedNode; + self.keepClaimedNodeIfValid = false; + self.keepClaimedNode = false; + + if ( self UseCoverNode( node ) ) + { + return true; + } + else + { + /#self thread DebugFailedCoverUsage( node );#/ + } + + self.keepClaimedNodeIfValid = oldKeepNodeInGoal; + self.keepClaimedNode = oldKeepNode; + + return false; +} + + /# +DebugFailedCoverUsage( node ) +{ + if ( getdvar( "scr_debugfailedcover" ) == "" ) + setdvar( "scr_debugfailedcover", "0" ); + if ( getdebugdvarint( "scr_debugfailedcover" ) == 1 ) + { + self endon( "death" ); + for ( i = 0; i < 20; i++ ) + { + line( self.origin, node.origin ); + print3d( node.origin, "failed" ); + wait .05; + } + } +} +#/ + +// this function seems okish, +// but the idea behind FindReacquireNode() is that you call it once, +// and then call GetReacquireNode() many times until it returns undefined. +// if we're just taking the first node (the best), we might as well just be using +// FindBestCoverNode(). +/* +tryReacquireNode() +{ + self FindReacquireNode(); + node = self GetReacquireNode(); + if (!isdefined(node)) + return false; + return (self UseReacquireNode(node)); +} +*/ + +shouldHelpAdvancingTeammate() +{ + // if teammate advanced recently + if ( level._advanceToEnemyGroup[ self.team ] > 0 && level._advanceToEnemyGroup[ self.team ] < level._advanceToEnemyGroupMax ) + { + if ( gettime() - level._lastAdvanceToEnemyTime[ self.team ] > 4000 ) + return false; + + leadAttacker = level._lastAdvanceToEnemyAttacker[ self.team ]; + nearLeadAttacker = isdefined( leadAttacker ) && distanceSquared( self.origin, leadAttacker.origin ) < 256 * 256; + + if ( ( nearLeadAttacker || distanceSquared( self.origin, level._lastAdvanceToEnemySrc[ self.team ] ) < 256 * 256 ) && + ( !isdefined( self.enemy ) || distanceSquared( self.enemy.origin, level._lastAdvanceToEnemyDest[ self.team ] ) < 512 * 512 ) ) + { + return true; + } + } + + return false; +} + +checkAdvanceOnEnemyConditions() +{ + if ( !isdefined( level._lastAdvanceToEnemyTime[ self.team ] ) ) + return false; + + if ( shouldHelpAdvancingTeammate() ) + return true; + + if ( gettime() - level._lastAdvanceToEnemyTime[ self.team ] < level._advanceToEnemyInterval ) + return false; + + + if ( !isSentient( self.enemy ) ) + return false; + + if ( level._advanceToEnemyGroup[ self.team ] ) + level._advanceToEnemyGroup[ self.team ] = 0; + + if ( getAICount( self.team ) < getAICount( self.enemy.team ) ) + return false; + + return true; +} + +tryRunningToEnemy( ignoreSuppression ) +{ + if ( !isdefined( self.enemy ) ) + return false; + + if ( self.fixedNode ) + return false; + + if ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" ) + return false; + + if ( !self isingoal( self.enemy.origin ) ) + return false; + + if ( self isLongRangeAI() ) + return false; + + if ( !checkAdvanceOnEnemyConditions() ) + return false; + + self FindReacquireDirectPath( ignoreSuppression ); + + // TrimPathToAttack is supposed to be called multiple times, until it returns false. + // it trims the path a little more each time, until trimming it more would make the enemy invisible from the end of the path. + // we're skipping this step and just running until we get within close range of the enemy. + // maybe later we can periodically check while moving if the enemy is visible, and if so, enter exposed. + //self TrimPathToAttack(); + + if ( self ReacquireMove() ) + { + self.keepClaimedNodeIfValid = false; + self.keepClaimedNode = false; + + self.a.magicReloadWhenReachEnemy = true; + + if ( level._advanceToEnemyGroup[ self.team ] == 0 ) + { + level._lastAdvanceToEnemyTime[ self.team ] = gettime(); + level._lastAdvanceToEnemyAttacker[ self.team ] = self; + } + + level._lastAdvanceToEnemySrc[ self.team ] = self.origin; + level._lastAdvanceToEnemyDest[ self.team ] = self.enemy.origin; + + level._advanceToEnemyGroup[ self.team ]++; + return true; + } + + return false; +} + +delayedBadplace( org ) +{ + self endon( "death" ); + wait( 0.5 ); + /# + if ( getdebugdvar( "debug_displace" ) == "on" ) + thread badplacer( 5, org, 16 ); + #/ + + string = "" + anim.badPlaceInt; + badplace_cylinder( string, 5, org, 16, 64, self.team ); + anim.badPlaces[ anim.badPlaces.size ] = string; + if ( anim.badPlaces.size >= 10 )// too many badplaces, delete the oldest one and then remove it from the array + { + newArray = []; + for ( i = 1;i < anim.badPlaces.size;i++ ) + newArray[ newArray.size ] = anim.badPlaces[ i ]; + badplace_delete( anim.badPlaces[ 0 ] ); + anim.badPlaces = newArray; + } + anim.badPlaceInt++ ; + if ( anim.badPlaceInt > 10 ) + anim.badPlaceInt -= 20; +} + +valueIsWithin( value, min, max ) +{ + if ( value > min && value < max ) + return true; + return false; +} + +getGunYawToShootEntOrPos() +{ + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + return 0; + } + + yaw = self getMuzzleAngle()[ 1 ] - GetYaw( self.shootPos ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +getGunPitchToShootEntOrPos() +{ + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + return 0; + } + + pitch = self getMuzzleAngle()[ 0 ] - VectorToAngles( self.shootPos - self getMuzzlePos() )[ 0 ]; + pitch = AngleClamp180( pitch ); + return pitch; +} + +getPitchToEnemy() +{ + if ( !isdefined( self.enemy ) ) + return 0; + + vectorToEnemy = self.enemy getshootatpos() - self getshootatpos(); + vectorToEnemy = vectornormalize( vectortoenemy ); + pitchDelta = 360 - vectortoangles( vectorToEnemy )[ 0 ]; + + return AngleClamp180( pitchDelta ); +} + +getPitchToSpot( spot ) +{ + if ( !isdefined( spot ) ) + return 0; + + vectorToEnemy = spot - self getshootatpos(); + vectorToEnemy = vectornormalize( vectortoenemy ); + pitchDelta = 360 - vectortoangles( vectorToEnemy )[ 0 ]; + + return AngleClamp180( pitchDelta ); +} + +watchReloading() +{ + // this only works on the player. + self.isreloading = false; + self.lastReloadStartTime = -1; + + while ( 1 ) + { + self waittill( "reload_start" ); + self.isreloading = true; + self.lastReloadStartTime = GetTime(); + + self waittillreloadfinished(); + self.isreloading = false; + } +} + +waittillReloadFinished() +{ + self thread timedNotify( 4, "reloadtimeout" ); + self endon( "reloadtimeout" ); + self endon( "weapon_taken" ); + + while ( 1 ) + { + self waittill( "reload" ); + + weap = self getCurrentWeapon(); + if ( weap == "none" ) + break; + + if ( self getCurrentWeaponClipAmmo() >= weaponClipSize( weap ) ) + break; + } + self notify( "reloadtimeout" ); +} + +timedNotify( time, msg ) +{ + self endon( msg ); + wait time; + self notify( msg ); +} + +maxFlashThrowDistSq = 768 * 768; +minGrenadeThrowDistSq = 200 * 200; +maxGrenadeThrowDistSq = 1250 * 1250; + +checkGrenadeThrowDist() +{ + diff = self.enemy.origin - self.origin; + distSq = lengthSquared( ( diff[ 0 ], diff[ 1 ], 0 ) ); + + // Flashbangs are threated separately + if ( self.grenadeWeapon == "flash_grenade" ) + return (distSq < maxFlashThrowDistSq); + + // All other grenades have a min/max range + return (distSq >= minGrenadeThrowDistSq) && (distSq <= maxGrenadeThrowDistSq); +} + +monitorFlash() +{ + self endon( "death" ); + self endon( "stop_monitoring_flash" ); + + while ( 1 ) + { + // "flashbang" is code notifying that the AI can be flash banged + // "doFlashBanged" is sent below if the AI should do flash banged behavior + self waittill( "flashbang", origin, amount_distance, amount_angle, attacker, attackerteam ); + + if ( isDefined( self.flashBangImmunity ) && self.flashBangImmunity ) + continue; + + if ( isdefined( self.script_immunetoflash ) && self.script_immunetoflash != 0 ) + continue; + + if ( isdefined( self.onSnowMobile ) ) + continue; + + if ( isdefined( self.team ) && isdefined( attackerteam ) && self.team == attackerteam ) + { + // AI get a break when their own team flashbangs them. + amount_distance = 3 * ( amount_distance - .75 ); + if ( amount_distance < 0 ) + continue; + + if ( isdefined( self.teamFlashbangImmunity ) ) + continue; + } + + // at 200 or less of the full range of 1000 units, get the full effect + minamountdist = 0.2; + if ( amount_distance > 1 - minamountdist ) + amount_distance = 1.0; + else + amount_distance = amount_distance / ( 1 - minamountdist ); + + duration = 4.5 * amount_distance; + + if ( duration < 0.25 ) + continue; + + self.flashingTeam = attackerteam; + self flashBangStart( duration ); + self notify( "doFlashBanged", origin, attacker ); + } +} + +isShotgunAI() +{ + return isShotgun( self.primaryweapon ); +} + +isSniper() +{ + return isSniperRifle( self.primaryweapon ); +} + +isLongRangeAI() +{ + return isSniper() || usingRocketLauncher(); +} + +fasterAnimSpeed() +{ + return 1.5; +} + +randomfasterAnimSpeed() +{ + return randomfloatrange( 1, 1.2 ); +} + +getRandomCoverMode( modes ) +{ + if ( modes.size == 0 ) + return undefined; + if ( modes.size == 1 ) + return modes[0]; + + // 20% chance of attempting to repeat same corner mode + if ( isdefined( self.a.prevAttack ) && randomint( 100 ) > 20 ) + { + foreach ( i, mode in modes ) + { + if ( mode == self.a.prevAttack ) + { + if ( i < modes.size - 1 ) + modes[ i ] = modes[ modes.size - 1 ]; + + modes[ modes.size - 1 ] = undefined; + break; + } + } + } + + return modes[ randomint( modes.size ) ]; +} + + +player_sees_my_scope() +{ + // player sees the scope glint if the dot is within a certain range + start = self geteye(); + foreach ( player in level._players ) + { + if ( !self cansee( player ) ) + continue; + + end = player GetEye(); + + angles = VectorToAngles( start - end ); + forward = AnglesToForward( angles ); + player_angles = player GetPlayerAngles(); + player_forward = AnglesToForward( player_angles ); + + dot = VectorDot( forward, player_forward ); + if ( dot < 0.805 ) + continue; + + if ( cointoss() && dot >= 0.996 ) + continue; + + return true; + } + return false; +} + +have_i_landed() +{ + while ( !level._player IsOnGround() ) + { + wait 0.05; + } + + // Landed! +} \ No newline at end of file diff --git a/animscripts/corner.gsc b/animscripts/corner.gsc new file mode 100644 index 0000000..106f37c --- /dev/null +++ b/animscripts/corner.gsc @@ -0,0 +1,1402 @@ +#include maps\_utility; +#include animscripts\Combat_utility; +#include animscripts\utility; +#include common_scripts\Utility; + +#using_animtree( "generic_human" ); + +corner_think( direction, nodeAngleOffset ) +{ + self endon( "killanimscript" ); + + if ( isdefined( self.locked_combat ) && self.locked_combat ) + { + return; + } + + self.animArrayFuncs[ "exposed" ][ "stand" ] = animscripts\corner::set_standing_animarray_aiming; + self.animArrayFuncs[ "exposed" ][ "crouch" ] = animscripts\corner::set_crouching_animarray_aiming; + + if ( IsDefined( self.customAnimFunc ) && IsDefined( self.customAnimFunc[ "corner_exposed" ] ) ) + { + if ( IsDefined( self.customAnimFunc[ "corner_exposed" ][ "stand" ] ) ) + { + self.animArrayFuncs[ "exposed" ][ "stand" ] = self.customAnimFunc[ "corner_exposed" ][ "stand" ]; + } + + if ( IsDefined( self.customAnimFunc[ "corner_exposed" ][ "crouch" ] ) ) + { + self.animArrayFuncs[ "exposed" ][ "crouch" ] = self.customAnimFunc[ "corner_exposed" ][ "crouch" ]; + } + } + + self.coverNode = self.node; + self.cornerDirection = direction; + self.a.cornerMode = "unknown"; + + self.a.aimIdleThread = undefined; + + animscripts\cover_behavior::turnToMatchNodeDirection( nodeAngleOffset ); + + set_corner_anim_array(); + + self.isshooting = false; + self.tracking = false; + + self.cornerAiming = false; + + animscripts\shared::setAnimAimWeight( 0 ); + + self.haveGoneToCover = false; + + behaviorCallbacks = spawnstruct(); + + if ( !self.fixedNode ) + behaviorCallbacks.moveToNearByCover = animscripts\cover_behavior::moveToNearbyCover; + + behaviorCallbacks.mainLoopStart = ::mainLoopStart; + behaviorCallbacks.reload = ::cornerReload; + behaviorCallbacks.leaveCoverAndShoot = ::stepOutAndShootEnemy; + behaviorCallbacks.look = ::lookForEnemy; + behaviorCallbacks.fastlook = ::fastlook; + behaviorCallbacks.idle = ::idle; + behaviorCallbacks.grenade = ::tryThrowingGrenade; + behaviorCallbacks.grenadehidden = ::tryThrowingGrenadeStayHidden; + behaviorCallbacks.blindfire = ::blindfire; + + animscripts\cover_behavior::main( behaviorCallbacks ); +} + +end_script_corner() +{ + self.stepOutYaw = undefined; + self.a.leanAim = undefined; +} + +set_corner_anim_array() +{ + if ( self.a.pose == "crouch" ) + { + set_anim_array( "crouch" ); + } + else if ( self.a.pose == "stand" ) + { + set_anim_array( "stand" ); + } + else + { + assert( self.a.pose == "prone" ); + self ExitProneWrapper( 1 ); + self.a.pose = "crouch"; + self set_anim_array( "crouch" ); + } +} + +shouldChangeStanceForFun() // and for variety +{ + if ( !isDefined( self.enemy ) ) + return false; + + if ( !isDefined( self.changeStanceForFunTime ) ) + self.changeStanceForFunTime = gettime() + randomintrange( 5000, 20000 ); + + if ( gettime() > self.changeStanceForFunTime ) + { + self.changeStanceForFunTime = gettime() + randomintrange( 5000, 20000 ); + + if ( isDefined( self.ramboChance ) && self.a.pose == "stand" ) + return false; + + self.a.prevAttack = undefined; + return true; + } + + return false; +} + +mainLoopStart() +{ + desiredStance = "stand"; + + if ( self.a.pose == "crouch" ) + { + desiredStance = "crouch"; + if ( self.coverNode doesNodeAllowStance( "stand" ) && (self IsStanceAllowed("stand")) ) + { + if ( !self.coverNode doesNodeAllowStance( "crouch" ) || shouldChangeStanceForFun() ) + desiredStance = "stand"; + } + } + else + { + if ( self.coverNode doesNodeAllowStance( "crouch" ) && (self IsStanceAllowed("crouch")) ) + { + if ( !self.coverNode doesNodeAllowStance( "stand" ) || shouldChangeStanceForFun() ) + desiredStance = "crouch"; + } + } + + /# + if ( getdvarint( "scr_cornerforcecrouch" ) == 1 ) + desiredStance = "crouch"; + #/ + + if ( self.haveGoneToCover ) + { + self transitionToStance( desiredStance ); + } + else + { + if ( self.a.pose == desiredStance ) + { + GoToCover( animArray( "alert_idle" ), .3, .4 ); + } + else + { + stanceChangeAnim = animarray( "stance_change" ); + GoToCover( stanceChangeAnim, .3, getAnimLength( stanceChangeAnim ) ); + set_anim_array( desiredStance );// ( sets anim_pose to stance ) + } + assert( self.a.pose == desiredStance ); + self.haveGoneToCover = true; + } +} + +printYaws() +{ + wait( 2 ); + for ( ;; ) + { + println( "coveryaw = ", self.coverNode GetYawToOrigin( getEnemyEyePos() ) ); + printYawToEnemy(); + wait( 0.05 ); + } +} + +// used within canSeeEnemyFromExposed() (in utility.gsc) +canSeePointFromExposedAtCorner( point, node ) +{ + yaw = node GetYawToOrigin( point ); + if ( ( yaw > 60 ) || ( yaw < - 60 ) ) + return false; + + if ( ( node.type == "Cover Left" ) && yaw > 14 ) + return false; + if ( ( node.type == "Cover Right" ) && yaw < - 12 ) + return false; + + return true; +} + +shootPosOutsideLegalYawRange() +{ + if ( !isdefined( self.shootPos ) ) + return false; + + yaw = self.coverNode GetYawToOrigin( self.shootPos ); + + if ( self.a.cornerMode == "over" ) + return yaw < self.leftAimLimit || self.rightAimLimit < yaw; + + if ( self.cornerDirection == "left" ) + { + if ( self.a.cornerMode == "B" ) + { + return yaw < 0 - self.ABangleCutoff || yaw > 14; + } + else if ( self.a.cornerMode == "A" ) + { + return yaw > 0 - self.ABangleCutoff; + } + else + { + assert( self.a.cornerMode == "lean" ); + return yaw < - 50 || yaw > 8;// TODO + } + } + else + { + assert( self.cornerDirection == "right" ); + if ( self.a.cornerMode == "B" ) + { + return yaw > self.ABangleCutoff || yaw < - 12; + } + else if ( self.a.cornerMode == "A" ) + { + return yaw < self.ABangleCutoff; + } + else + { + assert( self.a.cornerMode == "lean" ); + return yaw > 50 || yaw < - 8;// TODO + } + } +} + +// getCornerMode will return "none" if no corner modes are acceptable. +getCornerMode( node, point ) +{ + /# + dvarval = getdvar( "scr_cornerforcestance" ); + if ( dvarval == "lean" || dvarval == "A" || dvarval == "B" || dvarval == "over" ) + return dvarval; + #/ + + noStepOut = false; + yaw = 0; + + if ( isdefined( point ) ) + yaw = node GetYawToOrigin( point ); + + modes = []; + + // don't want to get cover peekouts for crouch while standing + if ( isdefined( node ) && self.a.pose == "crouch" && ( yaw > self.leftAimLimit && self.rightAimLimit > yaw ) ) + modes = node GetValidCoverPeekOuts(); + + if ( self.cornerDirection == "left" ) + { + if ( canLean( yaw, -40, 0 ) ) + { + noStepOut = shouldLean(); + modes[ modes.size ] = "lean"; + } + + if ( !noStepOut && yaw < 14 ) + { + if ( yaw < 0 - self.ABangleCutoff ) + modes[ modes.size ] = "A"; + else + modes[ modes.size ] = "B"; + } + } + else + { + assert( self.cornerDirection == "right" ); + + if ( canLean( yaw, 0, 40 ) ) + { + noStepOut = shouldLean(); + modes[ modes.size ] = "lean"; + } + + if ( !noStepOut && yaw > -12 ) + { + if ( yaw > self.ABangleCutoff ) + modes[ modes.size ] = "A"; + else + modes[ modes.size ] = "B"; + } + } + + return getRandomCoverMode( modes ); +} + +// getBestStepOutPos never returns "none". +// it returns the best stepoutpos that we can get to from our current one. +getBestStepOutPos() +{ + yaw = 0; + if ( canSuppressEnemy() ) + yaw = self.coverNode GetYawToOrigin( getEnemySightPos() ); + else if ( self.doingAmbush && isdefined( self.shootPos ) ) + yaw = self.coverNode GetYawToOrigin( self.shootPos ); + + /# + dvarval = getdvar( "scr_cornerforcestance" ); + if ( dvarval == "lean" || dvarval == "A" || dvarval == "B" || dvarval == "over" ) + return dvarval; + #/ + + if ( self.a.cornerMode == "lean" ) + return "lean"; + if ( self.a.cornerMode == "over" ) + return "over"; + else if ( self.a.cornerMode == "B" ) + { + if ( self.cornerDirection == "left" ) + { + if ( yaw < 0 - self.ABangleCutoff ) + return "A"; + } + else if ( self.cornerDirection == "right" ) + { + if ( yaw > self.ABangleCutoff ) + return "A"; + } + return "B"; + } + else if ( self.a.cornerMode == "A" ) + { + positionToSwitchTo = "B"; + if ( self.cornerDirection == "left" ) + { + if ( yaw > 0 - self.ABangleCutoff ) + return "B"; + } + else if ( self.cornerDirection == "right" ) + { + if ( yaw < self.ABangleCutoff ) + return "B"; + } + return "A"; + } +} + +changeStepOutPos() +{ + self endon( "killanimscript" ); + + positionToSwitchTo = getBestStepOutPos(); + + if ( positionToSwitchTo == self.a.cornerMode ) + return false; + + // can't switch between lean/over and other stepoutposes + // so if this assert fails then getBestStepOutPos gave us a bad return value + assert( self.a.cornerMode != "lean" && positionToSwitchTo != "lean" ); + assert( self.a.cornerMode != "over" && positionToSwitchTo != "over" ); + + self.changingCoverPos = true; self notify( "done_changing_cover_pos" ); + + animname = self.a.cornerMode + "_to_" + positionToSwitchTo; + assert( animArrayAnyExist( animname ) ); + switchanim = animArrayPickRandom( animname ); + + midpoint = getPredictedPathMidpoint(); + if ( !self mayMoveToPoint( midpoint ) ) + return false; + if ( !self mayMoveFromPointToPoint( midpoint, getAnimEndPos( switchanim ) ) ) + return false; + + self endAimIdleThread(); + + // turn off aiming while we move. + self StopAiming( .3 ); + + prev_anim_pose = self.a.pose; + + self setanimlimited( animarray( "straight_level" ), 0, .2 ); + + self setFlaggedAnimKnob( "changeStepOutPos", switchanim, 1, .2, 1.2 ); + self thread DoNoteTracksWithEndon( "changeStepOutPos" ); + + if ( animHasNotetrack( switchanim, "start_aim" ) ) + { + self waittillmatch( "changeStepOutPos", "start_aim" ); + } + else + { + /#println( "^1Corner position switch animation \"" + animname + "\" in corner_" + self.cornerDirection + " " + self.a.pose + " didn't have \"start_aim\" notetrack" );#/ + self waittillmatch( "changeStepOutPos", "end" ); + } + + self thread StartAiming( undefined, false, .3 ); + + self waittillmatch( "changeStepOutPos", "end" ); + self clearanim( switchanim, .1 ); + self.a.cornerMode = positionToSwitchTo; + + self.changingCoverPos = false; + self.coverPosEstablishedTime = gettime(); + + assert( self.a.pose == "stand" || self.a.pose == "crouch" ); + if ( self.a.pose != prev_anim_pose ) + set_anim_array( self.a.pose );// don't call this if we don't have to, because we don't want to reset %exposed_aiming + + self thread ChangeAiming( undefined, true, .3 ); + + return true; +} + +canLean( yaw, yawMin, yawMax ) +{ + if ( self.a.neverLean ) + return false; + + return ( yawMin <= yaw && yaw <= yawMax ); +} + +shouldLean() +{ + if ( self.team == "allies" ) + return true; + + if ( self isPartiallySuppressedWrapper() ) + return true; + + return false; +} + +DoNoteTracksWithEndon( animname ) +{ + self endon( "killanimscript" ); + self animscripts\shared::DoNoteTracks( animname ); +} + +StartAiming( spot, fullbody, transtime ) +{ + assert( !self.cornerAiming ); + self.cornerAiming = true; + if ( self.a.cornerMode == "lean" ) + self.a.leanAim = true; + else + self.a.leanAim = undefined; + + self SetAimingParams( spot, fullbody, transTime ); +} + +ChangeAiming( spot, fullbody, transtime ) +{ + assert( self.cornerAiming ); + if ( self.a.cornerMode == "lean" ) + self.a.leanAim = true; + else + self.a.leanAim = undefined; + + self SetAimingParams( spot, fullbody, transTime ); +} + +StopAiming( transtime ) +{ + assert( self.cornerAiming ); + self.cornerAiming = false; + + // turn off shooting + self clearAnim( %add_fire, transtime ); + // and turn off aiming + animscripts\shared::setAnimAimWeight( 0, transtime ); +} + +SetAimingParams( spot, fullbody, transTime ) +{ + assert( isdefined( fullbody ) ); + + self.spot = spot;// undefined is ok + + self setanimlimited( %exposed_modern, 1, transTime ); + self setanimlimited( %exposed_aiming, 1, transTime ); + self setanimlimited( %add_idle, 1, transTime ); + animscripts\shared::setAnimAimWeight( 1, transTime ); + + leanAnim = undefined; + if ( isdefined( self.a.array[ "lean_aim_straight" ] ) ) + leanAnim = self.a.array[ "lean_aim_straight" ]; + + self thread aimIdleThread(); + + if ( isdefined( self.a.leanAim ) ) + { + self setAnimLimited( leanAnim, 1, transTime ); + self setAnimLimited( animArray( "straight_level" ), 0, 0 ); + + self setAnimKnobLimited( animArray( "lean_aim_left" ), 1, transTime ); + self setAnimKnobLimited( animArray( "lean_aim_right" ), 1, transTime ); + self setAnimKnobLimited( animArray( "lean_aim_up" ), 1, transTime ); + self setAnimKnobLimited( animArray( "lean_aim_down" ), 1, transTime ); + } + else if ( fullbody ) + { + self setAnimLimited( animarray( "straight_level" ), 1, transTime ); + if ( isdefined( leanAnim ) ) + self setAnimLimited( leanAnim, 0, 0 ); + + self setAnimKnobLimited( animArray( "add_aim_up" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_down" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_left" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_aim_right" ), 1, transTime ); + } + else + { + self setAnimLimited( animarray( "straight_level" ), 0, transTime ); + if ( isdefined( leanAnim ) ) + self setAnimLimited( leanAnim, 0, 0 ); + + self setAnimKnobLimited( animArray( "add_turn_aim_up" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_turn_aim_down" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_turn_aim_left" ), 1, transTime ); + self setAnimKnobLimited( animArray( "add_turn_aim_right" ), 1, transTime ); + } +} + +// These should be adjusted in animation data +stepOutAndHideSpeed() +{ + if ( self.a.cornerMode == "over" ) + return 1; + + //if ( self.a.cornerMode == "B" ) + // return 1; + + return randomFasterAnimSpeed(); +} + +stepOut() /* bool */ +{ + self.a.cornerMode = "alert"; + + if ( self.goalRadius < 64 ) + self.goalRadius = 64; + + self animMode( "zonly_physics" ); + + if ( self.a.pose == "stand" ) + { + self.ABangleCutoff = 38; + } + else + { + assert( self.a.pose == "crouch" ); + self.ABangleCutoff = 31; + } + + thisNodePose = self.a.pose; + set_anim_array( thisNodePose ); + + self setDefaultAimLimits(); // do exposed animations once stepped out + + newCornerMode = "none"; + if ( hasEnemySightPos() ) + newCornerMode = getCornerMode( self.coverNode, getEnemySightPos() ); + else + newCornerMode = getCornerMode( self.coverNode ); + + if ( !isdefined( newCornerMode ) ) + return false; + + /# + setdvar( "scr_current_corner_mode", newCornerMode ); + #/ + + animname = "alert_to_" + newCornerMode; + assert( animArrayAnyExist( animname ) ); + switchanim = animArrayPickRandom( animname ); + + if ( newCornerMode == "lean" && !self isPeekOutPosClear() ) + return false; + + if ( newCornerMode != "over" && !isPathClear( switchanim, newCornerMode != "lean" ) ) + return false; + + self.a.cornerMode = newCornerMode; + self.a.prevAttack = newCornerMode; + + if ( self.a.cornerMode == "lean" ) + { + if ( !isdefined( self.locked_combat ) ) + { + self setDefaultAimLimits( self.coverNode ); + } + } + + if ( newCornerMode == "A" || newCornerMode == "B" ) + self.a.special = "cover_" + self.cornerDirection + "_" + self.a.pose + "_" + newCornerMode; + else if ( newCornerMode == "over" ) + self.a.special = "cover_crouch_aim"; + else + self.a.special = "none"; + + self.keepClaimedNodeIfValid = true; + + hasStartAim = false; + + self.changingCoverPos = true; + self notify( "done_changing_cover_pos" ); + + animRate = stepOutAndHideSpeed(); + + self.pushable = false; + + self setFlaggedAnimKnobAllRestart( "stepout", switchanim, %root, 1, .2, animRate ); + self thread DoNoteTracksWithEndon( "stepout" ); + + hasStartAim = animHasNotetrack( switchanim, "start_aim" ); + if ( hasStartAim ) + { + // Store our final step out angle so that we may use it when doing track loop aiming + self.stepOutYaw = self.angles[1] + getAngleDelta( switchanim, 0, 1 ); + + self waittillmatch( "stepout", "start_aim" ); + } + else + { + /#println( "^1Corner stepout animation \"" + animname + "\" in corner_" + self.cornerDirection + " " + self.a.pose + " didn't have \"start_aim\" notetrack" );#/ + self waittillmatch( "stepout", "end" ); + } + + if ( newCornerMode == "B" && coinToss() && self.cornerDirection == "right" ) + self.a.special = "corner_right_martyrdom"; + + set_anim_array_aiming( thisNodePose ); + + fullbody = ( newCornerMode == "over" ); + + self StartAiming( undefined, fullbody, .3 ); + self thread animscripts\shared::trackShootEntOrPos(); + + if ( hasStartAim ) + { + self waittillmatch( "stepout", "end" ); + + // Clear the forced yaw after the animation is fully played + self.stepOutYaw = undefined; + } + + self ChangeAiming( undefined, true, 0.2 ); + self clearAnim( %cover, 0.1 ); + self clearAnim( %corner, 0.1 ); + + self.changingCoverPos = false; + self.coverPosEstablishedTime = gettime(); + + self.pushable = true; + + return true; +} + +stepOutAndShootEnemy() +{ + self.keepClaimedNodeIfValid = true; + + // do rambo behavior sometimes on rambo AI guys. Normal AI never do rambo + if ( isdefined( self.ramboChance ) && randomFloat( 1 ) < self.ramboChance ) + { + if ( rambo() ) + return true; + } + + if ( !StepOut() ) // may not be room to step out + return false; + + shootAsTold(); + + if ( isDefined( self.shootPos ) ) + { + distSqToShootPos = lengthsquared( self.origin - self.shootPos ); + // too close for RPG or out of ammo + if ( usingRocketLauncher() && ( distSqToShootPos < squared( 512 ) || self.a.rockets < 1 ) ) + { + if ( self.a.pose == "stand" ) + animscripts\shared::throwDownWeapon( %RPG_stand_throw ); + else + animscripts\shared::throwDownWeapon( %RPG_crouch_throw ); + + self thread runCombat(); + return; + } + } + + returnToCover(); + + self.keepClaimedNodeIfValid = false; + + return true; +} + +haventRamboedWithinTime( time ) +{ + if ( !isdefined( self.lastRamboTime ) ) + return true; + return gettime() - self.lastRamboTime > time * 1000; +} + +rambo() +{ + if ( !hasEnemySightPos() ) + return false; + + ramboAimOffset = 0; + angle = 90; + yaw = self.coverNode GetYawToOrigin( getEnemySightPos() ); + if ( self.cornerDirection == "left" ) + yaw = 0 - yaw; + if ( yaw > 30 ) // this cutoff works better visually than 22.5 + { + angle = 45; + if ( self.cornerDirection == "left" ) + ramboAimOffset = 45; + else + ramboAimOffset = -45; + } + + animType = "rambo" + angle; + if ( !animArrayAnyExist( animType ) ) + return false; + + // commented out so we see rambo a lot, might want to adjust this later + //if ( !haventRamboedWithinTime( 2 ) ) + // return false; + + // move check + ramboAnim = animArrayPickRandom( animType ); + midpoint = getPredictedPathMidpoint( 48 ); + if ( !self mayMoveToPoint( midpoint ) ) + return false; + // no point doing this check, since the animation will end at the cover node. + //if ( !self mayMoveFromPointToPoint( midpoint, getAnimEndPos( ramboAnim ) ) ) + // return false; + + self.coverPosEstablishedTime = gettime(); + + self animMode( "zonly_physics" ); + self.keepClaimedNodeIfValid = true; + self.isRambo = true; + self.a.prevAttack = "rambo"; + + self.changingCoverPos = true; + + self thread animscripts\shared::ramboAim( ramboAimOffset ); + + self setFlaggedAnimKnobAllRestart( "rambo", ramboAnim, %body, 1, 0, 1 ); + self animscripts\shared::DoNoteTracks( "rambo" ); + + self notify( "rambo_aim_end" ); + + self.changingCoverPos = false; + + self.keepClaimedNodeIfValid = false; + self.lastRamboTime = getTime(); + + self.changingCoverPos = false; + self.isRambo = undefined; + + return true; +} + +shootAsTold() +{ + self maps\_gameskill::didSomethingOtherThanShooting(); + + while ( 1 ) + { + while ( 1 ) + { + if ( isdefined( self.shouldReturnToCover ) ) + break; + + if ( !isdefined( self.shootPos ) ) { + assert( !isdefined( self.shootEnt ) ); + // give shoot_behavior a chance to iterate + self waittill( "do_slow_things" ); + waittillframeend; + if ( isdefined( self.shootPos ) ) + continue; + break; + } + + if ( !self.bulletsInClip ) + break; + + if ( shootPosOutsideLegalYawRange() ) + { + if ( !changeStepOutPos() ) + { + // if we failed because there's no better step out pos, give up + if ( getBestStepOutPos() == self.a.cornerMode ) + break; + + // couldn't change position, shoot for a short bit and we'll try again + shootUntilShootBehaviorChangeForTime( .2 ); + continue; + } + + // if they're moving back and forth too fast for us to respond intelligently to them, + // give up on firing at them for the moment + if ( shootPosOutsideLegalYawRange() ) + break; + + continue; + } + + shootUntilShootBehaviorChange_corner( true ); + + self clearAnim( %add_fire, .2 ); + } + + if ( self canReturnToCover( self.a.cornerMode != "lean" ) ) + break; + + // couldn't return to cover. keep shooting and try again + + // (change step out pos if necessary and possible) + if ( shootPosOutsideLegalYawRange() && changeStepOutPos() ) + continue; + + shootUntilShootBehaviorChangeForTime( .2 ); + } +} + +shootUntilShootBehaviorChangeForTime( time ) +{ + self thread notifyStopShootingAfterTime( time ); + + starttime = gettime(); + + shootUntilShootBehaviorChange_corner( false ); + self notify( "stopNotifyStopShootingAfterTime" ); + + timepassed = ( gettime() - starttime ) / 1000; + if ( timepassed < time ) + wait time - timepassed; +} + +notifyStopShootingAfterTime( time ) +{ + self endon( "killanimscript" ); + self endon( "stopNotifyStopShootingAfterTime" ); + + wait time; + + self notify( "stopShooting" ); +} + +shootUntilShootBehaviorChange_corner( runAngleRangeThread ) +{ + self endon( "return_to_cover" ); + + if ( runAngleRangeThread ) + self thread angleRangeThread();// gives stopShooting notify when shootPosOutsideLegalYawRange returns true + self thread aimIdleThread(); + + shootUntilShootBehaviorChange(); +} + + +angleRangeThread() +{ + self endon( "killanimscript" ); + self notify( "newAngleRangeCheck" ); + self endon( "newAngleRangeCheck" ); + self endon( "take_cover_at_corner" ); + + while ( 1 ) + { + if ( shootPosOutsideLegalYawRange() ) + break; + wait( 0.1 ); + } + + self notify( "stopShooting" );// For changing shooting pose to compensate for player moving +} + +showstate() +{ + self.enemy endon( "death" ); + self endon( "enemy" ); + self endon( "stopshowstate" ); + + while ( 1 ) + { + wait .05; + print3d( self.origin + ( 0, 0, 60 ), self.statetext ); + } +} + +canReturnToCover( doMidpointCheck ) +{ + if ( isdefined( self.locked_combat ) ) + { + return true; + } + + if ( doMidpointCheck ) + { + midpoint = getPredictedPathMidpoint(); + + if ( !self mayMoveToPoint( midpoint ) ) + return false; + + return self mayMoveFromPointToPoint( midpoint, self.coverNode.origin ); + } + else + { + return self mayMoveToPoint( self.coverNode.origin ); + } +} + +returnToCover() +{ + assert( self canReturnToCover( self.a.cornerMode != "lean" ) ); + + endFireAndAnimIdleThread(); + + // Go back into hiding. + suppressed = issuppressedWrapper(); + self notify( "take_cover_at_corner" );// Stop doing the adjust - stance transition thread + + self.changingCoverPos = true; + self notify( "done_changing_cover_pos" ); + + animname = self.a.cornerMode + "_to_alert"; + assert( animArrayAnyExist( animname ) ); + switchanim = animArrayPickRandom( animname ); + + self StopAiming( .3 ); + + reloading = false; + if ( self.a.cornerMode != "lean" && suppressed && animArrayAnyExist( animname + "_reload" ) && randomfloat( 100 ) < 75 ) + { + switchanim = animArrayPickRandom( animname + "_reload" ); + reloading = true; + } + + rate = stepOutAndHideSpeed(); + + self clearanim( %body, 0.1 ); + self setFlaggedAnimRestart( "hide", switchanim, 1, .1, rate ); + self animscripts\shared::DoNoteTracks( "hide" ); + + if ( reloading ) + self animscripts\weaponList::RefillClip(); + + self.changingCoverPos = false; + if ( self.cornerDirection == "left" ) + self.a.special = "cover_left"; + else + self.a.special = "cover_right"; + + self.keepClaimedNodeIfValid = false; + + self clearAnim( switchanim, 0.2 ); +} + +blindfire() +{ + if ( !animArrayAnyExist( "blind_fire" ) ) + return false; + + self animMode( "zonly_physics" ); + self.keepClaimedNodeIfValid = true; + + self setFlaggedAnimKnobAllRestart( "blindfire", animArrayPickRandom( "blind_fire" ), %body, 1, 0, 1 ); + self animscripts\shared::DoNoteTracks( "blindfire" ); + + self.keepClaimedNodeIfValid = false; + + return true; +} + +linethread( a, b, col ) +{ + if ( !isdefined( col ) ) + col = ( 1, 1, 1 ); + for ( i = 0; i < 100; i++ ) + { + line( a, b, col ); + wait .05; + } +} + +tryThrowingGrenadeStayHidden( throwAt ) +{ + return tryThrowingGrenade( throwAt, true ); +} + +tryThrowingGrenade( throwAt, safe ) +{ + if ( !self mayMoveToPoint( self getPredictedPathMidpoint() ) ) + return false; + + if ( isdefined( self.dontEverShoot ) || isdefined( throwAt.dontAttackMe ) ) + return false; + + theanim = undefined; + if ( isdefined( self.ramboChance ) && randomFloat( 1 ) < self.ramboChance ) + { + if ( isdefined( self.a.array[ "grenade_rambo" ] ) ) + theanim = animArray( "grenade_rambo" ); + } + if ( !isdefined( theanim ) ) + { + if ( isdefined( safe ) && safe ) + { + if ( !isdefined( self.a.array[ "grenade_safe" ] ) ) + return false; + theanim = animArray( "grenade_safe" ); + } + else + { + if ( !isdefined( self.a.array[ "grenade_exposed" ] ) ) + return false; + theanim = animArray( "grenade_exposed" ); + } + } + assert( isdefined( theanim ) ); + + self animMode( "zonly_physics" );// Unlatch the feet + self.keepClaimedNodeIfValid = true; + + threwGrenade = TryGrenade( throwAt, theanim ); + + self.keepClaimedNodeIfValid = false; + return threwGrenade; +} + +printYawToEnemy() +{ + println( "yaw: ", self getYawToEnemy() ); +} + +lookForEnemy( lookTime ) +{ + if ( !isdefined( self.a.array[ "alert_to_look" ] ) ) + return false; + + self animMode( "zonly_physics" );// Unlatch the feet + self.keepClaimedNodeIfValid = true; + + // look out from alert + if ( !peekOut() ) + return false; + + animscripts\shared::playLookAnimation( animarray( "look_idle" ), lookTime, ::canStopPeeking ); + + lookanim = undefined; + if ( self isSuppressedWrapper() ) + lookanim = animArray( "look_to_alert_fast" ); + else + lookanim = animArray( "look_to_alert" ); + + self setflaggedanimknoballrestart( "looking_end", lookanim, %body, 1, .1, 1.0 ); + animscripts\shared::DoNoteTracks( "looking_end" ); + + self animMode( "zonly_physics" );// Unlatch the feet + + self.keepClaimedNodeIfValid = false; + + return true; +} + + +PEEKOUT_OFFSET = 30; + +isPeekOutPosClear() +{ + assert( isdefined( self.coverNode ) ); + + eyePos = self geteye(); + rightDir = anglestoright( self.coverNode.angles ); + if ( self.cornerDirection == "right" ) + eyePos = eyePos + (rightDir * PEEKOUT_OFFSET); + else + eyePos = eyePos - (rightDir * PEEKOUT_OFFSET); + + lookAtPos = eyePos + anglesToForward( self.coverNode.angles ) * PEEKOUT_OFFSET; + + // /# thread debugLine( eyePos, lookAtPos, ( 1, 0, 0 ), 1.5 ); #/ + + return sightTracePassed( eyePos, lookAtPos, true, self ); +} + + +peekOut() +{ + if ( isdefined( self.coverNode.script_dontpeek ) ) + return false; + + if ( isdefined( self.nextPeekOutAttemptTime ) && gettime() < self.nextPeekOutAttemptTime ) + return false; + + if ( !self isPeekOutPosClear() ) + { + self.nextPeekOutAttemptTime = gettime() + 3000; + return false; + } + + peekanim = animArray( "alert_to_look" ); + + // assuming no delta, so no maymovetopoint check + //if ( !self mayMoveToPoint( getAnimEndPos( peekanim ) ) ) + // return false; + + // not safe to stop peeking in the middle because it will screw up our deltas + //self thread _peekStop(); + //self endon ("stopPeeking"); + + self setflaggedanimknobAll( "looking_start", peekanim, %body, 1, .2, 1 ); + animscripts\shared::DoNoteTracks( "looking_start" ); + //self notify ("stopPeekCheckThread"); + + return true; +} + +canStopPeeking() +{ + return self mayMoveToPoint( self.coverNode.origin ); +} + +fastlook() +{ + // corner fast look animations aren't set up right. + return false; + + /* + if ( !isdefined( self.a.array["look"] ) ) + return false; + + self setFlaggedAnimKnobAllRestart( "look", animArray( "look" ), %body, 1, .1 ); + self animscripts\shared::DoNoteTracks( "look" ); + + return true; + */ +} + +cornerReload() +{ + assert( animArrayAnyExist( "reload" ) ); + + reloadanim = animArrayPickRandom( "reload" ); + self setFlaggedAnimKnobRestart( "cornerReload", reloadanim, 1, .2 ); + + self animscripts\shared::DoNoteTracks( "cornerReload" ); + + self animscripts\weaponList::RefillClip(); + + self setAnimRestart( animarray( "alert_idle" ), 1, .2 ); + self clearAnim( reloadanim, .2 ); + + return true; +} + +isPathClear( stepoutanim, doMidpointCheck ) +{ + if ( isdefined( self.locked_combat ) ) + { + return true; + } + + if ( doMidpointCheck ) + { + midpoint = getPredictedPathMidpoint(); + + if ( !self maymovetopoint( midpoint ) ) + return false; + + return self maymovefrompointtopoint( midpoint, getAnimEndPos( stepoutanim ) ); + } + else + { + return self maymovetopoint( getAnimEndPos( stepoutanim ) ); + } +} + +getPredictedPathMidpoint( dist ) +{ + angles = self.coverNode.angles; + right = anglestoright( angles ); + if ( !isdefined( dist ) ) + dist = 36; + switch( self.script ) + { + case "cover_left": + right = vector_multiply( right, 0-dist ); + break; + + case "cover_right": + right = vector_multiply( right, dist ); + break; + + default: + assertEx( 0, "What kind of node is this????" ); + } + + return self.coverNode.origin + ( right[ 0 ], right[ 1 ], 0 ); +} + +idle() +{ + self endon( "end_idle" ); + + while ( 1 ) + { + useTwitch = ( randomint( 2 ) == 0 && animArrayAnyExist( "alert_idle_twitch" ) ); + if ( useTwitch ) + idleanim = animArrayPickRandom( "alert_idle_twitch" ); + else + idleanim = animarray( "alert_idle" ); + + playIdleAnimation( idleAnim, useTwitch ); + } +} + +flinch() +{ + if ( !animArrayAnyExist( "alert_idle_flinch" ) ) + return false; + + playIdleAnimation( animArrayPickRandom( "alert_idle_flinch" ), true ); + + return true; +} + +playIdleAnimation( idleAnim, needsRestart ) +{ + if ( needsRestart ) + self setFlaggedAnimKnobAllRestart( "idle", idleAnim, %body, 1, .1, 1 ); + else + self setFlaggedAnimKnobAll( "idle", idleAnim, %body, 1, .1, 1 ); + + self animscripts\shared::DoNoteTracks( "idle" ); +} + + +set_anim_array( stance ) +{ + [[ self.animArrayFuncs[ "hiding" ][ stance ] ]](); + [[ self.animArrayFuncs[ "exposed" ][ stance ] ]](); +} +set_anim_array_aiming( stance ) +{ + [[ self.animArrayFuncs[ "exposed" ][ stance ] ]](); +} + +transitionToStance( stance ) +{ + if ( self.a.pose == stance ) + { + set_anim_array( stance ); + return; + } + +// self ExitProneWrapper(0.5); + self setFlaggedAnimKnobAllRestart( "changeStance", animarray( "stance_change" ), %body ); + + set_anim_array( stance );// ( sets anim_pose to stance ) + + self animscripts\shared::DoNoteTracks( "changeStance" ); + assert( self.a.pose == stance ); + wait( 0.2 ); +} + +GoToCover( coveranim, transTime, playTime ) +{ + cornerAngle = GetNodeDirection(); + cornerOrigin = GetNodeOrigin(); + + desiredYaw = cornerAngle + self.hideyawoffset; + + self OrientMode( "face angle", desiredYaw ); + + self animMode( "normal" ); + + assert( transTime <= playTime ); + + self thread animscripts\shared::moveToOriginOverTime( cornerOrigin, transTime ); + self setFlaggedAnimKnobAllRestart( "coveranim", coveranim, %body, 1, transTime ); + self animscripts\shared::DoNoteTracksForTime( playTime, "coveranim" ); + + while ( AbsAngleClamp180( self.angles[ 1 ] - desiredYaw ) > 1 ) + { + self animscripts\shared::DoNoteTracksForTime( 0.1, "coveranim" ); + } + + self animMode( "zonly_physics" ); + + if ( self.cornerDirection == "left" ) + self.a.special = "cover_left"; + else + self.a.special = "cover_right"; +} + +drawoffset() +{ + self endon( "killanimscript" ); + for ( ;; ) + { + line( self.node.origin + ( 0, 0, 20 ), ( 0, 0, 20 ) + self.node.origin + vector_multiply( anglestoright( self.node.angles + ( 0, 0, 0 ) ), 16 ) ); + wait( 0.05 ); + } +} + + +set_standing_animarray_aiming() +{ + if ( !isdefined( self.a.array ) ) + { + assertmsg( "set_standing_animarray_aiming_AandC::this function needs to be called after the initial corner set_ functions" ); + } + + self.a.array = array_combine_keys( self.a.array, anim.standingAiming ); + + if ( weapon_pump_action_shotgun() ) + { + self.a.array[ "single" ] = self.a.array[ "shotgun_single" ]; + } + + if ( self.a.cornerMode == "lean" ) + { + // use the lean animations set up in cover_left and cover_right.gsc + leanfire = self.a.array[ "lean_fire" ]; + leanSemiFire = self.a.array[ "lean_single" ]; + self.a.array[ "fire" ] = leanfire; + self.a.array[ "single" ] = [ leanSemiFire ]; + + self.a.array[ "semi2" ] = leanSemiFire; + self.a.array[ "semi3" ] = leanSemiFire; + self.a.array[ "semi4" ] = leanSemiFire; + self.a.array[ "semi5" ] = leanSemiFire; + + self.a.array[ "burst2" ] = leanfire; + self.a.array[ "burst3" ] = leanfire; + self.a.array[ "burst4" ] = leanfire; + self.a.array[ "burst5" ] = leanfire; + self.a.array[ "burst6" ] = leanfire; + + self.a.array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + } +} + +set_crouching_animarray_aiming() +{ + if ( !isdefined( self.a.array ) ) + { + assertmsg( "set_standing_animarray_aiming_AandC::this function needs to be called after the initial corner set_ functions" ); + } + + if ( self.a.cornerMode == "over" ) + { + self.a.array = array_combine_keys( self.a.array, anim.crouchingAimingOver ); + + if ( weapon_pump_action_shotgun() ) + { + self.a.array[ "single" ] = self.a.array[ "shotgun_single" ]; + } + + return; + } + + self.a.array = array_combine_keys( self.a.array, anim.crouchingAiming ); + + if ( weapon_pump_action_shotgun() ) + { + self.a.array[ "single" ] = self.a.array[ "shotgun_single" ]; + } + + if ( self.a.cornerMode == "lean" ) + { + // use the lean animations set up in cover_left and cover_right.gsc + leanfire = self.a.array[ "lean_fire" ]; + leanSemiFire = self.a.array[ "lean_single" ]; + self.a.array[ "fire" ] = leanfire; + self.a.array[ "single" ] = [ leanSemiFire ]; + + self.a.array[ "semi2" ] = leanSemiFire; + self.a.array[ "semi3" ] = leanSemiFire; + self.a.array[ "semi4" ] = leanSemiFire; + self.a.array[ "semi5" ] = leanSemiFire; + + self.a.array[ "burst2" ] = leanfire; + self.a.array[ "burst3" ] = leanfire; + self.a.array[ "burst4" ] = leanfire; + self.a.array[ "burst5" ] = leanfire; + self.a.array[ "burst6" ] = leanfire; + + self.a.array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + } +} + +runCombat() +{ + self notify( "killanimscript" ); + self thread animscripts\combat::main(); +} \ No newline at end of file diff --git a/animscripts/cover_arrival.gsc b/animscripts/cover_arrival.gsc new file mode 100644 index 0000000..8cbe7d7 --- /dev/null +++ b/animscripts/cover_arrival.gsc @@ -0,0 +1,2460 @@ +#include animscripts\SetPoseMovement; +#include animscripts\combat_utility; +#include animscripts\utility; +#include common_scripts\utility; +#include maps\_utility; + +#using_animtree( "generic_human" ); + +// constants for exposed approaches +maxSpeed = 250;// units / sec +allowedError = 8; + + +main() +{ + self endon( "killanimscript" ); + self endon( "abort_approach" ); + + approachnumber = self.approachNumber; + + assert( isdefined( self.approachtype ) ); + + arrivalAnim = anim.coverTrans[ self.approachtype ][ approachnumber ]; + if ( use_custom_coverset( self.approachtype ) ) + { + arrivalAnim = self.customCoverEnterTrans[ self.approachtype ][ approachnumber ]; + } + + assert( isdefined( arrivalAnim ) ); + + if ( !isdefined( self.heat ) ) + self thread abortApproachIfThreatened(); + + // tagJW: Use the blend_finish tag to determine blend time on the arrival animation + blend_time = 0.2; + if( self IsCQBWalking() ) + { + blend_finish_time = GetNotetrackTimes( arrivalAnim, "cqb_blend_finish" ); + } + else + { + blend_finish_time = GetNotetrackTimes( arrivalAnim, "blend_finish" ); + } + + if ( blend_finish_time.size > 0 ) + { + blend_time = blend_finish_time[0] * GetAnimLength( arrivalAnim ); + } + + self clearanim( %body, blend_time ); + self setFlaggedAnimRestart( "coverArrival", arrivalAnim, 1, blend_time, self.moveTransitionRate ); + self animscripts\shared::DoNoteTracks( "coverArrival", ::handleStartAim ); + + newstance = anim.arrivalEndStance[ self.approachType ]; + assertex( isdefined( newstance ), "bad node approach type: " + self.approachtype ); + + if ( isdefined( newstance ) ) + self.a.pose = newstance; + + self.a.movement = "stop"; + + self.a.arrivalType = self.approachType; + + // we rely on cover to start doing something else with animations very soon. + // in the meantime, we don't want any of our parent nodes lying around with positive weights. + self clearanim( %root, .3 ); + + self.lastApproachAbortTime = undefined; +} + +handleStartAim( note ) +{ + if ( note == "start_aim" ) + { + if ( self.a.pose == "stand" ) + { + self animscripts\init_common::set_animarray_standing(); + } + else if ( self.a.pose == "crouch" ) + { + self animscripts\init_common::set_animarray_crouching(); + } + else + { + assertMsg( "Unsupported self.a.pose: " + self.a.pose ); + } + + self animscripts\combat::set_aim_and_turn_limits(); + + self.previousPitchDelta = 0.0; + + setupAim( 0 ); + + self thread animscripts\shared::trackShootEntOrPos(); + } +} + + +isThreatenedByEnemy() +{ + if ( !isdefined( self.node ) ) + return false; + + if ( isdefined( self.enemy ) && self seeRecently( self.enemy, 1.5 ) && distanceSquared( self.origin, self.enemy.origin ) < 250000 ) + return !( self isCoverValidAgainstEnemy() ); + + return false; +} + + +abortApproachIfThreatened() +{ + self endon( "killanimscript" ); + + while ( 1 ) + { + if ( !isdefined( self.node ) ) + return; + + if ( isThreatenedByEnemy() ) + { + self clearanim( %root, .3 ); + self notify( "abort_approach" ); + self.lastApproachAbortTime = getTime(); + return; + } + + wait 0.1; + } +} + +getNodeStanceYawOffset( approachtype ) +{ + // returns the base stance's yaw offset when hiding at a node, based off the approach type + if ( isdefined( self.heat ) ) + return 0; + + if ( approachtype == "left" || approachtype == "left_crouch" || approachtype == "left_cqb" || approachtype == "left_crouch_cqb" ) + return 90.0; + else if ( approachtype == "right" || approachtype == "right_crouch" || approachtype == "right_cqb" || approachtype == "right_crouch_cqb" ) + return - 90.0; + + return 0; +} + + +canUseSawApproach( node ) +{ + if ( !usingMG() ) + return false; + + if ( !isDefined( node.turretInfo ) ) + return false; + + if ( node.type != "Cover Stand" && node.type != "Cover Prone" && node.type!= "Cover Crouch" ) + return false; + + if ( isDefined( self.enemy ) && distanceSquared( self.enemy.origin, node.origin ) < 256 * 256 ) + return false; + + if ( GetNodeYawToEnemy() > 40 || GetNodeYawToEnemy() < - 40 ) + return false; + + return true; +} + +determineNodeApproachType( node ) +{ + if ( canUseSawApproach( node ) ) + { + if ( node.type == "Cover Stand" ) + return "stand_saw"; + if ( node.type == "Cover Crouch" ) + return "crouch_saw"; + else if ( node.type == "Cover Prone" ) + return "prone_saw"; + } + + if ( !isdefined( anim.approach_types[ node.type ] ) ) + return; + + if ( isdefined( node.arrivalStance ) ) + stance = node.arrivalStance; + else + stance = node getHighestNodeStance(); + + // no approach to prone + if ( stance == "prone" ) + stance = "crouch"; + + type = anim.approach_types[ node.type ][ stance ]; + + if ( self shouldCQB() ) + { + cqbType = type + "_cqb"; + if ( isdefined( anim.coverTrans[ cqbType ] ) ) + type = cqbType; + } + + return type; +} + +determineNodeExitType( node ) +{ + if ( canUseSawApproach( node ) ) + { + if ( node.type == "Cover Stand" ) + return "stand_saw"; + if ( node.type == "Cover Crouch" ) + return "crouch_saw"; + else if ( node.type == "Cover Prone" ) + return "prone_saw"; + } + + if ( !isdefined( anim.approach_types[ node.type ] ) ) + return; + + if ( isdefined( anim.requiredExitStance[ node.type ] ) && anim.requiredExitStance[ node.type ] != self.a.pose ) + return; + + stance = self.a.pose; + + // no exit from prone + if ( stance == "prone" ) + stance = "crouch"; + + type = anim.approach_types[ node.type ][ stance ]; + + if ( self shouldCQB() ) + { + cqbType = type + "_cqb"; + if ( isdefined( anim.coverExit[ cqbType ] ) ) + type = cqbType; + } + + return type; +} + +determineExposedApproachType( node ) +{ + if ( isdefined( self.heat ) ) + { + return "heat"; + } + + if ( isdefined( node.arrivalStance ) ) + stance = node.arrivalStance; + else + stance = node getHighestNodeStance(); + + // no approach to prone + if ( stance == "prone" ) + stance = "crouch"; + + if ( stance == "crouch" ) + type = "exposed_crouch"; + else + type = "exposed"; + + if ( shouldCQB() ) + return type + "_cqb"; + + return type; +} + + +getMaxDirectionsAndExcludeDirFromApproachType( node ) +{ + returnobj = spawnstruct(); + + if ( isdefined( node ) && isdefined( anim.maxDirections[ node.type ] ) ) + { + returnobj.maxDirections = anim.maxDirections[ node.type ]; + returnobj.excludeDir = anim.excludeDir[ node.type ]; + } + else + { + returnobj.maxDirections = 9; + returnobj.excludeDir = -1; + } + + return returnobj; +} + +shouldApproachToExposed( approachType ) +{ + // decide whether it's a good idea to go directly into the exposed position as we approach this node. + + if ( !isdefined( self.enemy ) ) + return false;// nothing to shoot! + + if ( self NeedToReload( 0.5 ) ) + return false; + + if ( self isSuppressedWrapper() ) + return false;// too dangerous, we need cover + + // path nodes have no special "exposed" position + if ( isdefined( anim.exposedTransition[ approachtype ] ) ) + return false; + + // no arrival animations into exposed for left/right crouch + if ( approachtype == "left_crouch" || approachtype == "right_crouch" ) + return false; + + return canSeePointFromExposedAtNode( self.enemy getShootAtPos(), self.node ); +} + + +calculateNodeOffsetFromAnimationDelta( nodeAngles, delta ) +{ + // in the animation, forward = +x and right = -y + right = anglestoright( nodeAngles ); + forward = anglestoforward( nodeAngles ); + + return vector_multiply( forward, delta[ 0 ] ) + vector_multiply( right, 0 - delta[ 1 ] ); +} + +getApproachEnt() +{ + if ( isdefined( self.scriptedArrivalEnt ) ) + return self.scriptedArrivalEnt; + + if ( isdefined( self.node ) ) + return self.node; + + return undefined; +} + +getApproachPoint( node, approachtype ) +{ + if ( approachType == "stand_saw" ) + { + approachPoint = ( node.turretInfo.origin[ 0 ], node.turretInfo.origin[ 1 ], node.origin[ 2 ] ); + forward = anglesToForward( ( 0, node.turretInfo.angles[ 1 ], 0 ) ); + right = anglesToRight( ( 0, node.turretInfo.angles[ 1 ], 0 ) ); + approachPoint = approachPoint + vector_multiply( forward, -32.545 ) - vector_multiply( right, 6.899 ); // -41.343 would work better for the first number if that weren't too far from the node =( + } + else if ( approachType == "crouch_saw" ) + { + approachPoint = ( node.turretInfo.origin[ 0 ], node.turretInfo.origin[ 1 ], node.origin[ 2 ] ); + forward = anglesToForward( ( 0, node.turretInfo.angles[ 1 ], 0 ) ); + right = anglesToRight( ( 0, node.turretInfo.angles[ 1 ], 0 ) ); + approachPoint = approachPoint + vector_multiply( forward, -32.545 ) - vector_multiply( right, 6.899 ); + } + else if ( approachType == "prone_saw" ) + { + approachPoint = ( node.turretInfo.origin[ 0 ], node.turretInfo.origin[ 1 ], node.origin[ 2 ] ); + forward = anglesToForward( ( 0, node.turretInfo.angles[ 1 ], 0 ) ); + right = anglesToRight( ( 0, node.turretInfo.angles[ 1 ], 0 ) ); + approachPoint = approachPoint + vector_multiply( forward, -37.36 ) - vector_multiply( right, 13.279 ); + } + else if ( isdefined( self.scriptedArrivalEnt ) ) + { + approachPoint = self.goalpos; + } + else + { + approachPoint = node.origin; + } + + return approachPoint; +} + + +checkApproachPreConditions() +{ + // if we're going to do a negotiation, we want to wait until it's over and move.gsc is called again + if ( isdefined( self getnegotiationstartnode() ) ) + { + /# debug_arrival( "Not doing approach: path has negotiation start node" ); #/ + return false; + } + + if ( isdefined( self.disableArrivals ) && self.disableArrivals ) + { + /# debug_arrival( "Not doing approach: self.disableArrivals is true" ); #/ + return false; + } + +/# + if ( isdefined( self.disableCoverArrivalsOnly ) ) + { + debug_arrival( "Not doing approach: self.disableCoverArrivalsOnly is true" ); + return false; + } +#/ + + /*if ( self shouldCQB() ) + { + /# debug_arrival("Not doing approach: self.cqbwalking is true"); #/ + return false; + }*/ + + return true; +} + + +checkApproachConditions( approachType, approach_dir, node ) +{ + // we're doing default exposed approaches in doLastMinuteExposedApproach now + if ( isdefined( anim.exposedTransition[ approachtype ] ) ) + return false; + + if ( approachType == "stand" || approachType == "crouch" ) + { + assert( isdefined( node ) ); + if ( AbsAngleClamp180( vectorToYaw( approach_dir ) - node.angles[ 1 ] + 180 ) < 60 ) + { + /# debug_arrival( "approach aborted: approach_dir is too far forward for node type " + node.type ); #/ + return false; + } + } + + if ( self isThreatenedByEnemy() || ( isdefined( self.lastApproachAbortTime ) && self.lastApproachAbortTime + 500 > getTime() ) ) + { + /# debug_arrival( "approach aborted: nearby enemy threat" ); #/ + return false; + } + + return true; +} + +/# +setupApproachNode_debugInfo( actor, approachType, approach_dir, approachNodeYaw, node ) +{ + if ( debug_arrivals_on_actor() ) + { + println( "^5approaching cover (ent " + actor getentnum() + ", type \"" + approachType + "\"):" ); + println( " approach_dir = (" + approach_dir[ 0 ] + ", " + approach_dir[ 1 ] + ", " + approach_dir[ 2 ] + ")" ); + angle = AngleClamp180( vectortoyaw( approach_dir ) - approachNodeYaw + 180 ); + if ( angle < 0 ) + println( " (Angle of " + ( 0 - angle ) + " right from node forward.)" ); + else + println( " (Angle of " + angle + " left from node forward.)" ); + + if ( approachType == "exposed" ) + { + if ( isdefined( node ) ) + { + if ( isdefined( approachtype ) ) + debug_arrival( "Aborting cover approach: node approach type was " + approachtype ); + else + debug_arrival( "Aborting cover approach: node approach type was undefined" ); + } + else + { + debug_arrival( "Aborting cover approach: node is undefined" ); + } + } + else + { + thread drawApproachVec( approach_dir ); + } + } +} +#/ + + +setupApproachNode( firstTime ) +{ + self endon( "killanimscript" ); + //self endon("path_changed"); + + if ( isdefined( self.heat ) ) + { + self thread doLastMinuteExposedApproachWrapper(); + return; + } + + // this lets code know that script is expecting the "cover_approach" notify + if ( firstTime ) + self.requestArrivalNotify = true; + + self.a.arrivalType = undefined; + self thread doLastMinuteExposedApproachWrapper(); + + self waittill( "cover_approach", approach_dir ); + + if ( !self checkApproachPreConditions() ) + return; + + self thread setupApproachNode( false ); // wait again incase path goal changes + + approachType = "exposed"; + approachPoint = self.pathGoalPos; + approachNodeYaw = vectorToYaw( approach_dir ); + approachFinalYaw = approachNodeYaw; + + node = getApproachEnt(); + + if ( isdefined( node ) ) + { + approachType = determineNodeApproachType( node ); + if ( isdefined( approachtype ) && approachtype != "exposed" ) + { + approachPoint = getApproachPoint( node, approachtype ); + approachNodeYaw = node.angles[ 1 ]; + approachFinalYaw = getNodeForwardYaw( node ); + } + } + + /# setupApproachNode_debugInfo( self, approachType, approach_dir, approachNodeYaw, node ); #/ + + if ( !checkApproachConditions( approachType, approach_dir, node ) ) + return; + + startCoverApproach( approachType, approachPoint, approachNodeYaw, approachFinalYaw, approach_dir ); +} + + +coverApproachLastMinuteCheck( approachPoint, approachFinalYaw, approachType, approachNumber, requiredYaw ) +{ + if ( isdefined( self.disableArrivals ) && self.disableArrivals ) + { + /# debug_arrival( "approach aborted at last minute: self.disableArrivals is true" ); #/ + return false; + } + + // so we don't make guys turn around when they're (smartly) facing their enemy as they walk away + if ( abs( self getMotionAngle() ) > 45 && isdefined( self.enemy ) && vectorDot( anglesToForward( self.angles ), vectorNormalize( self.enemy.origin - self.origin ) ) > .8 ) + { + /# debug_arrival( "approach aborted at last minute: facing enemy instead of current motion angle" ); #/ + return false; + } + + if ( self.a.pose != "stand" || ( self.a.movement != "run" && !( self isCQBWalkingOrFacingEnemy() ) ) ) + { + /# debug_arrival( "approach aborted at last minute: not standing and running" ); #/ + return false; + } + + if ( AbsAngleClamp180( requiredYaw - self.angles[ 1 ] ) > 30 ) + { + // don't do an approach away from an enemy that we would otherwise face as we moved away from them + if ( isdefined( self.enemy ) && self canSee( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < 256 * 256 ) + { + // check if enemy is in frontish of us + if ( vectorDot( anglesToForward( self.angles ), self.enemy.origin - self.origin ) > 0 ) + { + /# debug_arrival( "aborting approach at last minute: don't want to turn back to nearby enemy" ); #/ + return false; + } + } + } + + // make sure the path is still clear + if ( !checkCoverEnterPos( approachPoint, approachFinalYaw, approachType, approachNumber, false ) ) + { + /# debug_arrival( "approach blocked at last minute" ); #/ + return false; + } + + return true; +} + +approachWaitTillClose( node, checkDist ) +{ + if ( !isdefined( node ) ) + return; + + // wait until we get to the point where we have to decide what approach animation to play + while ( 1 ) + { + if ( !isdefined( self.pathGoalPos ) ) + self waitForPathGoalPos(); + + dist = distance( self.origin, self.pathGoalPos ); + + if ( dist <= checkDist + allowedError ) + break; + + // underestimate how long to wait so we don't miss the crucial point + waittime = ( dist - checkDist ) / maxSpeed - .1; + if ( waittime < .05 ) + waittime = .05; + + wait waittime; + } +} + +startCoverApproach( approachType, approachPoint, approachNodeYaw, approachFinalYaw, approach_dir ) +{ + self endon( "killanimscript" ); + self endon( "cover_approach" ); + + assert( isdefined( approachType ) ); + assert( approachType != "exposed" ); + + node = getApproachEnt(); + result = getMaxDirectionsAndExcludeDirFromApproachType( node ); + maxDirections = result.maxDirections; + excludeDir = result.excludeDir; + + arrivalFromFront = vectorDot( approach_dir, anglestoforward( node.angles ) ) >= 0; + + // find best possible position to start arrival animation + result = self CheckArrivalEnterPositions( approachPoint, approachFinalYaw, approachType, approach_dir, maxDirections, excludeDir, arrivalFromFront ); + + if ( result.approachNumber < 0 ) + { + /# debug_arrival( "approach aborted: " + result.failure ); #/ + self notify( "approach_aborted" ); + return; + } + + approachNumber = result.approachNumber; + /# debug_arrival( "approach success: dir " + approachNumber ); #/ + + if ( level._newArrivals && approachNumber <= 6 && arrivalFromFront ) + { + self endon( "goal_changed" ); + + if (use_custom_coverset( approachtype ) ) + { + self.arrivalStartDist = self.customCoverTransLongestDist[ approachtype ]; + } + else + { + self.arrivalStartDist = anim.coverTransLongestDist[ approachtype ]; + } + + approachWaitTillClose( node, self.arrivalStartDist ); + + // get the best approach direction from current position + dirToNode = vectorNormalize( approachPoint - self.origin ); + result = self CheckArrivalEnterPositions( approachPoint, approachFinalYaw, approachType, dirToNode, maxDirections, excludeDir, arrivalFromFront ); + + if (use_custom_coverset( approachtype ) ) + { + self.arrivalStartDist = length( self.customCoverEnterDist[ approachtype ][ approachNumber ] ); + } + else + { + self.arrivalStartDist = length( anim.coverTransDist[ approachtype ][ approachNumber ] ); + } + approachWaitTillClose( node, self.arrivalStartDist ); + + if ( !( self maymovetopoint( approachPoint ) ) ) + { + /# debug_arrival( "approach blocked at last minute" ); #/ + self.arrivalStartDist = undefined; + self notify( "approach_aborted" ); + return; + } + + if ( result.approachNumber < 0 ) + { + /# debug_arrival( "final approach aborted: " + result.failure ); #/ + self.arrivalStartDist = undefined; + self notify( "approach_aborted" ); + return; + } + + approachNumber = result.approachNumber; + /# debug_arrival( "final approach success: dir " + approachNumber ); #/ + + requiredYaw = approachFinalYaw - get_approach_yaw( approachType, approachNumber ); + } + else + { + // set arrival position and wait + self setRunToPos( self.coverEnterPos ); + self waittill( "runto_arrived" ); + + requiredYaw = approachFinalYaw - get_approach_yaw( approachType, approachNumber ); + + if ( !self coverApproachLastMinuteCheck( approachPoint, approachFinalYaw, approachType, approachNumber, requiredYaw ) ) + { + self notify( "approach_aborted" ); + return; + } + } + + self.approachNumber = approachNumber; // used in cover_arrival::main() + self.approachType = approachType; + self.arrivalStartDist = undefined; + self startcoverarrival( self.coverEnterPos, requiredYaw ); +} + +get_approach_yaw( approachType, approachNumber ) +{ + if( use_custom_coverset( approachtype ) ) + { + requiredYaw = self.customCoverEnterAngles[ approachType ][ approachNumber ]; + } + else + { + requiredYaw = anim.coverTransAngles[ approachType ][ approachNumber ]; + } + + return requiredYaw; +} + + +CheckArrivalEnterPositions( approachpoint, approachYaw, approachtype, approach_dir, maxDirections, excludeDir, arrivalFromFront ) +{ + assert( approachtype != "exposed" ); + angleDataObj = spawnstruct(); + + calculateNodeTransitionAngles( angleDataObj, approachtype, true, approachYaw, approach_dir, maxDirections, excludeDir ); + sortNodeTransitionAngles( angleDataObj, maxDirections ); + + resultobj = spawnstruct(); + /#resultobj.data = [];#/ + + arrivalPos = ( 0, 0, 0 ); + resultobj.approachNumber = -1; + + numAttempts = 2; + + for ( i = 1; i <= numAttempts; i++ ) + { + assert( angleDataObj.transIndex[ i ] != excludeDir );// shouldn't hit excludeDir unless numAttempts is too big + + resultobj.approachNumber = angleDataObj.transIndex[ i ]; + + if ( !self checkCoverEnterPos( approachpoint, approachYaw, approachtype, resultobj.approachNumber, arrivalFromFront ) ) + { + /#resultobj.data[ resultobj.data.size ] = "approach blocked: dir " + resultobj.approachNumber;#/ + continue; + } + break; + } + + if ( i > numAttempts ) + { + /#resultobj.failure = numAttempts + " direction attempts failed";#/ + resultobj.approachNumber = -1; + return resultobj; + } + + // if AI is closer to node than coverEnterPos is, don't do arrival + distToApproachPoint = distanceSquared( approachpoint, self.origin ); + distToAnimStart = distanceSquared( approachpoint, self.coverEnterPos ); + if ( distToApproachPoint < distToAnimStart * 2 * 2 ) + { + if ( distToApproachPoint < distToAnimStart ) + { + /#resultobj.failure = "too close to destination";#/ + resultobj.approachNumber = -1; + return resultobj; + } + + if ( !level._newArrivals || !arrivalFromFront ) + { + // if AI is less than twice the distance from the node than the beginning of the approach animation, + // make sure the angle we'll turn when we start the animation is small. + selfToAnimStart = vectorNormalize( self.coverEnterPos - self.origin ); + + if( use_custom_coverset( approachtype ) ) + { + requiredYaw = approachYaw - self.customCoverEnterAngles[ approachType ][ resultobj.approachNumber ]; + } + else + { + requiredYaw = approachYaw - anim.coverTransAngles[ approachType ][ resultobj.approachNumber ]; + } + + AnimStartToNode = anglesToForward( ( 0, requiredYaw, 0 ) ); + cosAngle = vectorDot( selfToAnimStart, AnimStartToNode ); + + if ( cosAngle < 0.707 )// 0.707 == cos( 45 ) + { + /#resultobj.failure = "angle to start of animation is too great (angle of " + acos( cosAngle ) + " > 45)";#/ + resultobj.approachNumber = -1; + return resultobj; + } + } + } + + /# + for ( i = 0; i < resultobj.data.size; i++ ) + debug_arrival( resultobj.data[ i ] ); + #/ + + return resultobj; +} + +doLastMinuteExposedApproachWrapper() +{ + self endon( "killanimscript" ); + self endon( "move_interrupt" ); + + self notify( "doing_last_minute_exposed_approach" ); + self endon( "doing_last_minute_exposed_approach" ); + + self thread watchGoalChanged(); + + while ( 1 ) + { + doLastMinuteExposedApproach(); + + // try again when our goal pos changes + while ( 1 ) + { + self waittill_any( "goal_changed", "goal_changed_previous_frame" ); + + // our goal didn't *really* change if it only changed because we called setRunToPos + if ( isdefined( self.coverEnterPos ) && isdefined( self.pathGoalPos ) && distance2D( self.coverEnterPos, self.pathGoalPos ) < 1 ) + continue; + break; + } + } +} + +watchGoalChanged() +{ + self endon( "killanimscript" ); + self endon( "doing_last_minute_exposed_approach" ); + + while ( 1 ) + { + self waittill( "goal_changed" ); + wait .05; + self notify( "goal_changed_previous_frame" ); + } +} + + +exposedApproachConditionCheck( node, goalMatchesNode ) +{ + if ( !isdefined( self.pathGoalPos ) ) + { + /# debug_arrival( "Aborting exposed approach because I have no path" ); #/ + return false; + } + + if ( isdefined( self.disableArrivals ) && self.disableArrivals ) + { + /# debug_arrival( "Aborting exposed approach because self.disableArrivals is true" ); #/ + return false; + } + + if ( isdefined( self.approachConditionCheckFunc ) ) + { + if ( !self [[self.approachConditionCheckFunc]]( node ) ) + return false; + } + else + { + if ( !self.faceMotion && ( !isdefined( node ) || node.type == "Path" ) ) + { + /# debug_arrival( "Aborting exposed approach because not facing motion and not going to a node" ); #/ + return false; + } + + if ( self.a.pose != "stand" ) + { + /# debug_arrival( "approach aborted at last minute: not standing" ); #/ + return false; + } + } + + if ( self isThreatenedByEnemy() || ( isdefined( self.lastApproachAbortTime ) && self.lastApproachAbortTime + 500 > getTime() ) ) + { + /# debug_arrival( "approach aborted: nearby enemy threat" ); #/ + return false; + } + + // only do an arrival if we have a clear path + if ( !self maymovetopoint( self.pathGoalPos ) ) + { + /#debug_arrival( "Aborting exposed approach: maymove check failed" );#/ + return false; + } + + return true; +} + +exposedApproachWaitTillClose() +{ + // wait until we get to the point where we have to decide what approach animation to play + while ( 1 ) + { + if ( !isdefined( self.pathGoalPos ) ) + self waitForPathGoalPos(); + + node = getApproachEnt(); + if ( isdefined( node ) && !isdefined( self.heat ) ) + arrivalPos = node.origin; + else + arrivalPos = self.pathGoalPos; + + dist = distance( self.origin, arrivalPos ); + + checkDist = anim.longestExposedApproachDist; + + if ( dist <= checkDist + allowedError ) + break; + + // underestimate how long to wait so we don't miss the crucial point + waittime = ( dist - checkDist ) / maxSpeed - .1; + if ( waittime < 0 ) + break; + + if ( waittime < .05 ) + waittime = .05; + + // /#self thread animscripts\shared::showNoteTrack("wait " + waittime);#/ + wait waittime; + } +} + + +faceEnemyAtEndOfApproach( node ) +{ + if ( !isdefined( self.enemy ) ) + return false; + + if ( isdefined( self.heat ) && isdefined( node ) ) + return false; + + if ( self.combatmode == "cover" && isSentient( self.enemy ) && gettime() - self lastKnownTime( self.enemy ) > 15000 ) + return false; + + return sightTracePassed( self.enemy getShootAtPos(), self.pathGoalPos + ( 0, 0, 60 ), false, undefined ); +} + + +doLastMinuteExposedApproach() +{ + self endon( "goal_changed" ); + self endon( "move_interrupt" ); + + if ( isdefined( self getnegotiationstartnode() ) ) + return; + + self exposedApproachWaitTillClose(); + + if ( isdefined( self.grenade ) && isdefined( self.grenade.activator ) && self.grenade.activator == self ) + return; + + approachType = "exposed"; + maxDistToNodeSq = 1; + + if ( isdefined( self.approachTypeFunc ) ) + { + approachtype = self [[ self.approachTypeFunc ]](); + } + else if ( self shouldCQB() ) + { + approachtype = "exposed_cqb"; + } + else if ( isdefined( self.heat ) ) + { + approachtype = "heat"; + maxDistToNodeSq = 64 * 64; + } + + node = getApproachEnt(); + + if ( isdefined( node ) && isdefined( self.pathGoalPos ) && !isdefined( self.disableCoverArrivalsOnly ) ) + goalMatchesNode = distanceSquared( self.pathGoalPos, node.origin ) < maxDistToNodeSq; + else + goalMatchesNode = false; + + if ( goalMatchesNode ) + approachtype = determineExposedApproachType( node ); + + approachDir = VectorNormalize( self.pathGoalPos - self.origin ); + + // by default, want to face forward + desiredFacingYaw = vectorToYaw( approachDir ); + + if ( isdefined( self.faceEnemyArrival ) ) + { + desiredFacingYaw = self.angles[1]; + } + else if ( faceEnemyAtEndOfApproach( node ) ) + { + desiredFacingYaw = vectorToYaw( self.enemy.origin - self.pathGoalPos ); + } + else + { + faceNodeAngle = isdefined( node ) && goalMatchesNode; + faceNodeAngle = faceNodeAngle && ( node.type != "Path" ) && ( node.type != "Ambush" || !recentlySawEnemy() ); + + if ( faceNodeAngle ) + { + desiredFacingYaw = getNodeForwardYaw( node ); + } + else + { + likelyEnemyDir = self getAnglesToLikelyEnemyPath(); + if ( isdefined( likelyEnemyDir ) ) + desiredFacingYaw = likelyEnemyDir[ 1 ]; + } + } + + angleDataObj = spawnstruct(); + calculateNodeTransitionAngles( angleDataObj, approachType, true, desiredFacingYaw, approachDir, 9, -1 ); + + // take best animation + best = 1; + for ( i = 2; i <= 9; i++ ) + { + if ( angleDataObj.transitions[ i ] > angleDataObj.transitions[ best ] ) + best = i; + } + self.approachNumber = angleDataObj.transIndex[ best ]; + self.approachType = approachType; + + /# debug_arrival( "Doing exposed approach in direction " + self.approachNumber ); #/ + + approachAnim = anim.coverTrans[ approachType ][ self.approachNumber ]; + + if ( use_custom_coverset( approachtype ) ) + { + animDist = length( self.customCoverEnterDist[ approachtype ][ self.approachNumber ] ); + } + else + { + animDist = length( anim.coverTransDist[ approachtype ][ self.approachNumber ] ); + } + + requiredDistSq = animDist + allowedError; + requiredDistSq = requiredDistSq * requiredDistSq; + + // we should already be close + while ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) > requiredDistSq ) + wait .05; + + if ( isdefined( self.arrivalStartDist ) && self.arrivalStartDist < animDist + allowedError ) + { + /# debug_arrival( "Aborting exposed approach because cover arrival dist is shorter" ); #/ + return; + } + + if ( !self exposedApproachConditionCheck( node, goalMatchesNode ) ) + { + return; + } + + dist = distance( self.origin, self.pathGoalPos ); + if ( abs( dist - animDist ) > allowedError ) + { + /# debug_arrival( "Aborting exposed approach because distance difference exceeded allowed error: " + dist + " more than " + allowedError + " from " + animDist ); #/ + return; + } + + facingYaw = vectorToYaw( self.pathGoalPos - self.origin ); + + if ( isdefined( self.heat ) && goalMatchesNode ) + { + requiredYaw = desiredFacingYaw - get_approach_yaw( approachType, self.approachNumber ); + idealStartPos = getArrivalStartPos( self.pathGoalPos, desiredFacingYaw, approachtype, self.approachNumber ); + } + else if ( animDist > 0 ) + { + if ( use_custom_coverset( approachtype ) ) + { + delta = self.customCoverEnterDist[ approachtype ][ self.approachNumber ]; + } + else + { + delta = anim.coverTransDist[ approachtype ][ self.approachNumber ]; + } + + assert( delta[ 0 ] != 0 ); + yawToMakeDeltaMatchUp = atan( delta[ 1 ] / delta[ 0 ] ); + + if ( !isdefined( self.faceEnemyArrival ) || self.faceMotion ) + { + requiredYaw = facingYaw - yawToMakeDeltaMatchUp; + if ( AbsAngleClamp180( requiredYaw - self.angles[ 1 ] ) > 30 ) + { + /# debug_arrival( "Aborting exposed approach because angle change was too great" ); #/ + return; + } + } + else + { + requiredYaw = self.angles[1]; + } + + closerDist = dist - animDist; + idealStartPos = self.origin + vector_multiply( vectorNormalize( self.pathGoalPos - self.origin ), closerDist ); + } + else + { + requiredYaw = self.angles[1]; + idealStartPos = self.origin; + } + + self startcoverarrival( idealStartPos, requiredYaw ); +} + +waitForPathGoalPos() +{ + while ( 1 ) + { + if ( isdefined( self.pathgoalpos ) ) + return; + + wait 0.1; + } +} + + +startMoveTransitionPreConditions() +{ + // if we don't know where we're going, we can't check if it's a good idea to do the exit animation + // (and it's probably not) + if ( !isdefined( self.pathGoalPos ) ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.pathGoalPos is undefined" ); #/ + return false; + } + + if ( !self shouldFaceMotion() ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.faceMotion is false" ); #/ + return false; + } + + if ( self.a.pose == "prone" ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.a.pose is \"prone\"" ); #/ + return false; + } + + if ( isdefined( self.disableExits ) && self.disableExits ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.disableExits is true" ); #/ + return false; + } + + if ( self.stairsState != "none" ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): on stairs" ); #/ + return false; + } + + if ( !self isStanceAllowed( "stand" ) && !isdefined( self.heat ) ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): not allowed to stand" ); #/ + return false; + } + + if ( distanceSquared( self.origin, self.pathGoalPos ) < 10000 ) + { + /# debug_arrival( "not exiting cover (ent " + self getentnum() + "): too close to goal" ); #/ + return false; + } + + return true; +} + + +startMoveTransitionConditions( exittype, exitNode ) +{ + if ( !isdefined( exittype ) ) + { + /# debug_arrival( "aborting exit: not supported for node type " + exitNode.type ); #/ + return false; + } + + // since we transition directly into a standing run anyway, + // we might as well just use the standing exits when crouching too + if ( exittype == "exposed" || isdefined( self.heat ) ) + { + if ( self.a.pose != "stand" && self.a.pose != "crouch" ) + { + /# debug_arrival( "exposed exit aborted because anim_pose is not \"stand\" or \"crouch\"" ); #/ + return false; + } + if ( self.a.movement != "stop" ) + { + /# debug_arrival( "exposed exit aborted because anim_movement is not \"stop\"" ); #/ + return false; + } + } + + // don't do an exit away from an enemy that we would otherwise face as we moved away from them + if ( !isdefined( self.heat ) && isdefined( self.enemy ) && vectorDot( self.lookaheaddir, self.enemy.origin - self.origin ) < 0 ) + { + if ( self canSeeEnemyFromExposed() && distanceSquared( self.origin, self.enemy.origin ) < 300 * 300 ) + { + /# debug_arrival( "aborting exit: don't want to turn back to nearby enemy" ); #/ + return false; + } + } + + return true; +} + +/# +startMoveTransition_debugInfo( exittype, exityaw ) +{ + if ( debug_arrivals_on_actor() ) + { + println( "^3exiting cover (ent " + self getentnum() + ", type \"" + exittype + "\"):" ); + println( " lookaheaddir = (" + self.lookaheaddir[ 0 ] + ", " + self.lookaheaddir[ 1 ] + ", " + self.lookaheaddir[ 2 ] + ")" ); + angle = AngleClamp180( vectortoyaw( self.lookaheaddir ) - exityaw ); + if ( angle < 0 ) + println( " (Angle of " + ( 0 - angle ) + " right from node forward.)" ); + else + println( " (Angle of " + angle + " left from node forward.)" ); + } +} +#/ + +getExitNode() +{ + exitNode = undefined; + + if ( !isdefined( self.heat ) ) + limit = 400; // 20 * 20 + else + limit = 4096; // 64 * 64 + + if ( isdefined( self.node ) && ( distanceSquared( self.origin, self.node.origin ) < limit ) ) + exitNode = self.node; + else if ( isdefined( self.prevNode ) && ( distanceSquared( self.origin, self.prevNode.origin ) < limit ) ) + exitNode = self.prevNode; + + if ( isdefined( exitNode ) && isdefined( self.heat ) && AbsAngleClamp180( self.angles[1] - exitNode.angles[1] ) > 30 ) + return undefined; + + return exitNode; +} + +customMoveTransitionFunc() +{ + if ( !isdefined( self.startMoveTransitionAnim ) ) + return; + + self animmode( "zonly_physics", false ); + self orientmode( "face current" ); + + self SetFlaggedAnimKnobAllRestart( "move", self.startMoveTransitionAnim, %root, 1 ); + + if ( animHasNotetrack( self.startMoveTransitionAnim, "code_move" ) ) + { + self animscripts\shared::DoNoteTracks( "move" ); // return on code_move + self OrientMode( "face motion" ); // want to face motion since we are only playing exit animation( no l / r / b animations ) + self animmode( "none", false ); + } + + self animscripts\shared::DoNoteTracks( "move" ); +} + + +determineNonNodeExitType( exittype ) +{ + if ( self.a.pose == "stand" ) + exittype = "exposed"; + else + exittype = "exposed_crouch"; + + if ( shouldCQB() ) + exittype = exittype + "_cqb"; + else if ( isdefined( self.heat ) ) + exittype = "heat"; + + return exittype; +} + +determineHeatCoverExitType( exitNode, exittype ) +{ + if ( exitNode.type == "Cover Right" ) + exittype = "heat_right"; + else if ( exitNode.type == "Cover Left" ) + exittype = "heat_left"; + + return exittype; +} + + +startMoveTransition() +{ + if ( isdefined( self.customMoveTransition ) ) + { + customTransition = self.customMoveTransition; + if ( !isdefined( self.permanentCustomMoveTransition ) ) + self.customMoveTransition = undefined; + + [[ customTransition ]](); + + if ( !isdefined( self.permanentCustomMoveTransition ) ) + self.startMoveTransitionAnim = undefined; + + self clearanim( %root, 0.2 ); + self orientmode( "face default" ); + self animmode( "none", false ); + + return; + } + + self endon( "killanimscript" ); + + if ( !self startMoveTransitionPreConditions() ) + return; + + // assume an exit from exposed. + exitpos = self.origin; + exityaw = self.angles[ 1 ]; + exittype = "exposed"; + exitTypeFromNode = false; + + exitNode = getExitNode(); + + // if we're at a node, try to do an exit from the node. + if ( isdefined( exitNode ) ) + { + nodeExitType = determineNodeExitType( exitNode ); + + if ( isdefined( nodeExitType ) ) + { + exitType = nodeExitType; + exitTypeFromNode = true; + + if ( isdefined( self.heat ) ) + exitType = determineHeatCoverExitType( exitNode, exittype ); + + if ( !isdefined( anim.exposedTransition[ exitType ] ) && exittype != "stand_saw" && exittype != "crouch_saw" ) + { + // if angle is wrong, don't do exit behavior for the node. Distance check already done in getExitNode + + anglediff = AbsAngleClamp180( self.angles[ 1 ] - GetNodeForwardYaw( exitNode ) ); + if ( anglediff < 5 ) + { + // do exit behavior for the node. + if ( !isdefined( self.heat ) ) + exitpos = exitNode.origin; + exityaw = GetNodeForwardYaw( exitNode ); + } + } + } + } + + /# self startMoveTransition_debugInfo( exittype, exityaw ); #/ + + if ( !self startMoveTransitionConditions( exittype, exitNode ) ) + return; + + isExposedExit = isdefined( anim.exposedTransition[ exittype ] ); + if ( !exitTypeFromNode ) + exittype = determineNonNodeExitType(); + + // since we're leaving, take the opposite direction of lookahead + leaveDir = ( -1 * self.lookaheaddir[ 0 ], -1 * self.lookaheaddir[ 1 ], 0 ); + + result = getMaxDirectionsAndExcludeDirFromApproachType( exitNode ); + maxDirections = result.maxDirections; + excludeDir = result.excludeDir; + + angleDataObj = spawnstruct(); + + calculateNodeTransitionAngles( angleDataObj, exittype, false, exityaw, leaveDir, maxDirections, excludeDir ); + sortNodeTransitionAngles( angleDataObj, maxDirections ); + + approachnumber = -1; + numAttempts = 3; + + for ( i = 1; i <= numAttempts; i++ ) + { + assert( angleDataObj.transIndex[ i ] != excludeDir );// shouldn't hit excludeDir unless numAttempts is too big + + approachNumber = angleDataObj.transIndex[ i ]; + if ( self checkCoverExitPos( exitpos, exityaw, exittype, isExposedExit, approachNumber ) ) + break; + + /# debug_arrival( "exit blocked: dir " + approachNumber ); #/ + } + + if ( i > numAttempts ) + { + /# debug_arrival( "aborting exit: too many exit directions blocked" ); #/ + return; + } + + // if AI is closer to destination than exitPos is, don't do exit + allowedDistSq = distanceSquared( self.origin, self.coverExitPos ) * 1.25 * 1.25; + if ( distanceSquared( self.origin, self.pathgoalpos ) < allowedDistSq ) + { + /# debug_arrival( "exit failed, too close to destination" ); #/ + return; + } + + /# debug_arrival( "exit success: dir " + approachNumber ); #/ + self doCoverExitAnimation( exittype, approachNumber ); +} + +str( val ) +{ + if ( !isdefined( val ) ) + return "{undefined}"; + return val; +} + +doCoverExitAnimation( exittype, approachNumber ) +{ + assert( isdefined( approachNumber ) ); + assert( approachnumber > 0 ); + + assert( isdefined( exittype ) ); + + leaveAnim = anim.coverExit[ exittype ][ approachnumber ]; + if ( IsDefined( self.customCoverExitTrans ) && IsDefined( self.customCoverExitTrans[ exittype ] ) && IsDefined( self.customCoverExitTrans[ exittype ][ approachnumber ] ) ) + { + leaveAnim = self.customCoverExitTrans[ exittype ][ approachnumber ]; + } + + assert( isdefined( leaveAnim ) ); + + lookaheadAngles = vectortoangles( self.lookaheaddir ); + + /# + if ( debug_arrivals_on_actor() ) + { + endpos = self.origin + vector_multiply( self.lookaheaddir, 100 ); + thread debugLine( self.origin, endpos, ( 1, 0, 0 ), 1.5 ); + } + #/ + + if ( self.a.pose == "prone" ) + return; + + // tagJW: Use the blend_finish tag to determine blend time on the arrival animation + transTime = 0.2; + blend_out_time = 0.2; + self.cqb_blend_finish_time = undefined; + self.run_blend_finish_time = undefined; + + if ( animHasNotetrack( leaveAnim, "blend_finish" ) && !(self IsCQBWalking()) ) + { + finish_time = GetNoteTrackTimes( leaveAnim, "finish" ); + blend_finish_time = GetNotetrackTimes( leaveAnim, "blend_finish" ); + + /# + if ( blend_finish_time[0] < finish_time[0] ) + { + AssertMsg( "blend_finish tag must occur later than finish tag." ); + } + #/ + + assert( blend_finish_time[0] > finish_time[0] ); + self.run_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( leaveAnim ); + blend_out_time = self.run_blend_finish_time; + } + else if ( animHasNotetrack( leaveAnim, "cqb_blend_finish" ) && (self IsCQBWalking()) ) + { + finish_time = GetNoteTrackTimes( leaveAnim, "finish" ); + blend_finish_time = GetNotetrackTimes( leaveAnim, "cqb_blend_finish" ); + + /# + if ( blend_finish_time[0] < finish_time[0] ) + { + AssertMsg( "blend_finish tag must occur later than finish tag." ); + } + #/ + + assert( blend_finish_time[0] > finish_time[0] ); + self.cqb_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( leaveAnim ); + blend_out_time = self.cqb_blend_finish_time; + } + + // tagJW: Check to see if a run anim start has been defined for this anim + self animscripts\utility::handle_move_transition_notes( leaveAnim ); + + self animMode( "zonly_physics", false ); + self OrientMode( "face angle", self.angles[ 1 ] ); + self setFlaggedAnimKnobAllRestart( "coverexit", leaveAnim, %body, 1, transTime, self.moveTransitionRate ); + + assert( animHasNotetrack( leaveAnim, "code_move" ) ); + + self animscripts\shared::DoNoteTracks( "coverexit" ); // until "code_move" + + self.a.pose = "stand"; + self.a.movement = "run"; + + self.ignorePathChange = undefined; + self OrientMode( "face motion" ); // want to face motion since we are only playing exit animation( no l / r / b animations ) + self animmode( "none", false ); + + self finishCoverExitNotetracks( "coverexit" ); + + // need to clear everything above leaveAnim + //self clearanim( leaveAnim, 0.2 ); + self clearanim( %root, blend_out_time ); + + self OrientMode( "face default" ); + //self thread faceEnemyOrMotionAfterABit(); + self animMode( "normal", false ); +} + +finishCoverExitNotetracks( flagname ) +{ + self endon( "move_loop_restart" ); + self animscripts\shared::DoNoteTracks( flagname ); +} + +/*faceEnemyOrMotionAfterABit() +{ + self endon( "killanimscript" ); + self endon( "move_interrupt" ); + + wait 1.0; + + // don't want to spin around if we're almost where we're going anyway + while ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) < 200 * 200 ) + wait .25; + + self OrientMode( "face default" ); +}*/ + + +drawVec( start, end, duration, color ) +{ + for ( i = 0; i < duration * 100; i++ ) + { + line( start + ( 0, 0, 30 ), end + ( 0, 0, 30 ), color ); + wait 0.05; + } +} + +drawApproachVec( approach_dir ) +{ + self endon( "killanimscript" ); + for ( ;; ) + { + if ( !isdefined( self.node ) ) + break; + line( self.node.origin + ( 0, 0, 20 ), ( self.node.origin - vector_multiply( approach_dir, 64 ) ) + ( 0, 0, 20 ) ); + wait( 0.05 ); + } +} + +calculateNodeTransitionAngles( angleDataObj, approachtype, isarrival, arrivalYaw, approach_dir, maxDirections, excludeDir ) +{ + angleDataObj.transitions = []; + angleDataObj.transIndex = []; + + anglearray = undefined; + sign = 1; + offset = 0; + if ( isarrival ) + { + if( use_custom_coverset( approachtype ) ) + { + anglearray = self.customCoverEnterAngles[ approachtype ]; + } + else + { + anglearray = anim.coverTransAngles[ approachtype ]; + } + sign = -1; + offset = 0; + } + else + { + if( use_custom_coverset( approachtype ) ) + { + anglearray = self.customCoverExitAngles[ approachtype ]; + } + else + { + anglearray = anim.coverExitAngles[ approachtype ]; + } + + sign = 1; + offset = 180; + } + + for ( i = 1; i <= maxDirections; i++ ) + { + angleDataObj.transIndex[ i ] = i; + + if ( i == 5 || i == excludeDir || !isdefined( anglearray[ i ] ) ) + { + angleDataObj.transitions[ i ] = -1.0003; // cos180 - epsilon + continue; + } + + angles = ( 0, arrivalYaw + sign * anglearray[ i ] + offset, 0 ); + + dir = vectornormalize( anglestoforward( angles ) ); + angleDataObj.transitions[ i ] = vectordot( approach_dir, dir ); + } +} + +/# +printdebug( pos, offset, text, color, linecolor ) +{ + for ( i = 0; i < 20 * 5; i++ ) + { + line( pos, pos + offset, linecolor ); + print3d( pos + offset, text, ( color, color, color ) ); + wait .05; + } +} +#/ + + +sortNodeTransitionAngles( angleDataObj, maxDirections ) +{ + for ( i = 2; i <= maxDirections; i++ ) + { + currentValue = angleDataObj.transitions[ angleDataObj.transIndex[ i ] ]; + currentIndex = angleDataObj.transIndex[ i ]; + + for ( j = i - 1; j >= 1; j -- ) + { + if ( currentValue < angleDataObj.transitions[ angleDataObj.transIndex[ j ] ] ) + break; + + angleDataObj.transIndex[ j + 1 ] = angleDataObj.transIndex[ j ]; + } + + angleDataObj.transIndex[ j + 1 ] = currentIndex; + } +} + +checkCoverExitPos( exitpoint, exityaw, exittype, isExposedExit, approachNumber ) +{ + angle = ( 0, exityaw, 0 ); + + forwardDir = anglestoforward( angle ); + rightDir = anglestoright( angle ); + + if( use_custom_coverset( exittype ) ) + { + forward = vector_multiply( forwardDir, self.customCoverExitDist[ exittype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, self.customCoverExitDist[ exittype ][ approachNumber ][ 1 ] ); + } + else + { + forward = vector_multiply( forwardDir, anim.coverExitDist[ exittype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, anim.coverExitDist[ exittype ][ approachNumber ][ 1 ] ); + } + + exitPos = exitpoint + forward - right; + self.coverExitPos = exitPos; + + /# + if ( debug_arrivals_on_actor() ) + thread debugLine( self.origin, exitpos, ( 1, .5, .5 ), 1.5 ); + #/ + + if ( !isExposedExit && !( self checkCoverExitPosWithPath( exitPos ) ) ) + { + /# + debug_arrival( "cover exit " + approachNumber + " path check failed" ); + #/ + return false; + } + + if ( !( self maymovefrompointtopoint( self.origin, exitPos ) ) ) + return false; + + if ( approachNumber <= 6 || isExposedExit ) + return true; + + // if 7, 8, 9 direction, split up check into two parts of the 90 degree turn around corner + // (already did the first part, from node to corner, now doing from corner to end of exit anim) + if( use_custom_coverset( exittype ) ) + { + forward = vector_multiply( forwardDir, self.customCoverExitPostDist[ exittype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, self.customCoverExitPostDist[ exittype ][ approachNumber ][ 1 ] ); + } + else + { + forward = vector_multiply( forwardDir, anim.coverExitPostDist[ exittype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, anim.coverExitPostDist[ exittype ][ approachNumber ][ 1 ] ); + } + + + finalExitPos = exitPos + forward - right; + self.coverExitPos = finalExitPos; + + /# + if ( debug_arrivals_on_actor() ) + thread debugLine( exitpos, finalExitPos, ( 1, .5, .5 ), 1.5 ); + #/ + return( self maymovefrompointtopoint( exitPos, finalExitPos ) ); +} + +// don't want to pass in anim.coverTransDist or coverTransPreDist as paramter, since it will be copied +getArrivalStartPos( arrivalPoint, arrivalYaw, approachType, approachNumber ) +{ + if( use_custom_coverset( approachtype ) ) + { + angle = ( 0, arrivalYaw - self.customCoverEnterAngles[ approachtype ][ approachNumber ], 0 ); + } + else + { + angle = ( 0, arrivalYaw - anim.coverTransAngles[ approachtype ][ approachNumber ], 0 ); + } + + forwardDir = anglestoforward( angle ); + rightDir = anglestoright( angle ); + + if ( use_custom_coverset( approachtype ) ) + { + animDist = length( self.customCoverEnterDist[ approachtype ][ approachNumber ] ); + forward = vector_multiply( forwardDir, self.customCoverEnterDist[ approachtype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, self.customCoverEnterDist[ approachtype ][ approachNumber ][ 1 ] ); + } + else + { + forward = vector_multiply( forwardDir, anim.coverTransDist[ approachtype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, anim.coverTransDist[ approachtype ][ approachNumber ][ 1 ] ); + } + + return arrivalpoint - forward + right; +} + +getArrivalPreStartPos( arrivalPoint, arrivalYaw, approachType, approachNumber ) +{ + if( use_custom_coverset( approachtype ) ) + { + angle = ( 0, arrivalYaw - self.customCoverEnterAngles[ approachtype ][ approachNumber ], 0 ); + } + else + { + angle = ( 0, arrivalYaw - anim.coverTransAngles[ approachtype ][ approachNumber ], 0 ); + } + + forwardDir = anglestoforward( angle ); + rightDir = anglestoright( angle ); + + if(use_custom_coverset( approachtype )) + { + forward = vector_multiply( forwardDir, self.customCoverTransPreDist[ approachtype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, self.customCoverTransPreDist[ approachtype ][ approachNumber ][ 1 ] ); + } + else + { + forward = vector_multiply( forwardDir, anim.coverTransPreDist[ approachtype ][ approachNumber ][ 0 ] ); + right = vector_multiply( rightDir, anim.coverTransPreDist[ approachtype ][ approachNumber ][ 1 ] ); + } + + return arrivalpoint - forward + right; +} + + +checkCoverEnterPos( arrivalpoint, arrivalYaw, approachtype, approachNumber, arrivalFromFront ) +{ + enterPos = getArrivalStartPos( arrivalPoint, arrivalYaw, approachType, approachNumber ); + self.coverEnterPos = enterPos; + + /# + if ( debug_arrivals_on_actor() ) + thread debugLine( enterPos, arrivalpoint, ( 1, .5, .5 ), 1.5 ); + #/ + + if ( level._newArrivals && approachNumber <= 6 && arrivalFromFront ) + return true; + + if ( !( self maymovefrompointtopoint( enterPos, arrivalpoint ) ) ) + return false; + + if ( approachNumber <= 6 || isdefined( anim.exposedTransition[ approachtype ] ) ) + return true; + + // if 7, 8, 9 direction, split up check into two parts of the 90 degree turn around corner + // (already did the second part, from corner to node, now doing from start of enter anim to corner) + + originalEnterPos = getArrivalPreStartPos( enterPos, arrivalYaw, approachType, approachNumber ); + self.coverEnterPos = originalEnterPos; + + /# + if ( debug_arrivals_on_actor() ) + thread debugLine( originalEnterPos, enterPos, ( 1, .5, .5 ), 1.5 ); + #/ + return( self maymovefrompointtopoint( originalEnterPos, enterPos ) ); +} + +use_custom_coverset( approachtype ) +{ + if ( IsDefined( self.customCoverEnterTrans ) && IsDefined( self.customCoverEnterTrans[ approachtype ] ) ) + { + return true; + } + else + { + return false; + } +} + +debug_arrivals_on_actor() +{ + /# + dvar = getdebugdvar( "debug_arrivals" ); + if ( dvar == "off" ) + return false; + + if ( dvar == "on" ) + return true; + + if ( int( dvar ) == self getentnum() ) + return true; + #/ + + return false; +} + + +debug_arrival( msg ) +{ + if ( !debug_arrivals_on_actor() ) + return; + + if ( IsDefined( self.name ) ) + { + msg = self.name + ": " + msg; + } + println( msg ); +} + +// -------------------------------------------------------------------------------- +// ---- Cover Arrival/Exit Tool ---- +// -------------------------------------------------------------------------------- + +/# +fakeAiLogic() +{ + self.animType = "default"; + self.a.script = "move"; + self.a.pose = "stand"; + self.a.prevPose = "stand"; + self.weapon = "ak47_sp"; + + self.ignoreMe = true; + self.ignoreAll = true; + + self AnimMode("nogravity"); + self ForceTeleport( level._player.origin + (0,0,-1000), self.origin ); + + while( IsDefined(self) && IsAlive(self) ) + { + wait(0.05); + } +} + +coverArrivalDebugTool() +{ + if ( isdefined( level.cover_arrival_debug_tool ) ) + { + return; + } + + level.cover_arrival_debug_tool = 1; + + wait 1; + + // same as nodeColorTable in pathnode.cpp + nodeColors = []; + nodeColors["Cover Stand"] = (0, 0.54, 0.66); + nodeColors["Cover Crouch"] = (0, 0.93, 0.72); + nodeColors["Cover Crouch Window"] = (0, 0.7, 0.5); + nodeColors["Cover Prone"] = (0, 0.6, 0.46); + nodeColors["Cover Right"] = (0.85, 0.85, 0.1); + nodeColors["Cover Left"] = (1, 0.7, 0); + nodeColors["Cover Wide Right"] = (0.75, 0.75, 0); + nodeColors["Cover Wide Left"] = (0.75, 0.53, 0.38); + nodeColors["Conceal Stand"] = (0, 0, 1); + nodeColors["Conceal Crouch"] = (0, 0, 0.75); + nodeColors["Conceal Prone"] = (0, 0, 0.5); + nodeColors["Turret"] = (0, 0.93, 0.72); + nodeColors["Bad"] = (1, 0, 0); + nodeColors["Poor"] = (1,0.5,0); + nodeColors["Ok"] = (1, 1, 0); + nodeColors["Good"] = (0, 1, 0); + + //wait_for_first_player(); + + player = level._player; + + // same as PREDICTION_TRACE_MIN + AIPHYS_STEPSIZE and PREDICTION_TRACE_MAX in actor_navigation.cpp + traceMin = (-15,-15,18); + traceMax = (15,15,72); + + hudGood = newHudElem(); + hudGood.location = 0; + hudGood.alignX = "left"; + hudGood.alignY = "middle"; + hudGood.foreground = 1; + hudGood.fontScale = 1.3; + hudGood.sort = 20; + hudGood.x = 680; + hudGood.y = 240; + hudGood.og_scale = 1; + hudGood.color = nodeColors["Good"]; + hudGood.alpha = 1; + + hudOk = newHudElem(); + hudOk.location = 0; + hudOk.alignX = "left"; + hudOk.alignY = "middle"; + hudOk.foreground = 1; + hudOk.fontScale = 1.3; + hudOk.sort = 20; + hudOk.x = 680; + hudOk.y = 260; + hudOk.og_scale = 1; + hudOk.color = nodeColors["Ok"]; + hudOk.alpha = 1; + + hudPoor = newHudElem(); + hudPoor.location = 0; + hudPoor.alignX = "left"; + hudPoor.alignY = "middle"; + hudPoor.foreground = 1; + hudPoor.fontScale = 1.3; + hudPoor.sort = 20; + hudPoor.x = 680; + hudPoor.y = 280; + hudPoor.og_scale = 1; + hudPoor.color = nodeColors["Poor"]; + hudPoor.alpha = 1; + + hudBad = newHudElem(); + hudBad.location = 0; + hudBad.alignX = "left"; + hudBad.alignY = "middle"; + hudBad.foreground = 1; + hudBad.fontScale = 1.3; + hudBad.sort = 20; + hudBad.x = 680; + hudBad.y = 300; + hudBad.og_scale = 1; + hudBad.color = nodeColors["Bad"]; + hudBad.alpha = 1; + + hudTotal = newHudElem(); + hudTotal.location = 0; + hudTotal.alignX = "left"; + hudTotal.alignY = "middle"; + hudTotal.foreground = 1; + hudTotal.fontScale = 1.3; + hudTotal.sort = 20; + hudTotal.x = 680; + hudTotal.y = 330; + hudTotal.og_scale = 1; + hudTotal.color = (1,1,1); + hudTotal.alpha = 1; + + hudGoodText = newHudElem(); + hudGoodText.location = 0; + hudGoodText.alignX = "right"; + hudGoodText.alignY = "middle"; + hudGoodText.foreground = 1; + hudGoodText.fontScale = 1.3; + hudGoodText.sort = 20; + hudGoodText.x = 670; + hudGoodText.y = 240; + hudGoodText.og_scale = 1; + hudGoodText.color = nodeColors["Good"]; + hudGoodText.alpha = 1; + hudGoodText SetText("Good: "); + + hudOkText = newHudElem(); + hudOkText.location = 0; + hudOkText.alignX = "right"; + hudOkText.alignY = "middle"; + hudOkText.foreground = 1; + hudOkText.fontScale = 1.3; + hudOkText.sort = 20; + hudOkText.x = 670; + hudOkText.y = 260; + hudOkText.og_scale = 1; + hudOkText.color = nodeColors["Ok"]; + hudOkText.alpha = 1; + hudOkText SetText("Ok: "); + + hudPoorText = newHudElem(); + hudPoorText.location = 0; + hudPoorText.alignX = "right"; + hudPoorText.alignY = "middle"; + hudPoorText.foreground = 1; + hudPoorText.fontScale = 1.3; + hudPoorText.sort = 20; + hudPoorText.x = 670; + hudPoorText.y = 280; + hudPoorText.og_scale = 1; + hudPoorText.color = nodeColors["Poor"]; + hudPoorText.alpha = 1; + hudPoorText SetText("Poor: "); + + hudBadText = newHudElem(); + hudBadText.location = 0; + hudBadText.alignX = "right"; + hudBadText.alignY = "middle"; + hudBadText.foreground = 1; + hudBadText.fontScale = 1.3; + hudBadText.sort = 20; + hudBadText.x = 670; + hudBadText.y = 300; + hudBadText.og_scale = 1; + hudBadText.color = nodeColors["Bad"]; + hudBadText.alpha = 1; + hudBadText SetText("Bad: "); + + hudLineText = newHudElem(); + hudLineText.location = 0; + hudLineText.alignX = "left"; + hudLineText.alignY = "middle"; + hudLineText.foreground = 1; + hudLineText.fontScale = 1.3; + hudLineText.sort = 20; + hudLineText.x = 630; + hudLineText.y = 315; + hudLineText.og_scale = 1; + hudLineText.color = (1,1,1); + hudLineText.alpha = 1; + hudLineText SetText("------------------"); + + hudTotalText = newHudElem(); + hudTotalText.location = 0; + hudTotalText.alignX = "right"; + hudTotalText.alignY = "middle"; + hudTotalText.foreground = 1; + hudTotalText.fontScale = 1.3; + hudTotalText.sort = 20; + hudTotalText.x = 670; + hudTotalText.y = 330; + hudTotalText.og_scale = 1; + hudTotalText.color = (1,1,1); + hudTotalText.alpha = 1; + hudTotalText SetText("Total: "); + + badNode = undefined; + + fakeAi = undefined; + + while(1) + { + tool = getdvarint( "ai_debugCoverArrivalsTool"); + + // check if there's a node selected in Radiant + if( tool <= 0 && IsDefined(level.nodedrone) && IsDefined(level.nodedrone.currentnode) ) + { + tool = 1; + } + + if( tool <= 0 ) + { + if( IsDefined(fakeAi) ) + { + fakeAi Delete(); + fakeAi = undefined; + } + + // hide the UI + hudBad.alpha = 0; + hudPoor.alpha = 0; + hudOk.alpha = 0; + hudGood.alpha = 0; + hudTotal.alpha = 0; + hudBadText.alpha = 0; + hudPoorText.alpha = 0; + hudOkText.alpha = 0; + hudGoodText.alpha = 0; + hudLineText.alpha = 0; + hudTotalText.alpha = 0; + + wait 0.2; + continue; + } + + // spawn the fakeAi + if( !IsDefined(fakeAi) ) + { + spawners = getSpawnerArray(); + + for( i=0; i < spawners.size; i++ ) + { + fakeAi = spawners[i] StalingradSpawn(); + + if( IsDefined(fakeAi) ) + { + spawners[0].count++; + fakeAi AnimCustom( ::fakeAiLogic ); + break; + } + } + + if( !IsDefined(fakeAi) ) + { + //iprintlnbold("no suitable spawners found in level"); + wait 0.2; + continue; + } + } + + // show the UI + hudBad.alpha = 1; + hudPoor.alpha = 1; + hudOk.alpha = 1; + hudGood.alpha = 1; + hudTotal.alpha = 1; + hudBadText.alpha = 1; + hudPoorText.alpha = 1; + hudOkText.alpha = 1; + hudGoodText.alpha = 1; + hudLineText.alpha = 1; + hudTotalText.alpha = 1; + + numBad = 0; + numPoor = 0; + numOk = 0; + numGood = 0; + tracesThisFrame = 0; + renderedThisFrame = 0; + evaluatedThisframe = 0; + + // find all cover node within given radius + radius = getdvarfloat( "ai_debugCoverArrivalsToolRadius"); + nodes = GetNodesInRadiusSorted( player.origin, radius, 0, 128, "Cover" ); + + // show the node selected in Radiant + if( tool > 0 && IsDefined(level.nodedrone) && IsDefined(level.nodedrone.currentnode) ) + { + nodes = []; + nodes[0] = level.nodedrone.currentnode; + } + + showNodes = getdvarint( "ai_debugCoverArrivalsToolShowNodes"); + + totalNodes = 1; + if( showNodes > 0 || nodes.size == 0 ) + { + totalNodes = nodes.size; + } + + fakeAi.cqbwalking = false; + fakeAi.weapon = "ak47_sp"; + fakeAi.heat = false; + + // set cqb/pistol + toolType = getdvarint( "ai_debugCoverArrivalsToolType"); + + if( toolType == 0 ) + { + fakeAi.cqbwalking = false; + } + else if( toolType == 1 ) + { + fakeAi.cqbwalking = true; + } + else if( toolType == 2 ) + { + fakeAi.weapon = "m1911_sp"; + } + + // ALEXP_TODO: this is pretty useless.. need to do it off FPS + // scale based on number of AI (more AI -> lower FPS) + minAi = 5; + maxAi = 15; + nodesMin = 5; + nodesMax = 30; + + allAi = GetAiArray(); //entsearch( level.CONTENTS_ACTOR, player.origin, 10000 ); + numAi = allAi.size; + + // clamp + if( numAi < minAi ) + { + numAi = minAi; + } + else if( numAi > maxAi ) + { + numAi = maxAi; + } + + maxNodesPerFrame = (numAi - minAi) / (maxAi - minAi); + maxNodesPerFrame = (1-maxNodesPerFrame) * (nodesMax - nodesMin) + nodesMin; + + // how often to evaluate nodes (spread them out for optimization) + frameInterval = int( ceil( totalNodes / maxNodesPerFrame ) ); + //println("interval: " + frameInterval + " nodes: " + maxNodesPerFrame + " ai: " + numAi); + + for( i=0; i < totalNodes && i < nodes.size; i++ ) + { + node = nodes[i]; + + // clear the cached data + if( !IsDefined(node.tool) || node.tool != tool || !IsDefined(node.toolType) || node.toolType != toolType ) + { + node.angleDeltaArray = undefined; + node.moveDeltaArray = undefined; + node.preMoveDeltaArray = undefined; + node.postMoveDeltaArray = undefined; + node.tool = tool; + node.toolType = toolType; + } + + assert( IsDefined(node) ); + if( !IsDefined(node) || node.type == "BAD NODE" || node.type == "Path" ) + { + totalNodes++; + continue; + } + else if( !IsDefined( anim.approach_types[node.type] ) ) + { + totalNodes++; + continue; + } + + // skip node evaluation every few frames (PhysicsTrace is expensive) + if( IsDefined(node.lastCheckedTime) && (gettime() - node.lastCheckedTime) < 50*frameInterval ) + { + if( node.lastRatio == 0 ) + { + numBad++; + } + else if( node.lastRatio < 0.5 ) + { + numPoor++; + } + else if( node.lastRatio < 1.0 ) + { + numOk++; + } + else + { + numGood++; + } + + continue; + } + + // limit the number of traces per frame + if( evaluatedThisFrame > maxNodesPerFrame ) // 30 nodes + { + continue; + } + + // use the the AI that's on the node if there is one + testAi = fakeAi; + /*nearAiArray = entsearch( level.CONTENTS_ACTOR, node.origin, 16 ); + + if( nearAiArray.size > 0 ) + { + testAi = nearAiArray[0]; + }*/ + + renderNode = true; + if( DistanceSquared(node.origin, player.origin) > 800*800 ) //|| renderedThisFrame*frameInterval > maxNodesPerFrame ) + { + renderNode = false; + } + + nodeColor = nodeColors["Good"]; + + // find transition type + //stance = node isNodeDontStand() && !node isNodeDontCrouch(); + //transType = anim.approach_types[node.type][stance]; + transType = fakeAi determineNodeApproachType( node ); + + totalTransitions = 0; + validTransitions = 0; + + /*animName = "arrive_" + transType; + + // exit support + if( tool == 2 ) + { + animName = "exit_" + transType; + } + + if( fakeAi shouldDoCQBTransition( node, transType ) ) + animName = animName + "_cqb";*/ + + // cache for performance + if( !IsDefined(node.angleDeltaArray) ) + { + if ( tool == 2 ) // Exit + { + node.angleDeltaArray = anim.coverExitAngles[ transType ]; + } + else + { + node.angleDeltaArray = anim.coverTransAngles[ transType ]; + } + } + + // go through all available transitions + for( j=1; j <= 9; j++ ) + { + if( IsDefined(node.angleDeltaArray[j]) ) + { + totalTransitions++; + + lineColor = (0.5,0,0); + + approachIsGood = false; + originalEnterPos = undefined; + + // final yaw at node + approachFinalYaw = node.angles[1] + animscripts\cover_arrival::getNodeStanceYawOffset( transType ); + + angle = (0, approachFinalYaw - node.angleDeltaArray[j], 0); + + // exits + if( tool == 2 ) + { + angle = (0, approachFinalYaw, 0 ); + } + + forwardDir = AnglesToForward( angle ); + rightDir = AnglesToRight( angle ); + + // cache for performance + if( !IsDefined(node.moveDeltaArray) ) + { + if ( tool == 2 ) + { + node.moveDeltaArray = anim.coverExitDist[ transType ]; + } + else + { + node.moveDeltaArray = anim.coverTransDist[ transType ]; + } + } + + enterPos = node.origin; + + forward = vector_multiply( forwardDir, node.moveDeltaArray[j][0] ); + right = vector_multiply( rightDir, node.moveDeltaArray[j][1] ); + + // start position of arrival animation + if( tool == 1 ) // arrival + { + enterPos = node.origin - forward + right; + } + else + { + enterPos = node.origin + forward - right; + } + + // check if the AI can move between the node and anim start pos or corner + if( testAi MayMoveFromPointToPoint(node.origin, enterPos) ) + { + approachIsGood = true; + + lineColor = (0,0.75,0); + } + + if( renderNode ) + { + line( node.origin, enterPos, lineColor, 1, 1, frameInterval ); + } + + // if 7, 8, 9 direction, split up check into two parts of the 90 degree turn around corner + // (already did the second part, from corner to node, now doing from start of enter anim to corner) + if( approachIsGood && j >= 7 && !isSubstr(transType, "exposed") ) + { + originalEnterPos = enterPos; + + if( tool == 1 ) // arrival + { + if( !IsDefined(node.preMoveDeltaArray) ) + { + node.preMoveDeltaArray = anim.coverTransPreDist[ transType ];//fakeAi preMoveDeltaArray( animName, "move" ); + } + + forward = vector_multiply( forwardDir, node.preMoveDeltaArray[j][0] ); + right = vector_multiply( rightDir, node.preMoveDeltaArray[j][1] ); + + originalEnterPos = enterPos - forward + right; + } + else // exit + { + if( !IsDefined(node.postMoveDeltaArray) ) + { + node.postMoveDeltaArray = anim.coverExitPostDist[ transType ];//fakeAi postMoveDeltaArray( animName, "move" ); + } + + forward = vector_multiply( forwardDir, node.postMoveDeltaArray[j][0] ); + right = vector_multiply( rightDir, node.postMoveDeltaArray[j][1] ); + + originalEnterPos = enterPos + forward - right; + } + + // check if the AI can move between the corner and anim start pos + if( !testAi MayMoveFromPointToPoint(originalEnterPos, enterPos) ) + { + approachIsGood = false; + lineColor = (0.5,0,0); + } + + if( renderNode ) + { + line( originalEnterPos, enterPos, lineColor, 1, 1, frameInterval ); + print3d( originalEnterPos, j, lineColor, 1, 0.2, frameInterval ); + } + } + else if( renderNode ) + { + print3d( enterPos, j, lineColor, 1, 0.2, frameInterval ); + } + + if( approachIsGood ) + { + validTransitions++; + } + + tracesThisFrame++; + } + } + + // assign categories based on number of valid transitions + node.lastRatio = validTransitions / totalTransitions; + if( node.lastRatio == 0 ) + { + nodeColor = nodeColors["Bad"]; + numBad++; + + badNode = node; + } + else if( node.lastRatio < 0.5 ) + { + nodeColor = nodeColors["Poor"]; + numPoor++; + } + else if( node.lastRatio < 1.0 ) + { + nodeColor = nodeColors["Ok"]; + numOk++; + } + else + { + numGood++; + } + + if( renderNode ) + { + // render box, node type and node forward + print3d( node.origin, node.type + " (" + transType + ")", nodeColor, 1, 0.35, frameInterval ); + box( node.origin, 16, node.angles[1], nodeColor, 1, 1, frameInterval ); + + nodeForward = anglesToForward( node.angles ); + nodeForward = vector_multiply( nodeForward, 8 ); + line( node.origin, node.origin + nodeForward, nodeColor, 1, 1, frameInterval ); + + renderedThisFrame++; + } + + // save last time + evaluatedThisFrame++; + node.lastCheckedTime = gettime(); + } + + //println("evaluated: " + evaluatedThisFrame + " traced: " + tracesThisFrame + " rendered: " + renderedThisFrame); + + // render stats + hudTotal SetValue( numBad + numPoor + numOk + numGood ); + hudBad SetValue( numBad ); + hudPoor SetValue( numPoor ); + hudOk SetValue( numOk ); + hudGood SetValue( numGood ); + + wait(0.05); + } +} +#/ \ No newline at end of file diff --git a/animscripts/cover_behavior.gsc b/animscripts/cover_behavior.gsc new file mode 100644 index 0000000..9d8c787 --- /dev/null +++ b/animscripts/cover_behavior.gsc @@ -0,0 +1,979 @@ +#include maps\_utility; +#include animscripts\combat_utility; +#include animscripts\utility; +#include animscripts\shared; +#include common_scripts\utility; + +/* +This file contains the overall behavior for all "whack-a-mole" cover nodes. + +Callbacks which must be defined: + + All callbacks should return true or false depending on whether they succeeded in doing something. + If functionality for a callback isn't available, just don't define it. + +mainLoopStart() + optional +reload() + plays a reload animation in a hidden position +leaveCoverAndShoot() + does the main attacking; steps out or stands up and fires, goes back to hiding. + should obey orders from decideWhatAndHowToShoot in shoot_behavior.gsc. +look( maxtime ) + looks for up to maxtime, stopping and returning if enemy becomes visible or if suppressed +fastlook() + looks quickly +idle() + idles until the "end_idle" notify. +flinch() + flinches briefly (1-2 seconds), doesn't need to return true or false. +grenade( throwAt ) + steps out and throws a grenade at the given player / ai +grenadehidden( throwAt ) + throws a grenade at the given player / ai without leaving cover +blindfire() + blindfires from cover + +example: +behaviorCallbacks = spawnstruct(); +behaviorCallbacks.reload = ::reload; +... +animscripts\cover_behavior::main( behaviorCallbacks ); + +*/ + +#using_animtree( "generic_human" ); + + +MELEE_GRACE_PERIOD_REQUIRED_TIME = 3000; +MELEE_GRACE_PERIOD_GIVEN_TIME = 5000; + +main( behaviorCallbacks ) +{ + self.couldntSeeEnemyPos = self.origin;// ( set couldntSeeEnemyPos to a place the enemy can't be while we're in corner behavior ) + + behaviorStartTime = gettime(); + coverTimers = spawnstruct(); + coverTimers.nextAllowedLookTime = behaviorStartTime - 1; + coverTimers.nextAllowedSuppressTime = behaviorStartTime - 1; + + // we won't look for better cover purely out of boredom until this time + resetLookForBetterCoverTime(); + resetRespondToDeathTime(); + + self.seekOutEnemyTime = gettime(); + + self.a.lastEncounterTime = behaviorStartTime; + self.a.idlingAtCover = false; + self.a.movement = "stop"; + + // if we break out of cover mode after this time, we will get a grace period during which we can melee charge the player + self.meleeCoverChargeMinTime = behaviorStartTime + MELEE_GRACE_PERIOD_REQUIRED_TIME; + + /# + if ( getdvar( "scr_coveridle" ) == "1" ) + self.coverNode.script_onlyidle = true; + #/ + + self thread watchSuppression(); + + desynched = ( gettime() > 2500 ); + + correctAngles = getCorrectCoverAngles(); + + for ( ;; ) + { + if ( shouldHelpAdvancingTeammate() ) + { + if ( tryRunningToEnemy( true ) ) + { + wait 0.05; + continue; + } + } + + if ( isdefined( behaviorCallbacks.mainLoopStart ) ) + { + startTime = gettime(); + self thread endIdleAtFrameEnd(); + + [[ behaviorCallbacks.mainLoopStart ]](); + + if ( gettime() == startTime ) + self notify( "dont_end_idle" ); + } + + if ( isdefined( behaviorCallbacks.moveToNearByCover ) ) + { + if ( [[ behaviorCallbacks.moveToNearByCover ]]() ) + continue; + } + + // tagJW: We do not need or want to teleport the actor when locked to an object + if ( !self isLinked() ) + { + self safeTeleport( self.covernode.origin, correctAngles ); + } + + if ( !desynched ) + { + idle( behaviorCallbacks, 0.05 + randomfloat( 1.5 ) ); + desynched = true; + continue; + } + + if ( doNonAttackCoverBehavior( behaviorCallbacks ) ) + continue; + + if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level._player ) ) + { + if ( tryThrowingGrenade( behaviorCallbacks, level._player ) ) + continue; + } + + if ( respondToDeadTeammate() ) + return; + + // determine visibility and suppressability of enemy. + visibleEnemy = false; + suppressableEnemy = false; + if ( isalive( self.enemy ) ) + { + visibleEnemy = isEnemyVisibleFromExposed(); + suppressableEnemy = canSuppressEnemyFromExposed(); + } + + // decide what to do. + if ( visibleEnemy ) + { + if ( self.a.getBoredOfThisNodeTime < gettime() ) + { + if ( lookForBetterCover() ) + return; + } + + attackVisibleEnemy( behaviorCallbacks ); + } + else + { + if ( isdefined( self.aggressiveMode ) || enemyIsHiding() ) + { + if ( advanceOnHidingEnemy() ) + return; + } + + if ( suppressableEnemy ) + { + attackSuppressableEnemy( behaviorCallbacks, coverTimers ); + } + else + { + if ( attackNothingToDo( behaviorCallbacks, coverTimers ) ) + return; + } + } + } +} + +end_script( coverMode ) +{ + self.turnToMatchNode = undefined; + self.a.prevAttack = undefined; + + if ( isDefined( self.meleeCoverChargeMinTime ) && (self.meleeCoverChargeMinTime <= getTime()) ) + { + // give the AI a chance to charge the player if he forced him out of cover + self.meleeCoverChargeGraceEndTime = getTime() + MELEE_GRACE_PERIOD_GIVEN_TIME; + self.meleeCoverChargeMinTime = undefined; + } +} + + +getCorrectCoverAngles() +{ + correctAngles = ( self.coverNode.angles[ 0 ], getNodeForwardYaw( self.coverNode ), self.coverNode.angles[ 2 ] ); + return correctAngles; +} + + +RESPOND_TO_DEATH_RETRY_INTERVAL = 30 * 1000; + +respondToDeadTeammate() +{ + if ( self atDangerousNode() && self.a.respondToDeathTime < gettime() ) + { + if ( lookForBetterCover() ) + return true; + + self.a.respondToDeathTime = gettime() + RESPOND_TO_DEATH_RETRY_INTERVAL; + } + + return false; +} + + +doNonAttackCoverBehavior( behaviorCallbacks ) +{ + /# + if ( isDefined( self.coverNode.script_onlyidle ) ) + { + assert( self.coverNode.script_onlyidle );// true or undefined + idle( behaviorCallbacks ); + return true; + } + #/ + + // if we're suppressed, we do other things. + if ( suppressedBehavior( behaviorCallbacks ) ) + { + if ( isEnemyVisibleFromExposed() ) + resetSeekOutEnemyTime(); + self.a.lastEncounterTime = gettime(); + return true; + } + + // reload if we need to; everything in this loop involves shooting. + if ( coverReload( behaviorCallbacks, 0 ) ) + return true; + + return false; +} + +attackVisibleEnemy( behaviorCallbacks ) +{ + if ( distanceSquared( self.origin, self.enemy.origin ) > 750 * 750 ) + { + if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) ) + return; + } + + if ( leaveCoverAndShoot( behaviorCallbacks, "normal" ) ) + { + resetSeekOutEnemyTime(); + self.a.lastEncounterTime = gettime(); + } + else + { + idle( behaviorCallbacks ); + } +} + +attackSuppressableEnemy( behaviorCallbacks, coverTimers ) +{ + if ( self.doingAmbush ) + { + if ( leaveCoverAndShoot( behaviorCallbacks, "ambush" ) ) + return; + } + else if ( self.provideCoveringFire || gettime() >= coverTimers.nextAllowedSuppressTime ) + { + preferredActivity = "suppress"; + if ( !self.provideCoveringFire && ( gettime() - self.lastSuppressionTime ) > 5000 && randomint( 3 ) < 2 ) + preferredActivity = "ambush"; + else if ( !self animscripts\shoot_behavior::shouldSuppress() ) + preferredActivity = "ambush"; + + if ( leaveCoverAndShoot( behaviorCallbacks, preferredActivity ) ) + { + coverTimers.nextAllowedSuppressTime = gettime() + randomintrange( 3000, 20000 ); + // if they're there, we've seen them + if ( isEnemyVisibleFromExposed() ) + self.a.lastEncounterTime = gettime(); + return; + } + } + + if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) ) + return; + + idle( behaviorCallbacks ); +} + + +attackNothingToDo( behaviorCallbacks, coverTimers ) +{ + if ( coverReload( behaviorCallbacks, 0.1 ) ) + return false; + + if ( isdefined( self.enemy ) ) + { + if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) ) + return false; + } + + if ( !self.doingAmbush && gettime() >= coverTimers.nextAllowedLookTime ) + { + if ( lookForEnemy( behaviorCallbacks ) ) + { + coverTimers.nextAllowedLookTime = gettime() + randomintrange( 4000, 15000 ); + + // if they're there, we've seen them + return false; + } + } + + // we're *really* bored right now + if ( gettime() > self.a.getBoredOfThisNodeTime ) + { + if ( cantFindAnythingToDo() ) + return true; + } + + if ( self.doingAmbush || ( gettime() >= coverTimers.nextAllowedSuppressTime && isdefined( self.enemy ) ) ) + { + // be ready to ambush them if they happen to show up + if ( leaveCoverAndShoot( behaviorCallbacks, "ambush" ) ) + { + if ( isEnemyVisibleFromExposed() ) + resetSeekOutEnemyTime(); + self.a.lastEncounterTime = gettime(); + coverTimers.nextAllowedSuppressTime = gettime() + randomintrange( 6000, 20000 ); + return false; + } + } + + idle( behaviorCallbacks ); + return false; +} + + +isEnemyVisibleFromExposed() +{ + if ( !isdefined( self.enemy ) ) + return false; + + // if we couldn't see our enemy last time we stepped out, and they haven't moved, assume we still can't see them. + if ( distanceSquared( self.enemy.origin, self.couldntSeeEnemyPos ) < 16 * 16 ) + return false; + else + return canSeeEnemyFromExposed(); +} + +suppressedBehavior( behaviorCallbacks ) +{ + if ( !isSuppressedWrapper() ) + return false; + + nextAllowedBlindfireTime = gettime(); + + justlooked = true; + + //prof_begin( "suppressedBehavior" ); + + while ( isSuppressedWrapper() ) + { + justlooked = false; + + // tagJW: We do not need or want to teleport the actor when locked to an object + if ( !self isLinked() ) + { + self safeTeleport( self.coverNode.origin ); + } + + tryMovingNodes = true; + + // guys that favor blindfire should try to blindfire instead of move a lot more + if ( isdefined( self.favor_blindfire ) ) + tryMovingNodes = coinToss(); + + if ( tryMovingNodes && !isdefined( self.locked_combat ) ) + { + if ( tryToGetOutOfDangerousSituation( behaviorCallbacks ) ) + { + self notify( "killanimscript" ); + //prof_end( "suppressedBehavior" ); + return true; + } + } + + // if we're only at a concealment node, and it's not providing cover, we shouldn't try to use the cover to keep us safe! + if ( self.a.atConcealmentNode && self canSeeEnemy() ) + { + //prof_end( "suppressedBehavior" ); + return false; + } + + if ( isEnemyVisibleFromExposed() || canSuppressEnemyFromExposed() ) + { + if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level._player ) ) + { + if ( tryThrowingGrenade( behaviorCallbacks, level._player ) ) + continue; + } + + if ( coverReload( behaviorCallbacks, 0 ) ) + continue; + + if ( self.team != "allies" && gettime() >= nextAllowedBlindfireTime ) + { + if ( blindfire( behaviorCallbacks ) ) + { + nextAllowedBlindfireTime = gettime(); + if ( !isdefined( self.favor_blindfire ) ) + nextAllowedBlindfireTime += randomintrange( 3000, 12000 ); + + continue; + } + } + + if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) ) + { + justlooked = true; + continue; + } + } + + if ( coverReload( behaviorCallbacks, 0.1 ) ) + continue; + + //prof_end( "suppressedBehavior" ); + idle( behaviorCallbacks ); + } + + if ( !justlooked && randomint( 2 ) == 0 ) + lookfast( behaviorCallbacks ); + + //prof_end( "suppressedBehavior" ); + return true; +} + +// returns array of integers 0 through n-1, in random order +getPermutation( n ) +{ + permutation = []; + assert( n > 0 ); + if ( n == 1 ) + { + permutation[ 0 ] = 0; + } + else if ( n == 2 ) + { + permutation[ 0 ] = randomint( 2 ); + permutation[ 1 ] = 1 - permutation[ 0 ]; + } + else + { + for ( i = 0; i < n; i++ ) + permutation[ i ] = i; + for ( i = 0; i < n; i++ ) + { + switchIndex = i + randomint( n - i ); + temp = permutation[ switchIndex ]; + permutation[ SwitchIndex ] = permutation[ i ]; + permutation[ i ] = temp; + } + } + return permutation; +} + +callOptionalBehaviorCallback( callback, arg, arg2, arg3 ) +{ + if ( !isdefined( callback ) ) + return false; + + //prof_begin( "callOptionalBehaviorCallback" ); + self thread endIdleAtFrameEnd(); + + starttime = gettime(); + + val = undefined; + if ( isdefined( arg3 ) ) + val = [[ callback ]]( arg, arg2, arg3 ); + else if ( isdefined( arg2 ) ) + val = [[ callback ]]( arg, arg2 ); + else if ( isdefined( arg ) ) + val = [[ callback ]]( arg ); + else + val = [[ callback ]](); + + /# + // if this assert fails, a behaviorCallback callback didn't return true or false. + assert( isdefined( val ) && ( val == true || val == false ) ); + + // behaviorCallbacks must return true if and only if they let time pass. + // (it is also important that they only let time pass if they did what they were supposed to do, + // but that's not so easy to enforce.) + if ( val ) + assert( gettime() != starttime ); + else + assert( gettime() == starttime ); + #/ + + if ( !val ) + self notify( "dont_end_idle" ); + + //prof_end( "callOptionalBehaviorCallback" ); + + return val; +} + +watchSuppression() +{ + self endon( "killanimscript" ); + + // self.lastSuppressionTime is the last time a bullet whizzed by. + // self.suppressionStart is the last time we were thinking it was safe when a bullet whizzed by. + + self.lastSuppressionTime = gettime() - 100000; + self.suppressionStart = self.lastSuppressionTime; + + while ( 1 ) + { + self waittill( "suppression" ); + + time = gettime(); + if ( self.lastSuppressionTime < time - 700 ) + self.suppressionStart = time; + self.lastSuppressionTime = time; + } +} + +coverReload( behaviorCallbacks, threshold ) +{ + if ( self.bulletsInClip > weaponClipSize( self.weapon ) * threshold ) + return false; + + self.isreloading = true; + + result = callOptionalBehaviorCallback( behaviorCallbacks.reload ); + + self.isreloading = false; + + return result; +} + +// initialGoal can be either "normal", "suppress", or "ambush". +leaveCoverAndShoot( behaviorCallbacks, initialGoal ) +{ + self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( initialGoal ); + + if ( !self.fixedNode && !self.doingAmbush && !isdefined( self.locked_combat ) ) + self thread breakOutOfShootingIfWantToMoveUp(); + + val = callOptionalBehaviorCallback( behaviorCallbacks.leaveCoverAndShoot ); + + self notify( "stop_deciding_how_to_shoot" ); + + return val; +} + +lookForEnemy( behaviorCallbacks ) +{ + if ( self.a.atConcealmentNode && self canSeeEnemy() ) + return false; + + if ( self.a.lastEncounterTime + 6000 > gettime() ) + { + return lookfast( behaviorCallbacks ); + } + else + { + // look slow if possible + result = callOptionalBehaviorCallback( behaviorCallbacks.look, 2 + randomfloat( 2 ) ); + if ( result ) + return true; + return callOptionalBehaviorCallback( behaviorCallbacks.fastlook ); + } +} + +lookfast( behaviorCallbacks ) +{ + // look fast if possible + result = callOptionalBehaviorCallback( behaviorCallbacks.fastlook ); + if ( result ) + return true; + return callOptionalBehaviorCallback( behaviorCallbacks.look, 0 ); +} + +idle( behaviorCallbacks, howLong ) +{ + self.flinching = false; + + if ( isdefined( behaviorCallbacks.flinch ) ) + { + // flinch if we just started getting shot at very recently + if ( !self.a.idlingAtCover && gettime() - self.suppressionStart < 600 ) + { + if ( [[ behaviorCallbacks.flinch ]]() ) + return true; + } + else + { + // if bullets aren't already whizzing by, idle for now but flinch if we get incoming fire + self thread flinchWhenSuppressed( behaviorCallbacks ); + } + } + + if ( !self.a.idlingAtCover ) + { + assert( isdefined( behaviorCallbacks.idle ) );// idle must be available! + self thread idleThread( behaviorCallbacks.idle );// this thread doesn't stop until "end_idle", which must be notified before we start anything else! use endIdleAtFrameEnd() to do this. + self.a.idlingAtCover = true; + } + + if ( isdefined( howLong ) ) + self idleWait( howLong ); + else + self idleWaitABit(); + + if ( self.flinching ) + self waittill( "flinch_done" ); + + self notify( "stop_waiting_to_flinch" ); +} + +idleWait( howLong ) +{ + self endon( "end_idle" ); + wait howLong; +} + +idleWaitAbit() +{ + self endon( "end_idle" ); + wait 0.3 + randomfloat( 0.1 ); + self waittill( "do_slow_things" ); +} + +idleThread( idlecallback ) +{ + self endon( "killanimscript" ); + self [[ idlecallback ]](); +} + +flinchWhenSuppressed( behaviorCallbacks ) +{ + self endon( "killanimscript" ); + self endon( "stop_waiting_to_flinch" ); + + lastSuppressionTime = self.lastSuppressionTime; + + while ( 1 ) + { + self waittill( "suppression" ); + + time = gettime(); + + if ( lastSuppressionTime < time - 2000 ) + break; + + lastSuppressionTime = time; + } + + self.flinching = true; + + self thread endIdleAtFrameEnd(); + + assert( isdefined( behaviorCallbacks.flinch ) ); + val = [[ behaviorCallbacks.flinch ]](); + + if ( !val ) + self notify( "dont_end_idle" ); + + self.flinching = false; + self notify( "flinch_done" ); +} + +endIdleAtFrameEnd() +{ + self endon( "killanimscript" ); + self endon( "dont_end_idle" ); + waittillframeend; + + if ( !isdefined( self ) ) + return; + + self notify( "end_idle" ); + self.a.idlingAtCover = false; +} + +tryThrowingGrenade( behaviorCallbacks, throwAt ) +{ + assert( isdefined( throwAt ) ); + + // don't throw backwards + forward = anglesToForward( self.angles ); + dir = vectorNormalize( throwAt.origin - self.origin ); + if ( vectorDot( forward, dir ) < 0 ) + return false; + + if ( self.doingAmbush && !recentlySawEnemy() ) + return false; + + if ( self isPartiallySuppressedWrapper() ) + { + return callOptionalBehaviorCallback( behaviorCallbacks.grenadehidden, throwAt ); + } + else + { + return callOptionalBehaviorCallback( behaviorCallbacks.grenade, throwAt ); + } +} + +blindfire( behaviorCallbacks ) +{ + if ( !canBlindFire() ) + return false; + + return callOptionalBehaviorCallback( behaviorCallbacks.blindfire ); +} + +// Need this? +breakOutOfShootingIfWantToMoveUp() +{ + self endon( "killanimscript" ); + self endon( "stop_deciding_how_to_shoot" ); + + while ( 1 ) + { + if ( self.fixedNode || self.doingAmbush ) + return; + + wait 0.5 + randomfloat( 0.75 ); + + if ( !isdefined( self.enemy ) ) + continue; + + if ( enemyIsHiding() ) + { + if ( advanceOnHidingEnemy() ) + return; + } + + if ( !self recentlySawEnemy() && !self canSuppressEnemy() ) + { + if ( gettime() > self.a.getBoredOfThisNodeTime ) + { + if ( cantFindAnythingToDo() ) + return; + } + } + } +} + +enemyIsHiding() +{ + // if this function is called, we already know that our enemy is not visible from exposed. + // check to see if they're doing anything hiding-like. + + if ( !isdefined( self.enemy ) ) + return false; + + if ( self.enemy isFlashed() ) + return true; + + if ( isplayer( self.enemy ) ) + { + if ( isdefined( self.enemy.health ) && self.enemy.health < self.enemy.maxhealth ) + return true; + } + else + { + if ( isAI( self.enemy ) && self.enemy isSuppressedWrapper() ) + return true; + } + + if ( isdefined( self.enemy.isreloading ) && self.enemy.isreloading ) + return true; + + return false; +} + +resetRespondToDeathTime() +{ + self.a.respondToDeathTime = 0; +} + + +resetLookForBetterCoverTime() +{ + currentTime = gettime(); + + // treat group of shuffle nodes as one node, don't increase getBoredOfThisNodeTime by too much + if ( isdefined( self.didShuffleMove ) && currentTime > self.a.getBoredOfThisNodeTime ) + { + self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 2000, 5000 ); + } + else if ( isdefined( self.enemy ) ) + { + dist = distance2D( self.origin, self.enemy.origin ); + if ( dist < self.engageMinDist ) + self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 5000, 10000 ); + else if ( dist > self.engageMaxDist && dist < self.goalradius ) + self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 2000, 5000 ); + else + self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 10000, 15000 ); + } + else + { + self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 5000, 15000 ); + } +} + +resetSeekOutEnemyTime() +{ + // we'll be willing to actually run right up to our enemy in order to find them if we haven't seen them by this time. + // however, we'll try to find better cover before seeking them out + if ( isdefined( self.aggressiveMode ) ) + self.seekOutEnemyTime = gettime() + randomintrange( 500, 1000 ); + else + self.seekOutEnemyTime = gettime() + randomintrange( 3000, 5000 ); +} + +// these next functions are "look for better cover" functions. +// they don't always need to cause the actor to leave the node immediately, +// but if they keep being called over and over they need to become more and more likely to do so, +// as this indicates that new cover is strongly needed. +cantFindAnythingToDo() +{ + return advanceOnHidingEnemy(); +} + +advanceOnHidingEnemy() +{ + if ( self.fixedNode || self.doingAmbush ) + return false; + + if ( isdefined( self.aggressiveMode ) && gettime() >= self.seekOutEnemyTime ) + { + return tryRunningToEnemy( false ); + } + + foundBetterCover = false; + if ( !isdefined( self.enemy ) || !self.enemy isFlashed() ) + foundBetterCover = lookForBetterCover(); + + if ( !foundBetterCover && isdefined( self.enemy ) && !self canSeeEnemyFromExposed() ) + { + if ( gettime() >= self.seekOutEnemyTime ) + { + return tryRunningToEnemy( false ); + } + } + + // maybe at this point we could look for someone who's suppressing our enemy, + // and if someone is, we can say "cover me!" and have them say "i got you covered" or something. + + return foundBetterCover; +} + +tryToGetOutOfDangerousSituation( behaviorCallbacks ) +{ + if ( isdefined( behaviorCallbacks.moveToNearByCover ) ) + { + if ( [[ behaviorCallbacks.moveToNearByCover ]]() ) + return true; + } + + return lookForBetterCover(); +} + +// TEMP move these into animsets +set_standing_turns() +{ + self.a.array[ "turn_left_45" ] = %exposed_tracking_turn45L; + self.a.array[ "turn_left_90" ] = %exposed_tracking_turn90L; + self.a.array[ "turn_left_135" ] = %exposed_tracking_turn135L; + self.a.array[ "turn_left_180" ] = %exposed_tracking_turn180L; + self.a.array[ "turn_right_45" ] = %exposed_tracking_turn45R; + self.a.array[ "turn_right_90" ] = %exposed_tracking_turn90R; + self.a.array[ "turn_right_135" ] = %exposed_tracking_turn135R; + self.a.array[ "turn_right_180" ] = %exposed_tracking_turn180R; +} + +set_crouching_turns() +{ + self.a.array[ "turn_left_45" ] = %exposed_crouch_turn_90_left; + self.a.array[ "turn_left_90" ] = %exposed_crouch_turn_90_left; + self.a.array[ "turn_left_135" ] = %exposed_crouch_turn_180_left; + self.a.array[ "turn_left_180" ] = %exposed_crouch_turn_180_left; + self.a.array[ "turn_right_45" ] = %exposed_crouch_turn_90_right; + self.a.array[ "turn_right_90" ] = %exposed_crouch_turn_90_right; + self.a.array[ "turn_right_135" ] = %exposed_crouch_turn_180_right; + self.a.array[ "turn_right_180" ] = %exposed_crouch_turn_180_right; +} + + +turnToMatchNodeDirection( nodeAngleOffset ) +{ + if ( isdefined( self.node ) ) + { + node = self.node; + + absRelYaw = abs( AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) ) ); + + if ( self.a.pose == "stand" && node getHighestNodeStance() != "stand" ) + { + if ( absRelYaw > 45 && absRelYaw < 90 ) + self orientmode( "face angle", self.angles[1] ); + else + self orientmode( "face current" ); + + rate = 1.5; + noteTime = getNotetrackTimes( %exposed_stand_2_crouch, "anim_pose = \"crouch\"" )[0]; + noteTime = min( 1, noteTime * 1.1 ); + time = noteTime * getAnimLength( %exposed_stand_2_crouch ) / rate; + + self setflaggedanimknoballrestart( "crouchanim", %exposed_stand_2_crouch, %body, 1, .2, rate ); + self animscripts\shared::DoNoteTracksForTime( time, "crouchanim" ); + self clearanim( %body, 0.2 ); + } + + self orientmode( "face angle", self.angles[1] ); + + relYaw = AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) ); + + if ( abs( relYaw ) > 45 ) + { + if ( self.a.pose == "stand" ) + set_standing_turns(); + else + set_crouching_turns(); + + self.turnThreshold = 45; + self.turnToMatchNode = true; + animscripts\combat::TurnToFaceRelativeYaw( relYaw ); + self.turnToMatchNode = undefined; + } + } +} + +moveToNearbyCover() +{ + if ( !isdefined( self.enemy ) ) + return false; + + if ( isdefined( self.didShuffleMove ) ) + { + self.didShuffleMove = undefined; + return false; + } + + if ( !isdefined( self.node ) ) + return false; + + if ( randomint( 3 ) == 0 ) + return false; + + if ( self.fixedNode || self.doingAmbush || self.keepClaimedNode || self.keepClaimedNodeIfValid ) + return false; + + if ( distanceSquared( self.origin, self.node.origin ) > 16 * 16 ) + return false; + + node = self findshufflecovernode(); + + if ( isdefined( node ) && ( node != self.node ) && self useCoverNode( node ) ) + { + self.shuffleMove = true; + self.shuffleNode = node; + self.didShuffleMove = true; + + // give code a chance use new cover node + wait 0.5; + return true; + } + + return false; +} + diff --git a/animscripts/cover_crouch.gsc b/animscripts/cover_crouch.gsc new file mode 100644 index 0000000..42f3c5d --- /dev/null +++ b/animscripts/cover_crouch.gsc @@ -0,0 +1,25 @@ +#include animscripts\Combat_utility; +#include animscripts\Utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + + +main() +{ +// assert( !usingSidearm() ); + + self endon( "killanimscript" ); + +// [[ self.exception[ "cover_crouch" ] ]](); + + animscripts\utility::initialize( "cover_crouch" ); + + self animscripts\cover_wall::cover_wall_think( "crouch" ); +} + +end_script() +{ + self.coverCrouchLean_aimmode = undefined; + + animscripts\cover_behavior::end_script( "crouch" ); +} diff --git a/animscripts/cover_left.gsc b/animscripts/cover_left.gsc new file mode 100644 index 0000000..d878fcf --- /dev/null +++ b/animscripts/cover_left.gsc @@ -0,0 +1,53 @@ +#include maps\_utility; +#include animscripts\Combat_utility; +#include animscripts\utility; +#using_animtree( "generic_human" ); + +// (Note that animations called right are used with left corner nodes, and vice versa.) + +main() +{ + self.animArrayFuncs = []; + self.animArrayFuncs[ "hiding" ][ "stand" ] = ::set_animarray_standing_left; + self.animArrayFuncs[ "hiding" ][ "crouch" ] = ::set_animarray_crouching_left; + + if ( IsDefined( self.customAnimFunc ) && IsDefined( self.customAnimFunc[ "cover_left" ] ) ) + { + if ( IsDefined( self.customAnimFunc[ "cover_left" ][ "stand" ] ) ) + { + self.animArrayFuncs[ "hiding" ][ "stand" ] = self.customAnimFunc[ "cover_left" ][ "stand" ]; + } + + if ( IsDefined( self.customAnimFunc[ "cover_left" ][ "crouch" ] ) ) + { + self.animArrayFuncs[ "hiding" ][ "crouch" ] = self.customAnimFunc[ "cover_left" ][ "crouch" ]; + } + } + + self endon( "killanimscript" ); + animscripts\utility::initialize( "cover_left" ); + + animscripts\corner::corner_think( "left", 90 ); +} + +end_script() +{ + animscripts\corner::end_script_corner(); + animscripts\cover_behavior::end_script( "left" ); +} + +set_animarray_standing_left() /* void */ +{ + assert( IsDefined(anim.coverLeftStand) ); + self.hideYawOffset = 90; + self.a.array = anim.coverLeftStand; +} + + +set_animarray_crouching_left() +{ + assert( IsDefined(anim.coverLeftCrouch) ); + self.hideYawOffset = 90; + self.a.array = anim.coverLeftCrouch; +} + diff --git a/animscripts/cover_prone.gsc b/animscripts/cover_prone.gsc new file mode 100644 index 0000000..8f678bf --- /dev/null +++ b/animscripts/cover_prone.gsc @@ -0,0 +1,360 @@ +#include animscripts\combat_utility; +#include animscripts\utility; +#include animscripts\shared; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + + +// TODO: +// - figure out why aiming range is incorrect (aiming arc seems a bit off) + +main() +{ + self endon( "killanimscript" ); + animscripts\utility::initialize( "cover_prone" ); + + // TODO: run cover crouch or exposed crouch + if ( weaponClass( self.weapon ) == "rocketlauncher" ) + { + animscripts\combat::main(); + return; + } + + if ( isDefined( self.a.arrivalType ) && self.a.arrivalType == "prone_saw" ) + { + assert( isDefined( self.node.turretInfo ) ); + self animscripts\cover_wall::useSelfPlacedTurret( "saw_bipod_prone", "weapon_saw_MG_Setup" ); + } + else if ( isDefined( self.node.turret ) ) + { + self animscripts\cover_wall::useStationaryTurret(); + } + + // if we're too close to our enemy, stand up + // (285 happens to be the same distance at which we leave cover and go into exposed if our enemy approaches) + if ( isDefined( self.enemy ) && lengthSquared( self.origin - self.enemy.origin ) < squared( 512 ) ) + { + self thread animscripts\combat::main(); + return; + } + + self setup_cover_prone(); + + self.coverNode = self.node; + self OrientMode( "face angle", self.coverNode.angles[ 1 ] ); + + self.a.goingToProneAim = true; + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_modern, %prone_legs_up ); + if ( self.a.pose != "prone" ) + self prone_transitionTo( "prone" ); + else + self EnterProneWrapper( 0 ); + + self thread aimIdleThread(); + + setupProneAim( 0.2 ); + self setAnim( %prone_aim_5, 1, 0.1 ); + + // face the direction of our covernode + self OrientMode( "face angle", self.coverNode.angles[ 1 ] ); + self animmode( "zonly_physics" ); + + self proneCombatMainLoop(); + + self notify( "stop_deciding_how_to_shoot" ); + +} + +end_script() +{ + self.a.goingToProneAim = undefined; +} + +idleThread() +{ + self endon( "killanimscript" ); + self endon( "kill_idle_thread" ); + for ( ;; ) + { + idleAnim = animArrayPickRandom( "prone_idle" ); + self setflaggedanimlimited( "idle", idleAnim ); + self waittillmatch( "idle", "end" ); + self clearanim( idleAnim, .2 ); + } +} + +UpdateProneWrapper( time ) +{ + self UpdateProne( %prone_aim_feet_45up, %prone_aim_feet_45down, 1, time, 1 ); + self setanim( %exposed_aiming, 1, .2 ); +} + +proneCombatMainLoop() +{ + self endon( "killanimscript" ); + + self thread trackShootEntOrPos(); + +// self thread ReacquireWhenNecessary(); + + self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" ); + +// self resetGiveUpOnEnemyTime(); + + desynched = ( gettime() > 2500 ); + + //prof_begin("prone_combat"); + + for ( ;; ) + { + self animscripts\utility::IsInCombat();// reset our in - combat state + + self UpdateProneWrapper( 0.05 ); + + if ( !desynched ) + { + wait( 0.05 + randomfloat( 1.5 ) ); + desynched = true; + continue; + } + + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + if ( considerThrowGrenade() ) + continue; + + wait( 0.05 ); + continue; + } + + assert( isdefined( self.shootPos ) );// we can use self.shootPos after this point. +// self resetGiveUpOnEnemyTime(); + + // if we're too close to our enemy, stand up + // (285 happens to be the same distance at which we leave cover and go into exposed if our enemy approaches) + distSqToShootPos = lengthsquared( self.origin - self.shootPos ); + + if ( self.a.pose != "crouch" && self isStanceAllowed( "crouch" ) && distSqToShootPos < squared( 400 ) ) + { + if ( distSqToShootPos < squared( 285 ) ) + { + prone_transitionTo( "crouch" ); + self thread animscripts\combat::main(); + return; + } + } + + if ( considerThrowGrenade() )// TODO: make considerThrowGrenade work with shootPos rather than only self.enemy + continue; + + if ( self proneReload( 0 ) ) + continue; + + if ( aimedAtShootEntOrPos() ) + { + shootUntilShootBehaviorChange(); + self clearAnim( %add_fire, .2 ); + continue; + } + + // idleThread() is running, so just waiting a bit will cause us to idle + wait( 0.05 ); + } + + //prof_end("prone_combat"); +} + + + + +proneReload( threshold ) +{ + return Reload( threshold, self animArray( "reload" ) ); +} + + +setup_cover_prone() +{ + self setDefaultAimLimits( self.node ); + + anim_array = []; + + anim_array[ "straight_level" ] = %prone_aim_5; + + anim_array[ "fire" ] = %prone_fire_1; + anim_array[ "semi2" ] = %prone_fire_burst; + anim_array[ "semi3" ] = %prone_fire_burst; + anim_array[ "semi4" ] = %prone_fire_burst; + anim_array[ "semi5" ] = %prone_fire_burst; + + anim_array[ "single" ] = array( %prone_fire_1 ); + anim_array[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim_array[ "burst2" ] = %prone_fire_burst;// ( will be limited to 2 shots ) + anim_array[ "burst3" ] = %prone_fire_burst; + anim_array[ "burst4" ] = %prone_fire_burst; + anim_array[ "burst5" ] = %prone_fire_burst; + anim_array[ "burst6" ] = %prone_fire_burst; + + anim_array[ "reload" ] = %prone_reload; + + anim_array[ "look" ] = array( %prone_twitch_look, %prone_twitch_lookfast, %prone_twitch_lookup ); + + anim_array[ "grenade_safe" ] = array( %prone_grenade_A, %prone_grenade_A ); + anim_array[ "grenade_exposed" ] = array( %prone_grenade_A, %prone_grenade_A ); + + anim_array[ "exposed_idle" ] = array( %prone_idle ); + + anim_array[ "hide_to_look" ] = %coverstand_look_moveup; + anim_array[ "look_idle" ] = %coverstand_look_idle; + anim_array[ "look_to_hide" ] = %coverstand_look_movedown; + anim_array[ "look_to_hide_fast" ] = %coverstand_look_movedown_fast; + + anim_array[ "stand_2_prone" ] = %stand_2_prone_nodelta; + anim_array[ "crouch_2_prone" ] = %crouch_2_prone; + anim_array[ "prone_2_stand" ] = %prone_2_stand_nodelta; + anim_array[ "prone_2_crouch" ] = %prone_2_crouch; + anim_array[ "stand_2_prone_firing" ] = %stand_2_prone_firing; + anim_array[ "crouch_2_prone_firing" ] = %crouch_2_prone_firing; + anim_array[ "prone_2_stand_firing" ] = %prone_2_stand_firing; + anim_array[ "prone_2_crouch_firing" ] = %prone_2_crouch_firing; + + self.a.array = anim_array; +} + + +tryThrowingGrenade( throwAt, safe ) +{ + theanim = undefined; + if ( isdefined( safe ) && safe ) + theanim = animArrayPickRandom( "grenade_safe" ); + else + theanim = animArrayPickRandom( "grenade_exposed" ); + + self animMode( "zonly_physics" );// Unlatch the feet + self.keepClaimedNodeIfValid = true; + + armOffset = ( 32, 20, 64 );// needs fixing! + threwGrenade = TryGrenade( throwAt, theanim ); + + self.keepClaimedNodeIfValid = false; + return threwGrenade; +} + + +considerThrowGrenade() +{ + if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level._player ) ) + { + if ( tryThrowingGrenade( level._player, 200 ) ) + return true; + } + + if ( isdefined( self.enemy ) ) + return tryThrowingGrenade( self.enemy, 850 ); + + return false; +} + +shouldFireWhileChangingPose() +{ + if ( !isdefined( self.weapon ) || !WeaponIsAuto( self.weapon ) ) + return false; + + if ( isdefined( self.node ) && distanceSquared( self.origin, self.node.origin ) < 16 * 16 ) + return false;// we're on a node and can't use an animation with a delta + if ( isDefined( self.enemy ) && self canSee( self.enemy ) && !isdefined( self.grenade ) && self getAimYawToShootEntOrPos() < 20 ) + return animscripts\move::MayShootWhileMoving(); + return false; +} + +prone_transitionTo( newPose ) +{ + if ( newPose == self.a.pose ) + return; + + self clearanim( %root, .3 ); + + self endFireAndAnimIdleThread(); + + if ( shouldFireWhileChangingPose() ) + transAnim = animArray( self.a.pose + "_2_" + newPose + "_firing" ); + else + transAnim = animArray( self.a.pose + "_2_" + newPose ); + + if ( newPose == "prone" ) + { + // this is crucial. if it doesn't have this notetrack, we won't call enterProneWrapper! + assert( animHasNotetrack( transAnim, "anim_pose = \"prone\"" ) ); + } + + self setFlaggedAnimKnobAllRestart( "trans", transAnim, %body, 1, .2, 1.0 ); + animscripts\shared::DoNoteTracks( "trans" ); + + assert( self.a.pose == newPose ); + + self setAnimKnobAllRestart( animarray( "straight_level" ), %body, 1, .25 ); + setupProneAim( .25 ); +} + +finishNoteTracks( animname ) +{ + self endon( "killanimscript" ); + animscripts\shared::DoNoteTracks( animname ); +} + + +setupProneAim( transTime ) +{ + self setAnimKnobAll( %prone_aim_5, %body, 1, transTime ); + self setAnimLimited( %prone_aim_2_add, 1, transTime ); + self setAnimLimited( %prone_aim_4_add, 1, transTime ); + self setAnimLimited( %prone_aim_6_add, 1, transTime ); + self setAnimLimited( %prone_aim_8_add, 1, transTime ); +} + + +proneTo( newPose, rate ) +{ + assert( self.a.pose == "prone" ); + +// self OrientMode( "face angle", self.angles[1] ); + self clearanim( %root, .3 ); + + transAnim = undefined; + + if ( shouldFireWhileChangingPose() ) + { + if ( newPose == "crouch" ) + transAnim = %prone_2_crouch_firing; + else if ( newPose == "stand" ) + transAnim = %prone_2_stand_firing; + } + else + { + if ( newPose == "crouch" ) + transAnim = %prone_2_crouch; + else if ( newPose == "stand" ) + transAnim = %prone_2_stand_nodelta; + } + + if ( isdefined( self.prone_anim_override ) ) + transAnim = self.prone_anim_override; + if ( isdefined( self.prone_rate_override ) ) + rate = self.prone_rate_override; + + assert( isDefined( transAnim ) ); + + if ( !isdefined( rate ) ) + rate = 1; + + self ExitProneWrapper( getAnimLength( transAnim ) / 2 ); + self setFlaggedAnimKnobAllRestart( "trans", transAnim, %body, 1, .2, rate ); + animscripts\shared::DoNoteTracks( "trans" ); + + self clearAnim( transAnim, 0.1 ); + + assert( self.a.pose == newPose ); +// self.a.pose = newPose; // failsafe +} diff --git a/animscripts/cover_right.gsc b/animscripts/cover_right.gsc new file mode 100644 index 0000000..f8752ce --- /dev/null +++ b/animscripts/cover_right.gsc @@ -0,0 +1,53 @@ +#include maps\_utility; +#include animscripts\Combat_utility; +#include animscripts\utility; +#using_animtree( "generic_human" ); + +// (Note that animations called left are used with right corner nodes, and vice versa.) + +main() +{ + self.animArrayFuncs = []; + self.animArrayFuncs[ "hiding" ][ "stand" ] = ::set_animarray_standing_right; + self.animArrayFuncs[ "hiding" ][ "crouch" ] = ::set_animarray_crouching_right; + + if ( IsDefined( self.customAnimFunc ) && IsDefined( self.customAnimFunc[ "cover_right" ] ) ) + { + if ( IsDefined( self.customAnimFunc[ "cover_right" ][ "stand" ] ) ) + { + self.animArrayFuncs[ "hiding" ][ "stand" ] = self.customAnimFunc[ "cover_right" ][ "stand" ]; + } + if ( IsDefined( self.customAnimFunc[ "cover_right" ][ "crouch" ] ) ) + { + self.animArrayFuncs[ "hiding" ][ "crouch" ] = self.customAnimFunc[ "cover_right" ][ "crouch" ]; + } + } + + self endon( "killanimscript" ); + animscripts\utility::initialize( "cover_right" ); + + animscripts\corner::corner_think( "right", -90 ); +} + +end_script() +{ + animscripts\corner::end_script_corner(); + animscripts\cover_behavior::end_script( "right" ); +} + + +set_animarray_standing_right() /* void */ +{ + assert( IsDefined(anim.coverRightStand)); + self.hideYawOffset = -90; + self.a.array = anim.coverRightStand; +} + +set_animarray_crouching_right() +{ + assert( IsDefined(anim.coverRightCrouch)); + self.hideYawOffset = -90; + self.a.array = anim.coverRightCrouch; +} + + diff --git a/animscripts/cover_stand.gsc b/animscripts/cover_stand.gsc new file mode 100644 index 0000000..acf5d3a --- /dev/null +++ b/animscripts/cover_stand.gsc @@ -0,0 +1,22 @@ +#include animscripts\Combat_utility; +#include animscripts\Utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + +main() +{ +// assert( !usingSidearm() ); + self endon( "killanimscript" ); + +// [[ self.exception[ "cover_stand" ] ]](); + + animscripts\utility::initialize( "cover_stand" ); + + self animscripts\cover_wall::cover_wall_think( "stand" ); +} + + +end_script() +{ + animscripts\cover_behavior::end_script( "stand" ); +} diff --git a/animscripts/cover_wall.gsc b/animscripts/cover_wall.gsc new file mode 100644 index 0000000..166a8ef --- /dev/null +++ b/animscripts/cover_wall.gsc @@ -0,0 +1,893 @@ +#include animscripts\Combat_utility; +#include animscripts\Utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + +cover_wall_think( coverType ) +{ + self endon( "killanimscript" ); + + if ( isdefined( self.locked_combat ) && self.locked_combat ) + { + return; + } + + self.coverNode = self.node; + self.coverType = coverType; + + if ( !isDefined( self.node.turret ) ) + animscripts\cover_behavior::turnToMatchNodeDirection( 0 ); + + if ( coverType == "crouch" ) + { + self setup_cover_crouch( "unknown" ); + self.coverNode initCoverCrouchNode(); + } + else + { + self setup_cover_stand( "unknown" ); + } + + self.a.aimIdleThread = undefined; + + // face the direction of our covernode + self OrientMode( "face angle", self.coverNode.angles[ 1 ] ); + + if ( isDefined( self.weapon ) && usingMG() && isDefined( self.node ) && isDefined( self.node.turretInfo ) && canspawnturret() ) + { + if ( coverType == "crouch" ) + { + if ( isRPD( self.weapon ) ) + weaponInfo = "rpd_bipod_crouch"; + else + weaponInfo = "saw_bipod_crouch"; + } + else + { + if ( isRPD( self.weapon ) ) + weaponInfo = "rpd_bipod_stand"; + else + weaponInfo = "saw_bipod_stand"; + } + + if ( isRPD( self.weapon ) ) + weaponModel = "weapon_rpd_MG_Setup"; + else + weaponModel = "weapon_saw_MG_Setup"; + + self useSelfPlacedTurret( weaponInfo, weaponModel ); + } + else if ( isDefined( self.node ) && isDefined( self.node.turret ) ) + { + self useStationaryTurret(); + } + + self animmode( "normal" ); + + //start in hide position + if ( coverType == "crouch" && self.a.pose == "stand" ) + { + transAnim = animArray( "stand_2_hide" ); + time = getAnimLength( transAnim ); + self setAnimKnobAllRestart( transAnim, %body, 1, 0.2, fasterAnimSpeed() ); + self thread animscripts\shared::moveToOriginOverTime( self.coverNode.origin, time ); + wait time; + self.a.coverMode = "hide"; + } + else + { + loopHide( .4 );// need to transition to hide here in case we didn't do an approach + + if ( distanceSquared( self.origin, self.coverNode.origin ) > 1 ) + { + self thread animscripts\shared::moveToOriginOverTime( self.coverNode.origin, .4 ); + wait( .2 ); + if ( coverType == "crouch" ) + self.a.pose = "crouch"; + wait( .2 ); + } + else + { + wait 0.1; + } + } + + self animmode( "zonly_physics" ); + + if ( coverType == "crouch" ) + { + if ( self.a.pose == "prone" ) + self ExitProneWrapper( 1 ); + self.a.pose = "crouch";// in case we only lerped into the pose + } + + if ( self.coverType == "stand" ) + self.a.special = "cover_stand"; + else + self.a.special = "cover_crouch"; + + behaviorCallbacks = spawnstruct(); + if ( !self.fixedNode ) + behaviorCallbacks.moveToNearByCover = animscripts\cover_behavior::moveToNearbyCover; + + behaviorCallbacks.reload = ::coverReload; + behaviorCallbacks.leaveCoverAndShoot = ::popUpAndShoot; + behaviorCallbacks.look = ::look; + behaviorCallbacks.fastlook = ::fastLook; + behaviorCallbacks.idle = ::idle; + behaviorCallbacks.flinch = ::flinch; + behaviorCallbacks.grenade = ::tryThrowingGrenade; + behaviorCallbacks.grenadehidden = ::tryThrowingGrenadeStayHidden; + behaviorCallbacks.blindfire = ::blindfire; + + animscripts\cover_behavior::main( behaviorCallbacks ); +} + +isRPD( weapon ) +{ + return getSubStr( weapon, 0, 3 ) == "rpd" && (weapon.size == 3 || weapon[3] == "_"); +} + +initCoverCrouchNode() +{ + if ( isdefined( self.crouchingIsOK ) ) + return; + + // it's only ok to crouch at this node if we can see out from a crouched position. + crouchHeightOffset = ( 0, 0, 42 ); + forward = anglesToForward( self.angles ); + self.crouchingIsOK = sightTracePassed( self.origin + crouchHeightOffset, self.origin + crouchHeightOffset + vector_multiply( forward, 64 ), false, undefined ); +} + + +setup_cover_crouch( exposedAnimSet ) +{ + if ( !isdefined( self.locked_combat ) ) + { + self setDefaultAimLimits( self.coverNode ); + } + + self setup_crouching_anim_array( exposedAnimSet ); +} + + +setup_cover_stand( exposedAnimSet ) +{ + if ( !isdefined( self.locked_combat ) ) + { + self setDefaultAimLimits( self.coverNode ); + } + + self setup_standing_anim_array( exposedAnimSet ); +} + + +coverReload() +{ + return Reload( 2.0, animArray( "reload" ) );// ( reload no matter what ) +} + + +popUpAndShoot() +{ + self.keepClaimedNodeIfValid = true; + + if ( isdefined( self.ramboChance ) && randomFloat( 1 ) < self.ramboChance ) + { + if ( rambo() ) + return true; + } + + if ( !pop_up() ) + return false; + + shootAsTold(); + + self endFireAndAnimIdleThread(); + + if ( isDefined( self.shootPos ) ) + { + distSqToShootPos = lengthsquared( self.origin - self.shootPos ); + // too close for RPG or out of ammo + if ( usingRocketLauncher() && ( distSqToShootPos < squared( 512 ) || self.a.rockets < 1 ) ) + { + if ( self.a.pose == "stand" ) + animscripts\shared::throwDownWeapon( %RPG_stand_throw ); + else + animscripts\shared::throwDownWeapon( %RPG_crouch_throw ); + } + } + + go_to_hide(); + + self.coverCrouchLean_aimmode = undefined; + self.keepClaimedNodeIfValid = false; + + return true; +} + + +shootAsTold() +{ + self endon( "return_to_cover" ); + + self maps\_gameskill::didSomethingOtherThanShooting(); + + while ( 1 ) + { + if ( isdefined( self.shouldReturnToCover ) ) + break; + + if ( !isdefined( self.shootPos ) ) { + assert( !isdefined( self.shootEnt ) ); + // give shoot_behavior a chance to iterate + self waittill( "do_slow_things" ); + waittillframeend; + if ( isdefined( self.shootPos ) ) + continue; + break; + } + + if ( !self.bulletsInClip ) + break; + + // crouch only + if ( self.coverType == "crouch" && needToChangeCoverMode() ) + { + break; + + // TODO: if changing between stances without returning to cover is implemented, + // we can't just endon("return_to_cover") because it will cause problems when it + // happens while changing stance. + // see corner's implementation of this idea for a better implementation. + + // NYI + /*changeCoverMode(); + + // if they're moving too fast for us to respond intelligently to them, + // give up on firing at them for the moment + if ( needToChangeCoverMode() ) + break; + + continue;*/ + } + + shootUntilShootBehaviorChange_coverWall(); + self clearAnim( %add_fire, .2 ); + } +} + +shootUntilShootBehaviorChange_coverWall() +{ + if ( self.coverType == "crouch" ) + self thread angleRangeThread();// gives stopShooting notify when shootPosOutsideLegalYawRange returns true + self thread aimIdleThread(); + + shootUntilShootBehaviorChange(); +} + + +rambo() +{ + if ( !hasEnemySightPos() ) + return false; + + animType = "rambo"; + if ( randomint( 10 ) < 2 ) + animType = "rambo_fail"; + + if ( !animArrayAnyExist( animType ) ) + return false; + + if ( self.coverType == "crouch" && !self.coverNode.crouchingIsOK ) + return false; + + pitch = getShootPosPitch( self.coverNode.origin + getNodeOffset( self.coverNode ) ); + if ( pitch > 15 ) + return false; + + forward = anglesToForward( self.angles ); + stepto = self.origin + vector_multiply( forward, -16 ); + + if ( !self mayMoveToPoint( stepto ) ) + return false; + + self.coverPosEstablishedTime = gettime(); + + self animMode( "zonly_physics" ); + self.keepClaimedNodeIfValid = true; + self.isRambo = true; + self.a.prevAttack = "rambo"; + + self.changingCoverPos = true; + + self thread animscripts\shared::ramboAim( 0 ); + + ramboAnim = animArrayPickRandom( animType ); + self setFlaggedAnimKnobAllRestart( "rambo", ramboAnim, %body, 1, .2, 1 ); + self animscripts\shared::DoNoteTracks( "rambo" ); + + self notify( "rambo_aim_end" ); + + self.changingCoverPos = false; + + self.keepClaimedNodeIfValid = false; + self.lastRamboTime = getTime(); + + self.changingCoverPos = false; + self.isRambo = undefined; + + return true; +} + + +idle() +{ + self endon( "end_idle" ); + + while ( 1 ) + { + useTwitch = ( randomint( 2 ) == 0 && animArrayAnyExist( "hide_idle_twitch" ) ); + if ( useTwitch ) + idleanim = animArrayPickRandom( "hide_idle_twitch" ); + else + idleanim = animarray( "hide_idle" ); + + playIdleAnimation( idleAnim, useTwitch ); + } +} + +flinch() +{ + if ( !animArrayAnyExist( "hide_idle_flinch" ) ) + return false; + + forward = anglesToForward( self.angles ); + stepto = self.origin + vector_multiply( forward, -16 ); + + if ( !self mayMoveToPoint( stepto ) ) + return false; + + self animmode( "zonly_physics" ); + self.keepClaimedNodeIfValid = true; + + flinchanim = animArrayPickRandom( "hide_idle_flinch" ); + playIdleAnimation( flinchanim, true ); + + self.keepClaimedNodeIfValid = false; + + return true; +} + +playIdleAnimation( idleAnim, needsRestart ) +{ + if ( needsRestart ) + self setFlaggedAnimKnobAllRestart( "idle", idleAnim, %body, 1, 0.25, 1 ); + else + self setFlaggedAnimKnobAll( "idle", idleAnim, %body, 1, 0.25, 1 ); + + self.a.coverMode = "hide"; + + self animscripts\shared::DoNoteTracks( "idle" ); +} + +look( lookTime ) +{ + if ( !isdefined( self.a.array[ "hide_to_look" ] ) ) + return false; + + if ( !peekOut() ) + return false; + + animscripts\shared::playLookAnimation( animArray( "look_idle" ), lookTime );// TODO: replace + + lookanim = undefined; + if ( self isSuppressedWrapper() ) + lookanim = animArray( "look_to_hide_fast" ); + else + lookanim = animArray( "look_to_hide" ); + + self setflaggedanimknoballrestart( "looking_end", lookanim, %body, 1, .1 ); + animscripts\shared::DoNoteTracks( "looking_end" ); + + return true; +} + +peekOut() +{ + if ( isdefined( self.coverNode.script_dontpeek ) ) + return false; + + // assuming no delta, so no maymovetopoint check + + self setFlaggedAnimKnobAll( "looking_start", animArray( "hide_to_look" ), %body, 1, .2 ); + animscripts\shared::DoNoteTracks( "looking_start" ); + + return true; +} + +fastLook() +{ + self setFlaggedAnimKnobAllRestart( "look", animArrayPickRandom( "look" ), %body, 1, .1 ); + self animscripts\shared::DoNoteTracks( "look" ); + + return true; +} + + +// These should be adjusted in animation data +pop_up_and_hide_speed() +{ + if ( self.a.coverMode == "left" || self.a.coverMode == "right" || self.a.coverMode == "over" ) + return 1; + + return randomfasterAnimSpeed(); +} + + +pop_up() +{ + assert( !isdefined( self.a.coverMode ) || self.a.coverMode == "hide" ); + + newCoverMode = getBestCoverMode(); + + timeleft = .1; + + popupAnim = animArray( "hide_2_" + newCoverMode ); + + if ( !self mayMoveToPoint( getAnimEndPos( popupAnim ) ) ) + return false; + + if ( self.script == "cover_crouch" && newCoverMode == "lean" ) + self.coverCrouchLean_aimmode = true; + + if ( self.coverType == "crouch" ) + self setup_cover_crouch( newCoverMode ); + else + self setup_cover_stand( newCoverMode ); + + self.a.special = "none"; + self.specialDeathFunc = undefined; + + if ( self.coverType == "stand" ) + self.a.special = "cover_stand_aim"; + else + self.a.special = "cover_crouch_aim"; + + self.changingCoverPos = true; + self notify( "done_changing_cover_pos" ); + + self animmode( "zonly_physics" ); + + animRate = pop_up_and_hide_speed(); + + self setFlaggedAnimKnobAllRestart( "pop_up", popUpAnim, %body, 1, .1, animRate ); + self thread DoNoteTracksForPopup( "pop_up" ); + + if ( animHasNoteTrack( popupAnim, "start_aim" ) ) + { + self waittillmatch( "pop_up", "start_aim" ); + timeleft = getAnimLength( popupAnim ) / animRate * ( 1 - self getAnimTime( popupAnim ) ); + } + else + { + self waittillmatch( "pop_up", "end" ); + timeleft = .1; + } + + self clearAnim( popUpAnim, timeleft + 0.05 ); + + self.a.coverMode = newCoverMode; + self.a.prevAttack = newCoverMode; + self setup_additive_aim( timeleft ); + self thread animscripts\shared::trackShootEntOrPos(); + + wait( timeleft ); + + if ( self isSniper() ) + { + thread animscripts\shoot_behavior::sniper_glint_behavior(); + } + + self.changingCoverPos = false; + self.coverPosEstablishedTime = gettime(); + + self notify( "stop_popup_donotetracks" ); + + return true; +} + +DoNoteTracksForPopup( animname ) +{ + self endon( "killanimscript" ); + self endon( "stop_popup_donotetracks" ); + self animscripts\shared::DoNoteTracks( animname ); +} + + +setup_additive_aim( transTime ) +{ + if ( self.a.coverMode == "left" || self.a.coverMode == "right" ) + aimCoverMode = "crouch"; + else + aimCoverMode = self.a.coverMode; + + self setAnimKnobAll( animArray( aimCoverMode + "_aim" ), %body, 1, transTime ); + if ( aimCoverMode == "crouch" ) + { + self setanimlimited( %covercrouch_aim2_add, 1, 0 ); + self setanimlimited( %covercrouch_aim4_add, 1, 0 ); + self setanimlimited( %covercrouch_aim6_add, 1, 0 ); + self setanimlimited( %covercrouch_aim8_add, 1, 0 ); + } + else if ( aimCoverMode == "stand" ) + { + self setanimlimited( %exposed_aim_2, 1, 0 ); + self setanimlimited( %exposed_aim_4, 1, 0 ); + self setanimlimited( %exposed_aim_6, 1, 0 ); + self setanimlimited( %exposed_aim_8, 1, 0 ); + } + else if ( aimCoverMode == "lean" ) + { + self setanimlimited( %exposed_aim_2, 1, 0 ); + self setanimlimited( %exposed_aim_4, 1, 0 ); + self setanimlimited( %exposed_aim_6, 1, 0 ); + self setanimlimited( %exposed_aim_8, 1, 0 ); + // these don't seem to have 45 degree aiming limits, + // so i'm using the exposed ones instead + /*self setanimlimited(%covercrouch_lean_aim2_add,1,0); + self setanimlimited(%covercrouch_lean_aim4_add,1,0); + self setanimlimited(%covercrouch_lean_aim6_add,1,0); + self setanimlimited(%covercrouch_lean_aim8_add,1,0);*/ + } + else if ( aimCoverMode == "over" ) + { + self setanimlimited( %coverstandaim_aim2_add, 1, 0 ); + self setanimlimited( %coverstandaim_aim4_add, 1, 0 ); + self setanimlimited( %coverstandaim_aim6_add, 1, 0 ); + self setanimlimited( %coverstandaim_aim8_add, 1, 0 ); + } +} + + +go_to_hide() +{ + self notify( "return_to_cover" ); + + self.changingCoverPos = true; self notify( "done_changing_cover_pos" ); + + self endAimIdleThread(); + + animRate = pop_up_and_hide_speed(); + + self setFlaggedAnimKnobAll( "go_to_hide", animArray( self.a.coverMode + "_2_hide" ), %body, 1, 0.2, animRate ); + self clearAnim( %exposed_modern, 0.2 ); + + self animscripts\shared::DoNoteTracks( "go_to_hide" ); + + self.a.coverMode = "hide"; + + if ( self.coverType == "stand" ) + self.a.special = "cover_stand"; + else + self.a.special = "cover_crouch"; + + self.changingCoverPos = false; +} + + +tryThrowingGrenadeStayHidden( throwAt ) +{ + // TODO: check suppression and add rambo grenade support + return tryThrowingGrenade( throwAt, true ); +} + + +tryThrowingGrenade( throwAt, safe ) +{ + if ( isdefined( self.dontEverShoot ) || isdefined( throwAt.dontAttackMe ) ) + return false; + + theanim = undefined; + if ( isdefined( self.ramboChance ) && randomfloat( 1.0 ) < self.ramboChance ) + { + theanim = animArrayPickRandom( "grenade_rambo" ); + } + else + { + if ( isdefined( safe ) && safe ) + theanim = animArrayPickRandom( "grenade_safe" ); + else + theanim = animArrayPickRandom( "grenade_exposed" ); + } + + self animMode( "zonly_physics" );// Unlatch the feet + self.keepClaimedNodeIfValid = true; + + threwGrenade = TryGrenade( throwAt, theanim ); + + self.keepClaimedNodeIfValid = false; + return threwGrenade; +} + + +blindfire() +{ + if ( !animArrayAnyExist( "blind_fire" ) ) + return false; + + self animMode( "zonly_physics" ); + self.keepClaimedNodeIfValid = true; + + self setFlaggedAnimKnobAllRestart( "blindfire", animArrayPickRandom( "blind_fire" ), %body, 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "blindfire" ); + + self.keepClaimedNodeIfValid = false; + + return true; +} + + +createTurret( posEnt, weaponInfo, weaponModel ) +{ + turret = spawnTurret( "misc_turret", posEnt.origin, weaponInfo ); + turret.angles = posEnt.angles; + turret.aiOwner = self; + turret setModel( weaponModel ); + turret makeUsable(); + turret setDefaultDropPitch( 0 ); + + if ( isDefined( posEnt.leftArc ) ) + turret.leftArc = posEnt.leftArc; + if ( isDefined( posEnt.rightArc ) ) + turret.rightArc = posEnt.rightArc; + if ( isDefined( posEnt.topArc ) ) + turret.topArc = posEnt.topArc; + if ( isDefined( posEnt.bottomArc ) ) + turret.bottomArc = posEnt.bottomArc; + + return turret; +} + +deleteIfNotUsed( owner ) +{ + self endon( "death" ); + self endon( "being_used" ); + + wait .1; + + if ( isdefined( owner ) ) + { + assert( !isdefined( owner.a.usingTurret ) || owner.a.usingTurret != self ); + owner notify( "turret_use_failed" ); + } + self delete(); +} + +useSelfPlacedTurret( weaponInfo, weaponModel ) +{ + turret = self createTurret( self.node.turretInfo, weaponInfo, weaponModel ); + + if ( self useTurret( turret ) ) + { + turret thread deleteIfNotUsed( self ); + if ( isdefined( self.turret_function ) ) + thread [[ self.turret_function ]]( turret ); +// self setAnimKnob( %cover, 0, 0 ); + self waittill( "turret_use_failed" );// generally this won't notify, and we'll just not do any more cover_wall for now + } + else + { + turret delete(); + } +} + + +useStationaryTurret() +{ + assert( isdefined( self.node ) ); + assert( isdefined( self.node.turret ) ); + + turret = self.node.turret; + if ( !turret.isSetup ) + return; + +// turret setmode( "auto_ai" ); // auto, auto_ai, manual, manual_ai +// turret startFiring(); // seems to be a bug with the turret being in manual mode to start with +// wait( 1 ); + thread maps\_mg_penetration::gunner_think( turret ); + self waittill( "continue_cover_script" ); + +// turret thread maps\_spawner::restorePitch(); +// self useturret( turret ); // dude should be near the mg42 +} + +get_standing_wall_cover_anim() +{ + if ( IsDefined( self.customStandWallAnims ) ) + { + return self.customStandWallAnims; + } + else + { + return anim.animsets.standWallAnimSet; + } +} + +get_crouching_wall_cover_anim() +{ + if ( IsDefined( self.customCrouchWallAnims ) ) + { + return self.customCrouchWallAnims; + } + else + { + return anim.animsets.crouchWallAnimSet; + } +} + +setup_crouching_anim_array( exposedAnimSet ) +{ + anim_array = get_crouching_wall_cover_anim(); + + if ( weapon_pump_action_shotgun() ) + { + if ( exposedAnimSet == "lean" || exposedAnimSet == "stand" ) + anim_array[ "single" ] = anim_array[ "shotgun_lean_single" ]; + else + anim_array[ "single" ] = anim_array[ "shotgun_over_single" ]; + } + else + { + anim_array[ "single" ] = anim_array[ "normal_single" ]; + } + + if ( isDefined( anim.ramboAnims ) ) + { + anim_array[ "rambo" ] = anim.ramboAnims.covercrouch; + anim_array[ "rambo_fail" ] = anim.ramboAnims.covercrouchfail; + anim_array[ "grenade_rambo" ] = anim.ramboAnims.covercrouchgrenade; + } + + self.a.array = anim_array; +} + + +setup_standing_anim_array( exposedAnimSet ) +{ + anim_array = get_standing_wall_cover_anim(); + + if ( exposedAnimSet == "over" ) + { + anim_array[ "fire" ] = anim_array[ "over_fire" ]; + anim_array[ "semi2" ] = anim_array[ "over_semi2" ]; + anim_array[ "semi3" ] = anim_array[ "over_semi3" ]; + anim_array[ "semi4" ] = anim_array[ "over_semi4" ]; + anim_array[ "semi5" ] = anim_array[ "over_semi5" ]; + + anim_array[ "single" ] = anim_array[ "over_single" ]; + + anim_array[ "burst2" ] = anim_array[ "over_burst2" ]; + anim_array[ "burst3" ] = anim_array[ "over_burst3" ]; + anim_array[ "burst4" ] = anim_array[ "over_burst4" ]; + anim_array[ "burst5" ] = anim_array[ "over_burst5" ]; + anim_array[ "burst6" ] = anim_array[ "over_burst6" ]; + + anim_array[ "continuous" ] = anim_array[ "over_continuous" ]; + } + else + { + anim_array[ "fire" ] = anim_array[ "stand_fire" ]; + anim_array[ "semi2" ] = anim_array[ "stand_semi2" ]; + anim_array[ "semi3" ] = anim_array[ "stand_semi3" ]; + anim_array[ "semi4" ] = anim_array[ "stand_semi4" ]; + anim_array[ "semi5" ] = anim_array[ "stand_semi5" ]; + + if ( weapon_pump_action_shotgun() ) + anim_array[ "single" ] = anim_array[ "stand_shotgun_single" ]; + else + anim_array[ "single" ] = anim_array[ "stand_normal_single" ]; + + anim_array[ "burst2" ] = anim_array[ "stand_burst2" ]; + anim_array[ "burst3" ] = anim_array[ "stand_burst3" ]; + anim_array[ "burst4" ] = anim_array[ "stand_burst4" ]; + anim_array[ "burst5" ] = anim_array[ "stand_burst5" ]; + anim_array[ "burst6" ] = anim_array[ "stand_burst6" ]; + + anim_array[ "continuous" ] = anim_array[ "stand_continuous" ]; + } + + if ( isDefined( anim.ramboAnims ) ) + { + anim_array[ "rambo" ] = anim.ramboAnims.coverstand; + anim_array[ "rambo_fail" ] = anim.ramboAnims.coverstandfail; + anim_array[ "grenade_rambo" ] = anim.ramboAnims.coverstandgrenade; + } + + self.a.array = anim_array; +} + + +loopHide( transTime ) +{ + if ( !isdefined( transTime ) ) + transTime = .1; + + self setanimknoballrestart( animArray( "hide_idle" ), %body, 1, transTime ); + self.a.coverMode = "hide"; +} + + +angleRangeThread() +{ + self endon( "killanimscript" ); + self notify( "newAngleRangeCheck" ); + self endon( "newAngleRangeCheck" ); + self endon( "return_to_cover" ); + + while ( 1 ) + { + if ( needToChangeCoverMode() ) + break; + wait( 0.1 ); + } + + self notify( "stopShooting" );// For changing shooting pose to compensate for player moving +} + + +needToChangeCoverMode() +{ + if ( self.coverType != "crouch" ) + return false; + + pitch = getShootPosPitch( self getEye() ); + + if ( self.a.coverMode == "lean" ) + { + return pitch < 10; + } + else + { + return pitch > 45; + } +} + + +getBestCoverMode() +{ + modes = []; + assert( isdefined( self.coverNode ) ); + + if ( isdefined( self.get_valid_peekout_func ) ) + { + modes = self.coverNode [[ self.get_valid_peekout_func ]](); + } + else if ( self.coverType == "stand" ) + { + modes = self.coverNode GetValidCoverPeekOuts(); + modes[ modes.size ] = "stand"; + } + else + { + pitch = getShootPosPitch( self.coverNode.origin + getNodeOffset( self.coverNode ) ); + + if ( pitch > 30 ) + return "lean"; + if ( pitch > 15 || !self.coverNode.crouchingIsOK ) + return "stand"; + + modes = self.coverNode GetValidCoverPeekOuts(); + modes[ modes.size ] = "crouch"; + } + + return getRandomCoverMode( modes ); +} + + +getShootPosPitch( fromPos ) +{ + shootPos = getEnemyEyePos(); + + return AngleClamp180( vectorToAngles( shootPos - fromPos )[ 0 ] ); +} diff --git a/animscripts/cqb.gsc b/animscripts/cqb.gsc new file mode 100644 index 0000000..b246673 --- /dev/null +++ b/animscripts/cqb.gsc @@ -0,0 +1,304 @@ +#include animscripts\utility; +#include animscripts\combat_utility; +#include animscripts\shared; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + +MoveCQB() +{ + animscripts\run::changeWeaponStandRun(); + + // any endons in this function must also be in CQBShootWhileMoving and CQBDecideWhatAndHowToShoot + + if ( self.a.pose != "stand" ) + { + // (get rid of any prone or other stuff that might be going on) + self clearAnim( %root, 0.2 ); + if ( self.a.pose == "prone" ) + self ExitProneWrapper( 1 ); + self.a.pose = "stand"; + } + self.a.movement = self.moveMode; + + //self clearanim(%combatrun, 0.2); + + self thread CQBTracking(); + + cqbWalkAnim = DetermineCQBAnim(); + + rate = self.moveplaybackrate; + + if ( self.moveMode == "walk" ) + rate *= 0.6; + + transTime = get_cqb_blend_in_time(); + + // (we don't use %body because that would reset the aiming knobs) + self setFlaggedAnimKnobAll( "runanim", cqbWalkAnim, %walk_and_run_loops, 1, transTime, rate, true ); + self set_move_anim_start_point(); + self animscripts\run::SetMoveNonForwardAnims( GetCQBAnim("move_b"), GetCQBAnim("move_l"), GetCQBAnim("move_r") ); + self thread animscripts\run::SetCombatStandMoveAnimWeights( "cqb" ); + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); + + self thread animscripts\run::stopShootWhileMovingThreads(); +} + +DetermineCQBAnim() +{ + //TagCC: This is rather fail. the self.MoveMode that GetRunAnim uses is used to set the anim are always run + //so effectively this line says that if a custom cqb animset is defined, don't use it, or even the normal cqb anims, + //but instead use the run anim set. Great. No one noticed this bug because cqb isn't ever overridden where run isn't + //also overriden to the same thing in mw2 (see riotshield). + //if ( isdefined( self.customMoveAnimSet ) && isdefined( self.customMoveAnimSet[ "cqb" ] ) ) + // return animscripts\run::GetRunAnim(); + + if ( self.stairsState == "up" ) + { + return GetCQBAnim( "stairs_up" ); + } + + if ( self.stairsState == "down" ) + { + return GetCQBAnim( "stairs_down" ); + } + + if ( self.movemode == "walk" ) + { + return GetCQBAnim( "move_f" ); + } + + variation = getRandomIntFromSeed( self.a.runLoopCount, 2 ); + if ( variation == 0 ) + { + return GetCQBAnim( "straight" ); + } + + return GetCQBAnim( "straight_variation" ); +} + +get_cqb_blend_in_time() +{ + transTime = 0.1; + if ( self.stairsState == "none" ) + { + transTime = 0.3; + //tagCC:for debugging, lets allow for this to be tweaked in script. remove at some point. + if( self.subclass == "moon" ) + { + transTime = 0.8; + + if( IsDefined( self.cqb_blend_finish_time ) ) + { + transTime = self.cqb_blend_finish_time; + self.cqb_blend_finish_time = undefined; + } + + run_blend_str = GetDvar("scr_run_blend"); + if( run_blend_str != "" ) + { + transTime = Float( run_blend_str ); + } + } + } + else + { + transTime = 0.1; // need to transition to stairs quickly + } + + return transTime; +} + +GetCQBAnim( anim_name ) +{ + if ( IsDefined( self.customMoveAnimSet ) && IsDefined( self.customMoveAnimSet[ "cqb" ] ) && IsDefined( self.customMoveAnimSet[ "cqb" ][anim_name] ) ) + { + return self.customMoveAnimSet[ "cqb" ][ anim_name ]; + } + else + { + return anim.animsets.move[ "cqb" ][ anim_name ]; + } +} + +CQBTracking() +{ + assert( isdefined( self.aim_while_moving_thread ) == isdefined( self.trackLoopThread ) ); + assertex( !isdefined( self.trackLoopThread ) || (self.trackLoopThreadType == "faceEnemyAimTracking"), self.trackLoopThreadType ); + + if ( animscripts\move::MayShootWhileMoving() ) + animscripts\run::runShootWhileMovingThreads(); + + animscripts\run::faceEnemyAimTracking(); +} + +setupCQBPointsOfInterest() +{ + level._cqbPointsOfInterest = []; + pointents = getEntArray( "cqb_point_of_interest", "targetname" ); + for ( i = 0; i < pointents.size; i++ ) + { + level._cqbPointsOfInterest[ i ] = pointents[ i ].origin; + pointents[ i ] delete(); + } +} + +findCQBPointsOfInterest() +{ + if ( isdefined( anim.findingCQBPointsOfInterest ) ) + return; + anim.findingCQBPointsOfInterest = true; + + // one AI per frame, find best point of interest. + if ( !level._cqbPointsOfInterest.size ) + return; + + while ( 1 ) + { + ai = getaiarray(); + waited = false; + foreach( guy in ai ) + { + if ( isAlive( guy ) && guy isCQBWalking() ) + { + moving = ( guy.a.movement != "stop" ); + + // if you change this, change the debug function below too + + shootAtPos = (guy.origin[0], guy.origin[1], guy getShootAtPos()[2]); + lookAheadPoint = shootAtPos; + forward = anglesToForward( guy.angles ); + if ( moving ) + { + trace = bulletTrace( lookAheadPoint, lookAheadPoint + forward * 128, false, undefined ); + lookAheadPoint = trace[ "position" ]; + } + + best = -1; + bestdist = 1024 * 1024; + for ( j = 0; j < level._cqbPointsOfInterest.size; j++ ) + { + point = level._cqbPointsOfInterest[ j ]; + + dist = distanceSquared( point, lookAheadPoint ); + if ( dist < bestdist ) + { + if ( moving ) + { + if ( distanceSquared( point, shootAtPos ) < 64 * 64 ) + continue; + dot = vectorDot( vectorNormalize( point - shootAtPos ), forward ); + if ( dot < 0.643 || dot > 0.966 )// 0.643 = cos( 50 ), 0.966 = cos( 15 ) + continue; + } + else + { + if ( dist < 50 * 50 ) + continue; + } + + if ( !sightTracePassed( lookAheadPoint, point, false, undefined ) ) + continue; + + bestdist = dist; + best = j; + } + } + + if ( best < 0 ) + guy.cqb_point_of_interest = undefined; + else + guy.cqb_point_of_interest = level._cqbPointsOfInterest[ best ]; + + wait .05; + waited = true; + } + } + if ( !waited ) + wait .25; + } +} + + /# +CQBDebug() +{ + self notify( "end_cqb_debug" ); + self endon( "end_cqb_debug" ); + self endon( "death" ); + + setDvarIfUninitialized( "scr_cqbdebug", "off" ); + + level thread CQBDebugGlobal(); + + while ( 1 ) + { + if ( getdebugdvar( "scr_cqbdebug" ) == "on" || getdebugdvarint( "scr_cqbdebug" ) == self getentnum() ) + { + shootAtPos = (self.origin[0], self.origin[1], self getShootAtPos()[2]); + if ( isdefined( self.shootPos ) ) + { + line( shootAtPos, self.shootPos, ( 1, 1, 1 ) ); + print3d( self.shootPos, "shootPos", ( 1, 1, 1 ), 1, 0.5 ); + } + else if ( isdefined( self.cqb_target ) ) + { + line( shootAtPos, self.cqb_target.origin, ( .5, 1, .5 ) ); + print3d( self.cqb_target.origin, "cqb_target", ( .5, 1, .5 ), 1, 0.5 ); + } + else + { + moving = ( self.a.movement != "stop" ); + + forward = anglesToForward( self.angles ); + lookAheadPoint = shootAtPos; + if ( moving ) + { + lookAheadPoint += forward * 128; + line( shootAtPos, lookAheadPoint, ( 0.7, .5, .5 ) ); + + right = anglesToRight( self.angles ); + leftScanArea = shootAtPos + ( forward * 0.643 - right ) * 64; + rightScanArea = shootAtPos + ( forward * 0.643 + right ) * 64; + line( shootAtPos, leftScanArea, ( 0.5, 0.5, 0.5 ), 0.7 ); + line( shootAtPos, rightScanArea, ( 0.5, 0.5, 0.5 ), 0.7 ); + } + + if ( isdefined( self.cqb_point_of_interest ) ) + { + line( lookAheadPoint, self.cqb_point_of_interest, ( 1, .5, .5 ) ); + print3d( self.cqb_point_of_interest, "cqb_point_of_interest", ( 1, .5, .5 ), 1, 0.5 ); + } + } + + wait .05; + continue; + } + + wait 1; + } +} + +CQBDebugGlobal() +{ + if ( isdefined( level._cqbdebugglobal ) ) + return; + level._cqbdebugglobal = true; + + while ( 1 ) + { + if ( getdebugdvar( "scr_cqbdebug" ) != "on" ) + { + wait 1; + continue; + } + + for ( i = 0; i < level._cqbPointsOfInterest.size; i++ ) + { + print3d( level._cqbPointsOfInterest[ i ], ".", ( .7, .7, 1 ), .7, 3 ); + } + + wait .05; + } +} +#/ + diff --git a/animscripts/custom.gsc b/animscripts/custom.gsc new file mode 100644 index 0000000..c59a2e5 --- /dev/null +++ b/animscripts/custom.gsc @@ -0,0 +1,36 @@ +#include animscripts\Combat_utility; +#include animscripts\Utility; +#include common_scripts\Utility; + +// Note that this script is called from the level script command animscripted, only for AI. If animscripted +// is done on a script model, this script is not called - startscriptedanim is called directly. + +#using_animtree( "generic_human" ); +main() +{ + //thread [[anim.println]]("Entering animscripts\\scripted. anim: ",self.codeScripted["anim"],", notify: ",self.codeScripted["notifyName"],", dialogue: ",self.scripted_dialogue,", facial: ",self.facial_animation, "root: ", self.codeScripted["root"]);#/ + self endon( "death" ); + +// wait (0); + self notify( "killanimscript" ); + self notify( "clearSuppressionAttack" ); + self.a.suppressingEnemy = false; + + + self.codeScripted[ "root" ] = %body; // TEMP! + + self endon( "end_sequence" ); +// Causes potential variable overflow in Stalingrad +// self thread DebugPrintEndSequence(); + + animation = %sprint_loop_distant; // Hardcoded default + if ( isdefined( self.node.script_animation ) ) + { + animation = self.node.script_animation; + } + + self setFlaggedAnimRestart( "custom", animation, 1, .1, 1.0 ); + + wait( 5.0 ); + self lookForBetterCover(); +} diff --git a/animscripts/death.gsc b/animscripts/death.gsc new file mode 100644 index 0000000..939fb2e --- /dev/null +++ b/animscripts/death.gsc @@ -0,0 +1,1773 @@ +#include common_scripts\utility; +#include animscripts\utility; +#include animscripts\combat_utility; +#include maps\_utility; + +#using_animtree( "generic_human" ); + + +// +// Damage Yaw +// +// front +// /----|----\ +// / 180 \ +// /\ | /\ +// / -135 | 135 \ +// | \ | / | +// left|-90----+----90-|right +// | / | \ | +// \ -45 | 45 / +// \/ | \/ +// \ 0 / +// \----|----/ +// back + +main() +{ + self endon( "killanimscript" ); + + // make sure the guy doesn't talk after death + self stopsoundchannel( "voice" ); + changeTime = 0.3; + self clearanim( %scripted_talking, changeTime ); + + // don't abort at this point unless you're going to play another animation! + // just playing ragdoll isn't sufficient because sometimes ragdoll fails, and then + // you'll just have a corpse standing around in limbo. + + if ( self.a.nodeath == true ) + return; + + if ( isdefined( self.deathFunction ) ) + { + result = self [[ self.deathFunction ]](); + if ( !isdefined( result ) ) + result = true; + if ( result ) + return; + } + + animscripts\utility::initialize( "death" ); + + // should move this to squad manager somewhere... + removeSelfFrom_SquadLastSeenEnemyPos( self.origin ); + + anim.numDeathsUntilCrawlingPain -- ; + anim.numDeathsUntilCornerGrenadeDeath -- ; + + self notify( "deathanim" ); + + if ( isDefined( self.ragdoll_immediate ) || self.forceRagdollImmediate ) + { + self doImmediateRagdollDeath(); + // ragdoll can fail so don't assume that we can quit the function + } + + if ( isDefined( self.deathanim ) ) + { + playDeathAnim( self.deathAnim ); + + // Added so that I can do special stuff in Level scripts on an ai + if ( isdefined( self.deathanimscript ) ) + self [[ self.deathanimscript ]](); + return; + } + + if ( isDefined( self.deathscript ) ) + { + self [[ self.deathscript ]](); + return; + } + + explosiveDamage = self animscripts\pain::wasDamagedByExplosive(); + if ( is_railgun( self.damageWeapon ) ) + { + if ( RandomInt( 3 ) == 0 ) + { + explosiveDamage = true; + } + } + + if ( self.damageLocation == "helmet" || self.damageLocation == "head" ) + self helmetPop( true ); + else if ( explosiveDamage && randomint( 3 ) == 0 ) + self helmetPop( false ); + + self clearanim( %root, 0.3 ); + + if ( !damageLocationIsAny( "head", "helmet" ) ) + { + if ( self.dieQuietly ) + { + // replace with actual die quietly gurglesque sound +// if ( randomint(3) < 2 ) +// self animscripts\face::SayGenericDialogue("pain"); + } + else + { + PlayDeathSound(); + } + } + + if ( explosiveDamage && playExplodeDeathAnim() ) + return; + + // different from deathFunction above, doesn't skip explosion deaths, immediate ragdoll, sounds, etc + if ( isdefined( self.specialDeathFunc ) ) + { + if ( [[ self.specialDeathFunc ]]() ) + return; + } + + // TODO: replace these with the above specialDeathFunc + if ( specialDeath() ) + return; + + // Dan - Commenting out gib. + //if ( play_bulletgibbed_death_anim()) + //{ + // return; + //} + + deathAnim = getDeathAnim(); + + /# + if ( getdvarint( "scr_paindebug" ) == 1 ) + println( "^2Playing pain: ", deathAnim, " ; pose is ", self.a.pose ); + #/ + + playDeathAnim( deathAnim ); +} + +doImmediateRagdollDeath() +{ + self animscripts\shared::DropAllAIWeapons(); + self.skipDeathAnim = true; // this helps playDeathAnim() do failsafes for ragdoll failures later + + initialImpulse = 10; + + damageType = common_scripts\_destructible::getDamageType( self.damageMod ); + if( IsDefined( self.attacker ) && self.attacker == level._player && damageType == "melee" ) + { + initialImpulse = 5; + } + + if ( self.subclass == "moon" ) + { + initialImpulse = 5; + + // tagBR< note >: This is to fix explosion barrel deaths during traversals (impulse was way too high) + if ( damageType == "splash" ) + { + initialImpulse = 2; + } + } + + damageTaken = self.damagetaken; + if ( damageType == "bullet" ) + damageTaken = max( damageTaken, 300 ); + + if ( GetDVarInt( "zero_g_proto" ) == 1 ) + { + self animscripts\shared::detachAllWeaponModels(); + initialImpulse = 4; + damageTaken /= 3; + } + + directionScale = initialImpulse * damageTaken; + directionUp = max( 0.3, self.damagedir[ 2 ] ); + direction = ( self.damagedir[ 0 ], self.damagedir[ 1 ], directionUp ); + direction *= directionScale; + + if ( self.forceRagdollImmediate ) + direction += self.prevAnimDelta * 20 * 10; // 20 frames/sec + + self startragdollfromimpact( self.damagelocation, direction ); + + // wait a bit so that the ragdoll can start before the death script tries to play a regular + // death animation as a failsafe - if ragdolling, the regular death anim won't do anything when called + wait( 0.2 ); +} + +playDeathAnim( deathAnim ) +{ + if ( !animHasNoteTrack( deathAnim, "dropgun" ) && !animHasNoteTrack( deathAnim, "fire_spray" ) )// && !animHasNotetrack( deathAnim, "gun keep" ) + self animscripts\shared::DropAllAIWeapons(); + + if ( isdefined( self.locked_combat ) ) + { + self StopAnimScripted(); + } + + //if ( isdefined( self.faceDamageDir ) ) + // self orientmode( "face angle", self.damageYaw ); + + self setFlaggedAnimKnobAllRestart( "deathanim", deathAnim, %body, 1, .1 ); + + if ( IsDefined( self.skipDeathAnim ) ) + { + ASSERTEX( self.skipDeathAnim, "self.skipDeathAnim must be either true or undefined." ); + + //self thread do_gib(); + + if( !isdefined( self.noragdoll ) || !self.noRagdoll ) + self startRagDoll(); + + wait( 0.05 ); + // failsafe in case ragdoll fails: he'll still be playing a deathanim, + // but at least he'll fall to the ground + self AnimMode( "gravity" ); + } + else if ( !animHasNotetrack( deathanim, "start_ragdoll" ) ) + { + //self thread do_gib(); + self thread waitForRagdoll( getanimlength( deathanim ) * 0.35 ); + } + + // do we really need this anymore? + /# + if ( getdebugdvar( "debug_grenadehand" ) == "on" ) + { + if ( animhasnotetrack( deathAnim, "bodyfall large" ) ) + return; + if ( animhasnotetrack( deathAnim, "bodyfall small" ) ) + return; + + println( "Death animation ", deathAnim, " does not have a bodyfall notetrack" ); + iprintlnbold( "Death animation needs fixing (check console and report bug in the animation to Boon)" ); + } + #/ + + // SRS 11/20/08: blood pools don't always line up with ragdoll corpses, so skip them if + // we did ragdoll without a death anim (which usually sends the body farther away from the death spot) + if ( !IsDefined( self.skipDeathAnim ) ) + { + self thread playDeathFX(); + } + + self animscripts\shared::DoNoteTracks( "deathanim" ); + self animscripts\shared::DropAllAIWeapons(); +} + +waitForRagdoll( time ) +{ + wait( time ); + if ( isdefined( self ) ) + self animscripts\shared::DropAllAIWeapons(); + if ( isdefined( self ) && ( !isdefined( self.noragdoll ) || !self.noRagdoll ) ) + self startragdoll(); +} + +playDeathFX() +{ + self endon( "killanimscript" ); + //iprintlnbold("bleed'n"); + + if ( self.stairsState != "none" ) + return; + + wait 2; + + play_blood_pool(); +} + +play_blood_pool( note, flagName ) +{ + if ( !isdefined( self ) ) + return; + + if ( isdefined( self.skipBloodPool ) ) + { + assertex( self.skipBloodPool, "Setting must be either true or undefined" ); + return; + } +/* + //play vacuum blood pool fx + if( GetDvar("environment_pressurized") == "0" ) + { + iprintlnbold("got moon blood"); + tagPos = self gettagorigin( "j_SpineUpper" ); // rough tag to play fx on + tagAngles = self gettagangles( "j_SpineUpper" ); + forward = anglestoforward( tagAngles ); + up = anglestoup( tagAngles ); + right = anglestoright( tagAngles ); + + tagPos = tagPos + vector_multiply( forward, -8.5 ) + vector_multiply( up, 5 ) + vector_multiply( right, 0 ); + + trace = bulletTrace( tagPos + ( 0, 0, 30 ), tagPos - ( 0, 0, 100 ), false, undefined ); + + if ( trace[ "normal" ][2] > 0.9 ) + { + playfx( level._effect[ "blood_pool_vacuum" ], tagPos, forward, up ); + } + return; + } +*/ + //play normal blood pool fx + tagPos = self gettagorigin( "j_SpineUpper" ); // rough tag to play fx on + tagAngles = self gettagangles( "j_SpineUpper" ); + forward = anglestoforward( tagAngles ); + up = anglestoup( tagAngles ); + right = anglestoright( tagAngles ); + + tagPos = tagPos + vector_multiply( forward, -8.5 ) + vector_multiply( up, 5 ) + vector_multiply( right, 0 ); + + trace = bulletTrace( tagPos + ( 0, 0, 30 ), tagPos - ( 0, 0, 100 ), false, undefined ); + + if( GetDvar("environment_pressurized") == "0" ) + { + for ( i = 0; i < 4; i++ ) + { + rand_x = RandomIntRange( -30, 30 ); + rand_y = RandomIntRange( -10, 10 ); + rand_z = RandomIntRange( 0, 1 ); + randomoffset = ( rand_x, rand_y, rand_z ); + bloodpos = tagpos + randomoffset; + newtrace = bulletTrace( bloodpos + ( 0, 0, 30 ), bloodpos - ( 0, 0, 100 ), false, undefined ); + if ( newtrace[ "normal" ][2] > 0.9 ) + { + if ( newtrace[ "position" ][2] < bloodpos[2] ) + { + playfx( level._effect[ "blood_pool_vacuum" ], bloodpos ); + //newbloodpos = vector_multiply( newtrace[ "position" ][0], bloodpos[0] ) + vector_multiply( newtrace[ "position" ][1], bloodpos[1] ) + vector_multiply( newtrace[ "position" ][2], bloodpos[2] ); + + //playfx( level._effect[ "blood_pool_steam" ], newbloodpos ); + + //iprintlnbold( newbloodpos ); + } + } + } + } + else if ( trace[ "normal" ][2] > 0.9 ) + { + playfx( level._effect[ "deathfx_bloodpool_generic" ], tagPos ); + } + +} + + +// TODO: replace these with specialDeathFunc +// Special death is for corners, rambo behavior, mg42's, anything out of the ordinary stand, crouch and prone. +// It returns true if it handles the death for the special animation state, or false if it wants the regular +// death function to handle it. +specialDeath() +{ + if ( self.a.special == "none" ) + return false; + + switch( self.a.special ) + { + case "cover_right": + if ( self.a.pose == "stand" ) + { + DoDeathFromArray( getDeathAnimByName("cover_right_stand") ); + } + else + { + deathArray = []; + //TagCC: why is this different from the left? it doesnt cut out death back. + if ( damageLocationIsAny( "head", "neck" ) ) + { + DoDeathFromArray( getDeathAnimByName("cover_right_crouch_head_neck") ); + } + else + { + DoDeathFromArray( getDeathAnimByName("cover_right_crouch") ); + } + } + return true; + + case "cover_left": + if ( self.a.pose == "stand" ) + { + DoDeathFromArray( getDeathAnimByName("cover_left_stand") ); + } + else + { + DoDeathFromArray( getDeathAnimByName("cover_left_crouch") ); + } + return true; + + case "cover_stand": + DoDeathFromArray( getDeathAnimByName("cover_stand") ); + return true; + + case "cover_crouch": + deathArray = []; + if ( damageLocationIsAny( "head", "neck" ) && ( self.damageyaw > 135 || self.damageyaw <= -45 ) ) // Front / Left quadrant + { + deathArray = getDeathAnimByName("cover_crouch_head"); + } + else if ( ( self.damageyaw > - 45 ) && ( self.damageyaw <= 45 ) ) // Back quadrant + { + deathArray = getDeathAnimByName("cover_crouch_back"); + } + + deathArray = array_combine( deathArray, getDeathAnimByName( "cover_crouch" ) ); + DoDeathFromArray( deathArray ); + + return true; + + case "saw": + if ( self.a.pose == "stand" ) + { + DoDeathFromArray( getDeathAnimByName("saw_stand") ); + } + else if ( self.a.pose == "crouch" ) + { + DoDeathFromArray( getDeathAnimByName("saw_crouch") ); + } + else + { + DoDeathFromArray( getDeathAnimByName("saw_prone") ); + } + return true; + + case "dying_crawl": + if ( isdefined( self.a.onback ) && self.a.pose == "crouch" ) + { + DoDeathFromArray( getDeathAnimByName("crawl_crouch") ); + } + else + { + DoDeathFromArray( getDeathAnimByName("crawl_prone") ); + } + return true; + } + return false; +} + + +DoDeathFromArray( deathArray ) +{ + deathAnim = deathArray[ randomint( deathArray.size ) ]; + + playDeathAnim( deathAnim ); + //nate - adding my own special death flag on top of special death. + if ( isdefined( self.deathanimscript ) ) + self [[ self.deathanimscript ]](); +} + + +PlayDeathSound() +{ + self animscripts\face::SayGenericDialogue( "death" ); +} + +print3dfortime( place, text, time ) +{ + numframes = time * 20; + for ( i = 0; i < numframes; i++ ) + { + print3d( place, text ); + wait .05; + } +} + +helmetPop( was_headshot ) +{ + if ( !isdefined( self ) ) + return; + // used to check self removableHat() in cod2... probably not necessary though + + // Potentially switch to a headshot version of the head model. + if ( was_headshot && isdefined( self.headshotModel ) ) + { + self Detach( self.headmodel ); + self.headmodel = self.headshotModel; + self Attach( self.headshotModel ); + } + + // Potentially pop a hat off. + if ( isdefined( self.hatModel ) ) + { + partName = GetPartName( self.hatModel, 0 ); + model = spawn( "script_model", self.origin + ( 0, 0, 64 ) ); + model setmodel( self.hatModel ); + model.origin = self GetTagOrigin( partName );// self . origin + ( 0, 0, 64 ); + model.angles = self GetTagAngles( partName );// ( -90, 0 + randomint( 90 ), 0 + randomint( 90 ) ); + model thread helmetLaunch( self.damageDir ); + + hatModel = self.hatModel; + self.hatModel = undefined; + + wait 0.05; + + if ( !isdefined( self ) ) + return; + self detach( hatModel, "" ); + } +} + +helmetLaunch( damageDir ) +{ + launchForce = damageDir; + launchForce = launchForce * randomFloatRange( 2000, 4000 ); + + forcex = launchForce[ 0 ]; + forcey = launchForce[ 1 ]; + forcez = randomFloatRange( 1500, 3000 ); + + contactPoint = self.origin + ( randomfloatrange( -1, 1 ), randomfloatrange( -1, 1 ), randomfloatrange( -1, 1 ) ) * 5; + + self PhysicsLaunchClient( contactPoint, ( forcex, forcey, forcez ) ); + + wait 60; + + while ( 1 ) + { + if ( !isdefined( self ) ) + return; + + if ( distanceSquared( self.origin, level._player.origin ) > 512 * 512 ) + break; + + wait 30; + } + + self delete(); +} + + +removeSelfFrom_SquadLastSeenEnemyPos( org ) +{ + for ( i = 0;i < anim.squadIndex.size;i++ ) + anim.squadIndex[ i ] clearSightPosNear( org ); +} + + +clearSightPosNear( org ) +{ + if ( !isdefined( self.sightPos ) ) + return; + + if ( distance( org, self.sightPos ) < 80 ) + { + self.sightPos = undefined; + self.sightTime = gettime(); + } +} + + +shouldDoRunningForwardDeath() +{ + if ( self.a.movement != "run" ) + return false; + + if ( self getMotionAngle() > 60 || self getMotionAngle() < - 60 ) + return false; + +/* + if ( ( self.damageyaw >= 120 ) || ( self.damageyaw <= -120 ) )// Front quadrant + return true; + + if ( ( self.damageyaw >= -45 ) && ( self.damageyaw <= 45 ) )// Back quadrant + return true; + + return false; +*/ + return true; +} + +shouldDoStrongBulletDamage( damageWeapon, damageMod, damagetaken, attacker ) +{ + ASSERT( IsDefined( damageWeapon ) ); + + if ( isdefined( self.a.doingLongDeath ) ) + { + return false; + } + + if ( self.a.pose == "prone" || isdefined( self.a.onback ) ) + { + return false; + } + + if( damageWeapon == "none" ) + { + return false; + } + + if ( damagetaken > 500 ) + { + return true; + } + + if( damageMod == "MOD_MELEE" ) + { + return false; + } + + // if I'm running, and the attacker is far enough away, sometimes let me do + // a running death instead. this helps minimize repetition of strong damage animations + // when a line of dudes is running towards you and you're mowing them down, etc. + if( self.a.movement == "run" && !isAttackerWithinDist( attacker, 275 ) ) + { + if( RandomInt( 100 ) < 65 ) + { + return false; + } + } + + if ( isSniperRifle( damageWeapon ) && self.maxHealth < damageTaken ) + { + return true; + } + + if( isShotgun( damageWeapon ) && isAttackerWithinDist( attacker, 512 ) ) + { + return true; + } + + if( isDesertEagle( damageWeapon ) && isAttackerWithinDist( attacker, 425 ) ) + { + return true; + } + + if( is_railgun( damageWeapon ) ) + { + return true; + } + + return false; +} + +isDesertEagle( damageWeapon ) +{ + if( damageWeapon == "deserteagle" ) + { + return true; + } + + return false; +} + +is_railgun( damageWeapon ) +{ + if( damageWeapon == "ugv_main_turret" || damageWeapon == "ugv_main_turret_player" || damageWeapon == "ugv_main_turret_mp" || damageWeapon == "nx_chinese_lgv_turret" ) + { + return true; + } + + return false; +} + +isAttackerWithinDist( attacker, maxDist ) +{ + if( !IsDefined( attacker ) ) + { + return false; + } + + if( Distance( self.origin, attacker.origin ) > maxDist ) + { + return false; + } + + return true; +} + +getDeathAnim() +{ + if ( shouldDoStrongBulletDamage( self.damageWeapon, self.damageMod, self.damagetaken, self.attacker ) ) + { + deathAnim = getStrongBulletDamageDeathAnim(); + + if ( IsDefined( deathAnim ) ) + { + return deathAnim; + } + } + + if ( isdefined( self.a.onback ) ) + { + if ( self.a.pose == "crouch" ) + return getBackDeathAnim(); + else + animscripts\shared::stopOnBack(); + } + + if ( self.a.pose == "stand" ) + { + if ( shouldDoRunningForwardDeath() ) + { + return getRunningForwardDeathAnim(); + } + else + { + return getStandDeathAnim(); + } + } + else if ( self.a.pose == "crouch" ) + { + return getCrouchDeathAnim(); + } + else if ( self.a.pose == "prone" ) + { + return getProneDeathAnim(); + } +} + + +// may return undefined +// large death animation for shotguns, snipers etc. +getStrongBulletDamageDeathAnim() +{ + damageYaw = abs( self.damageYaw ); + + //TagCC: note, it's using ABS of damageYaw + //TagCC: Shot from behind? + if ( damageYaw < 45 ) + return; + + //TagCC: This probably can all be optimized more by just using logic to decide array name. I'm not sure if it's passing + //a handle to the array, or the actual array around (my guess is it's the actual array). It's atleast not any worse than before. + + //TagCC: Front + if ( damageYaw > 150 ) + { + if ( damageLocationIsAny( "left_leg_upper", "left_leg_lower", "right_leg_upper", "right_leg_lower", "left_foot", "right_foot" ) ) + { + deathArray = getDeathAnimByName("strong_leg_front"); + } + else + { + if ( self.damageLocation == "torso_lower" ) + { + deathArray = getDeathAnimByName("strong_lower_torso_front"); + } + else + { + deathArray = getDeathAnimByName("strong_torso_front"); + } + } + } + else if ( self.damageYaw < 0 ) // LEFT + { + deathArray = getDeathAnimByName("strong_left"); + } + else // RIGHT + { + deathArray = getDeathAnimByName("strong_right"); + } + + return deathArray[ randomint( deathArray.size ) ]; +} + +getRunningForwardDeathAnim() +{ + deathArray = getDeathAnimByName("running_forward"); + sanityCheckDeathArray( deathArray ); + + deathArray = animscripts\pain::removeBlockedAnims( deathArray ); + + if ( !deathArray.size ) + return getStandDeathAnim(); + + return deathArray[ randomint( deathArray.size ) ]; +} + +// remove undefined entries from array +removeUndefined( array ) +{ + newArray = []; + for ( index = 0; index < array.size; index++ ) + { + if ( !isDefined( array[ index ] ) ) + continue; + + newArray[ newArray.size ] = array[ index ]; + } + return newArray; +} + +getStandPistolDeathAnim() +{ + deathArray = []; + + if ( abs( self.damageYaw ) < 50 ) + { + deathArray[ deathArray.size ] = getDeathAnimByName("stand_pistol_back");// falls forwards + } + else + { + if ( abs( self.damageYaw ) < 110 ) + deathArray[ deathArray.size ] = getDeathAnimByName("stand_pistol_back");// falls forwards + + if ( damageLocationIsAny( "torso_lower", "torso_upper", "left_leg_upper", "left_leg_lower", "right_leg_upper", "right_leg_lower" ) ) + { + deathArray[ deathArray.size ] = getDeathAnimByName("stand_pistol_legs");// hit in groin from front + if ( !damageLocationIsAny( "torso_upper" ) ) + deathArray[ deathArray.size ] = getDeathAnimByName("stand_pistol_legs");// ( twice as likely ) + } + + if ( !damageLocationIsAny( "head", "neck", "helmet", "left_foot", "right_foot", "left_hand", "right_hand", "gun" ) && randomint( 2 ) == 0 ) + deathArray[ deathArray.size ] = getDeathAnimByName("stand_pistol_chest");// hit at top and falls backwards, but more dragged out + + if ( deathArray.size == 0 || damageLocationIsAny( "torso_lower", "torso_upper", "neck", "head", "helmet", "right_arm_upper", "left_arm_upper" ) ) + deathArray[ deathArray.size ] = getDeathAnimByName("stand_pistol_head");// falls backwards + } + + return deathArray; +} + +getStandDeathAnim() +{ + deathArray = []; + extendedDeathArray = []; + + if ( usingSidearm() ) + { + deathArray = getStandPistolDeathAnim(); + } + else + { + // torso or legs + if ( damageLocationIsAny( "torso_lower", "left_leg_upper", "left_leg_lower", "right_leg_lower", "right_leg_lower" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_legs") ); + extendedDeathArray = array_combine( extendedDeathArray, getDeathAnimByName("stand_legs_extended") ); + } + + if ( damageLocationIsAny( "head", "helmet" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_head") ); + } + + if ( damageLocationIsAny( "neck" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_neck") ); + } + + if ( damageLocationIsAny( "left_arm_upper" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_upper_left") ); + } + + if ( damageLocationIsAny( "torso_upper" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_upper_torso") ); + extendedDeathArray = array_combine( extendedDeathArray, getDeathAnimByName("stand_upper_torso_extended") ); + } + + // quadrants + if ( ( self.damageyaw > 135 ) || ( self.damageyaw <= -135 ) )// Front quadrant + { + if ( damageLocationIsAny( "neck", "head", "helmet" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_front_head") ); + extendedDeathArray = array_combine( extendedDeathArray, getDeathAnimByName("stand_front_head_extended") ); + } + + if ( damageLocationIsAny( "torso_upper" ) ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_front_torso") ); + extendedDeathArray = array_combine( extendedDeathArray, getDeathAnimByName("stand_front_torso_extended") ); + } + } + else if ( ( self.damageyaw > -45 ) && ( self.damageyaw <= 45 ) )// Back quadrant + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_back") ); + } + + foundLocDamageDeath = ( deathArray.size > 0 ); + + if ( !foundLocDamageDeath || RandomInt( 100 ) < 15 ) + { + deathArray = array_combine( deathArray, getDeathAnimByName("stand_generic") ); + } + + sanityCheckDeathArray( deathArray ); + + if ( RandomInt( 100 ) < 10 && firingDeathAllowed() ) + { + standing_file = getDeathAnimByName("stand_firing"); + sanityCheckFireDeathArray( standing_file ); + deathArray = array_combine( deathArray, getDeathAnimByName("stand_firing") ); + } + + deathArray = removeUndefined( deathArray ); + } + + assertex( deathArray.size > 0, deathArray.size ); + if ( deathArray.size == 0 ) + deathArray = getDeathAnimByName("stand_exposed"); + + if ( !self.a.disableLongDeath && self.stairsState == "none" && !isdefined( self.a.painOnStairs ) ) + { + index = randomint( deathArray.size + extendedDeathArray.size ); + if ( index < deathArray.size ) + return deathArray[ index ]; + else + return extendedDeathArray[ index - deathArray.size ]; + } + + assertex( deathArray.size > 0, deathArray.size ); + return deathArray[ randomint( deathArray.size ) ]; +} + + +getCrouchDeathAnim() +{ + deathArray = []; + + if ( damageLocationIsAny( "head", "neck" ) ) // Front / Left quadrant + deathArray[ deathArray.size ] = getDeathAnimByName("crouch_head"); + + if ( damageLocationIsAny( "torso_upper", "torso_lower", "left_arm_upper", "right_arm_upper", "neck" ) ) + deathArray[ deathArray.size ] = getDeathAnimByName("crouch_torso"); + + if ( deathArray.size < 2 ) + deathArray[ deathArray.size ] = getDeathAnimByName("crouch_twist"); + if ( deathArray.size < 2 ) + deathArray[ deathArray.size ] = getDeathAnimByName("crouch_generic"); + + sanityCheckDeathArray( deathArray ); + + assertex( deathArray.size > 0, deathArray.size ); + return deathArray[ randomint( deathArray.size ) ]; +} + +/* Dan - Commenting out gib. +get_gib_ref( direction ) +{ + anim.gibDelay = 0; + + // Dvars for testing gibs. + //if ( GetDvarInt( "gib_delay" ) > 0 ) + //{ + // anim.gibDelay = GetDvarInt( "gib_delay" ); + //} + // + //if ( GetDvar( "gib_test" ) != "" ) + //{ + // self.a.gib_ref = GetDvar( "gib_test" ); + // return; + //} + + // If already set, then use it. Useful for canned gib deaths. + if ( IsDefined( self.a.gib_ref )) + { + return; + } + + // Don't gib if we haven't taken enough damage by the explosive + // Grenade damage usually range from 160 - 250, so we go above teh minimum + // so if the splash damage is near it's lowest, don't gib. + if( self.damageTaken < 165 ) + { + return; + } + + if ( GetTime() > anim.lastGibTime + anim.gibDelay && anim.totalGibs > 0 ) + { + anim.totalGibs--; + + anim thread set_last_gib_time(); + + refs = []; + switch( direction ) + { + case "right": + refs[refs.size] = "left_arm"; + refs[refs.size] = "left_leg"; + + gib_ref = get_random( refs ); + break; + + case "left": + refs[refs.size] = "right_arm"; + refs[refs.size] = "right_leg"; + + gib_ref = get_random( refs ); + break; + + case "forward": + refs[refs.size] = "right_arm"; + refs[refs.size] = "left_arm"; + refs[refs.size] = "right_leg"; + refs[refs.size] = "left_leg"; + //refs[refs.size] = "guts"; + refs[refs.size] = "no_legs"; + + gib_ref = get_random( refs ); + break; + + case "back": + refs[refs.size] = "right_arm"; + refs[refs.size] = "left_arm"; + refs[refs.size] = "right_leg"; + refs[refs.size] = "left_leg"; + refs[refs.size] = "no_legs"; + + gib_ref = get_random( refs ); + break; + + default: // "up" + refs[refs.size] = "right_arm"; + refs[refs.size] = "left_arm"; + refs[refs.size] = "right_leg"; + refs[refs.size] = "left_leg"; + refs[refs.size] = "no_legs"; + //refs[refs.size] = "guts"; + + gib_ref = get_random( refs ); + break; + } + + + self.a.gib_ref = gib_ref; + } + else + { + self.a.gib_ref = undefined; + } +} + +set_last_gib_time() +{ + anim notify( "stop_last_gib_time" ); + anim endon( "stop_last_gib_time" ); + + wait( 0.05 ); + anim.lastGibTime = GetTime(); + anim.totalGibs = RandomIntRange( anim.minGibs, anim.maxGibs ); +} + +get_random( array ) +{ + return array[RandomInt( array.size )]; +} + +do_gib() +{ + if( !IsDefined( self.a.gib_ref ) ) + { + return; + } + + gib_ref = self.a.gib_ref; + + limb_data = get_limb_data( gib_ref ); + + if ( !IsDefined( limb_data )) + { + println( "^3animscripts\death.gsc - limb_data is not setup for gib_ref on model: " + self.model + " and gib_ref of: " + self.a.gib_ref ); + return; + } + + forward = undefined; + velocity = undefined; + + pos1 = []; + pos2 = []; + velocities = []; + + if ( gib_ref == "head" ) + { + self Detach( self.headModel, "" ); + self helmetPop(); + + if ( IsDefined( self.hatModel ) ) + { + self detach( self.hatModel, "" ); + self.hatModel = undefined; + } + } + + if ( limb_data["spawn_tags"][0] != "" ) + { + if ( IsDefined( self.gib_vel ) ) + { + for ( i = 0; i < limb_data["spawn_tags"].size; i++ ) + { + velocities[i] = self.gib_vel; + } + } + else + { + for ( i = 0; i < limb_data["spawn_tags"].size; i++ ) + { + pos1[pos1.size] = self GetTagOrigin( limb_data["spawn_tags"][i] ); + } + + wait( 0.05 ); + + for ( i = 0; i < limb_data["spawn_tags"].size; i++ ) + { + pos2[pos2.size] = self GetTagOrigin( limb_data["spawn_tags"][i] ); + } + + for ( i = 0; i < pos1.size; i++ ) + { + forward = VectorNormalize( pos2[i] - pos1[i] ); + velocities[i] = forward * RandomIntRange( 600, 1000 ); + velocities[i] = velocities[i] +( 0, 0, RandomIntRange( 400, 700 ) ); + } + } + } + + if ( IsDefined( limb_data["fx"] ) ) + { + for ( i = 0; i < limb_data["spawn_tags"].size; i++ ) + { + if ( limb_data["spawn_tags"][i] == "" ) + { + continue; + } + + PlayFxOnTag( anim._effect[limb_data["fx"]], self, limb_data["spawn_tags"][i] ); + } + } + + //tagrRR: Play a sound here? + self thread throw_gib( limb_data["spawn_models"], limb_data["spawn_tags"], velocities ); + + // Set the upperbody model + self SetModel( limb_data["body_model"] ); + + // Attach the legs + self Attach( limb_data["legs_model"] ); +} + +precache_gib_fx() +{ + anim._effect["animscript_gib_fx"] = LoadFx( "impacts/flesh_hit_head_fatal_exit" );//LoadFx( "weapon/bullet/fx_flesh_gib_fatal_01" ); + anim._effect["animscript_gibtrail_fx"] = LoadFx( "impacts/flesh_hit_head_fatal_exit" );//LoadFx( "trail/fx_trail_blood_streak" ); + + // Not gib; split out into another function before this gets out of hand. + anim._effect["death_neckgrab_spurt"] = LoadFx( "impacts/flesh_hit_head_fatal_exit" ); //LoadFx( "impacts/fx_flesh_hit_neck_fatal" ); +} + +get_limb_data( gib_ref ) +{ + temp_array = []; + + // Slightly faster, store the IsDefined stuff before checking, which will be less code-calls. + torsoDmg1_defined = IsDefined( self.torsoDmg1 ); + torsoDmg2_defined = IsDefined( self.torsoDmg2 ); + torsoDmg3_defined = IsDefined( self.torsoDmg3 ); + torsoDmg4_defined = IsDefined( self.torsoDmg4 ); + torsoDmg5_defined = IsDefined( self.torsoDmg5 ); + legDmg1_defined = IsDefined( self.legDmg1 ); + legDmg2_defined = IsDefined( self.legDmg2 ); + legDmg3_defined = IsDefined( self.legDmg3 ); + legDmg4_defined = IsDefined( self.legDmg4 ); + + gibSpawn1_defined = IsDefined( self.gibSpawn1 ); + gibSpawn2_defined = IsDefined( self.gibSpawn2 ); + gibSpawn3_defined = IsDefined( self.gibSpawn3 ); + gibSpawn4_defined = IsDefined( self.gibSpawn4 ); + gibSpawn5_defined = IsDefined( self.gibSpawn5 ); + + gibSpawnTag1_defined = IsDefined( self.gibSpawnTag1 ); + gibSpawnTag2_defined = IsDefined( self.gibSpawnTag2 ); + gibSpawnTag3_defined = IsDefined( self.gibSpawnTag3 ); + gibSpawnTag4_defined = IsDefined( self.gibSpawnTag4 ); + gibSpawnTag5_defined = IsDefined( self.gibSpawnTag5 ); + + // Right arm is getting blown off! ///////////////////////////////////////////////////// + if ( torsoDmg2_defined && legDmg1_defined && gibSpawn1_defined && gibSpawnTag1_defined ) + { + temp_array["right_arm"]["body_model"] = self.torsoDmg2; + temp_array["right_arm"]["legs_model"] = self.legDmg1; + temp_array["right_arm"]["spawn_models"][0] = self.gibSpawn1; + + temp_array["right_arm"]["spawn_tags"][0] = self.gibSpawnTag1; + temp_array["right_arm"]["fx"] = "animscript_gib_fx"; + } + + // Left arm is getting blown off! ////////////////////////////////////////////////////// + if ( torsoDmg3_defined && legDmg1_defined && gibSpawn2_defined && gibSpawnTag2_defined ) + { + temp_array["left_arm"]["body_model"] = self.torsoDmg3; + temp_array["left_arm"]["legs_model"] = self.legDmg1; + temp_array["left_arm"]["spawn_models"][0] = self.gibSpawn2; + + temp_array["left_arm"]["spawn_tags"][0] = self.gibSpawnTag2; + temp_array["left_arm"]["fx"] = "animscript_gib_fx"; + } + + // Right leg is getting blown off! //////////////////////////////////////////////////// + if ( torsoDmg1_defined && legDmg2_defined && gibSpawn3_defined && gibSpawnTag3_defined ) + { + temp_array["right_leg"]["body_model"] = self.torsoDmg1; + temp_array["right_leg"]["legs_model"] = self.legDmg2; + temp_array["right_leg"]["spawn_models"][0] = self.gibSpawn3; + + temp_array["right_leg"]["spawn_tags"][0] = self.gibSpawnTag3; + temp_array["right_leg"]["fx"] = "animscript_gib_fx"; + } + + + // Left leg is getting blown off! ///////////////////////////////////////////////////// + if ( torsoDmg1_defined && legDmg3_defined && gibSpawn4_defined && gibSpawnTag4_defined ) + { + temp_array["left_leg"]["body_model"] = self.torsoDmg1; + temp_array["left_leg"]["legs_model"] = self.legDmg3; + temp_array["left_leg"]["spawn_models"][0] = self.gibSpawn4; + + temp_array["left_leg"]["spawn_tags"][0] = self.gibSpawnTag4; + temp_array["left_leg"]["fx"] = "animscript_gib_fx"; + } + + // No legs! /////////////////////////////////////////////////////////////////////////// + if ( torsoDmg1_defined && legDmg4_defined && gibSpawn4_defined && gibSpawn3_defined && gibSpawnTag3_defined && gibSpawnTag4_defined ) + { + temp_array["no_legs"]["body_model"] = self.torsoDmg1; + temp_array["no_legs"]["legs_model"] = self.legDmg4; + temp_array["no_legs"]["spawn_models"][0] = self.gibSpawn4; + temp_array["no_legs"]["spawn_models"][1] = self.gibSpawn3; + + temp_array["no_legs"]["spawn_tags"][0] = self.gibSpawnTag4; + temp_array["no_legs"]["spawn_tags"][1] = self.gibSpawnTag3; + temp_array["no_legs"]["fx"] = "animscript_gib_fx"; + } + + // Guts! ////////////////////////////////////////////////////////////////////////////// + if ( torsoDmg4_defined && legDmg1_defined ) + { + temp_array["guts"]["body_model"] = self.torsoDmg4; + temp_array["guts"]["legs_model"] = self.legDmg1; + + temp_array["guts"]["spawn_models"][0] = ""; + // temp_array["guts"]["spawn_tags"][0] = "J_SpineLower"; + temp_array["guts"]["spawn_tags"][0] = ""; + temp_array["guts"]["fx"] = "animscript_gib_fx"; + } + + // Head! ////////////////////////////////////////////////////////////////////////////// + if ( torsoDmg5_defined && legDmg1_defined ) + { + temp_array["head"]["body_model"] = self.torsoDmg5; + temp_array["head"]["legs_model"] = self.legDmg1; + + if( gibSpawn5_defined && gibSpawnTag5_defined ) + { + temp_array["head"]["spawn_models"][0] = self.gibSpawn5; + temp_array["head"]["spawn_tags"][0] = self.gibSpawnTag5; + } + else + { + temp_array["head"]["spawn_models"][0] = ""; + temp_array["head"]["spawn_tags"][0] = ""; + } + temp_array["head"]["fx"] = "animscript_gib_fx"; + } + + if ( IsDefined( temp_array[gib_ref] ) ) + { + return temp_array[gib_ref]; + } + else + { + return undefined; + } +} + +throw_gib( spawn_models, spawn_tags, velocities ) +{ + if ( velocities.size < 1 ) // For guts + { + return; + } + + for ( i = 0; i < spawn_models.size; i++ ) + { + origin = self GetTagOrigin( spawn_tags[i] ); + angles = self GetTagAngles( spawn_tags[i] ); + CreateDynEntAndLaunch( spawn_models[i], origin, angles, origin, velocities[i], anim._effect["animscript_gibtrail_fx"], 1 ); + + //gib = Spawn( "script_model", self GetTagOrigin( spawn_tags[i] ) ); + //gib.angles = self GetTagAngles( spawn_tags[i] ); + //gib SetModel( spawn_models[i] ); + + //// Play trail fX + //PlayFxOnTag( anim._effect["animscript_gibtrail_fx"], gib, "tag_fx" ); + + //gib PhysicsLaunch( self.origin, velocities[i] ); + + //gib thread gib_delete(); + } +} + +play_bulletgibbed_death_anim() +{ + maxDist = 300; + + if ( self.damagemod == "MOD_MELEE" ) + { + return false; + } + + // Allow script to turn off gibbing. + if ( IsDefined( self.no_gib ) && ( self.no_gib == 1 ) ) + { + return false; + } + + gib_chance = 75; + shotty_gib = false; + force_gib = IsDefined( self.force_gib ) && self.force_gib; + if ( WeaponClass( self.damageWeapon ) == "spread" ) // shotgun + { + maxDist = 300; + shotty_gib = true; + distSquared = DistanceSquared( self.origin, self.attacker.origin ); + if ( distSquared < 110*110 ) + { + gib_chance = 100; + } + else if ( distSquared < 200*200 ) + { + gib_chance = 75; + } + else if ( distSquared < 270*270 ) + { + gib_chance = 50; + } + else if ( distSquared < 330*330 ) + { + if ( RandomInt( 100 ) < 50 ) + { + gib_chance = 50; + } + else + { + return false; + } + } + else + { + return false; + } + } + else if ( IsDefined(self.damageWeapon) && self.damageWeapon != "none" && IsSubStr( self.damageWeapon, "dragunov" ) ) + { + // SUMEET - Adding special case for draganov for some levels with 30% chance + maxDist = WeaponMaxGibDistance( self.damageWeapon ); + gib_chance = 30; + } + else if ( IsDefined(self.damageWeapon) && self.damageWeapon != "none" && WeaponDoGibbing( self.damageWeapon )) + { + maxDist = WeaponMaxGibDistance( self.damageWeapon ); + gib_chance = 101; + } + else if ( !force_gib ) + { + return false; + } + + if ( force_gib ) + { + maxDist = 6000; + gib_chance = 101; + } + + if ( !IsDefined( self.attacker ) || !IsDefined( self.damageLocation )) + { + return false; + } + + // shotgun damage is less than 50 + if ( self.damagetaken < 50 && !shotty_gib && !force_gib) + { + return false; + } + + self.a.gib_ref = undefined; + + distSquared = DistanceSquared( self.origin, self.attacker.origin ); + + if ( RandomInt( 100 ) < gib_chance + && distSquared < maxDist*maxDist + && ( force_gib || GetTime() > anim.lastGibTime + anim.gibDelay )) + { + anim.lastGibTime = GetTime(); + + refs = []; + switch( self.damageLocation ) + { + case "torso_upper": + case "torso_lower": + //refs[refs.size] = "guts"; + refs[refs.size] = "right_arm"; + refs[refs.size] = "left_arm"; + break; + case "right_arm_upper": + case "right_arm_lower": + case "right_hand": + refs[refs.size] = "right_arm"; + break; + case "left_arm_upper": + case "left_arm_lower": + case "left_hand": + refs[refs.size] = "left_arm"; + break; + case "right_leg_upper": + case "right_leg_lower": + case "right_foot": + refs[refs.size] = "right_leg"; + refs[refs.size] = "no_legs"; + break; + case "left_leg_upper": + case "left_leg_lower": + case "left_foot": + refs[refs.size] = "left_leg"; + refs[refs.size] = "no_legs"; + break; + case "helmet": + case "head": + refs[refs.size] = "head"; + break; + } + + // Allow script to customize gib parts + if ( IsDefined( self.custom_gib_refs )) + { + refs = self.custom_gib_refs; + } + + if ( refs.size ) + { + self.a.gib_ref = get_random( refs ); + } + } + + range = 600; + nrange = -600; + self.gib_vel = self.damagedir * RandomIntRange( 500, 900 ); + self.gib_vel += ( RandomIntRange( nrange, range ), RandomIntRange( nrange, range ), RandomIntRange( 400, 1000 ) ); + + if ( try_gib_extended_death( 101 )) //50 )) + { + return true; + } + + deathAnim = getDeathAnim(); + playDeathAnim( deathAnim ); + + //self setFlaggedAnimKnobAllRestart( "deathanim", deathAnim, %body, 1, .1 ); + // + //wait 0.05; + // + //self launch_ragdoll_based_on_damage_type( 2.0 ); + //self thread death_anim_short_circuit(); // do this just for consistency + // + //// wait here so that the client can get the model changes before it becomes an AI_CORPSE + //wait 0.5; + + return true; +} + +// checks if the gib ref provided is valid one +isValidGibRef( gib_ref ) +{ + // SUMEET_TODO - make this list global list so that it can be updated/edited easily + refs = []; + + refs[refs.size] = "right_arm"; + refs[refs.size] = "left_arm"; + refs[refs.size] = "right_leg"; + refs[refs.size] = "left_leg"; + refs[refs.size] = "no_legs"; + refs[refs.size] = "head"; + + if( is_in_array( refs, gib_ref ) ) + return true; + + return false; +} + +try_gib_extended_death( chance ) +{ + if ( RandomInt( 100 ) >= chance ) + { + return false; + } + + if ( self.a.pose == "prone" || self.a.pose == "back" ) + { + return false; + } + + deathseq = get_gib_extended_death_anims(); + + if ( deathSeq.size == 3 ) + { + do_extended_death( deathSeq ); + return true; + } + + return false; +} +*/ + +do_extended_death( deathSeq ) +{ + self animscripts\shared::DropAllAIWeapons(); + + //self thread do_gib(); + + self thread end_extended_death( deathSeq ); + + numDeathLoops = RandomInt( 2 ) + 1; + self thread extended_death_loop( deathSeq, numDeathLoops ); + + // We must wait for the sequence to end, or else self will get removed before we're done. + self waittill( "extended_death_ended" ); +} + +end_extended_death( deathSeq ) +{ + assert( IsDefined( deathSeq[2] ) ); + + // Normally, the final death anim runs at the end of the loop, but the loop can be intterupted by shooting. + // Code sends a special notify "damage_afterdeath" if the AI is shot while in extended death + self waittill_any( "damage_afterdeath", "ending_extended_death" ); + + + self setFlaggedAnimKnobAllRestart( "deathdieanim", deathSeq[2], %body, 1, .1 ); + self animscripts\shared::DoNoteTracks( "deathdieanim" ); + + // All done with extended death sequence. + self notify( "extended_death_ended" ); +} + +extended_death_loop( deathSeq, numLoops ) +{ + // If someone shoots or damages self in any way, play final death immediately. + self endon( "damage" ); + + assert( IsDefined( deathSeq[1] ) ); + + animLength = GetAnimLength( deathSeq[1] ); + for ( i = 0; i < numLoops; i++ ) + { + self setFlaggedAnimKnobAllRestart( "deathloopanim", deathSeq[1], %body, 1, .1 ); + self animscripts\shared::DoNoteTracks( "deathloopanim" ); + } + + // If the loop hasn't already been cut short by the actor taking further damage, + // go into the final death anim. + self notify( "ending_extended_death" ); +} + +get_gib_extended_death_anims() +{ + hitfrom = undefined; + + if (( self.damageyaw > 90 ) || ( self.damageyaw <= -90 )) + { + hitfrom = "front"; + } + else + { + hitfrom = "back"; + } + + gib_ref = self.a.gib_ref; + + deathSeq = []; + if ( IsDefined( hitfrom ) && IsDefined( gib_ref ) && gib_ref != "head" ) + { + hitIndex = 0; + loopIndex = 1; + dieIndex = 2; + + if ( gib_ref == "guts" || gib_ref == "no_legs" ) // don't have directional anims + { + hitfrom = ""; + } + else + { + hitfrom = "_" + hitfrom; + } + + // TEMP + deathSeq[hitIndex] = anim.animsets.gibAnimSet["gib_shoulder_twist"]; + deathSeq[loopIndex] = anim.animsets.gibAnimSet["gib_shoulder_spin"]; + deathSeq[dieIndex] = anim.animsets.gibAnimSet["gib_shoulder_back"]; + //deathSeq[hitIndex] = animArray("gib_" + gib_ref + hitfrom + "_start"); + //deathSeq[loopIndex] = animArray("gib_" + gib_ref + hitfrom + "_loop"); + //deathSeq[dieIndex] = animArray("gib_" + gib_ref + hitfrom + "_end"); + } + + return deathSeq; +} + +getProneDeathAnim() +{ + if ( isdefined( self.a.proneAiming ) ) + return getDeathAnimByName("prone_aiming"); + else + return getDeathAnimByName("prone"); +} + +getBackDeathAnim() +{ + deathArray = getDeathAnimByName("back"); + return deathArray[ randomint( deathArray.size ) ]; +} + +firingDeathAllowed() +{ + if ( !isdefined( self.weapon ) || !usingRifleLikeWeapon() || !weaponIsAuto( self.weapon ) || self.dieQuietly ) + return false; + + if ( self.a.weaponPos[ "right" ] == "none" ) + return false; + + return true; +} + +tryAddDeathAnim( animName ) +{ + assert( !animHasNoteTrack( animName, "fire" ) && !animHasNoteTrack( animName, "fire_spray" ) ); + return animName; +} + +sanityCheckDeathArray( array ) +{ + for( i = 0; i < array.size; i = i+1 ) + { + assert( !animHasNoteTrack( array[i], "fire" ) && !animHasNoteTrack( array[i], "fire_spray" ) ); + } +} + +sanityCheckFireDeathArray( array ) +{ + for( i = 0; i < array.size; i = i+1 ) + { + assert( animHasNoteTrack( array[i], "fire" ) || animHasNoteTrack( array[i], "fire_spray" ) ); + } +} + +getDeathAnimByName( name ) +{ + assert( IsDefined( name ) ); + assert( IsDefined( anim.animsets.deathAnimSet[name] ) ); + + if( IsDefined( self.customDeathAnimSet ) && IsDefined( self.customDeathAnimSet[name] ) ) + { + return self.customDeathAnimSet[name]; + } + else + { + return anim.animsets.deathAnimSet[name]; + } +} + +tryAddFiringDeathAnim( animName ) +{ + assert( animHasNoteTrack( animName, "fire" ) || animHasNoteTrack( animName, "fire_spray" ) ); + return animName; +} + +playExplodeDeathAnim() +{ + if ( isdefined( self.juggernaut ) || isdefined( self.ares ) ) + return false; + + if ( self.damageLocation != "none" && self.damageMod != "MOD_GRENADE" ) + { + if( !is_railgun( self.damageWeapon ) ) + { + return false; + } + } + + deathArray = []; + + if ( self.a.movement != "run" ) + { + if ( ( self.damageyaw > 135 ) || ( self.damageyaw <= -135 ) ) // Front quadrant + { + deathArray = getDeathAnimByName("explode_stand_front"); + //get_gib_ref( "back" ); + } + else if ( ( self.damageyaw > 45 ) && ( self.damageyaw <= 135 ) ) // Right quadrant + { + deathArray = getDeathAnimByName("explode_stand_right"); + //get_gib_ref( "left" ); + } + else if ( ( self.damageyaw > - 45 ) && ( self.damageyaw <= 45 ) ) // Back quadrant + { + deathArray = getDeathAnimByName("explode_stand_back"); + //get_gib_ref( "forward" ); + } + else + { // Left quadrant + deathArray = getDeathAnimByName("explode_stand_left"); + //get_gib_ref( "right" ); + } + } + else + { + if ( ( self.damageyaw > 135 ) || ( self.damageyaw <= -135 ) ) // Front quadrant + { + deathArray = getDeathAnimByName("explode_run_front"); + //get_gib_ref( "back" ); + } + else if ( ( self.damageyaw > 45 ) && ( self.damageyaw <= 135 ) ) // Right quadrant + { + deathArray = getDeathAnimByName("explode_run_right"); + //get_gib_ref( "left" ); + } + else if ( ( self.damageyaw > - 45 ) && ( self.damageyaw <= 45 ) ) // Back quadrant + { + deathArray = getDeathAnimByName("explode_run_back"); + //get_gib_ref( "forward" ); + } + else + { // Left quadrant + deathArray = getDeathAnimByName("explode_run_left"); + //get_gib_ref( "right" ); + } + } + + //gib_chance = 50; + + deathAnim = deathArray[ randomint( deathArray.size ) ]; + + if ( getdvar( "scr_expDeathMayMoveCheck", "on" ) == "on" ) + { + ragdoll_point_array = GetNotetrackTimes( deathAnim, "start_ragdoll" ); + if ( ragdoll_point_array.size > 0 ) + { + ragdoll_point = ragdoll_point_array[ 0 ]; + } + else + { + ragdoll_point = 1; + } + + localDeltaVector = getMoveDelta( deathAnim, 0, ragdoll_point ); + endPoint = self localToWorldCoords( localDeltaVector ); + + // Draw trajectory. +// line( self.origin, endPoint, ( 1, 0, 0 ), 1, 0, 5000 ); + + if ( !self mayMoveToPoint( endPoint, false ) ) + { + //if( try_gib_extended_death( gib_chance ) ) + //{ + // return true; + //} + return false; + } + } + + // this should really be in the notetracks + self animMode( "nogravity" ); + + //if( try_gib_extended_death( gib_chance ) ) + //{ + // return true; + //} + + playDeathAnim( deathAnim ); + return true; +} + diff --git a/animscripts/door.gsc b/animscripts/door.gsc new file mode 100644 index 0000000..18af789 --- /dev/null +++ b/animscripts/door.gsc @@ -0,0 +1,302 @@ +#include animscripts\combat_utility; +#include animscripts\utility; +#include animscripts\shared; +#include common_scripts\utility; +#include maps\_utility; + +#using_animtree( "generic_human" ); + + +doorGrenadeInterval = 5000; +maxDistDoorToEnemySq = 600 * 600; + +doorEnterExitCheck() +{ + self endon( "killanimscript" ); + + if ( isdefined( self.disableDoorBehavior ) ) + return; + + while ( 1 ) + { + doorNode = self getDoorPathNode(); + if ( isdefined( doorNode ) ) + break; + + wait 0.2; + } + + goingInDoor = ( doorNode.type == "Door Interior" ) || self compareNodeDirToPathDir( doorNode ); + + if ( goingInDoor ) + self doorEnter( doorNode ); + else + self doorExit( doorNode ); + + // waittill doorNode changes + while ( 1 ) + { + newDoorNode = self getDoorPathNode(); + if ( !isdefined( newDoorNode ) || newDoorNode != doorNode ) + break; + + wait 0.2; + } + + self thread doorEnterExitCheck(); +} + +teamFlashBangImmune() +{ + self endon( "killanimscript" ); + + self.teamFlashbangImmunity = true; + wait 5; + self.teamFlashbangImmunity = undefined; +} + + +doDoorGrenadeThrow( node ) +{ + self thread teamFlashBangImmune(); + + if ( self.grenadeWeapon == "flash_grenade" ) + self notify( "flashbang_thrown" ); + + self OrientMode( "face current" ); + node.nextDoorGrenadeTime = gettime() + doorGrenadeInterval; + self.minInDoorTime = gettime() + 100000; // hack to not forget going indoor + + self notify( "move_interrupt" ); + self.update_move_anim_type = undefined; + + self clearanim( %combatrun, 0.2 ); + self.a.movement = "stop"; + self waittill( "done_grenade_throw" ); + self OrientMode( "face default" ); + + self.minInDoorTime = gettime() + 5000; + + self.grenadeWeapon = self.oldGrenadeWeapon; + self.oldGrenadeWeapon = undefined; + + self animscripts\run::endFaceEnemyAimTracking(); + self thread animscripts\move::pathChangeCheck(); + self thread animscripts\move::restartMoveLoop( true ); +} + + +// try throwing grenade before entering door +doorEnter_TryGrenade( node, grenadeType, ricochet, minDistSq, checkInterval ) +{ + safeCheckDone = false; + throwAttempts = 3; + throwAnim = undefined; + throwAnim = %CQB_stand_grenade_throw; + + doorForward = anglesToForward( node.angles ); + if ( node.type == "Door Interior" && !( self compareNodeDirToPathDir( node ) ) ) + doorForward = -1 * doorForward; + + doorPos = ( node.origin[ 0 ], node.origin[ 1 ], node.origin[ 2 ] + 64 ); + throwPos = doorPos; + + if ( ricochet ) + { + doorPlane = AnglesToRight( node.angles ); + dirToDoor = node.origin - self.origin; + + // upto 20 units to left or right of door depending on direction to door to make it likely to bounce off door edge + projLength = vectordot( doorPlane, dirToDoor ); + if ( projLength > 20 ) + projLength = 20; + else if ( projLength < - 20 ) + projLength = -20; + + throwPos = doorPos + projLength * doorPlane; + } + + while ( throwAttempts > 0 ) + { + if ( isdefined( self.grenade ) || !isdefined( self.enemy ) ) + return; + + if ( onSameSideOfDoor( node, doorForward ) ) + return; + + if ( !self seeRecently( self.enemy, 0.2 ) && self.a.pose == "stand" && distance2DAndHeightCheck( self.enemy.origin - node.origin, maxDistDoorToEnemySq, 128 * 128 ) ) + { + if ( isdefined( node.nextDoorGrenadeTime ) && node.nextDoorGrenadeTime > gettime() ) + return; + + if ( self canShootEnemy() ) + return; + + // too close to door + dirToDoor = node.origin - self.origin; + if ( lengthSquared( dirToDoor ) < minDistSq ) + return; + + // don't throw backwards + if ( vectordot( dirToDoor, doorForward ) < 0 ) + return; + + self.oldGrenadeWeapon = self.grenadeWeapon; + self.grenadeWeapon = grenadeType; + + self setActiveGrenadeTimer( self.enemy ); + + if ( !safeCheckDone ) + { + checkPos = doorPos + ( doorForward * 100 ); + if ( !( self isGrenadePosSafe( self.enemy, checkPos, 128 ) ) ) + return; + } + + safeCheckDone = true; // do this only once but do isGrenadePosSafe as late as possible + + if ( TryGrenadeThrow( self.enemy, throwPos, throwAnim, getGrenadeThrowOffset( throwAnim ), true, false, true ) ) + { + self doDoorGrenadeThrow( node ); + return; + } + } + + throwAttempts -- ; + wait checkInterval; + + // check if door node has past + newNode = self getDoorPathNode(); + if ( !isdefined( newNode ) || newNode != node ) + return; + } +} + + +inDoorCqbToggleCheck() +{ + self endon( "killanimscript" ); + + if ( isdefined( self.disableDoorBehavior ) ) + return; + + self.isInDoor = false; + + while ( 1 ) + { + if ( self isInDoor() && !self.doingAmbush ) + { + doorEnter_enable_cqbwalk(); + } + else if ( !isdefined( self.minInDoorTime ) || self.minInDoorTime < gettime() ) + { + self.minInDoorTime = undefined; + doorExit_disable_cqbwalk(); + } + + wait 0.2; + } +} + + +// substitute for enable_cqbwalk so LD can always disable cqb +doorEnter_enable_cqbwalk() +{ + if ( !isdefined( self.neverEnableCQB ) && !self.doingAmbush ) + { + self.isInDoor = true; + if ( !isdefined( self.cqbWalking ) || !self.cqbWalking ) + enable_cqbwalk( true ); + } +} + + +// substitute for disable_cqbwalk so LD can force CQB even after exiting to outdoor +doorExit_disable_cqbwalk() +{ + if ( !isdefined( self.cqbEnabled ) ) + { + self.isInDoor = false; + if ( isdefined( self.cqbWalking ) && self.cqbWalking ) + disable_cqbwalk(); + } +} + + +maxFragDistSq = 750 * 750; +minFragDistSq = 550 * 550; +maxFlashDistSq = 192 * 192; +minFlashDistSq = 64 * 64; +maxFragHeightDiffSq = 160 * 160; +maxFlashHeightDiffSq = 80 * 80; + +distance2DAndHeightCheck( vec, dist2DSq, heightSq ) +{ + return( ( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] ) < dist2DSq ) && ( ( vec[ 2 ] * vec[ 2 ] ) < heightSq ); +} + + +onSameSideOfDoor( node, doorForward ) +{ + assert( isdefined( self.enemy ) ); + + selfToDoor = node.origin - self.origin; + enemyToDoor = node.origin - self.enemy.origin; + + return( vectordot( selfToDoor, doorForward ) * vectordot( enemyToDoor, doorForward ) > 0 ); +} + + +doorEnter( node ) +{ + // try frag + while ( 1 ) + { + if ( isdefined( self.doorFragChance ) && ( self.doorFragChance == 0 || self.doorFragChance < randomfloat( 1 ) ) ) + break; + + if ( distance2DAndHeightCheck( self.origin - node.origin, maxFragDistSq, maxFragHeightDiffSq ) ) + { + self doorEnter_TryGrenade( node, "fraggrenade", false, minFragDistSq, 0.3 ); + + node = self getDoorPathNode(); + if ( !isdefined( node ) ) + return; + + break; + } + wait 0.1; + } + + // try flashbang + while ( 1 ) + { + if ( distance2DAndHeightCheck( self.origin - node.origin, maxFlashDistSq, maxFlashHeightDiffSq ) ) + { + self doorEnter_enable_cqbwalk(); + self.minInDoorTime = gettime() + 6000; + + if ( isdefined( self.doorFlashChance ) && ( self.doorFlashChance == 0 || self.doorFlashChance < randomfloat( 1 ) ) ) + return; + + self doorEnter_TryGrenade( node, "flash_grenade", true, minFlashDistSq, 0.2 ); + return; + } + + wait 0.1; + } +} + +doorExit( node ) +{ + while ( 1 ) + { + if ( !self.isInDoor || distanceSquared( self.origin, node.origin ) < 32 * 32 ) + { + //self doorExit_disable_cqbwalk(); + return; + } + + wait 0.1; + } +} \ No newline at end of file diff --git a/animscripts/ec_lgv/stand.gsc b/animscripts/ec_lgv/stand.gsc new file mode 100644 index 0000000..ae791d5 --- /dev/null +++ b/animscripts/ec_lgv/stand.gsc @@ -0,0 +1,75 @@ +#include maps\_utility; +#include common_scripts\utility; + +#using_animtree( "generic_human" ); + +// self = the guy using the turret +main() +{ + turret = self getTurret(); + + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + // .primaryTurretAnim is used by code so don't change this variable name + self.primaryTurretAnim = %LGVGunner_aim; + + self.additiveTurretRotateLeft = %nx_tp_chinese_lgv_gunner_aim_2_add; + self.additiveTurretRotateRight = %nx_tp_chinese_lgv_gunner_aim_4_add; + self.additiveRotateRoot = %additive_LGVGunner_aim_leftright; + + self.additiveTurretIdle = %nx_tp_chinese_lgv_gunner_idle; + self.additiveTurretDriveIdle = %nx_tp_chinese_lgv_gunner_driveidle; + self.additiveTurretFire = %nx_tp_chinese_lgv_gunner_fire; + self.additiveUsegunRoot = %additive_LGVGunner_usegun; + + self.turretDeathAnimRoot = %LGVGunner_death; + self.turretDeathAnim = %nx_tp_chinese_lgv_gunner_death; + self.turretPainAnims[ 0 ] = %nx_tp_chinese_lgv_gunner_pain; + + self.turretReloadAnim = %nx_tp_chinese_lgv_gunner_rechamber; + + self.turretSpecialAnimsRoot = %LGVGunner; + arr = []; + arr[ "nx_tp_chinese_lgv_gunner_rechamber" ] = %nx_tp_chinese_lgv_gunner_rechamber; + self.turretSpecialAnims = arr; + + turret setup_turret_anims(); + + self thread animscripts\hummer_turret\minigun_code::main( turret ); + + // Setting verious parameters for LGV turret after minigun_code::main, which sets it to hummer values. + + shots_per_second = turret GetTurretShotsPerSecondAI(); + if ( shots_per_second > 0.0 ) + { + turret.fireInterval = 1.0 / shots_per_second; + } + turret.secsOfFiringBeforeReload = 15.0; + turret.reloadDuration = 2.17; + turret.centerTurretForReload = true; + turret.extraFireTime_min = 0; + turret.extraFireTime_max = 1; + turret.wait_duration_after_aiming_before_firing = 0.2; + + turret thread reload_fx(); +} + +#using_animtree( "vehicles" ); +setup_turret_anims() +{ + self UseAnimTree( #animtree ); + self.passenger2turret_anime = %nx_vh_chinese_lgv_gunner_mount_turret; + self.turret2passenger_anime = %nx_vh_chinese_lgv_gunner_getout_turret; +} + +reload_fx() +{ + for (;;) + { + self waittill( "starting_reload" ); + + playFXOnTag( level._effect[ "nx_chinese_lgv_turret_steam" ], self, "tag_origin" ); + playFXOnTag( level._effect[ "nx_chinese_lgv_turret_steam_muzzle" ], self, "tag_flash" ); + } +} diff --git a/animscripts/face.gsc b/animscripts/face.gsc new file mode 100644 index 0000000..e86e791 --- /dev/null +++ b/animscripts/face.gsc @@ -0,0 +1,522 @@ +// face.gsc +// Supposed to handle all facial and dialogue animations from regular scripts. +//#using_animtree ("generic_human"); - This file doesn't call animations directly. + + + +InitCharacterFace() +{ + if ( !anim.useFacialAnims ) + return; + + // Does any per-character initialization which is required by this facial animation script. + // InitLevelFace must be called before this function. + if ( !isDefined( self.a.currentDialogImportance ) ) + { + self.a.currentDialogImportance = 0; // Indicates that we are not currently saying anything. + self.a.idleFace = anim.alertface; + self.faceWaiting = []; + self.faceLastNotifyNum = 0; + } +} + + +// Makes a character say the specified line in his voice, if he's not already saying something more +// important. +SayGenericDialogue( typeString ) +{ + // First pick a random voice number for our character. We have to do this every time because it's + // possible for the character to be changed after the level loads (generally right before it starts). + // Use (entity number) modulus (number of voices) to get a consistent result. + + if ( ( self.voice == "multilingual" ) || ( self.voice == "italian" ) || ( self.voice == "german" ) || ( self.voice == "spanish" ) ) + voiceString = "russian"; + else + voiceString = self.voice; + + ASSERT( isDefined( voiceString ) ); + + voicenum = undefined; + numVoices = undefined; + + switch( voiceString ) + { + case "american": + numVoices = anim.numAmericanVoices; + break; + + case "seal": + voiceString = "navyseal"; // soundaliases use the string "navyseal" (not "seal") + numVoices = anim.numNavySealVoices; + break; + + case "taskforce": + numVoices = anim.numTaskForceVoices; + break; + + case "secretservice": + numVoices = anim.numSecretServiceVoices; + break; + + case "british": + numVoices = anim.numBritishVoices; + break; + + case "russian": + numVoices = anim.numRussianVoices; + break; + + case "arab": + numVoices = anim.numArabVoices; + break; + + case "portuguese": + numVoices = anim.numPortugueseVoices; + break; + + case "shadowcompany": + numVoices = anim.numShadowCompanyVoices; + break; + + case "jswc": + numVoices = anim.numJSWCVoices; + break; + + case "usop": + numVoices = anim.numUSOPVoices; + break; + + case "indonesian": + voiceString = "indonesian"; + numVoices = anim.numIndonesianVoices; + + case "mexican": + //voiceString = "mexican"; //temporarily using arab vo in sound_generic.csv + numVoices = anim.numIndonesianVoices; + break; + } + + ASSERT( IsDefined( numVoices ) ); + + voicenum = 1 + ( self GetEntityNumber() % numVoices ); + + ASSERT( IsDefined( voicenum ) ); + + voiceString = voiceString + "_" + voicenum; + + faceAnim = undefined; + + switch( typeString ) + { + + case "meleecharge": + case "meleeattack": +// faceAnim = animscripts\face::ChooseAnimFromSet(anim.meleeFace); + importance = 0.5; + break; + case "flashbang": +// faceAnim = animscripts\face::ChooseAnimFromSet(anim.painFace); + importance = 0.7; + break; + case "pain": +// faceAnim = animscripts\face::ChooseAnimFromSet(anim.painFace); + importance = 0.9; + break; + case "death": +// faceAnim = animscripts\face::ChooseAnimFromSet(anim.deathFace); + importance = 1.0; + break; + default: + println( "Unexpected generic dialog string: " + typeString ); + importance = 0.3; + break; + } + // Now assemble the sound alias and try to play it. + soundAlias = "generic_" + typeString + "_" + voiceString; + // Note that faceAnim is allowed to be undefined. + self thread PlayFaceThread( faceAnim, soundAlias, importance ); +} + +SetIdleFaceDelayed( facialAnimationArray ) +{ + self animscripts\battleChatter::playBattleChatter(); + + self.a.idleFace = facialAnimationArray; +} + +// Sets the facial expression to return to when not saying dialogue. +// The array is animation1, weight1, animation2, weight2, etc. The animations will play in turn - each time +// one finishes a new one will be chosen randomly based on weight. +SetIdleFace( facialAnimationArray ) +{ + if ( !anim.useFacialAnims ) + return; + + self animscripts\battleChatter::playBattleChatter(); + + self.a.idleFace = facialAnimationArray; + self PlayIdleFace(); +} + +// Makes the character play the specified sound and animation. The anim and the sound are optional - you +// can just defined one if you don't have both. +// Generally, importance should be in the range of 0.6-0.8 for scripted dialogue. +// Importance is a float, from 0 to 1. +// 0.0 - Idle expressions +// 0.1-0.5 - most generic dialogue +// 0.6-0.8 - most scripted dialogue +// 0.9 - pain +// 1.0 - death +// Importance can also be one of these strings: "any", "pain" or "death", which specfies what sounds can +// interrupt this one. +SaySpecificDialogue( facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait ) +{ + ///("SaySpecificDialog, facial: ",facialanim,", sound: ",soundAlias,", importance: "+importance+", notify: ",notifyString, ", WaitOrNot: ", waitOrNot, ", timeToWait: ", timeToWait);#/ + self thread PlayFaceThread( facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait ); +} + +// Takes an array with a set of "anim" entries and a corresponding set of "weight" entries. +ChooseAnimFromSet( animSet ) +{ + return; // Facial animations are now part of full body aniamtions +/* + if (!anim.useFacialAnims) + return; + // First, normalize the weights. + totalWeight = 0; + numAnims = animSet["anim"].size; + for ( i=0 ; i= rand) + { + chosenAnim = i; + break; + } + } + assertEX(isDefined(chosenAnim), "Logic error in ChooseAnimFromSet. Rand is " + rand + "."); + return animSet["anim"][chosenAnim]; +*/ +} + + + +//----------------------------------------------------- +// Housekeeping functions - these are for internal use +//----------------------------------------------------- + +// PlayIdleFace doesn't force an idle animation to play - it will interrupt a current idle animation, but it +// won't play over a more important animation, like dialogue. +PlayIdleFace() +{ + return; // Idle facial animations are now in the full - body animations. +} + +// PlayFaceThread is the workhorse of the system - it checks the importance, and if it's high enough, it +// plays the animation and/or sound specified. +// The waitOrNot parameter specifies what to do if another animation/sound is already playing. +// Options: "wait" or undefined. TimeToWait is an optional timeout time for waiting. +// Waiting faces are queued. +PlayFaceThread( facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait ) +{ + self.a.facialAnimDone = true; + self.a.facialSoundDone = true; + + if ( isdefined( notifyString ) ) + { + if ( isDefined( soundAlias ) ) + { + self playsound( soundAlias, "animscript facesound" + notifyString, true ); + // so placefacethread doesnt block + self thread WaitForFaceSound( notifyString ); + } + } + else + self playsound( soundAlias ); + + if ( !anim.useFacialAnims ) + return; + + InitCharacterFace(); + + if ( !isDefined( facialanim ) && !isDefined( soundAlias ) ) + { + // This is not actually an error condition but it might mess up the calling script, so better to catch it now. + assertEX( 0, "Either facialanim or soundAlias should be defined when calling PlayFaceThread or SaySpecificDialogue" ); + if ( isDefined( notifyString ) ) + { + wait( 0 ); // This allows the calling script to get to a point where it's waiting for the notify + self.faceResult = "failed"; + self notify( notifyString ); + } + return; + } + + self endon( "death" ); + + if ( isString( importance ) ) + { + switch( importance ) + { + case "any": + importance = 0.1; + break; + + case "pain": + importance = 0.9; + break; + + case "death": + importance = 1.0; + break; + } + } + + if ( ( importance <= self.a.currentDialogImportance ) && ( isDefined( waitOrNot ) && ( waitOrNot == "wait" ) ) ) + { + //("Face: Waiting to play sound: ",soundAlias,", anim: ",facialanim,", ", notifyString,(", importance "+importance+", old one "+self.a.currentDialogImportance));#/ + // Put this face at the end of the queue + thisEntryNum = self.faceWaiting.size; + thisNotifyNum = self.faceLastNotifyNum + 1; + self.faceWaiting[ thisEntryNum ][ "facialanim" ] = facialanim; + self.faceWaiting[ thisEntryNum ][ "soundAlias" ] = soundAlias; + self.faceWaiting[ thisEntryNum ][ "importance" ] = importance; + self.faceWaiting[ thisEntryNum ][ "notifyString" ] = notifyString; + self.faceWaiting[ thisEntryNum ][ "waitOrNot" ] = waitOrNot; + self.faceWaiting[ thisEntryNum ][ "timeToWait" ] = timeToWait; + self.faceWaiting[ thisEntryNum ][ "notifyNum" ] = thisNotifyNum; // Unique identifier. + + // What we do now is, wait for both the notify and the time. If the time expires first, we give + // up and remove this entry from the queue. If the notify happens first, we stop waiting for the + // time and we play the face. + self thread PlayFace_WaitForNotify( ( "animscript face stop waiting " + self.faceWaiting[ thisEntryNum ][ "notifyNum" ] ), "Face done waiting", "Face done waiting" ); + if ( isDefined( timeToWait ) ) + self thread PlayFace_WaitForTime( timeToWait, "Face done waiting", "Face done waiting" ); + self waittill( "Face done waiting" ); + + // First, find the entry, since it may have been moved. + thisEntryNum = undefined; + for ( i = 0 ; i < self.faceWaiting.size ; i++ ) + { + if ( self.faceWaiting[ i ][ "notifyNum" ] == thisNotifyNum ) + { + thisEntryNum = i; + break; + } + } + assertEX( isDefined( thisEntryNum ) ); + + if ( self.a.faceWaitForResult == "notify" ) + { + // Play the face. + PlayFaceThread( self.faceWaiting[ thisEntryNum ][ "facialanim" ], + self.faceWaiting[ thisEntryNum ][ "soundAlias" ], + self.faceWaiting[ thisEntryNum ][ "importance" ], + self.faceWaiting[ thisEntryNum ][ "notifyString" ] + ); + } + else // ie We timed out. + { + if ( isDefined( notifyString ) ) + { + self.faceResult = "failed"; + self notify( notifyString ); + } + } + + // Remove this entry from the queue. If any entries have been added after this one, move them + // forward. + for ( i = thisEntryNum + 1 ; i < self.faceWaiting.size ; i++ ) + { + self.faceWaiting[ i - 1 ][ "facialanim" ] = self.faceWaiting[ i ][ "facialanim" ]; + self.faceWaiting[ i - 1 ][ "soundAlias" ] = self.faceWaiting[ i ][ "soundAlias" ]; + self.faceWaiting[ i - 1 ][ "importance" ] = self.faceWaiting[ i ][ "importance" ]; + self.faceWaiting[ i - 1 ][ "notifyString" ] = self.faceWaiting[ i ][ "notifyString" ]; + self.faceWaiting[ i - 1 ][ "waitOrNot" ] = self.faceWaiting[ i ][ "waitOrNot" ]; + self.faceWaiting[ i - 1 ][ "timeToWait" ] = self.faceWaiting[ i ][ "timeToWait" ]; + self.faceWaiting[ i - 1 ][ "notifyNum" ] = self.faceWaiting[ i ][ "notifyNum" ]; + } + self.faceWaiting[ self.faceWaiting.size - 1 ] = undefined; + + } + else if ( importance >= self.a.currentDialogImportance ) + { + // End any threads that are waiting on current facial animations or sounds. + self notify( "end current face" ); + self endon( "end current face" ); + //("Face: Playing facial sound/animation: ", facialanim, ", ",soundAlias,", ",notifyString, ", ",importance);#/ + //if (self.a.currentDialogImportance > 0) + //{ + //("Face: Interrupted facial sound/animation: ",self.a.currentDialogSound,", ",self.a.currentDialogNotifyString, ", ",self.a.currentDialogImportance);#/ + //} + if ( isDefined( notifyString ) ) + { + if ( isDefined( self.a.currentDialogNotifyString ) ) + { + self.faceResult = "interrupted"; + self notify( self.a.currentDialogNotifyString ); + } + } + // Remember what we're doing, so we can decide what to do if another face tries to interrupt this one. + self.a.currentDialogImportance = importance; + self.a.currentDialogSound = soundAlias; // ( This one is only used for debugging. ) + self.a.currentDialogNotifyString = notifyString; + + // Set finished to true so that if we don't play one of these, we don't have to wait for it to finish. + self.a.facialAnimDone = true; + self.a.facialSoundDone = true; + // Play the anim and sound, if they are defined. + if ( isDefined( facialanim ) ) + { +// self setanim(%facial, 0.01, .1, 1); // This doesn't work for non-AI + self setflaggedanimknobrestart( "animscript faceanim", facialanim, 1, .1, 1 ); + self.a.facialAnimDone = false; + self thread WaitForFacialAnim(); + //("Face: Waiting for facial animation ", facialanim);#/ + } + //else TODO play a generic, looping facial animation. + if ( isDefined( soundAlias ) ) + { +// TEMP These lines break sound for most lines because of a bug in facial animation (code bug?). When that +// bug is fixed, put these lines back in. +// if ( isDefined(facialanim) && animhasnotetrack(facialanim, "dialogue")) +// { +// self waittillmatch ("animscript faceanim", "dialogue"); +// } + self playsound( soundAlias, "animscript facesound", true ); + self.a.facialSoundDone = false; + self thread WaitForFaceSound(); + //("Face: Waiting for sound ",soundAlias);#/ + } + // Now wait until both animation and sound are finished + while ( ( !self.a.facialAnimDone ) || ( !self.a.facialSoundDone ) ) + { + self waittill( "animscript facedone" ); + } + // Set importance to 0 so that other facial anims (like the idle) can play. + //("Face: Finished facial sound: ",soundAlias,", animation: ",facialanim," notify: ",notifyString,", importance ",importance);#/ + self.a.currentDialogImportance = 0; + self.a.currentDialogSound = undefined; + self.a.currentDialogNotifyString = undefined; + if ( isDefined( notifyString ) ) + { + self.faceResult = "finished"; + self notify( notifyString ); + } + if ( isDefined( self.faceWaiting ) && ( self.faceWaiting.size > 0 ) ) + { + // Find out which face we want to play next. Look through the queue for the highest priority + // face. If we find more than one with the same importance, choose the one that was added first. + maxImportance = 0; + nextFaceNum = 1; + //("Choosing next face. List is:");#/ + for ( i = 0 ; i < self.faceWaiting.size ; i++ ) + { + /* + println]](" ",i," ", (self.faceWaiting[i]["facialanim"]),", ", + (self.faceWaiting[i]["soundAlias"]),", ", + (self.faceWaiting[i]["importance"]),", ", + (self.faceWaiting[i]["notifyString"]) + );#/ + */ + if ( self.faceWaiting[ i ][ "importance" ] > maxImportance ) + { + maxImportance = self.faceWaiting[ i ][ "importance" ]; + nextFaceNum = i; + } + } + //("Chose ", nextFaceNum);#/ + // Notify the entry in the queue, to play. + self notify( "animscript face stop waiting " + self.faceWaiting[ nextFaceNum ][ "notifyNum" ] ); + } + else + { + // We're done. Set an idle face going before we exit. + // TODO Make the idle face play whenever the animation finishes, for cases when it finishes before the sound. + if ( IsAI( self ) ) + { + self PlayIdleFace(); + } + } + } + else + { + if ( isDefined( notifyString ) ) + { + self.faceResult = "failed"; + self notify( notifyString ); + } + //("Face: Didn't play facial sound: ",soundAlias,", animation: ",facialanim," notify: ",notifyString,", importance ",importance,", old one ",self.a.currentDialogImportance);#/ + } +} + +WaitForFacialAnim() // Used solely by PlayFaceThread +{ + self endon( "death" ); + self endon( "end current face" ); + self animscripts\shared::DoNoteTracks( "animscript faceanim" ); + self.a.facialAnimDone = true; + self notify( "animscript facedone" ); +} + +WaitForFaceSound( msg ) // Used solely by PlayFaceThread +{ + self endon( "death" ); + self waittill( "animscript facesound" + msg ); + self notify( msg ); +} + +PlayFace_WaitForNotify( waitForString, notifyString, killmeString ) +{ + self endon( "death" ); + self endon( killmeString ); + self waittill( waitForString ); + self.a.faceWaitForResult = "notify"; + self notify( notifyString ); +} + +PlayFace_WaitForTime( time, notifyString, killmeString ) +{ + self endon( "death" ); + self endon( killmeString ); + wait( time ); + self.a.faceWaitForResult = "time"; + self notify( notifyString ); +} + + +#using_animtree( "generic_human" );// This section of the file calls animations directly since it's only used on AI. +InitLevelFace() +{ + // Does per-level initialization of facial stuff. + + // These numbers indicate how many different sound aliases there are in dialog_generic.csv for each + // nationality. This script will assign each guy a random voice number from 1 to the number indicated + // for his voice nationality below. If we add a new voice type to sound_generic.csv, we need to update + // these numbers accordingly. + anim.numAmericanVoices = 8; + anim.numNavySealVoices = 8; + anim.numTaskForceVoices = 8; + anim.numSecretServiceVoices = 8; + anim.numBritishVoices = 8; + anim.numRussianVoices = 8; + anim.numArabVoices = 8; + anim.numPortugueseVoices = 8; + anim.numShadowCompanyVoices = 8; + anim.numJSWCVoices = 4; + anim.numUSOPVoices = 4; + anim.numIndonesianVoices = 8; +} diff --git a/animscripts/first_frame.gsc b/animscripts/first_frame.gsc new file mode 100644 index 0000000..d363458 --- /dev/null +++ b/animscripts/first_frame.gsc @@ -0,0 +1,17 @@ +main() +{ + self endon( "death" ); + self endon( "stop_first_frame" ); + self notify( "killanimscript" ); + self.pushable = 0; + + self clearAnim( self.root_anim, 0.3 ); +// self setAnim( %body, 1, 0 ); // The %body node should always have weight 1. + + self OrientMode( "face angle", self.angles[ 1 ] ); + + self setanim( level._scr_anim[ self._animname ][ self._first_frame_anim ], 1, 0, 0 ); + self._first_frame_anim = undefined; + + self waittill( "killanimscript" ); +} \ No newline at end of file diff --git a/animscripts/flashed.gsc b/animscripts/flashed.gsc new file mode 100644 index 0000000..835e0c5 --- /dev/null +++ b/animscripts/flashed.gsc @@ -0,0 +1,94 @@ +#include animscripts\Utility; +#include animscripts\SetPoseMovement; +#include animscripts\Combat_utility; +#include maps\_anim; +#include maps\_utility; +#using_animtree( "generic_human" ); + + +initFlashed() +{ + anim.flashAnimArray[ 0 ] = %exposed_flashbang_v1; + anim.flashAnimArray[ 1 ] = %exposed_flashbang_v2; + anim.flashAnimArray[ 2 ] = %exposed_flashbang_v3; + anim.flashAnimArray[ 3 ] = %exposed_flashbang_v4; + anim.flashAnimArray[ 4 ] = %exposed_flashbang_v5; + + randomizeFlashAnimArray(); + + anim.flashAnimIndex = 0; +} + +randomizeFlashAnimArray() +{ + for ( i = 0; i < anim.flashAnimArray.size; i++ ) + { + switchwith = randomint( anim.flashAnimArray.size ); + temp = anim.flashAnimArray[ i ]; + anim.flashAnimArray[ i ] = anim.flashAnimArray[ switchwith ]; + anim.flashAnimArray[ switchwith ] = temp; + } +} + +getNextFlashAnim() +{ + anim.flashAnimIndex++; + if ( anim.flashAnimIndex >= anim.flashAnimArray.size ) + { + anim.flashAnimIndex = 0; + randomizeFlashAnimArray(); + } + return anim.flashAnimArray[ anim.flashAnimIndex ]; +} + +flashBangAnim( animation ) +{ + self endon( "killanimscript" ); + self setflaggedanimknoball( "flashed_anim", animation, %body, 0.2, randomFloatRange( 0.9, 1.1 ) ); + self animscripts\shared::DoNoteTracks( "flashed_anim" ); +} + +main() +{ + self endon( "death" ); + self endon( "killanimscript" ); + + animscripts\utility::initialize( "flashed" ); + + flashDuration = self flashBangGetTimeLeftSec(); + if ( flashDuration <= 0 ) + return; + + self animscripts\face::SayGenericDialogue( "flashbang" ); + + if ( isdefined( self.specialFlashedFunc ) ) + { + self [[ self.specialFlashedFunc ]](); + return; + } + + animation = getNextFlashAnim(); + flashBangedLoop( animation, flashDuration ); +} + +flashBangedLoop( animation, duration ) +{ + self endon( "death" ); + self endon( "killanimscript" ); + + assert( isDefined( animation ) ); + assert( isDefined( duration ) ); + assert( duration > 0 ); + + if ( self.a.pose == "prone" ) + self ExitProneWrapper( 1 ); + + self.a.pose = "stand"; + self.allowdeath = true; + + self thread flashBangAnim( animation ); + wait ( duration ); + + self notify( "stop_flashbang_effect" ); + self.flashed = false; +} \ No newline at end of file diff --git a/animscripts/grenade_cower.gsc b/animscripts/grenade_cower.gsc new file mode 100644 index 0000000..93b9f78 --- /dev/null +++ b/animscripts/grenade_cower.gsc @@ -0,0 +1,91 @@ +#include animscripts\Utility; +#using_animtree( "generic_human" ); + +main() +{ + self endon( "killanimscript" ); + animscripts\utility::initialize( "grenadecower" ); + + if ( isdefined( self.grenadeCowerFunction ) ) + { + self [[ self.grenadeCowerFunction ]](); + return; + } + + if ( self.a.pose == "prone" ) + { + // TODO: use an i-just-dived loop? + + animscripts\stop::main(); + return; // Should never get to here + } + + self AnimMode( "zonly_physics" ); + self OrientMode( "face angle", self.angles[ 1 ] ); + + grenadeAngle = 0; + + if ( isdefined( self.grenade ) )// failsafe + grenadeAngle = AngleClamp180( vectorToAngles( self.grenade.origin - self.origin )[ 1 ] - self.angles[ 1 ] ); + else + grenadeAngle = self.angles[ 1 ]; + + if ( self.a.pose == "stand" ) + { + if ( isdefined( self.grenade ) && TryDive( grenadeAngle ) ) + return; + + self setFlaggedAnimKnobAllRestart( "cowerstart", %exposed_squat_down_grenade_F, %body, 1, 0.2 ); + self animscripts\shared::DoNoteTracks( "cowerstart" ); + } + self.a.pose = "crouch"; + self.a.movement = "stop"; + + self setFlaggedAnimKnobAllRestart( "cower", %exposed_squat_idle_grenade_F, %body, 1, 0.2 ); + self animscripts\shared::DoNoteTracks( "cower" ); + + self waittill( "never" ); +} + +end_script() +{ + self.safeToChangeScript = true; +} + +TryDive( grenadeAngle ) +{ + if ( randomint( 2 ) == 0 ) + return false; + + if ( self.stairsState != "none" ) + return false; + + diveAnim = undefined; + if ( abs( grenadeAngle ) > 90 ) + { + // grenade behind us + diveAnim = %exposed_dive_grenade_B; + } + else + { + // grenade in front of us + diveAnim = %exposed_dive_grenade_F; + } + + // when the dives are split into a dive, idle, and get up, + // maybe we can get a better point to do the moveto check with + moveBy = getMoveDelta( diveAnim, 0, 0.5 ); + diveToPos = self localToWorldCoords( moveBy ); + + if ( !self MayMoveToPoint( diveToPos ) ) + return false; + + self.safeToChangeScript = false; + + self setFlaggedAnimKnobAllRestart( "cowerstart", diveAnim, %body, 1, 0.2 ); + self animscripts\shared::DoNoteTracks( "cowerstart" ); + + self.safeToChangeScript = true; + + return true; +} diff --git a/animscripts/grenade_return_throw.gsc b/animscripts/grenade_return_throw.gsc new file mode 100644 index 0000000..1aadc35 --- /dev/null +++ b/animscripts/grenade_return_throw.gsc @@ -0,0 +1,148 @@ +#include animscripts\Utility; +#using_animtree( "generic_human" ); + +// Grenade_return_throw +// Picks up a grenade from 32 units in front of the character, and throws it. + +main() +{ + /# + if ( getdvar( "scr_forcegrenadecower" ) == "on" && isdefined( self.grenade ) ) + { + self OrientMode( "face angle", randomfloat( 360 ) ); + self animmode( "gravity" ); + wait .2; + animscripts\grenade_cower::main(); + return; + } + #/ + + self orientMode( "face default" ); + self endon( "killanimscript" ); + + animscripts\utility::initialize( "grenade_return_throw" ); + + self animMode( "zonly_physics" );// Unlatch the feet + + throwAnim = undefined; + + throwDist = 1000; + if ( isdefined( self.enemy ) ) + throwDist = distance( self.origin, self.enemy.origin ); + + // unused: grenade_return_running_kick_forward_1; kicks don't read well to player + // unused: grenade_return_running_kick_forward_2 + // unused: %grenade_return_standing_throw_forward_2; similar to other ones + + animArray = []; + if ( throwDist < 600 && isLowThrowSafe() ) + { + if ( throwDist < 300 ) + { + // really short range + animArray[ 0 ] = %grenade_return_running_throw_forward; + animArray[ 1 ] = %grenade_return_standing_throw_forward_1; + } + else + { + // long range + animArray[ 0 ] = %grenade_return_running_throw_forward; + animArray[ 1 ] = %grenade_return_standing_throw_overhand_forward; + } + } + + if ( animArray.size == 0 ) + { + // really long range or can't do low throw + animArray[ 0 ] = %grenade_return_standing_throw_overhand_forward; + } + + assert( animArray.size ); + throwAnim = animArray[ randomint( animArray.size ) ]; + + /# + if ( getdvar( "scr_grenadereturnanim" ) != "" ) + { + val = getdvar( "scr_grenadereturnanim" ); + //if ( val == "kick1") + // throwAnim = %grenade_return_running_kick_forward_1; + //else if ( val == "kick2") + // throwAnim = %grenade_return_running_kick_forward_2; + //else + if ( val == "throw1" ) + throwAnim = %grenade_return_running_throw_forward; + else if ( val == "throw2" ) + throwAnim = %grenade_return_standing_throw_forward_1; + //else if ( val == "throw3") + // throwAnim = %grenade_return_standing_throw_forward_2; + else if ( val == "throw4" ) + throwAnim = %grenade_return_standing_throw_overhand_forward; + } + #/ + + assert( isdefined( throwAnim ) ); + self setFlaggedAnimKnoballRestart( "throwanim", throwAnim, %body, 1, .3 ); + + hasPickup = ( animHasNotetrack( throwAnim, "grenade_left" ) || animHasNotetrack( throwAnim, "grenade_right" ) ); + if ( hasPickup ) + { + self animscripts\shared::placeWeaponOn( self.weapon, "left" ); + self thread putWeaponBackInRightHand(); + + self thread notifyGrenadePickup( "throwanim", "grenade_left" ); + self thread notifyGrenadePickup( "throwanim", "grenade_right" ); + self waittill( "grenade_pickup" ); + self pickUpGrenade(); + + self animscripts\battleChatter_ai::evaluateAttackEvent( "grenade" ); + + self waittillmatch( "throwanim", "grenade_throw" ); + } + else + { + // kick animations don't have pickup. + self waittillmatch( "throwanim", "grenade_throw" ); + self pickUpGrenade(); + + self animscripts\battleChatter_ai::evaluateAttackEvent( "grenade" ); + } + + if ( isDefined( self.grenade ) ) // it may have exploded and we may have magic bullet shield + self throwGrenade(); + + // TODO: replace with a notetrack? + wait 1; + + if ( hasPickup ) + { + self notify( "put_weapon_back_in_right_hand" ); + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); + } +} + +isLowThrowSafe() +{ + start = ( self.origin[ 0 ], self.origin[ 1 ], self.origin[ 2 ] + 20 ); + end = start + anglesToForward( self.angles ) * 50; + + return sightTracePassed( start, end, false, undefined ); +} + +putWeaponBackInRightHand() +{ + self endon( "death" ); + self endon( "put_weapon_back_in_right_hand" ); + + self waittill( "killanimscript" ); + + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); +} + +notifyGrenadePickup( animFlag, notetrack ) +{ + self endon( "killanimscript" ); + self endon( "grenade_pickup" ); + + self waittillmatch( animFlag, notetrack ); + self notify( "grenade_pickup" ); +} diff --git a/animscripts/hummer_turret/common.gsc b/animscripts/hummer_turret/common.gsc new file mode 100644 index 0000000..2954b62 --- /dev/null +++ b/animscripts/hummer_turret/common.gsc @@ -0,0 +1,981 @@ +#include maps\_utility; +#include common_scripts\utility; +#include maps\_anim; + +#using_animtree( "generic_human" ); + +humvee_turret_init( turret, turretType ) +{ + self endon( "killanimscript" );// code + + Assert( IsDefined( turret ) ); + + animscripts\utility::initialize( turretType ); + + self.no_ai = true; + self.noDrop = true; + self.a.movement = "stop"; + self.a.special = turretType; + self.a.usingTurret = turret; + self.ignoreme = true; + self.isCustomAnimating = false; + self SetTurretAnim( self.primaryTurretAnim ); + self SetAnimKnobRestart( self.primaryTurretAnim, 1, 0.2, 1 ); + + if ( IsDefined( self.weapon ) ) + { + self animscripts\shared::placeWeaponOn( self.weapon, "none" ); + } + + self.onRotatingVehicleTurret = true; + self.getOffVehicleFunc = ::turret_cleanup_on_unload; + + // end some _vehicle and _mgturret threads that we don't want + self notify( "guy_man_turret_stop" ); + turret notify( "stop_burst_fire_unmanned" ); + + // setup the turret + turret.turretState = "start"; + turret.aiOwner = self; + turret.fireTime = 0; + turret SetMode( "sentry" ); + turret SetSentryOwner( self ); + turret SetDefaultDropPitch( 0 ); + turret SetTurretCanAIDetach( false ); + + if ( IsDefined( turret.hummer_turret_intial_mode ) ) + { + turret SetMode( turret.hummer_turret_intial_mode ); + } + + self gunner_pain_init(); + level thread handle_gunner_pain( self, turret ); + level thread handle_gunner_death( self, turret ); + + // start tracking the turret rotation + turret thread turret_track_rotatedirection( self ); + + // start turret fire director + turret.doFiring = false; + self thread fireDirector( turret ); + + wait( 0.05 ); + if ( IsAlive( self ) ) + { + self thread gunner_turning_anims( turret ); + } +} + +gunner_pain_init() +{ + self.allowPain = false; // we're going to handle it ourselves + self setFlashbangImmunity( true ); + + self.og_health = self.health; + self.health = 200; +} + +gunner_pain_reset() +{ + self.allowPain = true; + self setFlashbangImmunity( false ); + self.health = self.og_health; +} + +handle_gunner_pain( gunner, turret ) +{ + gunner endon( "death" ); + turret endon( "death" ); + gunner endon( "dismount" ); + gunner endon( "jumping_out" ); + + while ( 1 ) + { + flashedNotify = "flashbang"; + + //gunner waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, dflags ); + msg = gunner waittill_any_return( "damage", flashedNotify ); + + /* custom anim pre-empts pain or flash + if( gunner.isCustomAnimating ) + { + continue; + } + */ + + painAnim = random( gunner.turretPainAnims ); + if ( msg == flashedNotify ) + { + painAnim = gunner.turretFlashbangedAnim; + gunner animscripts\face::SayGenericDialogue( "flashbang" ); + } + + //turret thread turret_recenter(); + gunner DoCustomAnim( turret, painAnim, false ); + turret notify( "pain_done" ); + } +} + +turret_recenter() +{ + self turret_aim_straight(); + self waittill( "pain_done" ); + self turret_aim_restore(); +} + +handle_gunner_death( gunner, turret ) +{ + gunner endon( "dismount" ); + turret endon( "turret_cleanup" ); + + gunner.deathanim = gunner.turretDeathAnim; + gunner.noragdoll = true; + + gunner waittill( "death" ); + level thread turret_cleanup( gunner, turret ); +} + +// for when _vehicle_aianim wants to unload the gunner, it doesn't know as much as this script yet +turret_cleanup_on_unload() +{ + Assert( IsDefined( self.ridingVehicle ) ); + + turret = self.ridingVehicle.mgturret[ 0 ]; + Assert( IsDefined( turret ) ); + + // clean up AI - moved it here since it's only needed for unloading. + if ( IsAlive( self ) ) + { + self.no_ai = undefined; + self.noDrop = undefined; + self.ignoreme = false; + self.a.special = "none"; + self.a.usingTurret = undefined; + self.deathanim = undefined; + self gunner_pain_reset(); + self.isCustomAnimating = undefined; + + self.turretSpecialAnims = undefined; + self.turretPainAnims = undefined; + + self.onRotatingVehicleTurret = undefined; + self.getOffVehicleFunc = undefined; + + self StopUseTurret(); + + if ( IsDefined( self.weapon ) ) + { + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); + } + } + + level thread turret_cleanup( self, turret ); +} + +turret_cleanup( gunner, turret ) +{ + if ( !IsDefined( turret ) ) + { + return; + } + + turret notify( "kill_fireController" ); + turret notify( "turret_cleanup" ); + turret SetMode( "manual" ); + turret ClearTargetEntity(); + + turret SetDefaultDropPitch( turret.default_drop_pitch ); + + if ( IsDefined( gunner ) ) + { + gunner ClearAnim( gunner.additiveUsegunRoot, 0 ); + gunner ClearAnim( gunner.additiveRotateRoot, 0 ); + gunner ClearAnim( gunner.turretSpecialAnimsRoot, 0 ); + } + + // clean up turret + turret.fireInterval = undefined; + turret.closeEnoughAimDegrees = undefined; + turret.fireControllerFunc = undefined; + + turret.turretState = "free"; + turret.aiOwner = undefined; + turret.fireTime = undefined; + + if ( IsDefined( turret.specialCleanupFunc ) ) + { + level [[ turret.specialCleanupFunc ]]( gunner, turret ); + } +} + +// tracks the rotational direction of the turret +turret_track_rotatedirection( gunner ) +{ + self endon( "turret_cleanup" ); + self endon( "death" ); + gunner endon( "death" ); + gunner endon( "detach" ); + + tag = "tag_aim"; + lastAngles = self GetTagAngles( tag ); + + self turret_update_rotatedirection( "none" ); + + while ( 1 ) + { + currentAngles = self GetTagAngles( tag ); + + // the vectordot of the old right angles and the current forward angles is going to tell us whether + // the turret is rotating left, right, or not at all + oldRight = AnglesToRight( lastAngles ); + currentForward = AnglesToForward( currentAngles ); + + dot = VectorDot( oldRight, currentForward ); + + if ( dot == 0 ) + { + self turret_update_rotatedirection( "none" ); + } + else if ( dot > 0 ) + { + self turret_update_rotatedirection( "right" ); + } + else + { + self turret_update_rotatedirection( "left" ); + } + + lastAngles = self GetTagAngles( tag ); + + wait( 0.05 ); + } +} + +turret_update_rotatedirection( direction ) +{ + if ( !IsDefined( self.rotateDirection ) || self.rotateDirection != direction ) + { + self.rotateDirection = direction; + + //println( "spin direction change: " + self.rotateDirection ); + } +} + +gunner_turning_anims( turret ) +{ + self endon( "death" ); + turret endon( "death" ); + self endon( "dismount" ); + turret endon( "turret_cleanup" ); + + blendInTime = 0.3; + blendOutTime = 0.3; + + while ( 1 ) + { + turret waittill( "new_fireTarget" ); + wait( 0.05 );// give him a chance to start rotating to the new target so the direction updates + + if ( !IsDefined( turret.fireTarget ) || self.isCustomAnimating ) + { + continue; + } + + anime = undefined; + + if ( !turret turret_aiming_near_target( turret.fireTarget, turret.closeEnoughAimDegrees ) ) + { + if ( turret.rotateDirection == "right" ) + { + anime = self.additiveTurretRotateRight; + //println( "gunner anim RIGHT" ); + } + else if ( turret.rotateDirection == "left" ) + { + anime = self.additiveTurretRotateLeft; + //println( "gunner anim LEFT" ); + } + + if ( IsDefined( anime ) ) + { + // dial the parent branch up + self SetAnimLimited( self.additiveRotateRoot, 1, blendInTime, 1 ); + // also tell it which leaf anim to use + // (this is inheriting its parent's blend in time so we can set the time to 0) + self SetAnimKnobLimited( anime, 1, 0, 1 ); + + while ( IsDefined( turret.fireTarget ) && !turret turret_aiming_near_target( turret.fireTarget, turret.closeEnoughAimDegrees ) ) + { + if ( self.isCustomAnimating ) + { + break; + } + + wait( 0.05 ); + } + + //println( "gunner anim CLEAR" ); + self ClearAnim( self.additiveRotateRoot, blendOutTime ); + } + } + } +} + +// makes a passenger into the turret gunner - call instead of UseTurret() +vehicle_passenger_2_turret( vehicle, pos, turret, animation ) +{ + vehicle.usedPositions[ self.vehicle_position ] = false; + //vehicle.riders = array_remove( vehicle.riders, self ); + self maps\_vehicle_aianim::guy_cleanup_vehiclevars(); + guy_gets_on_turret( vehicle, pos, turret, animation ); +} + +// makes a passenger into the turret gunner - call instead of UseTurret() +guy_goes_directly_to_turret( vehicle, pos, turret, animation ) +{ + guy_gets_on_turret( vehicle, pos, turret, animation ); +} + +guy_gets_on_turret( vehicle, pos, turret, animation ) +{ + self endon( "death" ); + turret endon( "death" ); + + // forget about being a regular vehicle rider + self StopAnimScripted(); + self notify( "newanim" ); + + self.drivingVehicle = undefined; + + self.no_ai = true; + animation = %humvee_passenger_2_turret; + if ( !isdefined( animation ) ) + animation = self.passenger_2_turret_anim; + + // get the origin/angles of the vehicle tag where this guy is riding + animpos = maps\_vehicle_aianim::anim_pos( vehicle, pos ); + org = vehicle GetTagOrigin( animpos.sittag ); + angles = vehicle GetTagAngles( animpos.sittag ); + + turret SetDefaultDropPitch( 0 ); + turret thread turret_animate( turret.passenger2turret_anime ); + + // animate into position + self AnimScripted( "passenger2turret", org, angles, animation ); + wait( GetAnimLength( animation ) ); + self StopAnimScripted(); + + turret turret_aim_restore(); + + // now start running the regular turret scripts - GDT points to minigun_hummer\stand::main() + self UseTurret( turret ); +} + +turret_animate( anime ) +{ + if ( IsDefined( self.idleAnim ) ) + { + self ClearAnim( self.idleAnim, 0 ); + self.idleAnim = undefined; + } + + self SetFlaggedAnimKnobRestart( "minigun_turret", anime, 1, 0, 1 ); + self waittillmatch( "minigun_turret", "end" ); + self ClearAnim( anime, 0 ); +} + +turret_animfirstframe( anime ) +{ + self SetAnimKnobRestart( anime, 1, 0, 0 ); + self.idleAnim = anime; +} + +// "directs" the turret about whether it should be firing or not +// note: works with either code-controlled sentry targets or manually setting turret target +// - to make an AI aim without firing, set his .ignoreall to true +fireDirector( turret ) +{ + self endon( "death" ); + turret endon( "death" ); + self endon( "dismount" ); + turret endon( "kill_fireController" ); + + turret thread turret_target_updater( self ); + wait( 0.05 );// let the target updater kick off + + self thread [[ turret.fireControllerFunc ]]( turret ); + + target = undefined; + + wait_duration_after_aiming_before_firing = 0.0; + if ( IsDefined( turret.wait_duration_after_aiming_before_firing ) ) + { + wait_duration_after_aiming_before_firing = turret.wait_duration_after_aiming_before_firing; + }/* + + while ( 1 ) + { + // get a target + target = turret.fireTarget; + + // wait for the right time to start shooting + while ( turret target_confirm( target ) ) + { + // tried a CanSee check here too, didn't seem necessary after testing + if ( turret turret_aiming_near_target( target, turret.closeEnoughAimDegrees ) ) + { + break; + } + wait( 0.05 ); + } + + // wait while aiming at target before firing + if ( wait_duration_after_aiming_before_firing > 0.0 ) + { + wait( wait_duration_after_aiming_before_firing ); + } + + //TagCC: What if the state changes between the time target_confirmed was true and was aiming towards target and now? + if ( turret target_confirm( target ) && !self.ignoreall ) + { + // shoot at him + turret.doFiring = true; + } + + // wait for his death, or for the code/script to pick/designate a new target + while ( turret target_confirm( target ) && !self.ignoreall && !self.isCustomAnimating ) + { + wait( 0.05 ); + } + + // stop shooting + if ( turret.Dofiring || self.ignoreall ) + { + turret.doFiring = false; + } + + wait( 0.05 ); + } +*/ + + + while( true ) + { + target = turret.fireTarget; + + //short circuit the loop if self is ignore all. + if( self.ignoreall || !IsDefined( target ) ) + { + wait 0.05; + continue; + } + + //wait until target is confirmed and the turret is aiming towards the actor. + while( !IsDefined( target ) || !( (turret target_confirm( target )) && (turret turret_aiming_near_target( target, turret.closeEnoughAimDegrees )) ) ) + { + wait 0.05; + target = turret.fireTarget; + } + + if ( wait_duration_after_aiming_before_firing > 0.0 ) + { + wait wait_duration_after_aiming_before_firing; + } + + turret.doFiring = true; + + //once firing begins, wait until target is no longer confirmed (usually death of target), then stop. + while ( turret target_confirm( target ) && !self.ignoreall && !self.isCustomAnimating ) + { + wait 0.05; + } + + turret.doFiring = false; + + wait 0.05; + } +} + +// makes sure the target that the fireDirector is thinking about is still synced with turret_target_updater +target_confirm( target ) +{ + if ( IsDefined( self.dontshoot ) ) + { + AssertEx( self.dontshoot, ".dontshoot must be true or undefined." ); + return false; + } + + // maybe the turret can't see the target anymore + if ( !IsDefined( self.fireTarget ) ) + { + return false; + } + + if ( !turret_target_validate( target ) ) + { + return false; + } + + if ( target != self.fireTarget ) + { + return false; + } + + return true; +} + +// make sure the script knows about the most recent turret target +// (this is necessary because code sets the turret target silently and behind the scenes) +turret_target_updater( gunner ) +{ + gunner endon( "death" ); + self endon( "death" ); + gunner endon( "dismount" ); + self endon( "kill_fireController" ); + + + // initialize this, other threads are looking for it + self.fireTarget = undefined; + + target = undefined; + lastTarget = undefined; + + while ( 1 ) + { + target = self GetTurretTarget( false ); + + doUpdate = false; + + // target can come back undefined if the turret loses sight of its target + if ( turret_target_validate( target ) || !IsDefined( target ) ) + { + // if the old target was defined and the new target is undefined + // (e.g., the turret lost its target) we want to update + if ( !IsDefined( target ) && IsDefined( lastTarget ) ) + { + doUpdate = true; + } + // or, if the new target is defined and the old one isn't, do the update + else if ( IsDefined( target ) && !IsDefined( lastTarget ) ) + { + doUpdate = true; + } + // or, if the new target is defined and different from before, do the update + else if ( IsDefined( target ) && target != lastTarget ) + { + doUpdate = true; + } + + if ( doUpdate ) + { + self.fireTarget = target; + lastTarget = target; + self notify( "new_fireTarget" ); + } + } + + wait( 0.05 ); + } +} + +turret_target_validate( target ) +{ + if ( !IsDefined( target ) ) + { + return false; + } + + if ( IsDefined( target.ignoreme ) && target.ignoreme ) + { + return false; + } + + // can't use IsSentient because the sentient AIs will turn into non-sentient corpses + if ( IsSubStr( target.code_classname, "actor" ) && !IsAlive( target ) ) + { + return false; + } + + return true; +} + +// manually designate a target for the turret +// - fireTime_min: minimum amount of time to fire once the turret has centered on the target +// - fireTime_max (optional): if set, function will pick a random time between min and max +// - fireTime_message (optional): will fire until the turret is notified the message. will ignore min and max time. +set_manual_target( target, fireTime_min, fireTime_max, fireTime_message ) +{ + AssertEx( IsDefined( target ), "undefined target passed to set_manual_target()." ); + + self endon( "turret_cleanup" ); + + oldMode = self GetMode(); + if ( oldMode != "manual" ) + { + self SetMode( "manual" ); + } + + if ( !IsDefined( fireTime_min ) && !IsDefined( fireTime_max ) ) + { + fireTime_min = 1.5; + fireTime_max = 3; + } + + self animscripts\hummer_turret\common::custom_anim_wait(); + self SetTargetEntity( target ); + //println( "target set" ); + + self waittill( "turret_on_target" ); + //println( "turret on target" ); + + if ( IsDefined( fireTime_message ) ) + { + self waittill( fireTime_message ); + } + else if ( IsDefined( fireTime_max ) ) + { + wait( RandomFloatRange( fireTime_min, fireTime_max ) ); + } + else + { + wait( fireTime_min ); + } + + self custom_anim_wait(); + self ClearTargetEntity( target ); + + if ( IsDefined( oldMode ) ) + { + self SetMode( oldMode ); + } +} + +DoShoot( turret ) +{ + self notify( "doshoot_starting" ); + + self SetAnimLimited( self.additiveUsegunRoot, 1, .1 ); + self SetAnimKnobLimited( self.additiveTurretFire, 1, .1 ); + + turret.turretState = "fire"; + turret thread fire( self ); +} + +fire( gunner ) +{ + gunner endon( "death" ); + self endon( "death" ); + gunner endon( "dismount" ); + self endon( "kill_fireController" ); + self endon( "stopfiring" ); + self endon( "custom_anim" ); + + while ( 1 ) + { + self ShootTurret(); + wait( self.fireInterval ); + } +} + +DoAim( turret ) +{ + turret.turretState = "aim"; + turret notify( "stopfiring" ); + + self thread DoAim_idle_think( turret ); +} + +DoAim_idle_think( turret ) +{ + self notify( "doaim_idle_think" ); + self endon( "doaim_idle_think" ); + + self endon( "custom_anim" ); + self endon( "doshoot_starting" ); + self endon( "death" ); + + turret endon( "death" ); + assertex( isdefined( turret ), "The turret is gone!" ); + assertex( isalive( self ), "No, I can't die!" ); + + Assert( IsDefined( turret.ownervehicle ) ); + vehicle = turret.ownervehicle; + assertex( isdefined( vehicle ), "There is no vehicle!" ); + + idle = -1; + + for ( ;; ) + { + if ( vehicle Vehicle_GetSpeed() < 1 && idle ) + { + self SetAnimLimited( self.additiveUsegunRoot, 1, 0.1 ); + self SetAnimKnobLimited( self.additiveTurretIdle, 1, 0.1 ); + idle = 0; + } + else + if ( vehicle Vehicle_GetSpeed() >= 1 && !idle ) + { + self SetAnimLimited( self.additiveUsegunRoot, 1, 0.1 ); + self SetAnimKnobLimited( self.additiveTurretDriveIdle, 1, 0.1 ); + idle = 1; + } + + wait( 0.05 ); + } +} + +/* +============= +///ScriptDocBegin +"Name: turret_gunner_custom_anim( , , )" +"Summary: Call on a turret gunner do one of his predefined custom animations. All the custom anims for a turret gunner get set up in the array self.turretSpecialAnims, in the main script for the turret type - for example, animscripts\hummer_turret\minigun_stand.gsc." +"Module: Entity" +"CallOn: An AI using a vehicle turret. (Note: this only works for 360 degree vehicle turrets, for example those on the hummer or the suburban.)" +"MandatoryArg: The turret the AI is using." +"MandatoryArg: The name of the animation, as a string. used as the lookup index for self.turretSpecialAnims." +"OptionalArg: : Whether the turret should re-center to its "home" location before the animation plays. Defaults to false." +"Example: gunner animscripts\hummer_turret\common::turret_gunner_custom_anim( turret, anime, true );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +turret_gunner_custom_anim( turret, animStr, centerTurretFirst ) +{ + // this section of endons should be identical to the ones in the function below + self endon( "death" ); + turret endon( "death" ); + self endon( "dismount" ); + self endon( "jumping_out" ); + + anime = self.turretSpecialAnims[ animStr ]; + Assert( IsDefined( anime ) ); + + self custom_anim_wait(); + + disabledReload = turret reload_disable_safe(); + + self DoCustomAnim( turret, anime, centerTurretFirst ); + + if ( disabledReload ) + { + turret reload_enable(); + } +} + +reload_disable_safe() +{ + disabledReload = false; + if ( !IsDefined( self.disableReload ) || !self.disableReload ) + { + disabledReload = true; + self.disableReload = true; + } + + return disabledReload; +} + +reload_enable() +{ + self.disableReload = false; +} + +DoReload( turret ) +{ + if ( IsDefined( turret.disableReload ) ) + { + return; + } + + // this section of endons should be identical to the ones in the function above + self endon( "death" ); + turret endon( "death" ); + self endon( "dismount" ); + self endon( "jumping_out" ); + + self thread custom_battlechatter( "inform_reloading" ); + + turret notify( "starting_reload" ); + + self DoCustomAnim( turret, self.turretReloadAnim, turret.centerTurretForReload, turret.reloadDuration ); +} + +DoCustomAnim( turret, anime, centerTurretFirst, duration ) +{ + // NOTE! To stop behavior, don't notify do_custom_anim, do self notify( "special_anim", "end" ); + self notify( "do_custom_anim" ); + self endon( "do_custom_anim" ); + + if ( IsDefined( duration ) ) + { + anim_rate = GetAnimLength( anime ) / duration; + } + else + { + anim_rate = 1.0; + } + + /* + // these endons are dupes + + self endon( "death" ); + turret endon( "death" ); + self endon( "dismount" ); + self endon( "jumping_out" ); + */ + + Assert( IsDefined( anime ) ); + + self.isCustomAnimating = true; + self.customAnim = anime; + turret.turretState = "customanim"; + turret TurretFireDisable(); + + if ( turret GetBarrelSpinRate() > 0 ) + { + turret StopBarrelSpin(); + } + + // turn off threads that can mess up the animtree + turret notify( "kill_fireController" ); + self notify( "custom_anim" ); + + if ( IsDefined( centerTurretFirst ) && centerTurretFirst ) + { + turret turret_aim_straight(); + } + + // "knob" dials down all the siblings of the specialanimsroot, so we don't need to manually + // dial down self.primaryTurretAnim or self.additiveUsegunRoot right here + self SetAnimKnobLimitedRestart( self.turretSpecialAnimsRoot, 1, 0.2 ); + self SetFlaggedAnimKnobRestart( "special_anim", anime, 1, 0, anim_rate ); + + for ( ;; ) + { + // broke into a loop so I can debug the notetracks + self waittill( "special_anim", notetrack ); + if ( notetrack == "end" ) + break; + } + + // turn the ROOT down, not the anim under the root + self ClearAnim( self.turretSpecialAnimsRoot, 0.2 ); + // dial these up individually - can't use "knob" cause we want them both to be active + self SetAnimLimited( self.primaryTurretAnim, 1 ); // 0.2 blend time is the default + self SetAnimLimited( self.additiveUsegunRoot, 1 ); + + if ( IsDefined( centerTurretFirst ) && centerTurretFirst ) + { + turret turret_aim_restore(); + } + + self.customAnim = undefined; + self.isCustomAnimating = false; + turret TurretFireEnable(); + + // turn those animtree-messing-up threads back on + self thread fireDirector( turret ); +} + +// use this if you're manually setting the turret target entity - call this first (non-threaded) +// to make sure that the DoCustomAnim function doesn't clear out your custom target +custom_anim_wait() +{ + self endon( "death" ); + + if ( !IsDefined( self.isCustomAnimating ) ) + { + return; + } + + while ( self.isCustomAnimating ) + { + wait( 0.05 ); + } +} + +turret_aim_straight( straightAngles ) +{ + if ( !IsDefined( straightAngles ) ) + { + currentAngles = self GetTagAngles( "tag_flash" ); + straightAngles = ( 0, currentAngles[ 1 ], currentAngles[ 2 ] );// just keep the yaw + } + + self.oldMode = self GetMode(); + self SetMode( "manual" ); + + // use a temp target to make the gun point straight forward + forward = AnglesToForward( straightAngles ); + scalevec = vector_multiply( forward, 96 ); + targetOrigin = self GetTagOrigin( "tag_aim" ) + scalevec; + self.tempTarget = Spawn( "script_origin", targetOrigin ); + self.tempTarget.ignoreme = true; + + self.tempTarget LinkTo( self.ownerVehicle );// if the vehicle is moving we have to link the target to the gun so the gun doesn't rotate around as the vehicle angles change + + self ClearTargetEntity(); + self SetTargetEntity( self.tempTarget ); + + self waittill( "turret_on_target" ); +} + +turret_aim_restore() +{ + self ClearTargetEntity(); + + if ( IsDefined( self.tempTarget ) ) + { + self.tempTarget Unlink(); + self.tempTarget Delete(); + } + + if ( IsDefined( self.oldMode ) ) + { + self SetMode( self.oldMode ); + self.oldMode = undefined; + } +} + +// - closeEnoughAngle: we want to be pointing within [+/- of this value in degrees] +// to the target to return true +turret_aiming_near_target( target, closeEnoughAngle ) +{ + //delta = self turret_get_angle_to_target( target ); + + turret_angles = AnglesToForward( self GetTagAngles( "tag_flash" ) ); + + delta = acos(VectorDot( VectorNormalize(target.origin - self.origin), turret_angles )); + + if ( delta <= closeEnoughAngle ) + { + return true; + } + + return false; +} + +turret_get_angle_to_target( target ) +{ + // get the yaw angle of the vector between the target origin and the turret origin + yawAngleToTarget = VectorToYaw( target.origin - self.origin ); //tagCC: I don't think this is being used right. Doesn't take into account self's angles. + + // normalize the difference between the yaw angle and the angles of the end of the barrel: + // this tells us how far away we are from being perfectly centered on the target + turretYawAngle = self GetTagAngles( "tag_flash" )[ 1 ]; + delta = animscripts\utility::AbsAngleClamp180( turretYawAngle - yawAngleToTarget ); + + return delta; +} + +lerp_out_drop_pitch( time ) +{ + blend = self create_blend( ::blend_dropPitch, 20, 0 ); + blend.time = time; +} + +blend_dropPitch( progress, start, end ) +{ + val = start * ( 1 - progress ) + end * progress; + self SetDefaultDropPitch( val ); +} + diff --git a/animscripts/hummer_turret/minigun_code.gsc b/animscripts/hummer_turret/minigun_code.gsc new file mode 100644 index 0000000..20df992 --- /dev/null +++ b/animscripts/hummer_turret/minigun_code.gsc @@ -0,0 +1,203 @@ +#include maps\_utility; +#include common_scripts\utility; +#include animscripts\hummer_turret\common; + +main( turret ) +{ + turret.fireInterval = 0.1; // time between shots + turret.closeEnoughAimDegrees = 35; // how many degrees away from aiming exactly at the target should we be before we start doing "on/near target" stuff + turret.fireControllerFunc = ::fireController_minigun; // the function that tells the turret how exactly to shoot when the fireDirector tells it that it should be firing + turret.specialCleanupFunc = ::minigun_cleanup_func; // gets called when the turret is no longer in use + turret.default_drop_pitch = 20; + turret.secsOfFiringBeforeReload = 15; // secs; the weapon will need periodic operator "maintenance" + + humvee_turret_init( turret, "minigun" ); + wait( 0.05 ); + + turret notify( "turret_ready" ); +} + +minigun_cleanup_func( gunner, turret ) +{ + if ( turret GetBarrelSpinRate() > 0 ) + { + turret StopBarrelSpin(); + } + + // could be disabled if we're in a custom anim when the actor dies + turret TurretFireEnable(); +} + +// controls spinning & firing the minigun, responding to commands from fireDirector() +fireController_minigun( turret ) +{ + self endon( "death" ); + self endon( "dismount" ); + + assert( isdefined( turret ) ); + + turret endon( "kill_fireController" ); + turret endon( "death" ); + + // "extra" fire time makes the operator look like he has human reaction time + extraFireTime_min = 600; // ms + extraFireTime_max = 900; + if ( IsDefined( turret.extraFireTime_min ) ) + { + extraFireTime_min = turret.extraFireTime_min; + } + if ( IsDefined( turret.extraFireTime_max ) ) + { + extraFireTime_max = turret.extraFireTime_max; + } + startFireTime = -1; + ceaseFireTime = undefined; + extraFireTime = undefined; + + // extra spin time makes it look like the operator is scanning for more targets + turret.extraSpinTime_min = 250; + turret.extraSpinTime_max = 2250; + startExtraSpinningTime = -1; + extraSpinTime = undefined; + + isFiring = false; + isSpinning = false; + + turret.fireTime = 0; + + self DoAim( turret ); + + while ( 1 ) + { + // if we're supposed to be firing but we're not, and if we're not doing a custom anim... + if ( turret.doFiring && !isFiring && !self.isCustomAnimating ) + { + isFiring = true; + + // spin the barrel if need be + if ( !isSpinning ) + { + //println( "start spin" ); + turret minigun_spinup(); + isSpinning = true; + } + + // start firing + //println( "start firing" ); + turret notify( "startfiring" ); + startFireTime = GetTime(); + self DoShoot( turret ); + wait( 0.05 ); // let the shooting thread start before potentially killing it on the same frame + } + // if we're currently not supposed to be firing but still actually are... + else if ( !turret.doFiring && isFiring ) + { + if ( !IsDefined( ceaseFireTime ) ) + { + ceaseFireTime = GetTime(); // time when the turret stops firing + } + + if ( !IsDefined( extraFireTime ) ) + { + extraFireTime = RandomFloatRange( extraFireTime_min, extraFireTime_max ); + } + + // have we fired long enough after being told to stop? + if ( GetTime() - ceaseFireTime >= extraFireTime ) + { + isFiring = false; + + //println( "stop firing" ); + self DoAim( turret ); + startExtraSpinningTime = GetTime(); + + // reset counters + ceaseFireTime = undefined; + extraFireTime = undefined; + } + } + // if all we're still doing is spinning... + else if ( !turret.doFiring && !isFiring && isSpinning ) + { + if ( !IsDefined( extraSpinTime ) ) + { + extraSpinTime = RandomFloatRange( turret.extraSpinTime_min, turret.extraSpinTime_max ); + } + + // stop spin immediately for custom anims, or wait for extra spin time + if ( self.isCustomAnimating || ( GetTime() - startExtraSpinningTime >= extraSpinTime ) ) + { + //println( "stop spin" ); + turret StopBarrelSpin(); + isSpinning = false; + + extraSpinTime = undefined; // reset + } + } + + if ( turret.turretstate == "fire" ) + turret.fireTime += 0.05;// ( GetTime() - startFireTime ) / 1000; + + if ( turret.fireTime > turret.secsOfFiringBeforeReload ) + { + //println( "reload" ); + turret.doFiring = false; + isFiring = false; + self DoAim( turret ); + startExtraSpinningTime = -1; + ceaseFireTime = undefined; + extraFireTime = undefined; + + self thread DoReload( turret ); + turret.fireTime = 0; // reset counter + } + + wait( 0.05 ); + + if ( !isdefined( turret ) ) + break; + } +} + +// spins the minigun up to the full rate needed to fire +minigun_spinup() +{ + if ( self GetBarrelSpinRate() == 1 ) + { + return; + } + + self StartBarrelSpin(); + + wait( 0.05 ); + + // Dan: Wait for spin up only if this weapon actually spins up. + if ( self GetBarrelSpinRate() == 0 ) + { + return; + } + else if ( self GetBarrelSpinRate() == 1 ) + { + return; + } + + wait( 0.05 ); + + while ( self GetBarrelSpinRate() < 1 ) + { + wait( 0.05 ); + } +} + +//===================================== +// SRS TEMP - so I don't clutter generic_human with stuff I'm not using atm +/* +humveeGunner : complete nonloopsync +{ + //humvee_turret_2_passenger + //humvee_turret_duck + //humvee_turret_duck_left + //humvee_turret_duck_right + //humvee_turret_death +} +*/ diff --git a/animscripts/hummer_turret/minigun_stand.gsc b/animscripts/hummer_turret/minigun_stand.gsc new file mode 100644 index 0000000..e589a75 --- /dev/null +++ b/animscripts/hummer_turret/minigun_stand.gsc @@ -0,0 +1,63 @@ +#include maps\_utility; +#include common_scripts\utility; + +#using_animtree( "generic_human" ); + +// self = the guy using the turret +main() +{ + turret = self getTurret(); + + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + // .primaryTurretAnim is used by code so don't change this variable name + self.primaryTurretAnim = %humveeGunner_aim; + + self.additiveTurretRotateLeft = %humvee_turret_aim_6_add; + self.additiveTurretRotateRight = %humvee_turret_aim_4_add; + self.additiveRotateRoot = %additive_humveeGunner_aim_leftright; + + self.additiveTurretIdle = %humvee_turret_idle; + self.additiveTurretDriveIdle = %humvee_turret_driveidle; + self.additiveTurretFire = %humvee_turret_fire; + self.additiveUsegunRoot = %additive_humveeGunner_usegun; + + self.turretDeathAnimRoot = %humveeGunner_death; + self.turretDeathAnim = %humvee_turret_death; + + self.turretPainAnims[ 0 ] = %humvee_turret_painA; + self.turretPainAnims[ 1 ] = %humvee_turret_painB; + + self.turretFlashbangedAnim = %humvee_turret_flinchA; + + self.turretReloadAnim = %humvee_turret_rechamber; + + self.turretSpecialAnimsRoot = %humveeGunner; + arr = []; + arr[ "humvee_turret_bounce" ] = %humvee_turret_bounce; + arr[ "humvee_turret_idle_lookback" ] = %humvee_turret_idle_lookback; + arr[ "humvee_turret_idle_lookbackB" ] = %humvee_turret_idle_lookbackB; + arr[ "humvee_turret_idle_signal_forward" ] = %humvee_turret_idle_signal_forward; + arr[ "humvee_turret_idle_signal_side" ] = %humvee_turret_idle_signal_side; + arr[ "humvee_turret_radio" ] = %humvee_turret_radio; + arr[ "humvee_turret_flinchA" ] = %humvee_turret_flinchA; + arr[ "humvee_turret_flinchB" ] = %humvee_turret_flinchB; + arr[ "humvee_turret_rechamber" ] = %humvee_turret_rechamber; + self.turretSpecialAnims = arr; + + turret setup_turret_anims(); + + self thread animscripts\hummer_turret\minigun_code::main( turret ); + + turret.reloadDuration = 2.0; + turret.centerTurretForReload = true; +} + +#using_animtree( "vehicles" ); +setup_turret_anims() +{ + self UseAnimTree( #animtree ); + self.passenger2turret_anime = %humvee_passenger_2_turret_minigun; + self.turret2passenger_anime = %humvee_turret_2_passenger_minigun; +} diff --git a/animscripts/init.gsc b/animscripts/init.gsc new file mode 100644 index 0000000..b024d7c --- /dev/null +++ b/animscripts/init.gsc @@ -0,0 +1,35 @@ + +#include animscripts\init_common; + +main() +{ + prof_begin( "animscript_init" ); + + pre_first_init(); + firstInit(); + post_first_init(); + + prof_end( "animscript_init" ); +} + +firstInit() +{ + // Initialization that should happen once per level + if ( isDefined( anim.NotFirstTime ) )// Use this to trigger the first init + { + return false; + } + + pre_anim_init(); + + animscripts\animset::init_anim_sets(); + animscripts\init_move_transitions::initMoveStartStopTransitions(); + + anim.lastGibTime = 0; + anim.gibDelay = 3 * 1000; // 3 seconds + anim.minGibs = 2; + anim.maxGibs = 4; + anim.totalGibs = RandomIntRange( anim.minGibs, anim.maxGibs ); + + post_anim_init(); +} diff --git a/animscripts/init_common.gsc b/animscripts/init_common.gsc new file mode 100644 index 0000000..48b5082 --- /dev/null +++ b/animscripts/init_common.gsc @@ -0,0 +1,926 @@ +// Notes about scripts +//===================== +// +// Anim variables +// -------------- +// Anim variables keep track of what the character is doing with respect to his +// animations. They know if he's standing, crouching, kneeling, walking, running, etc, +// so that he can play appropriate transitions to get to the animation he wants. +// anim_movement - "stop", "walk", "run" +// anim_pose - "stand", "crouch", "prone", some others for pain poses. +// I'm putting functions to do the basic animations to change these variables in +// SetPoseMovement.gsc, +// +// Error Reporting +// --------------- +// To report a script error condition (similar to assert(0)), I assign a non-existent variable to +// the variable homemade_error I use the name of the non-existent variable to try to explain the +// error. For example: +// homemade_error = Unexpected_anim_pose_value + self.a.pose; +// I also have a kind of assert, called as follows: +// [[anim.assertEX(condition, message_string); +// If condition evaluates to 0, the assert fires, prints message_string and stops the server. Since +// I don't have stack traces of any kind, the message string needs to say from where the assert was +// called. + +#include animscripts\Utility; +#include maps\_utility; +#include animscripts\Combat_utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + +initWeapon( weapon ) +{ + self.weaponInfo[ weapon ] = spawnstruct(); + self.weaponInfo[ weapon ].position = "none"; + self.weaponInfo[ weapon ].hasClip = true; + self.weaponInfo[ weapon ].recharges = ( weapon != "none" && WeaponAmmoRecharges( weapon ) ); + + if ( getWeaponClipModel( weapon ) != "" ) + self.weaponInfo[ weapon ].useClip = true; + else + self.weaponInfo[ weapon ].useClip = false; +} + +isWeaponInitialized( weapon ) +{ + return isDefined( self.weaponInfo[ weapon ] ); +} + +// Persistent global aiming limits / tolerances +setGlobalAimSettings() +{ + anim.coverCrouchLeanPitch = 55; + + // Used by 'Explosed' combat (combat scirpt) + anim.aimYawDiffFarTolerance = 10; + anim.aimYawDiffCloseDistSQ = 64 * 64; + anim.aimYawDiffCloseTolerance = 45; + anim.aimPitchDiffTolerance = 20; + + // Used by LastStand (pain script) + anim.painYawDiffFarTolerance = 25; + anim.painYawDiffCloseDistSQ = anim.aimYawDiffCloseDistSQ; + anim.painYawDiffCloseTolerance = anim.aimYawDiffCloseTolerance; + anim.painPitchDiffTolerance = 30; + + // Absolute maximum trackLoop angles after which the weights are reset to 0 + // These must be greater than the maximum possible aiming limit for all stances + anim.maxAngleCheckYawDelta = 65; + anim.maxAngleCheckPitchDelta = 65; +} + +everUsesSecondaryWeapon() +{ + if ( isShotgun( self.secondaryweapon ) ) + return true; + if ( weaponClass( self.primaryweapon ) == "rocketlauncher" ) + return true; + return false; +} + +pre_first_init() +{ + self.a = spawnStruct(); + self.a.laserOn = false; + self.primaryweapon = self.weapon; +} + +post_first_init() +{ + if ( self.primaryweapon == "" ) + self.primaryweapon = "none"; + if ( self.secondaryweapon == "" ) + self.secondaryweapon = "none"; + if ( self.sidearm == "" ) + self.sidearm = "none"; + + self initWeapon( self.primaryweapon ); + self initWeapon( self.secondaryweapon ); + self initWeapon( self.sidearm ); + + // this will cause us to think we're using our sidearm when we're not. the aitype should not allow this. + assertex( self.primaryweapon != self.sidearm || self.primaryweapon == "none", "AI \"" + self.classname + "\" with export " + self.export + " has both a sidearm and primaryweapon of \"" + self.primaryweapon + "\"." ); + assertex( self.secondaryweapon != self.sidearm || self.secondaryweapon == "none" || !self everUsesSecondaryWeapon(), "AI \"" + self.classname + "\" with export " + self.export + " has both a sidearm and secondaryweapon of \"" + self.primaryweapon + "\"." ); + + self setDefaultAimLimits(); + + self.a.weaponPos[ "left" ] = "none"; + self.a.weaponPos[ "right" ] = "none"; + self.a.weaponPos[ "chest" ] = "none"; + self.a.weaponPos[ "back" ] = "none"; + + self.a.weaponPosDropping[ "left" ] = "none"; + self.a.weaponPosDropping[ "right" ] = "none"; + self.a.weaponPosDropping[ "chest" ] = "none"; + self.a.weaponPosDropping[ "back" ] = "none"; + + self.lastWeapon = self.weapon; + self.root_anim = %root; + + self thread beginGrenadeTracking(); + + hasRocketLauncher = usingRocketLauncher(); + self.a.neverLean = hasRocketLauncher; + if ( hasRocketLauncher ) + self thread animscripts\shared::rpgPlayerRepulsor(); + + // TODO: proper ammo tracking + self.a.rockets = 3; + self.a.rocketVisible = true; + +// SetWeaponDist(); + + // Set initial states for poses + self.a.pose = "stand"; + self.a.grenadeThrowPose = "stand"; + self.a.movement = "stop"; + self.a.state = "stop"; + self.a.special = "none"; + self.a.gunHand = "none"; // Initialize so that PutGunInHand works properly. + self.a.PrevPutGunInHandTime = -1; + self.dropWeapon = true; + self.minExposedGrenadeDist = 750; + + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); + if ( isShotgun( self.secondaryweapon ) ) + animscripts\shared::placeWeaponOn( self.secondaryweapon, "back" ); + + self.a.needsToRechamber = 0; + self.a.combatEndTime = gettime(); + self.a.lastEnemyTime = gettime(); + self.a.suppressingEnemy = false; + self.a.disableLongDeath = !( self isBadGuy() ); + self.a.lookangle = 0; + self.a.painTime = 0; + self.a.lastShootTime = 0; + self.a.nextGrenadeTryTime = 0; + self.a.reactToBulletChance = 0.8; + + if ( self.team != "allies" ) + { + // only select allies have IR laser and beacon + self.has_no_ir = true; + } + + self.a.postScriptFunc = undefined; + self.a.stance = "stand"; + self.choosePoseFunc = animscripts\utility::choosePose; + //self.a.state = "idle"; + + self._animActive = 0; + self._lastAnimTime = 0; + + self thread enemyNotify(); + + self.baseAccuracy = 1; + self.a.missTime = 0; + + self.a.nodeath = false; + self.a.missTime = 0; + self.a.missTimeDebounce = 0; + self.a.disablePain = false; + + self.accuracyStationaryMod = 1; + self.chatInitialized = false; + self.sightPosTime = 0; + self.sightPosLeft = true; + self.needRecalculateGoodShootPos = true; + self.defaultTurnThreshold = 55; + self.painPlaybackRate = 1.0; + + self.a.nextStandingHitDying = false; + + // Makes AI able to throw grenades at other AI. + if ( !isdefined( self.script_forcegrenade ) ) + self.script_forcegrenade = 0; + + /# self.a.lastDebugPrint = ""; #/ + + SetupUniqueAnims(); + + /# thread animscripts\utility::UpdateDebugInfo(); #/ + + self animscripts\weaponList::RefillClip(); // Start with a full clip. + + // state tracking + self.lastEnemySightTime = 0;// last time we saw our current enemy + self.combatTime = 0;// how long we've been in / out of combat + + self.suppressed = false;// if we're currently suppressed + self.suppressedTime = 0;// how long we've been in / out of suppression + + if ( self.team == "allies" ) + self.suppressionThreshold = 0.5; + else + self.suppressionThreshold = 0.0; + + // Random range makes the grenades less accurate and do less damage, but also makes it difficult to throw back. + if ( self.team == "allies" ) + self.randomGrenadeRange = 0; + else + self.randomGrenadeRange = 256; + + self.ammoCheatInterval = 8000; // if out of ammo and it's been this long since last time, do an instant reload + self.ammoCheatTime = 0; + animscripts\init_common::set_animset_run_n_gun(); + + self.exception = []; + + self.exception[ "corner" ] = 1; + self.exception[ "cover_crouch" ] = 1; + self.exception[ "stop" ] = 1; + self.exception[ "stop_immediate" ] = 1; + self.exception[ "move" ] = 1; + self.exception[ "exposed" ] = 1; + self.exception[ "corner_normal" ] = 1; + + keys = getArrayKeys( self.exception ); + for ( i = 0; i < keys.size; i++ ) + { + clear_exception( keys[ i ] ); + } + + self.reacquire_state = 0; + + self thread setNameAndRank_andAddToSquad(); + + self.shouldConserveAmmoTime = 0; + + /# + self thread printEyeOffsetFromNode(); + self thread showLikelyEnemyPathDir(); + #/ + + self thread monitorFlash(); + + self thread onDeath(); +} + +weapons_with_ir( weapon ) +{ + weapons[ 0 ] = "m4_grenadier"; + weapons[ 1 ] = "m4_grunt"; + weapons[ 2 ] = "m4_silencer"; + weapons[ 3 ] = "m4m203"; + + if ( !isdefined( weapon ) ) + return false; + + for ( i = 0 ; i < weapons.size ; i++ ) + { + if ( issubstr( weapon, weapons[ i ] ) ) + return true; + } + return false; +} + + /# +printEyeOffsetFromNode() +{ + self endon( "death" ); + while ( 1 ) + { + if ( getdvarint( "scr_eyeoffset" ) == self getentnum() ) + { + if ( isdefined( self.coverNode ) ) + { + offset = self geteye() - self.coverNode.origin; + forward = anglestoforward( self.coverNode.angles ); + right = anglestoright( self.coverNode.angles ); + trueoffset = ( vectordot( right, offset ), vectordot( forward, offset ), offset[ 2 ] ); + println( trueoffset ); + } + } + else + wait 2; + wait .1; + } +} + +showLikelyEnemyPathDir() +{ + self endon( "death" ); + setDvarIfUninitialized( "scr_showlikelyenemypathdir", "-1" ); + while ( 1 ) + { + if ( getdvarint( "scr_showlikelyenemypathdir" ) == self getentnum() ) + { + yaw = self.angles[ 1 ]; + dir = self getAnglesToLikelyEnemyPath(); + if ( isdefined( dir ) ) + yaw = dir[ 1 ]; + printpos = self.origin + ( 0, 0, 60 ) + anglestoforward( ( 0, yaw, 0 ) ) * 100; + line( self.origin + ( 0, 0, 60 ), printpos ); + if ( isdefined( dir ) ) + print3d( printpos, "likelyEnemyPathDir: " + yaw, ( 1, 1, 1 ), 1, 0.5 ); + else + print3d( printpos, "likelyEnemyPathDir: undefined", ( 1, 1, 1 ), 1, 0.5 ); + + wait .05; + } + else + wait 2; + } +} +#/ + +setNameAndRank_andAddToSquad() +{ + self endon( "death" ); + if ( !isdefined( level._loadoutComplete ) ) + level waittill( "loadout complete" ); + + self maps\_names::get_name(); + + // Init BC location cache + self.bc_last_get_location_time = 0; + self.bc_cache_time = 0; + self.bc_locations = []; + + // needs to run after the name has been set since bcs changes self.voice from "multilingual" + // to something more specific + self thread animscripts\squadManager::addToSquad();// slooooow +} + + +// Debug thread to see when stances are being allowed +PollAllowedStancesThread() +{ + for ( ;; ) + { + if ( self isStanceAllowed( "stand" ) ) + { + line[ 0 ] = "stand allowed"; + color[ 0 ] = ( 0, 1, 0 ); + } + else + { + line[ 0 ] = "stand not allowed"; + color[ 0 ] = ( 1, 0, 0 ); + } + if ( self isStanceAllowed( "crouch" ) ) + { + line[ 1 ] = "crouch allowed"; + color[ 1 ] = ( 0, 1, 0 ); + } + else + { + line[ 1 ] = "crouch not allowed"; + color[ 1 ] = ( 1, 0, 0 ); + } + if ( self isStanceAllowed( "prone" ) ) + { + line[ 2 ] = "prone allowed"; + color[ 2 ] = ( 0, 1, 0 ); + } + else + { + line[ 2 ] = "prone not allowed"; + color[ 2 ] = ( 1, 0, 0 ); + } + + + aboveHead = self getshootatpos() + ( 0, 0, 30 ); + offset = ( 0, 0, -10 ); + for ( i = 0 ; i < line.size ; i++ ) + { + textPos = ( aboveHead[ 0 ] + ( offset[ 0 ] * i ), aboveHead[ 1 ] + ( offset[ 1 ] * i ), aboveHead[ 2 ] + ( offset[ 2 ] * i ) ); + print3d( textPos, line[ i ], color[ i ], 1, 0.75 ); // origin, text, RGB, alpha, scale + } + wait 0.05; + } +} + +SetupUniqueAnims() +{ + if ( !isDefined( self.animplaybackrate ) || !isDefined( self.moveplaybackrate ) ) + { + set_anim_playback_rate(); + } +} + +set_anim_playback_rate() +{ + self.animplaybackrate = 0.9 + randomfloat( 0.2 ); + self.moveTransitionRate = 0.9 + randomfloat( 0.2 ); + self.moveplaybackrate = 1; + self.sideStepRate = 1.35; +} + + +infiniteLoop( one, two, three, whatever ) +{ + anim waittill( "new exceptions" ); +} + +empty( one, two, three, whatever ) +{ +} + +enemyNotify() +{ + self endon( "death" ); + if ( 1 ) return; + for ( ;; ) + { + self waittill( "enemy" ); + if ( !isalive( self.enemy ) ) + continue; + while ( isplayer( self.enemy ) ) + { + if ( hasEnemySightPos() ) + level._lastPlayerSighted = gettime(); + wait( 2 ); + } + } +} + + +initWindowTraverse() +{ + // used to blend the traverse window_down smoothly at the end + level._window_down_height[ 0 ] = -36.8552; + level._window_down_height[ 1 ] = -27.0095; + level._window_down_height[ 2 ] = -15.5981; + level._window_down_height[ 3 ] = -4.37769; + level._window_down_height[ 4 ] = 17.7776; + level._window_down_height[ 5 ] = 59.8499; + level._window_down_height[ 6 ] = 104.808; + level._window_down_height[ 7 ] = 152.325; + level._window_down_height[ 8 ] = 201.052; + level._window_down_height[ 9 ] = 250.244; + level._window_down_height[ 10 ] = 298.971; + level._window_down_height[ 11 ] = 330.681; +} + +pre_anim_init() +{ + anim.NotFirstTime = true; + + anim.useFacialAnims = false;// remove me when facial anims are fixed + + maps\_load::init_level_players(); + + level._player.invul = false; + level._nextGrenadeDrop = randomint( 3 ); + level._lastPlayerSighted = 100; + + anim.defaultException = animscripts\init_common::empty; + + initDeveloperDvars(); + + setdvar( "scr_expDeathMayMoveCheck", "on" ); + + maps\_names::setup_names(); + + anim.animFlagNameIndex = 0; +} + +post_anim_init() +{ + anim.combatMemoryTimeConst = 10000; + anim.combatMemoryTimeRand = 6000; + + initAdvanceToEnemy(); + + setEnv( "none" ); + + if ( !isdefined( anim.optionalStepEffectFunction ) ) + { + anim.optionalStepEffectSmallFunction = animscripts\shared::playFootStepEffectSmall; + anim.optionalStepEffectFunction = animscripts\shared::playFootStepEffect; + } + + if ( !isdefined( anim.optionalStepEffects ) ) + anim.optionalStepEffects = []; + + if ( !isdefined( anim.optionalStepEffectsSmall ) ) + anim.optionalStepEffectsSmall = []; + + + anim.shootEnemyWrapper_func = ::shootEnemyWrapper_shootNotify; + + // scripted mode uses a special function. Faster to use a function pointer based on script than use an if statement in a popular loop. + anim.fire_notetrack_functions[ "scripted" ] = animscripts\shared::fire_straight; + anim.fire_notetrack_functions[ "custom" ] = animscripts\shared::fire_straight; + anim.fire_notetrack_functions[ "cover_right" ] = animscripts\shared::shootNotetrack; + anim.fire_notetrack_functions[ "cover_left" ] = animscripts\shared::shootNotetrack; + anim.fire_notetrack_functions[ "cover_crouch" ] = animscripts\shared::shootNotetrack; + anim.fire_notetrack_functions[ "cover_stand" ] = animscripts\shared::shootNotetrack; + anim.fire_notetrack_functions[ "move" ] = animscripts\shared::shootNotetrack; + + // string based array for notetracks + animscripts\shared::registerNoteTracks(); + + /# + setDvarIfUninitialized( "debug_delta", "off" ); + #/ + + if ( !isdefined( level._flag ) ) + common_scripts\utility::init_flags(); + + maps\_gameskill::setSkill(); + level._painAI = undefined; + + animscripts\SetPoseMovement::InitPoseMovementFunctions(); + animscripts\face::InitLevelFace(); + + // probabilities of burst fire shots + anim.burstFireNumShots = array( 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5 ); + anim.fastBurstFireNumShots = array( 2, 3, 3, 3, 4, 4, 4, 5, 5 ); + anim.semiFireNumShots = array( 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5 ); + + anim.badPlaces = [];// queue for animscript badplaces + anim.badPlaceInt = 0;// assigns unique names to animscript badplaces since we cant save a badplace as an entity + + anim.player = getentarray( "player", "classname" )[ 0 ]; + + initBattlechatter(); + + initWindowTraverse(); + + animscripts\flashed::initFlashed(); + + animscripts\cqb::setupCQBPointsOfInterest(); + + initDeaths(); + + setGlobalAimSettings(); + + anim.lastCarExplosionTime = -100000; + + setupRandomTable(); + + level._player thread watchReloading(); + + thread AITurnNotifies(); +} + + + +initDeveloperDvars() +{ +/# + if ( getdebugdvar( "debug_noanimscripts" ) == "" ) + setdvar( "debug_noanimscripts", "off" ); + else if ( getdebugdvar( "debug_noanimscripts" ) == "on" ) + anim.defaultException = animscripts\init_common::infiniteLoop; + + if ( getdebugdvar( "debug_grenadehand" ) == "" ) + setdvar( "debug_grenadehand", "off" ); + if ( getdebugdvar( "anim_dotshow" ) == "" ) + setdvar( "anim_dotshow", "-1" ); + if ( getdebugdvar( "anim_debug" ) == "" ) + setdvar( "anim_debug", "" ); + if ( getdebugdvar( "debug_misstime" ) == "" ) + setdvar( "debug_misstime", "" ); +#/ +} + +initBattlechatter() +{ + animscripts\squadmanager::init_squadManager(); + anim.player thread animscripts\squadManager::addPlayerToSquad(); + + animscripts\battleChatter::init_battleChatter(); + anim.player thread animscripts\battleChatter_ai::addToSystem(); + + anim thread animscripts\battleChatter::bcsDebugWaiter(); +} + +initDeaths() +{ + anim.numDeathsUntilCrawlingPain = randomintrange( 0, 15 ); + anim.numDeathsUntilCornerGrenadeDeath = randomintrange( 0, 10 ); + anim.nextCrawlingPainTime = gettime() + randomintrange( 0, 20000 ); + anim.nextCrawlingPainTimeFromLegDamage = gettime() + randomintrange( 0, 10000 ); + anim.nextCornerGrenadeDeathTime = gettime() + randomintrange( 0, 15000 ); +} + + + + +initAdvanceToEnemy() +{ + // use team ID for now. Should be done per group of AI or something more specific + level._lastAdvanceToEnemyTime = []; + level._lastAdvanceToEnemyTime[ "axis" ] = 0; + level._lastAdvanceToEnemyTime[ "allies" ] = 0; + level._lastAdvanceToEnemyTime[ "team3" ] = 0; + level._lastAdvanceToEnemyTime[ "neutral" ] = 0; + + level._lastAdvanceToEnemyDest = []; + level._lastAdvanceToEnemyDest[ "axis" ] = ( 0, 0, 0 ); + level._lastAdvanceToEnemyDest[ "allies" ] = ( 0, 0, 0 ); + level._lastAdvanceToEnemyDest[ "team3" ] = ( 0, 0, 0 ); + level._lastAdvanceToEnemyDest[ "neutral" ] = ( 0, 0, 0 ); + + level._lastAdvanceToEnemySrc = []; + level._lastAdvanceToEnemySrc[ "axis" ] = ( 0, 0, 0 ); + level._lastAdvanceToEnemySrc[ "allies" ] = ( 0, 0, 0 ); + level._lastAdvanceToEnemySrc[ "team3" ] = ( 0, 0, 0 ); + level._lastAdvanceToEnemySrc[ "neutral" ] = ( 0, 0, 0 ); + + level._lastAdvanceToEnemyAttacker = []; + + level._advanceToEnemyGroup = []; + level._advanceToEnemyGroup[ "axis" ] = 0; + level._advanceToEnemyGroup[ "allies" ] = 0; + level._advanceToEnemyGroup[ "team3" ] = 0; + level._advanceToEnemyGroup[ "neutral" ] = 0; + + level._advanceToEnemyInterval = 30000; // how often AI will try to run directly to their enemy if the enemy is not visible + level._advanceToEnemyGroupMax = 3; // group size for AI running to their enemy +} + + +AITurnNotifies() +{ + numTurnsThisFrame = 0; + maxAIPerFrame = 3; + while ( 1 ) + { + ai = getAIArray(); + if ( ai.size == 0 ) + { + wait .05; + numTurnsThisFrame = 0; + continue; + } + for ( i = 0; i < ai.size; i++ ) + { + if ( !isdefined( ai[ i ] ) ) + continue; + ai[ i ] notify( "do_slow_things" ); + numTurnsThisFrame++ ; + if ( numTurnsThisFrame == maxAIPerFrame ) + { + wait .05; + numTurnsThisFrame = 0; + } + } + } +} + +setNextPlayerGrenadeTime() +{ + assert( isPlayer( self ) ); + waittillframeend; + // might not be defined if maps\_load::main() wasn't called + if ( isdefined( self.gs.playerGrenadeRangeTime ) ) + { + maxTime = int( self.gs.playerGrenadeRangeTime * 0.7 ); + if ( maxTime < 1 ) + maxTime = 1; + self.grenadeTimers[ "fraggrenade" ] = randomIntRange( 0, maxTime ); + self.grenadeTimers[ "flash_grenade" ] = randomIntRange( 0, maxTime ); + } + if ( isdefined( self.gs.playerDoubleGrenadeTime ) ) + { + maxTime = int( self.gs.playerDoubleGrenadeTime ); + minTime = int( maxTime / 2 ); + if ( maxTime <= minTime ) + maxTime = minTime + 1; + self.grenadeTimers[ "double_grenade" ] = randomIntRange( minTime, maxTime ); + } +} + +beginGrenadeTracking() +{ + self endon( "death" ); + + for ( ;; ) + { + self waittill( "grenade_fire", grenade, weaponName ); + grenade thread grenade_earthQuake(); + } +} + +setupRandomTable() +{ + // 60 is chosen because it is divisible by 1,2,3,4,5, and 6, + // and it's also high enough to get some good randomness over different seed values + anim.randomIntTableSize = 60; + + // anim.randomIntTable is a permutation of integers 0 through anim.randomIntTableSize - 1 + anim.randomIntTable = []; + for ( i = 0; i < anim.randomIntTableSize; i++ ) + anim.randomIntTable[ i ] = i; + + for ( i = 0; i < anim.randomIntTableSize; i++ ) + { + switchwith = randomint( anim.randomIntTableSize ); + temp = anim.randomIntTable[ i ]; + anim.randomIntTable[ i ] = anim.randomIntTable[ switchwith ]; + anim.randomIntTable[ switchwith ] = temp; + } +} + +onDeath() +{ + self waittill( "death" ); + if ( !isdefined( self ) ) + { + // we were deleted and we're not running the death script. + // still safe to access our variables as a removed entity though: + if ( isdefined( self.a.usingTurret ) ) + self.a.usingTurret delete(); + } +} + +init_animset_custom_stand( fireAnim, aimStraight, idleAnim, reloadAnim ) +{ + assert( isdefined( anim.animsets ) && isdefined( anim.animsets.defaultStand ) ); + + anim.initAnimSet = anim.animsets.defaultStand; + + if ( isdefined( aimStraight ) ) + anim.initAnimSet[ "straight_level" ] = aimStraight; + + if ( isdefined( fireAnim ) ) + { + anim.initAnimSet[ "fire" ] = fireAnim; + anim.initAnimSet[ "single" ] = array( fireAnim ); + set_animarray_custom_burst_and_semi_fire_stand( fireAnim ); + } + + if ( isdefined( idleAnim ) ) + anim.initAnimSet[ "exposed_idle" ] = array( idleAnim ); + + if ( isdefined( reloadAnim ) ) + { + anim.initAnimSet[ "reload" ] = array( reloadAnim ); + anim.initAnimSet[ "reload_crouchhide" ] = array( reloadAnim ); + } + + self.combatStandAnims = anim.initAnimSet; +} + +//////////////////////////////////////////// +// Crouch +//////////////////////////////////////////// + +set_animarray_crouching() +{ + if ( usingSidearm() ) + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); + + if ( isdefined( self.combatCrouchAnims ) ) + { + assert( isArray( self.combatCrouchAnims ) ); + self.a.array = self.combatCrouchAnims; + } + else if ( usingRocketLauncher() ) + { + self.a.array = anim.animsets.rpgCrouch; + } + else if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() ) + { + self.a.array = anim.animsets.shotgunCrouch; + } + else + { + self.a.array = anim.animsets.defaultCrouch; + } +} + +//////////////////////////////////////////// +// Stand +//////////////////////////////////////////// + +set_animarray_standing() +{ + if ( usingSidearm() ) + { + self.a.array = anim.animsets.pistolStand; + } + else if ( isdefined( self.combatStandAnims ) ) + { + assert( isArray( self.combatStandAnims ) ); + self.a.array = self.combatStandAnims; + } + else if ( isdefined( self.heat ) ) + { + self.a.array = anim.animsets.heatStand; + } + else if ( usingRocketLauncher() ) + { + self.a.array = anim.animsets.rpgStand; + } + else if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() ) + { + self.a.array = anim.animsets.shotgunStand; + } + else if ( self isCQBWalking() ) + { + if ( isdefined( self.combatStandCQBAnims ) ) + { + assert( isArray( self.combatStandCQBAnims ) ); + self.a.array = self.combatStandCQBAnims; + } + else + { + self.a.array = anim.animsets.cqbStand; + } + } + else + { + self.a.array = anim.animsets.defaultStand; + } +} + + +//////////////////////////////////////////// +// Prone +//////////////////////////////////////////// + +set_animarray_prone() +{ + if ( usingSidearm() ) + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); + + self.a.array = anim.animsets.defaultProne; +} + +MAX_RUN_N_GUN_ANGLE = 130; +RUN_N_GUN_TRANSITION_POINT = 60 / MAX_RUN_N_GUN_ANGLE; + +set_animset_run_n_gun() +{ + self.maxRunNGunAngle = MAX_RUN_N_GUN_ANGLE; + self.runNGunTransitionPoint = RUN_N_GUN_TRANSITION_POINT; + self.runNGunIncrement = 0.3; + + self.runNGunAnims = anim.runNGunAnims; +} + + +set_ambush_sidestep_anims() +{ + assert( isdefined( self.a.moveAnimSet ) ); + + self.a.moveAnimSet = array_combine_keys( self.a.moveAnimSet, anim.moveAnimSet ); +} + + +set_animarray_custom_burst_and_semi_fire_stand( fireAnim ) +{ + anim.initAnimSet[ "burst2" ] = fireAnim; + anim.initAnimSet[ "burst3" ] = fireAnim; + anim.initAnimSet[ "burst4" ] = fireAnim; + anim.initAnimSet[ "burst5" ] = fireAnim; + anim.initAnimSet[ "burst6" ] = fireAnim; + + anim.initAnimSet[ "semi2" ] = fireAnim; + anim.initAnimSet[ "semi3" ] = fireAnim; + anim.initAnimSet[ "semi4" ] = fireAnim; + anim.initAnimSet[ "semi5" ] = fireAnim; +} + +set_animset_complete_custom_stand( completeSet ) +{ + self.combatStandAnims = completeSet; +} + + +set_animset_complete_custom_crouch( completeSet ) +{ + self.combatCrouchAnims = completeSet; +} + +clear_custom_animset() +{ + self.customMoveAnimSet = undefined; + self.customIdleAnimSet = undefined; + + self.combatStandAnims = undefined; + self.combatCrouchAnims = undefined; + self.combatStandCQBAnims = undefined; + + self.customTurnAnimSet = undefined; + + self.customAnimFunc = undefined; + + self.customCoverEnterTrans = undefined; + self.customCoverExitTrans = undefined; + + self.customDeathAnimSet = undefined; + + self.customPainAnimSet = undefined; +} + +addGrenadeThrowAnimOffset( throwAnim, offset ) +{ + if ( !isdefined( anim.grenadeThrowAnims ) ) + { + anim.grenadeThrowAnims = []; + anim.grenadeThrowOffsets = []; + } + + assert( anim.grenadeThrowAnims.size == anim.grenadeThrowOffsets.size ); + + index = anim.grenadeThrowAnims.size; + anim.grenadeThrowAnims[ index ] = throwAnim; + anim.grenadeThrowOffsets[ index ] = offset; +} \ No newline at end of file diff --git a/animscripts/init_move_transitions.gsc b/animscripts/init_move_transitions.gsc new file mode 100644 index 0000000..cc1b8c0 --- /dev/null +++ b/animscripts/init_move_transitions.gsc @@ -0,0 +1,950 @@ +#include animscripts\Utility; +#include maps\_utility; +#include animscripts\Combat_utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + +init_move_transition_arrays() +{ + if ( isdefined( anim.move_transition_arrays ) ) + return; + + anim.move_transition_arrays = 1; + + anim.coverTrans = []; + anim.coverExit = []; + anim.maxDirections = []; + anim.excludeDir = []; + + anim.traverseInfo = []; + + anim.coverTransLongestDist = []; + anim.coverTransDist = []; + anim.coverExitDist = []; + + // this is the distance moved to get around corner for 7, 8, 9 directions + anim.coverExitPostDist = []; + + // this is the distance moved to get around corner for 7, 8, 9 directions + anim.coverTransPreDist = []; + + anim.coverTransAngles = []; + anim.coverExitAngles = []; + + anim.coverExitSplit = []; + anim.coverTransSplit = []; + + anim.arrivalEndStance = []; +} + + +initMoveStartStopTransitions() +{ + init_move_transition_arrays(); + + // TEMP, remove this flag + level._newArrivals = true; + + transTypes = []; + transTypes[ 0 ] = "left"; + transTypes[ 1 ] = "right"; + transTypes[ 2 ] = "left_crouch"; + transTypes[ 3 ] = "right_crouch"; + transTypes[ 4 ] = "crouch"; + transTypes[ 5 ] = "stand"; + transTypes[ 6 ] = "exposed"; + transTypes[ 7 ] = "exposed_crouch"; + transTypes[ 8 ] = "stand_saw"; + transTypes[ 9 ] = "prone_saw"; + transTypes[ 10 ] = "crouch_saw"; + transTypes[ 11 ] = "wall_over_40"; + transTypes[ 12 ] = "right_cqb"; + transTypes[ 13 ] = "right_crouch_cqb"; + transTypes[ 14 ] = "left_cqb"; + transTypes[ 15 ] = "left_crouch_cqb"; + transTypes[ 16 ] = "exposed_cqb"; + transTypes[ 17 ] = "exposed_crouch_cqb"; + transTypes[ 18 ] = "heat"; + transTypes[ 19 ] = "heat_left"; + transTypes[ 20 ] = "heat_right"; + + + lastCoverTrans = 6; + + // tagJW: Non-lunar movements do not use move transition notes + // Moon_actor's main is called before load, so don't override these if they exist + if ( !isdefined( anim.run_transition_notes ) ) + { + anim.cqb_transition_notes = []; + anim.cqb_transition_points = []; + + anim.run_transition_notes = []; + anim.run_transition_points = []; + } + + anim.approach_types = []; + + anim.approach_types[ "Cover Left" ] = []; + anim.approach_types[ "Cover Left" ][ "stand" ] = "left"; + anim.approach_types[ "Cover Left" ][ "crouch" ] = "left_crouch"; + anim.maxDirections[ "Cover Left" ] = 9; + anim.excludeDir[ "Cover Left" ] = 9; + + anim.approach_types[ "Cover Right" ] = []; + anim.approach_types[ "Cover Right" ][ "stand" ] = "right"; + anim.approach_types[ "Cover Right" ][ "crouch" ] = "right_crouch"; + anim.maxDirections[ "Cover Right" ] = 9; + anim.excludeDir[ "Cover Right" ] = 7; + + anim.approach_types[ "Cover Crouch" ] = []; + anim.approach_types[ "Cover Crouch" ][ "stand" ] = "crouch"; + anim.approach_types[ "Cover Crouch" ][ "crouch" ] = "crouch"; + anim.approach_types[ "Conceal Crouch" ] = anim.approach_types[ "Cover Crouch" ]; + anim.approach_types[ "Cover Crouch Window" ] = anim.approach_types[ "Cover Crouch" ]; + anim.maxDirections[ "Cover Crouch" ] = 6; + anim.excludeDir[ "Cover Crouch" ] = -1; + anim.maxDirections[ "Conceal Crouch" ] = 6; + anim.excludeDir[ "Conceal Crouch" ] = -1; + + anim.approach_types[ "Cover Stand" ] = []; + anim.approach_types[ "Cover Stand" ][ "stand" ] = "stand"; + anim.approach_types[ "Cover Stand" ][ "crouch" ] = "stand"; + anim.approach_types[ "Conceal Stand" ] = anim.approach_types[ "Cover Stand" ]; + anim.maxDirections[ "Cover Stand" ] = 6; + anim.excludeDir[ "Cover Stand" ] = -1; + anim.maxDirections[ "Conceal Stand" ] = 6; + anim.excludeDir[ "Conceal Stand" ] = -1; + + anim.approach_types[ "Cover Prone" ] = []; + anim.approach_types[ "Cover Prone" ][ "stand" ] = "exposed"; + anim.approach_types[ "Cover Prone" ][ "crouch" ] = "exposed"; + anim.approach_types[ "Conceal Prone" ] = anim.approach_types[ "Cover Prone" ]; + anim.excludeDir[ "Conceal Prone" ] = -1; + + anim.approach_types[ "Path" ] = []; + anim.approach_types[ "Path" ][ "stand" ] = "exposed"; + anim.approach_types[ "Path" ][ "crouch" ] = "exposed_crouch"; + anim.approach_types[ "Guard" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Ambush" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Scripted" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Custom" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Exposed" ] = anim.approach_types[ "Path" ]; + + anim.isCombatPathNode[ "Guard" ] = true; + anim.isCombatPathNode[ "Ambush" ] = true; + anim.isCombatPathNode[ "Exposed" ] = true; + + // used by level script to orient AI in certain ways at a node + anim.isCombatScriptNode[ "Guard" ] = true; + anim.isCombatScriptNode[ "Exposed" ] = true; + + // CORNER TRANSITIONS ANIMS + // indicies indicate the keyboard numpad directions (8 is forward) + // 7 8 9 + // 4 6 <- 5 is invalid + // 1 2 3 + + /************************************************* + * Entrance Animations + *************************************************/ + + anim.coverTrans[ "right" ][ 1 ] = %corner_standR_trans_IN_1; + anim.coverTrans[ "right" ][ 2 ] = %corner_standR_trans_IN_2; + anim.coverTrans[ "right" ][ 3 ] = %corner_standR_trans_IN_3; + anim.coverTrans[ "right" ][ 4 ] = %corner_standR_trans_IN_4; + anim.coverTrans[ "right" ][ 6 ] = %corner_standR_trans_IN_6; + //im.coverTrans[ "right" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right" ][ 8 ] = %corner_standR_trans_IN_8; + anim.coverTrans[ "right" ][ 9 ] = %corner_standR_trans_IN_9; + + anim.coverTrans[ "right_crouch" ][ 1 ] = %CornerCrR_trans_IN_ML; + anim.coverTrans[ "right_crouch" ][ 2 ] = %CornerCrR_trans_IN_M; + anim.coverTrans[ "right_crouch" ][ 3 ] = %CornerCrR_trans_IN_MR; + anim.coverTrans[ "right_crouch" ][ 4 ] = %CornerCrR_trans_IN_L; + anim.coverTrans[ "right_crouch" ][ 6 ] = %CornerCrR_trans_IN_R; + //im.coverTrans[ "right_crouch" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right_crouch" ][ 8 ] = %CornerCrR_trans_IN_F; + anim.coverTrans[ "right_crouch" ][ 9 ] = %CornerCrR_trans_IN_MF; + + anim.coverTrans[ "right_cqb" ][ 1 ] = %corner_standR_trans_CQB_IN_1; + anim.coverTrans[ "right_cqb" ][ 2 ] = %corner_standR_trans_CQB_IN_2; + anim.coverTrans[ "right_cqb" ][ 3 ] = %corner_standR_trans_CQB_IN_3; + anim.coverTrans[ "right_cqb" ][ 4 ] = %corner_standR_trans_CQB_IN_4; + anim.coverTrans[ "right_cqb" ][ 6 ] = %corner_standR_trans_CQB_IN_6; + //im.coverTrans[ "right_cqb" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right_cqb" ][ 8 ] = %corner_standR_trans_CQB_IN_8; + anim.coverTrans[ "right_cqb" ][ 9 ] = %corner_standR_trans_CQB_IN_9; + + anim.coverTrans[ "right_crouch_cqb" ][ 1 ] = %CornerCrR_CQB_trans_IN_1; + anim.coverTrans[ "right_crouch_cqb" ][ 2 ] = %CornerCrR_CQB_trans_IN_2; + anim.coverTrans[ "right_crouch_cqb" ][ 3 ] = %CornerCrR_CQB_trans_IN_3; + anim.coverTrans[ "right_crouch_cqb" ][ 4 ] = %CornerCrR_CQB_trans_IN_4; + anim.coverTrans[ "right_crouch_cqb" ][ 6 ] = %CornerCrR_CQB_trans_IN_6; + //im.coverTrans[ "right_crouch_cqb" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right_crouch_cqb" ][ 8 ] = %CornerCrR_CQB_trans_IN_8; + anim.coverTrans[ "right_crouch_cqb" ][ 9 ] = %CornerCrR_CQB_trans_IN_9; + + anim.coverTrans[ "left" ][ 1 ] = %corner_standL_trans_IN_1; + anim.coverTrans[ "left" ][ 2 ] = %corner_standL_trans_IN_2; + anim.coverTrans[ "left" ][ 3 ] = %corner_standL_trans_IN_3; + anim.coverTrans[ "left" ][ 4 ] = %corner_standL_trans_IN_4; + anim.coverTrans[ "left" ][ 6 ] = %corner_standL_trans_IN_6; + anim.coverTrans[ "left" ][ 7 ] = %corner_standL_trans_IN_7; + anim.coverTrans[ "left" ][ 8 ] = %corner_standL_trans_IN_8; + //im.coverTrans[ "left" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "left_crouch" ][ 1 ] = %CornerCrL_trans_IN_ML; + anim.coverTrans[ "left_crouch" ][ 2 ] = %CornerCrL_trans_IN_M; + anim.coverTrans[ "left_crouch" ][ 3 ] = %CornerCrL_trans_IN_MR; + anim.coverTrans[ "left_crouch" ][ 4 ] = %CornerCrL_trans_IN_L; + anim.coverTrans[ "left_crouch" ][ 6 ] = %CornerCrL_trans_IN_R; + anim.coverTrans[ "left_crouch" ][ 7 ] = %CornerCrL_trans_IN_MF; + anim.coverTrans[ "left_crouch" ][ 8 ] = %CornerCrL_trans_IN_F; + //im.coverTrans[ "left_crouch" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "left_cqb" ][ 1 ] = %corner_standL_trans_CQB_IN_1; + anim.coverTrans[ "left_cqb" ][ 2 ] = %corner_standL_trans_CQB_IN_2; + anim.coverTrans[ "left_cqb" ][ 3 ] = %corner_standL_trans_CQB_IN_3; + anim.coverTrans[ "left_cqb" ][ 4 ] = %corner_standL_trans_CQB_IN_4; + anim.coverTrans[ "left_cqb" ][ 6 ] = %corner_standL_trans_CQB_IN_6; + anim.coverTrans[ "left_cqb" ][ 7 ] = %corner_standL_trans_CQB_IN_7; + anim.coverTrans[ "left_cqb" ][ 8 ] = %corner_standL_trans_CQB_IN_8; + //im.coverTrans[ "left_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "left_crouch_cqb" ][ 1 ] = %CornerCrL_CQB_trans_IN_1; + anim.coverTrans[ "left_crouch_cqb" ][ 2 ] = %CornerCrL_CQB_trans_IN_2; + anim.coverTrans[ "left_crouch_cqb" ][ 3 ] = %CornerCrL_CQB_trans_IN_3; + anim.coverTrans[ "left_crouch_cqb" ][ 4 ] = %CornerCrL_CQB_trans_IN_4; + anim.coverTrans[ "left_crouch_cqb" ][ 6 ] = %CornerCrL_CQB_trans_IN_6; + anim.coverTrans[ "left_crouch_cqb" ][ 7 ] = %CornerCrL_CQB_trans_IN_7; + anim.coverTrans[ "left_crouch_cqb" ][ 8 ] = %CornerCrL_CQB_trans_IN_8; + //im.coverTrans[ "left_crouch_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "crouch" ][ 1 ] = %covercrouch_run_in_ML; + anim.coverTrans[ "crouch" ][ 2 ] = %covercrouch_run_in_M; + anim.coverTrans[ "crouch" ][ 3 ] = %covercrouch_run_in_MR; + anim.coverTrans[ "crouch" ][ 4 ] = %covercrouch_run_in_L; + anim.coverTrans[ "crouch" ][ 6 ] = %covercrouch_run_in_R; + //im.coverTrans[ "crouch" ][ 7 ] = can't approach from this direction; + //im.coverTrans[ "crouch" ][ 8 ] = can't approach from this direction; + //im.coverTrans[ "crouch" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "stand" ][ 1 ] = %coverstand_trans_IN_ML; + anim.coverTrans[ "stand" ][ 2 ] = %coverstand_trans_IN_M; + anim.coverTrans[ "stand" ][ 3 ] = %coverstand_trans_IN_MR; + anim.coverTrans[ "stand" ][ 4 ] = %coverstand_trans_IN_L; + anim.coverTrans[ "stand" ][ 6 ] = %coverstand_trans_IN_R; + //im.coverTrans[ "stand" ][ 7 ] = can't approach from this direction; + //im.coverTrans[ "stand" ][ 8 ] = can't approach from this direction; + //im.coverTrans[ "stand" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "stand_saw" ][ 1 ] = %saw_gunner_runin_ML; + anim.coverTrans[ "stand_saw" ][ 2 ] = %saw_gunner_runin_M; + anim.coverTrans[ "stand_saw" ][ 3 ] = %saw_gunner_runin_MR; + anim.coverTrans[ "stand_saw" ][ 4 ] = %saw_gunner_runin_L; + anim.coverTrans[ "stand_saw" ][ 6 ] = %saw_gunner_runin_R; + + anim.coverTrans[ "crouch_saw" ][ 1 ] = %saw_gunner_lowwall_runin_ML; + anim.coverTrans[ "crouch_saw" ][ 2 ] = %saw_gunner_lowwall_runin_M; + anim.coverTrans[ "crouch_saw" ][ 3 ] = %saw_gunner_lowwall_runin_MR; + anim.coverTrans[ "crouch_saw" ][ 4 ] = %saw_gunner_lowwall_runin_L; + anim.coverTrans[ "crouch_saw" ][ 6 ] = %saw_gunner_lowwall_runin_R; + + anim.coverTrans[ "prone_saw" ][ 1 ] = %saw_gunner_prone_runin_ML; + anim.coverTrans[ "prone_saw" ][ 2 ] = %saw_gunner_prone_runin_M; + anim.coverTrans[ "prone_saw" ][ 3 ] = %saw_gunner_prone_runin_MR; + + // we need 45 degree angle approaches for exposed... + anim.coverTrans[ "exposed" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed" ][ 1 ] = %CQB_stop_1; + anim.coverTrans[ "exposed" ][ 2 ] = %run_2_stand_F_6; + anim.coverTrans[ "exposed" ][ 3 ] = %CQB_stop_3; + anim.coverTrans[ "exposed" ][ 4 ] = %run_2_stand_90L; + anim.coverTrans[ "exposed" ][ 6 ] = %run_2_stand_90R; + anim.coverTrans[ "exposed" ][ 7 ] = %CQB_stop_7; + anim.coverTrans[ "exposed" ][ 8 ] = %run_2_stand_180L; + anim.coverTrans[ "exposed" ][ 9 ] = %CQB_stop_9; + + anim.coverTrans[ "exposed_crouch" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed_crouch" ][ 1 ] = %CQB_crouch_stop_1; + anim.coverTrans[ "exposed_crouch" ][ 2 ] = %run_2_crouch_F; + anim.coverTrans[ "exposed_crouch" ][ 3 ] = %CQB_crouch_stop_3; + anim.coverTrans[ "exposed_crouch" ][ 4 ] = %run_2_crouch_90L; + anim.coverTrans[ "exposed_crouch" ][ 6 ] = %run_2_crouch_90R; + anim.coverTrans[ "exposed_crouch" ][ 7 ] = %CQB_crouch_stop_7; + anim.coverTrans[ "exposed_crouch" ][ 8 ] = %run_2_crouch_180L; + anim.coverTrans[ "exposed_crouch" ][ 9 ] = %CQB_crouch_stop_9; + + anim.coverTrans[ "exposed_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed_cqb" ][ 1 ] = %CQB_stop_1; + anim.coverTrans[ "exposed_cqb" ][ 2 ] = %CQB_stop_2; // %CQB_stop_2_signal + anim.coverTrans[ "exposed_cqb" ][ 3 ] = %CQB_stop_3; + anim.coverTrans[ "exposed_cqb" ][ 4 ] = %CQB_stop_4; + anim.coverTrans[ "exposed_cqb" ][ 6 ] = %CQB_stop_6; + anim.coverTrans[ "exposed_cqb" ][ 7 ] = %CQB_stop_7; + anim.coverTrans[ "exposed_cqb" ][ 8 ] = %CQB_stop_8; + anim.coverTrans[ "exposed_cqb" ][ 9 ] = %CQB_stop_9; + + anim.coverTrans[ "exposed_crouch_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed_crouch_cqb" ][ 1 ] = %CQB_crouch_stop_1; + anim.coverTrans[ "exposed_crouch_cqb" ][ 2 ] = %CQB_crouch_stop_2; + anim.coverTrans[ "exposed_crouch_cqb" ][ 3 ] = %CQB_crouch_stop_3; + anim.coverTrans[ "exposed_crouch_cqb" ][ 4 ] = %CQB_crouch_stop_4; + anim.coverTrans[ "exposed_crouch_cqb" ][ 6 ] = %CQB_crouch_stop_6; + anim.coverTrans[ "exposed_crouch_cqb" ][ 7 ] = %CQB_crouch_stop_7; + anim.coverTrans[ "exposed_crouch_cqb" ][ 8 ] = %CQB_crouch_stop_8; + anim.coverTrans[ "exposed_crouch_cqb" ][ 9 ] = %CQB_crouch_stop_9; + + anim.coverTrans[ "heat" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "heat" ][ 1 ] = %heat_approach_1; + anim.coverTrans[ "heat" ][ 2 ] = %heat_approach_2; + anim.coverTrans[ "heat" ][ 3 ] = %heat_approach_3; + anim.coverTrans[ "heat" ][ 4 ] = %heat_approach_4; + anim.coverTrans[ "heat" ][ 6 ] = %heat_approach_6; + //anim.coverTrans[ "heat" ][ 7 ] = %heat_approach_8; + anim.coverTrans[ "heat" ][ 8 ] = %heat_approach_8; + //anim.coverTrans[ "heat" ][ 9 ] = %heat_approach_8; + + anim.coverTrans[ "heat_left" ] = []; + anim.coverTrans[ "heat_right" ] = []; + + /************************************************* + * Step in position Animations + *************************************************/ + + anim.coverStepInAnim = []; + anim.coverStepInAnim[ "right" ] = %corner_standR_trans_B_2_alert; + anim.coverStepInAnim[ "right_crouch" ] = %CornerCrR_trans_B_2_alert; + anim.coverStepInAnim[ "left" ] = %corner_standL_trans_B_2_alert_v2; + anim.coverStepInAnim[ "left_crouch" ] = %CornerCrL_trans_B_2_alert; + anim.coverStepInAnim[ "crouch" ] = %covercrouch_aim_2_hide; + anim.coverStepInAnim[ "stand" ] = %coverstand_aim_2_hide; + + anim.coverStepInOffsets = []; + anim.coverStepInAngles = []; + + for( i = 0; i < lastCoverTrans; i++ ) + { + trans = transTypes[i]; + anim.coverStepInOffsets[ trans ] = getMoveDelta( anim.coverStepInAnim[ trans ], 0, 1 ); + anim.coverStepInAngles[ trans ] = getAngleDelta( anim.coverStepInAnim[ trans ], 0, 1 ); + } + + anim.coverStepInAngles[ "right" ] += 90; + anim.coverStepInAngles[ "right_crouch" ] += 90; + anim.coverStepInAngles[ "left" ] -= 90; + anim.coverStepInAngles[ "left_crouch" ] -= 90; + + /************************************************* + * Traverse Animations + *************************************************/ + + anim.coverTrans[ "wall_over_96" ][ 1 ] = %traverse90_IN_ML; + anim.coverTrans[ "wall_over_96" ][ 2 ] = %traverse90_IN_M; + anim.coverTrans[ "wall_over_96" ][ 3 ] = %traverse90_IN_MR; + anim.traverseInfo[ "wall_over_96" ][ "height" ] = 96; + + anim.coverTrans[ "wall_over_40" ][ 1 ] = %traverse_window_M_2_run; + anim.coverTrans[ "wall_over_40" ][ 2 ] = %traverse_window_M_2_run; + anim.coverTrans[ "wall_over_40" ][ 3 ] = %traverse_window_M_2_run; + + /* + anim.coverTrans["wall_over_40"][1] = %traverse40_IN_ML; + anim.coverTrans["wall_over_40"][2] = %traverse40_IN_M; + anim.coverTrans["wall_over_40"][3] = %traverse40_IN_MR; + */ + + + + + /************************************************* + * Exit Animations + *************************************************/ + + anim.coverExit[ "right" ][ 1 ] = %corner_standR_trans_OUT_1; + anim.coverExit[ "right" ][ 2 ] = %corner_standR_trans_OUT_2; + anim.coverExit[ "right" ][ 3 ] = %corner_standR_trans_OUT_3; + anim.coverExit[ "right" ][ 4 ] = %corner_standR_trans_OUT_4; + anim.coverExit[ "right" ][ 6 ] = %corner_standR_trans_OUT_6; + //im.coverExit[ "right" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right" ][ 8 ] = %corner_standR_trans_OUT_8; + anim.coverExit[ "right" ][ 9 ] = %corner_standR_trans_OUT_9; + + anim.coverExit[ "right_crouch" ][ 1 ] = %CornerCrR_trans_OUT_ML; + anim.coverExit[ "right_crouch" ][ 2 ] = %CornerCrR_trans_OUT_M; + anim.coverExit[ "right_crouch" ][ 3 ] = %CornerCrR_trans_OUT_MR; + anim.coverExit[ "right_crouch" ][ 4 ] = %CornerCrR_trans_OUT_L; + anim.coverExit[ "right_crouch" ][ 6 ] = %CornerCrR_trans_OUT_R; + //im.coverExit[ "right_crouch" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right_crouch" ][ 8 ] = %CornerCrR_trans_OUT_F; + anim.coverExit[ "right_crouch" ][ 9 ] = %CornerCrR_trans_OUT_MF; + + anim.coverExit[ "right_cqb" ][ 1 ] = %corner_standR_trans_CQB_OUT_1; + anim.coverExit[ "right_cqb" ][ 2 ] = %corner_standR_trans_CQB_OUT_2; + anim.coverExit[ "right_cqb" ][ 3 ] = %corner_standR_trans_CQB_OUT_3; + anim.coverExit[ "right_cqb" ][ 4 ] = %corner_standR_trans_CQB_OUT_4; + anim.coverExit[ "right_cqb" ][ 6 ] = %corner_standR_trans_CQB_OUT_6; + //im.coverExit[ "right_cqb" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right_cqb" ][ 8 ] = %corner_standR_trans_CQB_OUT_8; + anim.coverExit[ "right_cqb" ][ 9 ] = %corner_standR_trans_CQB_OUT_9; + + anim.coverExit[ "right_crouch_cqb" ][ 1 ] = %CornerCrR_CQB_trans_OUT_1; + anim.coverExit[ "right_crouch_cqb" ][ 2 ] = %CornerCrR_CQB_trans_OUT_2; + anim.coverExit[ "right_crouch_cqb" ][ 3 ] = %CornerCrR_CQB_trans_OUT_3; + anim.coverExit[ "right_crouch_cqb" ][ 4 ] = %CornerCrR_CQB_trans_OUT_4; + anim.coverExit[ "right_crouch_cqb" ][ 6 ] = %CornerCrR_CQB_trans_OUT_6; + //im.coverExit[ "right_crouch_cqb" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right_crouch_cqb" ][ 8 ] = %CornerCrR_CQB_trans_OUT_8; + anim.coverExit[ "right_crouch_cqb" ][ 9 ] = %CornerCrR_CQB_trans_OUT_9; + + + anim.coverExit[ "left" ][ 1 ] = %corner_standL_trans_OUT_1; + anim.coverExit[ "left" ][ 2 ] = %corner_standL_trans_OUT_2; + anim.coverExit[ "left" ][ 3 ] = %corner_standL_trans_OUT_3; + anim.coverExit[ "left" ][ 4 ] = %corner_standL_trans_OUT_4; + anim.coverExit[ "left" ][ 6 ] = %corner_standL_trans_OUT_6; + anim.coverExit[ "left" ][ 7 ] = %corner_standL_trans_OUT_7; + anim.coverExit[ "left" ][ 8 ] = %corner_standL_trans_OUT_8; + //im.coverExit[ "left" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "left_crouch" ][ 1 ] = %CornerCrL_trans_OUT_ML; + anim.coverExit[ "left_crouch" ][ 2 ] = %CornerCrL_trans_OUT_M; + anim.coverExit[ "left_crouch" ][ 3 ] = %CornerCrL_trans_OUT_MR; + anim.coverExit[ "left_crouch" ][ 4 ] = %CornerCrL_trans_OUT_L; + anim.coverExit[ "left_crouch" ][ 6 ] = %CornerCrL_trans_OUT_R; + anim.coverExit[ "left_crouch" ][ 7 ] = %CornerCrL_trans_OUT_MF; + anim.coverExit[ "left_crouch" ][ 8 ] = %CornerCrL_trans_OUT_F; + //im.coverExit[ "left_crouch" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "left_cqb" ][ 1 ] = %corner_standL_trans_CQB_OUT_1; + anim.coverExit[ "left_cqb" ][ 2 ] = %corner_standL_trans_CQB_OUT_2; + anim.coverExit[ "left_cqb" ][ 3 ] = %corner_standL_trans_CQB_OUT_3; + anim.coverExit[ "left_cqb" ][ 4 ] = %corner_standL_trans_CQB_OUT_4; + anim.coverExit[ "left_cqb" ][ 6 ] = %corner_standL_trans_CQB_OUT_6; + anim.coverExit[ "left_cqb" ][ 7 ] = %corner_standL_trans_CQB_OUT_7; + anim.coverExit[ "left_cqb" ][ 8 ] = %corner_standL_trans_CQB_OUT_8; + //im.coverExit[ "left_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "left_crouch_cqb" ][ 1 ] = %CornerCrL_CQB_trans_OUT_1; + anim.coverExit[ "left_crouch_cqb" ][ 2 ] = %CornerCrL_CQB_trans_OUT_2; + anim.coverExit[ "left_crouch_cqb" ][ 3 ] = %CornerCrL_CQB_trans_OUT_3; + anim.coverExit[ "left_crouch_cqb" ][ 4 ] = %CornerCrL_CQB_trans_OUT_4; + anim.coverExit[ "left_crouch_cqb" ][ 6 ] = %CornerCrL_CQB_trans_OUT_6; + anim.coverExit[ "left_crouch_cqb" ][ 7 ] = %CornerCrL_CQB_trans_OUT_7; + anim.coverExit[ "left_crouch_cqb" ][ 8 ] = %CornerCrL_CQB_trans_OUT_8; + //im.coverExit[ "left_crouch_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "crouch" ][ 1 ] = %covercrouch_run_out_ML; + anim.coverExit[ "crouch" ][ 2 ] = %covercrouch_run_out_M; + anim.coverExit[ "crouch" ][ 3 ] = %covercrouch_run_out_MR; + anim.coverExit[ "crouch" ][ 4 ] = %covercrouch_run_out_L; + anim.coverExit[ "crouch" ][ 6 ] = %covercrouch_run_out_R; + //im.coverExit[ "crouch" ][ 7 ] = can't approach from this direction; + //im.coverExit[ "crouch" ][ 8 ] = can't approach from this direction; + //im.coverExit[ "crouch" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "stand" ][ 1 ] = %coverstand_trans_OUT_ML; + anim.coverExit[ "stand" ][ 2 ] = %coverstand_trans_OUT_M; + anim.coverExit[ "stand" ][ 3 ] = %coverstand_trans_OUT_MR; + anim.coverExit[ "stand" ][ 4 ] = %coverstand_trans_OUT_L; + anim.coverExit[ "stand" ][ 6 ] = %coverstand_trans_OUT_R; + //im.coverExit[ "stand" ][ 7 ] = can't approach from this direction; + //im.coverExit[ "stand" ][ 8 ] = can't approach from this direction; + //im.coverExit[ "stand" ][ 9 ] = can't approach from this direction; + anim.coverExit[ "stand_saw" ][ 1 ] = %saw_gunner_runout_ML; + anim.coverExit[ "stand_saw" ][ 2 ] = %saw_gunner_runout_M; + anim.coverExit[ "stand_saw" ][ 3 ] = %saw_gunner_runout_MR; + anim.coverExit[ "stand_saw" ][ 4 ] = %saw_gunner_runout_L; + anim.coverExit[ "stand_saw" ][ 6 ] = %saw_gunner_runout_R; + +// anim.coverExit["prone_saw" ][1] = %saw_gunner_prone_runout_ML; + anim.coverExit[ "prone_saw" ][ 2 ] = %saw_gunner_prone_runout_M; +// anim.coverExit["prone_saw" ][3] = %saw_gunner_prone_runout_MR; + anim.coverExit[ "prone_saw" ][ 4 ] = %saw_gunner_prone_runout_L; + anim.coverExit[ "prone_saw" ][ 6 ] = %saw_gunner_prone_runout_R; +// anim.coverExit["prone_saw" ][7] = %saw_gunner_prone_runout_F; // need this anim or a way to exclude it + anim.coverExit[ "prone_saw" ][ 8 ] = %saw_gunner_prone_runout_F; + + anim.coverExit[ "crouch_saw" ][ 1 ] = %saw_gunner_lowwall_runout_ML; + anim.coverExit[ "crouch_saw" ][ 2 ] = %saw_gunner_lowwall_runout_M; + anim.coverExit[ "crouch_saw" ][ 3 ] = %saw_gunner_lowwall_runout_MR; + anim.coverExit[ "crouch_saw" ][ 4 ] = %saw_gunner_lowwall_runout_L; + anim.coverExit[ "crouch_saw" ][ 6 ] = %saw_gunner_lowwall_runout_R; + + // we need 45 degree angle exits for exposed... + anim.coverExit[ "exposed" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed" ][ 1 ] = %CQB_start_1; + anim.coverExit[ "exposed" ][ 2 ] = %stand_2_run_180L; + anim.coverExit[ "exposed" ][ 3 ] = %CQB_start_3; + anim.coverExit[ "exposed" ][ 4 ] = %stand_2_run_L; + anim.coverExit[ "exposed" ][ 6 ] = %stand_2_run_R; + anim.coverExit[ "exposed" ][ 7 ] = %CQB_start_7; + anim.coverExit[ "exposed" ][ 8 ] = %surprise_start_v1; // %stand_2_run_F_2; + anim.coverExit[ "exposed" ][ 9 ] = %CQB_start_9; + + anim.coverExit[ "exposed_crouch" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed_crouch" ][ 1 ] = %CQB_crouch_start_1; + anim.coverExit[ "exposed_crouch" ][ 2 ] = %crouch_2run_180; + anim.coverExit[ "exposed_crouch" ][ 3 ] = %CQB_crouch_start_3; + anim.coverExit[ "exposed_crouch" ][ 4 ] = %crouch_2run_L; + anim.coverExit[ "exposed_crouch" ][ 6 ] = %crouch_2run_R; + anim.coverExit[ "exposed_crouch" ][ 7 ] = %CQB_crouch_start_7; + anim.coverExit[ "exposed_crouch" ][ 8 ] = %crouch_2run_F; + anim.coverExit[ "exposed_crouch" ][ 9 ] = %CQB_crouch_start_9; + + anim.coverExit[ "exposed_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed_cqb" ][ 1 ] = %CQB_start_1; + anim.coverExit[ "exposed_cqb" ][ 2 ] = %CQB_start_2; + anim.coverExit[ "exposed_cqb" ][ 3 ] = %CQB_start_3; + anim.coverExit[ "exposed_cqb" ][ 4 ] = %CQB_start_4; + anim.coverExit[ "exposed_cqb" ][ 6 ] = %CQB_start_6; + anim.coverExit[ "exposed_cqb" ][ 7 ] = %CQB_start_7; + anim.coverExit[ "exposed_cqb" ][ 8 ] = %CQB_start_8; + anim.coverExit[ "exposed_cqb" ][ 9 ] = %CQB_start_9; + + anim.coverExit[ "exposed_crouch_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed_crouch_cqb" ][ 1 ] = %CQB_crouch_start_1; + anim.coverExit[ "exposed_crouch_cqb" ][ 2 ] = %CQB_crouch_start_2; + anim.coverExit[ "exposed_crouch_cqb" ][ 3 ] = %CQB_crouch_start_3; + anim.coverExit[ "exposed_crouch_cqb" ][ 4 ] = %CQB_crouch_start_4; + anim.coverExit[ "exposed_crouch_cqb" ][ 6 ] = %CQB_crouch_start_6; + anim.coverExit[ "exposed_crouch_cqb" ][ 7 ] = %CQB_crouch_start_7; + anim.coverExit[ "exposed_crouch_cqb" ][ 8 ] = %CQB_crouch_start_8; + anim.coverExit[ "exposed_crouch_cqb" ][ 9 ] = %CQB_crouch_start_9; + + anim.coverExit[ "heat" ] = []; + anim.coverExit[ "heat" ][ 1 ] = %heat_exit_1; + anim.coverExit[ "heat" ][ 2 ] = %heat_exit_2; + anim.coverExit[ "heat" ][ 3 ] = %heat_exit_3; + anim.coverExit[ "heat" ][ 4 ] = %heat_exit_4; + anim.coverExit[ "heat" ][ 6 ] = %heat_exit_6; //%heat_exit_6a + anim.coverExit[ "heat" ][ 7 ] = %heat_exit_7; + anim.coverExit[ "heat" ][ 8 ] = %heat_exit_8; //%heat_exit_8a + anim.coverExit[ "heat" ][ 9 ] = %heat_exit_9; + + anim.coverExit[ "heat_left" ] = []; + anim.coverExit[ "heat_left" ][ 1 ] = %heat_exit_1; + anim.coverExit[ "heat_left" ][ 2 ] = %heat_exit_2; + anim.coverExit[ "heat_left" ][ 3 ] = %heat_exit_3; + anim.coverExit[ "heat_left" ][ 4 ] = %heat_exit_4; + anim.coverExit[ "heat_left" ][ 6 ] = %heat_exit_6; + anim.coverExit[ "heat_left" ][ 7 ] = %heat_exit_8L; + anim.coverExit[ "heat_left" ][ 8 ] = %heat_exit_8L; + anim.coverExit[ "heat_left" ][ 9 ] = %heat_exit_8R; + + anim.coverExit[ "heat_right" ] = []; + anim.coverExit[ "heat_right" ][ 1 ] = %heat_exit_1; + anim.coverExit[ "heat_right" ][ 2 ] = %heat_exit_2; + anim.coverExit[ "heat_right" ][ 3 ] = %heat_exit_3; + anim.coverExit[ "heat_right" ][ 4 ] = %heat_exit_4; + anim.coverExit[ "heat_right" ][ 6 ] = %heat_exit_6; + anim.coverExit[ "heat_right" ][ 7 ] = %heat_exit_8L; + anim.coverExit[ "heat_right" ][ 8 ] = %heat_exit_8R; + anim.coverExit[ "heat_right" ][ 9 ] = %heat_exit_8R; + + + for ( i = 1; i <= 6; i++ ) + { + if ( i == 5 ) + continue; + + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + if ( isdefined( anim.coverTrans[ trans ][ i ] ) ) + { + anim.coverTransDist [ trans ][ i ] = getMoveDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + anim.coverTransAngles[ trans ][ i ] = getAngleDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + } + + if ( isdefined( anim.coverExit [ trans ] ) && isdefined( anim.coverExit [ trans ][ i ] ) ) + { + // get exit dist only to code_move + if ( animHasNotetrack( anim.coverExit[ trans ][ i ], "code_move" ) ) + codeMoveTime = getNotetrackTimes( anim.coverExit[ trans ][ i ], "code_move" )[ 0 ]; + else + codeMoveTime = 1; + + anim.coverExitDist [ trans ][ i ] = getMoveDelta( anim.coverExit [ trans ][ i ], 0, codeMoveTime ); + anim.coverExitAngles [ trans ][ i ] = getAngleDelta( anim.coverExit [ trans ][ i ], 0, 1 ); + } + } + } + + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + anim.coverTransLongestDist[ trans ] = 0; + + for ( i = 1; i <= 6; i++ ) + { + if ( i == 5 || !isdefined( anim.coverTrans[ trans ][ i ] ) ) + continue; + + lengthSq = lengthSquared( anim.coverTransDist[ trans ][ i ] ); + if ( anim.coverTransLongestDist[ trans ] < lengthSq ) + anim.coverTransLongestDist[ trans ] = lengthSq; + } + + anim.coverTransLongestDist[ trans ] = sqrt( anim.coverTransLongestDist[ trans ] ); + } + + anim.exposedTransition[ "exposed" ] = true; + anim.exposedTransition[ "exposed_crouch" ] = true; + anim.exposedTransition[ "exposed_cqb" ] = true; + anim.exposedTransition[ "exposed_crouch_cqb" ] = true; + anim.exposedTransition[ "heat" ] = true; + + anim.longestExposedApproachDist = 0; + + foreach ( trans, transType in anim.exposedTransition ) + { + for ( i = 7; i <= 9; i++ ) + { + if ( isdefined( anim.coverTrans[ trans ][ i ] ) ) + { + anim.coverTransDist [ trans ][ i ] = getMoveDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + anim.coverTransAngles[ trans ][ i ] = getAngleDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + } + + if ( isdefined( anim.coverExit [ trans ][ i ] ) ) + { + // get exit dist only to code_move + assert( animHasNotetrack( anim.coverExit[ trans ][ i ], "code_move" ) ); + codeMoveTime = getNotetrackTimes( anim.coverExit[ trans ][ i ], "code_move" )[ 0 ]; + + anim.coverExitDist [ trans ][ i ] = getMoveDelta( anim.coverExit [ trans ][ i ], 0, codeMoveTime ); + anim.coverExitAngles [ trans ][ i ] = getAngleDelta( anim.coverExit [ trans ][ i ], 0, 1 ); + } + } + + for ( i = 1; i <= 9; i++ ) + { + if ( !isdefined( anim.coverTrans[ trans ][ i ] ) ) + continue; + + len = length( anim.coverTransDist[ trans ][ i ] ); + if ( len > anim.longestExposedApproachDist ) + anim.longestExposedApproachDist = len; + } + } + + + // the FindBestSplitTime calls below are used to find these values. + // all of this is for corner nodes. + + anim.coverTransSplit[ "left" ][ 7 ] = 0.369369;// delta of( 35.5356, 3.27114, 0 ) + anim.coverTransSplit[ "left_crouch" ][ 7 ] = 0.319319; // delta of (32.2281, 0.356673, 0) + anim.coverTransSplit[ "left_cqb" ][ 7 ] = 0.451451; // delta of (33.1115, 1.05645, 0) + anim.coverTransSplit[ "left_crouch_cqb" ][ 7 ] = 0.246246; // delta of (34.2986, 2.32586, 0) + anim.coverExitSplit[ "left" ][ 7 ] = 0.547548;// delta of( 37.5652, 5.61999, 0 ) + anim.coverExitSplit[ "left_crouch" ][ 7 ] = 0.593594;// delta of( 35.9166, 3.88091, 0 ) + anim.coverExitSplit[ "left_cqb" ][ 7 ] = 0.702703; // delta of (32.9692, 0.881301, 0) + anim.coverExitSplit[ "left_crouch_cqb" ][ 7 ] = 0.718719; // delta of (33.6642, 1.70904, 0) + anim.coverExitSplit[ "heat_left" ][ 7 ] = 0.42; + + anim.coverTransSplit[ "left" ][ 8 ] = 0.525526;// delta of( 32.9863, 0.925748, 0 ) + anim.coverTransSplit[ "left_crouch" ][ 8 ] = 0.428428;// delta of( 38.4125, 6.445, 0 ) + anim.coverTransSplit[ "left_cqb" ][ 8 ] = 0.479479; // delta of (33.892, 1.86121, 0) + anim.coverTransSplit[ "left_crouch_cqb" ][ 8 ] = 0.33033; // delta of (35.8107, 3.70985, 0) + anim.coverExitSplit[ "left" ][ 8 ] = 0.614615;// delta of( 34.298, 2.26239, 0 ) + anim.coverExitSplit[ "left_crouch" ][ 8 ] = 0.451451; // delta of (33.0388, 0.964628, 0) + anim.coverExitSplit[ "left_cqb" ][ 8 ] = 0.439439; // delta of (35.6563, 3.64754, 0)) + anim.coverExitSplit[ "left_crouch_cqb" ][ 8 ] = 0.603604; // delta of (33.0797, 1.14774, 0) + anim.coverExitSplit[ "heat_left" ][ 8 ] = 0.42; + + anim.coverTransSplit[ "right" ][ 8 ] = 0.458458;// delta of( 35.6571, 3.63511, 0 ) + anim.coverTransSplit[ "right_crouch" ][ 8 ] = 0.248248; // delta of (34.6368, 2.67554, 0) + anim.coverTransSplit[ "right_cqb" ][ 8 ] = 0.458458; // delta of (35.6571, 3.63511, 0) + anim.coverTransSplit[ "right_crouch_cqb" ][ 8 ] = 0.311311; // delta of (34.2736, 2.32471, 0) + anim.coverExitSplit[ "right" ][ 8 ] = 0.457457;// delta of( 36.3085, 4.34586, 0 ) + anim.coverExitSplit[ "right_crouch" ][ 8 ] = 0.545546; // delta of (33.1181, 1.14301, -0.0001) + anim.coverExitSplit[ "right_cqb" ][ 8 ] = 0.540541; // delta of (33.0089, 1.0005, 0) + anim.coverExitSplit[ "right_crouch_cqb" ][ 8 ] = 0.399399; // delta of (34.7739, 2.41176, 0) + anim.coverExitSplit[ "heat_right" ][ 8 ] = 0.4; + + anim.coverTransSplit[ "right" ][ 9 ] = 0.546547;// delta of( 37.7732, 5.76641, 0 ) + anim.coverTransSplit[ "right_crouch" ][ 9 ] = 0.2002; // delta of (36.3871, 4.39434, 0) + anim.coverTransSplit[ "right_cqb" ][ 9 ] = 0.546547;// delta of( 37.7732, 5.76641, 0 ) + anim.coverTransSplit[ "right_crouch_cqb" ][ 9 ] = 0.232232; // delta of (35.8102, 3.81592, 0) + anim.coverExitSplit[ "right" ][ 9 ] = 0.483483; // delta of (35.251, 3.31115, 0) + anim.coverExitSplit[ "right_crouch" ][ 9 ] = 0.493493; // delta of (34.4959, 2.45688, -0.0001) + anim.coverExitSplit[ "right_cqb" ][ 9 ] = 0.565566; // delta of (35.4487, 3.42926, 0) + anim.coverExitSplit[ "right_crouch_cqb" ][ 9 ] = 0.518519; // delta of (35.4592, 1.47273, 0) + anim.coverExitSplit[ "heat_right" ][ 9 ] = 0.4; + + /# + setDvarIfUninitialized( "scr_findsplittimes", "0" ); + #/ + + splitArrivals = []; + splitArrivals[ "left" ] = 1; + splitArrivals[ "left_crouch" ] = 1; + splitArrivals[ "left_crouch_cqb" ] = 1; + splitArrivals[ "left_cqb" ] = 1; + + splitExits = []; + splitExits[ "left" ] = 1; + splitExits[ "left_crouch" ] = 1; + splitExits[ "left_crouch_cqb" ] = 1; + splitExits[ "left_cqb" ] = 1; + splitExits[ "heat_left" ] = 1; + + GetSplitTimes( 7, 8, false, splitArrivals, splitExits ); + + + splitArrivals = []; + splitArrivals[ "right" ] = 1; + splitArrivals[ "right_crouch" ] = 1; + splitArrivals[ "right_cqb" ] = 1; + splitArrivals[ "right_crouch_cqb" ] = 1; + + splitExits = []; + splitExits[ "right" ] = 1; + splitExits[ "right_crouch" ] = 1; + splitExits[ "right_cqb" ] = 1; + splitExits[ "right_crouch_cqb" ] = 1; + splitExits[ "heat_right" ] = 1; + + GetSplitTimes( 8, 9, true, splitArrivals, splitExits ); + + + /# + //thread checkApproachAngles( transTypes ); + #/ + + anim.arrivalEndStance["left"] = "stand"; + anim.arrivalEndStance["left_cqb"] = "stand"; + anim.arrivalEndStance["right"] = "stand"; + anim.arrivalEndStance["right_cqb"] = "stand"; + anim.arrivalEndStance["stand"] = "stand"; + anim.arrivalEndStance["stand_saw"] = "stand"; + anim.arrivalEndStance["exposed"] = "stand"; + anim.arrivalEndStance["exposed_cqb"] = "stand"; + anim.arrivalEndStance["heat"] = "stand"; + anim.arrivalEndStance["left_crouch"] = "crouch"; + anim.arrivalEndStance["left_crouch_cqb"] = "crouch"; + anim.arrivalEndStance["right_crouch"] = "crouch"; + anim.arrivalEndStance["right_crouch_cqb"] = "crouch"; + anim.arrivalEndStance["crouch_saw"] = "crouch"; + anim.arrivalEndStance["crouch"] = "crouch"; + anim.arrivalEndStance["exposed_crouch"] = "crouch"; + anim.arrivalEndStance["exposed_crouch_cqb"] = "crouch"; + anim.arrivalEndStance["prone_saw"] = "prone"; + + anim.requiredExitStance[ "Cover Stand" ] = "stand"; + anim.requiredExitStance[ "Conceal Stand" ] = "stand"; + anim.requiredExitStance[ "Cover Crouch" ] = "crouch"; + anim.requiredExitStance[ "Conceal Crouch" ] = "crouch"; +} + + +GetSplitTimes( begin, end, isRightSide, splitArrivals, splitExits ) +{ + for ( i = begin; i <= end; i++ ) + { + foreach ( type, val in splitArrivals ) + { + anim.coverTransPreDist[ type ][ i ] = getMoveDelta( anim.coverTrans[ type ][ i ], 0, getTransSplitTime( type, i ) ); + anim.coverTransDist [ type ][ i ] = getMoveDelta( anim.coverTrans[ type ][ i ], 0, 1 ) - anim.coverTransPreDist[ type ][ i ]; + anim.coverTransAngles [ type ][ i ] = getAngleDelta( anim.coverTrans[ type ][ i ], 0, 1 ); + } + + foreach ( type, val in splitExits ) + { + anim.coverExitDist [ type ][ i ] = getMoveDelta( anim.coverExit [ type ][ i ], 0, getExitSplitTime( type, i ) ); + anim.coverExitPostDist[ type ][ i ] = getMoveDelta( anim.coverExit [ type ][ i ], 0, 1 ) - anim.coverExitDist[ type ][ i ]; + anim.coverExitAngles [ type ][ i ] = getAngleDelta( anim.coverExit [ type ][ i ], 0, 1 ); + } + + /# + if ( getdebugdvar( "scr_findsplittimes" ) != "0" ) + { + foreach ( type, val in splitArrivals ) + { + if ( isSubStr( type, "heat" ) ) + continue; + + FindBestSplitTime( anim.coverTrans[ type ][ i ], true, isRightSide, "anim.coverTransSplit[ \"" + type + "\" ][ " + i + " ]", type + " arrival in dir " + i ); + AssertIsValidSplitDelta( DeltaRotate( anim.coverTransDist[ type ][ i ], 180 - anim.coverTransAngles[ type ][ i ] ), isRightSide, type + " arrival in dir " + i ); + } + + foreach ( type, val in splitExits ) + { + if ( isSubStr( type, "heat" ) ) + continue; + + FindBestSplitTime( anim.coverExit [ type ][ i ], false, isRightSide, "anim.coverExitSplit[ \"" + type + "\" ][ " + i + " ]", type + " exit in dir " + i ); + AssertIsValidSplitDelta( anim.coverExitDist[ type ][ i ], isRightSide, type + " exit in dir " + i ); + } + } + #/ + } +} + +/# +FindBestSplitTime( exitanim, isapproach, isright, arrayname, debugname ) +{ + angleDelta = getAngleDelta( exitanim, 0, 1 ); + fullDelta = getMoveDelta( exitanim, 0, 1 ); + numiter = 1000; + + bestsplit = -1; + bestvalue = -100000000; + bestdelta = ( 0, 0, 0 ); + + for ( i = 0; i < numiter; i++ ) + { + splitTime = 1.0 * i / ( numiter - 1 ); + + delta = getMoveDelta( exitanim, 0, splitTime ); + if ( isapproach ) + delta = DeltaRotate( fullDelta - delta, 180 - angleDelta ); + if ( isright ) + delta = ( delta[ 0 ], 0 - delta[ 1 ], delta[ 2 ] ); + + val = min( delta[ 0 ] - 32, delta[ 1 ] ); + + if ( val > bestvalue || bestsplit < 0 ) + { + bestvalue = val; + bestsplit = splitTime; + bestdelta = delta; + } + } + + if ( bestdelta[ 0 ] < 32 || bestdelta[ 1 ] < 0 ) + { + println( "^0 ^1" + debugname + " has no valid split time available! Best was at " + bestsplit + ", delta of " + bestdelta ); + return; + } + //println("^0 ^2" + debugname + " has best split time at " + bestsplit + ", delta of " + bestdelta ); + println( "^0 ^2" + arrayname + " = " + bestsplit + "; // delta of " + bestdelta ); +} + + +DeltaRotate( delta, yaw ) +{ + cosine = cos( yaw ); + sine = sin( yaw ); + return( delta[ 0 ] * cosine - delta[ 1 ] * sine, delta[ 1 ] * cosine + delta[ 0 ] * sine, 0 ); +} + +AssertIsValidSplitDelta( delta, isRightSide, debugname ) +{ + if ( isRightSide ) + delta = ( delta[ 0 ], 0 - delta[ 1 ], delta[ 2 ] ); + + // in a delta, x is forward and y is left + + // assert the delta goes out far enough from the node + if ( delta[ 0 ] < 32 ) + println( "^0 ^1" + debugname + " doesn't go out from the node far enough in the given split time (delta = " + delta + ")" ); + + // assert the delta doesn't go into the wall + if ( delta[ 1 ] < 0 ) + println( "^0 ^1" + debugname + " goes into the wall during the given split time (delta = " + delta + ")" ); +} + +checkApproachAngles( transTypes ) +{ + idealTransAngles[ 1 ] = 45; + idealTransAngles[ 2 ] = 0; + idealTransAngles[ 3 ] = -45; + idealTransAngles[ 4 ] = 90; + idealTransAngles[ 6 ] = -90; + idealTransAngles[ 7 ] = 135; + idealTransAngles[ 8 ] = 180; + idealTransAngles[ 9 ] = -135; + + wait .05; + + for ( i = 1; i <= 9; i++ ) + { + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + idealAdd = 0; + if ( trans == "left" || trans == "left_crouch" ) + idealAdd = 90; + else if ( trans == "right" || trans == "right_crouch" ) + idealAdd = -90; + + if ( isdefined( anim.coverTransAngles[ trans ][ i ] ) ) + { + correctAngle = AngleClamp180( idealTransAngles[ i ] + idealAdd ); + actualAngle = AngleClamp180( anim.coverTransAngles[ trans ][ i ] ); + if ( AbsAngleClamp180( actualAngle - correctAngle ) > 7 ) + { + println( "^1Cover approach animation has bad yaw delta: anim.coverTrans[\"" + trans + "\"][" + i + "]; is ^2" + actualAngle + "^1, should be closer to ^2" + correctAngle + "^1." ); + } + } + } + } + + for ( i = 1; i <= 9; i++ ) + { + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + idealAdd = 0; + if ( trans == "left" || trans == "left_crouch" ) + idealAdd = 90; + else if ( trans == "right" || trans == "right_crouch" ) + idealAdd = -90; + + if ( isdefined( anim.coverExitAngles[ trans ][ i ] ) ) + { + correctAngle = AngleClamp180( -1 * ( idealTransAngles[ i ] + idealAdd + 180 ) ); + actualAngle = AngleClamp180( anim.coverExitAngles[ trans ][ i ] ); + if ( AbsAngleClamp180( actualAngle - correctAngle ) > 7 ) + { + println( "^1Cover exit animation has bad yaw delta: anim.coverTrans[\"" + trans + "\"][" + i + "]; is ^2" + actualAngle + "^1, should be closer to ^2" + correctAngle + "^1." ); + } + } + } + } +} +#/ + +getExitSplitTime( approachType, dir ) +{ + exitAnim = anim.coverExit[ approachType ][ dir ]; + if ( animHasNotetrack( exitAnim, "split_time" ) ) + { + exitAlignTimes = getNotetrackTimes( exitAnim, "split_time" ); + + assert( exitAlignTimes.size == 1 ); + if ( exitAlignTimes.size == 0 ) + { + return anim.coverExitSplit[ approachType ][ dir ]; + } + + return exitAlignTimes[0]; + } + + return anim.coverExitSplit[ approachType ][ dir ]; +} + +getTransSplitTime( approachType, dir ) +{ + arrivalAnim = anim.coverTrans[ approachType ][ dir ]; + if ( animHasNotetrack( arrivalAnim, "split_time" ) ) + { + arrivalSplitTimes = getNotetrackTimes( arrivalAnim, "split_time" ); + + assert( arrivalSplitTimes.size == 1 ); + if ( arrivalSplitTimes.size == 0 ) + { + return anim.coverTransSplit[ approachType ][ dir ]; + } + + return arrivalSplitTimes[0]; + } + + return anim.coverTransSplit[ approachType ][ dir ]; +} diff --git a/animscripts/locked_combat.gsc b/animscripts/locked_combat.gsc new file mode 100644 index 0000000..5c214ce --- /dev/null +++ b/animscripts/locked_combat.gsc @@ -0,0 +1,638 @@ +//**************************************************************************** +// ** +// Confidential - (C) Activision Publishing, Inc. 2011 ** +// ** +//**************************************************************************** +// ** +// Module: Actor locked combat system ** +// Created: 7/13/11 - John Webb ** +// ** +//**************************************************************************** + +#include common_scripts\utility; +#include animscripts\combat_utility; +#include animscripts\utility; + +#using_animtree( "generic_human" ); +//******************************************************************* +// * +// * +//******************************************************************* +locked_combat() +{ + self notify( "killanimscript" ); + + self endon( "death" ); + self endon( "killanimscript" ); + self endon( "locked_combat_transition" ); + animscripts\utility::initialize( "locked_combat" ); + self.noRunNGun = true; + self.dontMelee = true; + self.disableExits = true; + self.disableArrivals = true; + self.disableBulletWhizbyReaction = true; + self.keepClaimedNode = false; + self.combatMode = "no_cover"; + + self animmode( "point_relative" ); + + // Setup the locked covernode + self locked_setup_covernode( level._locked_combat.nodes[ self.current_node_key ] ); + + // Run the cover behavior + while ( 1 ) + { + self [[ level._locked_combat.coverTypes[ self.coverType ].behavior_func ]](); + wait 0.05; + } + +} + +locked_setup_covernode( node ) +{ + self.coverNode = node; + self.coverType = self.coverNode.type; + self.a.coverMode = "hide"; + self.a.atConcealmentNode = false; + self.locked_combat = true; + self.fixed_node = true; // Need to use something else + self.a.pose = "stand"; + self.fixedNode = false; + + AssertEx( isdefined( level._locked_combat.coverTypes[ self.coverType ] ), "Locked Combat Cover Type " + self.coverType + " has not been initialized." ); + + self.hideYawOffset = 0; + self.a.leanAim = undefined; + self [[ level._locked_combat.coverTypes[ self.coverType ].init_func ]](); + + locked_combat_orient( self.hideYawOffset ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_combat_orient( nodeAngleOffset ) +{ + node = self.coverNode; + self orientmode( "face angle", self.angles[1] ); + //self animmode( "point_relative" ); + + relYaw = AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) ); + + self thread maintain_orientation(); +} + +//******************************************************************* +// * +// * +//******************************************************************* +maintain_orientation() +{ + self endon( "killanimscript" ); + while ( 1 ) + { + self OrientMode( "face angle", self.coverNode.angles[ 1 ] ); + wait 0.05; + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* +get_valid_peekouts() +{ + modes = []; + modes[ modes.size ] = "stand"; + modes[ modes.size ] = "crouch"; + return modes; +} + + +//******************************************************************* +// * +// * +//******************************************************************* +locked_popUpAndShoot() +{ + self.keepClaimedNodeIfValid = true; + + locked_combat_orient( self.hideYawOffset ); + + if ( !locked_pop_up() ) + return false; + + locked_shoot(); + + self endFireAndAnimIdleThread(); + + locked_go_to_hide(); + + self.coverCrouchLean_aimmode = undefined; + self.keepClaimedNodeIfValid = false; + + return true; +} + + +//******************************************************************* +// * +// * +//******************************************************************* +locked_shoot() +{ + self endon( "return_to_cover" ); + + self maps\_gameskill::didSomethingOtherThanShooting(); + + while ( 1 ) + { + if ( isdefined( self.shouldReturnToCover ) ) + break; + + if ( !isdefined( self.shootPos ) ) + { + assert( !isdefined( self.shootEnt ) ); + // give shoot_behavior a chance to iterate + self waittill( "do_slow_things" ); + waittillframeend; + if ( isdefined( self.shootPos ) ) + { + continue; + } + break; + } + + if ( !self.bulletsInClip ) + break; + + self thread aimIdleThread(); + //println( "Shooting" ); + shootUntilShootBehaviorChange(); + + self clearAnim( %add_fire, .2 ); + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* +locked_go_to_hide() +{ + self notify( "return_to_cover" ); + + self.changingCoverPos = true; + self notify( "done_changing_cover_pos" ); + + self endAimIdleThread(); + + animRate = 1.0; + //animRate = pop_up_and_hide_speed(); + animation = animArray( self.a.coverMode + "_2_hide" ); + + self clearAnim( %body, 0.2 ); + if ( isdefined( self.current_node_key ) ) + { + node = level._locked_combat.nodes[ self.current_node_key ]; + assert( isdefined( node ) ); + self animrelative( "go_to_hide", self.origin, self.angles, animation ); + } + + //println( "Returning to hide" ); + + self animscripts\shared::DoNoteTracks( "go_to_hide" ); + + self StopAnimScripted(); + + self.a.coverMode = "hide"; + self animmode( "point_relative" ); + + if ( self.coverType == "stand" ) + self.a.special = "cover_stand"; + else + self.a.special = "cover_crouch"; + + self.changingCoverPos = false; +} + + +//******************************************************************* +// * +// * +//******************************************************************* +locked_pop_up() +{ + assert( !isdefined( self.a.coverMode ) || self.a.coverMode == "hide" ); + + newCoverMode = self [[ level._locked_combat.coverTypes[ self.coverType ].cover_mode_func ]](); + + if ( !isdefined( newCoverMode ) ) + { + //println( "No valid coverMode found" ); + return false; + } + //println( "Entering coverMode " + newCoverMode ); + + timeleft = .1; + + popupAnim = animArray( "hide_2_" + newCoverMode ); + + //if ( !self mayMoveToPoint( getAnimEndPos( popupAnim ) ) ) + //return false; + + if ( self.script == "cover_crouch" && newCoverMode == "lean" ) + self.coverCrouchLean_aimmode = true; + + self.a.special = "none"; + self.specialDeathFunc = undefined; + + if ( self.coverType == "stand" ) + self.a.special = "cover_stand_aim"; + else if ( self.coverType == "crouch" ) + self.a.special = "cover_crouch_aim"; + + self.changingCoverPos = true; + self notify( "done_changing_cover_pos" ); + + self animmode( "point_relative" ); + + animRate = animscripts\cover_wall::pop_up_and_hide_speed(); + + node = level._locked_combat.nodes[ self.current_node_key ]; + assert( isdefined( node ) ); + offset_angles = node.angles + ( 0, self.hideYawOffset, 0 ); + + self animrelative( "pop_up", node.origin, offset_angles, popUpAnim ); + + self thread animscripts\cover_wall::DoNoteTracksForPopup( "pop_up" ); + + if ( animHasNoteTrack( popupAnim, "start_aim" ) ) + { + // Store our final step out angle so that we may use it when doing track loop aiming + self.stepOutYaw = self.angles[1] + getAngleDelta( popupAnim, 0, 1 ); + + self waittillmatch( "pop_up", "start_aim" ); + timeleft = getAnimLength( popupAnim ) / animRate * ( 1 - self getAnimTime( popupAnim ) ); + } + else + { + self waittillmatch( "pop_up", "end" ); + timeleft = .1; + } + + //self clearAnim( %cover, timeleft + 0.05 ); + + self.a.coverMode = newCoverMode; + self.a.prevAttack = newCoverMode; + + self locked_setup_additive_aim( timeleft ); + self thread animscripts\shared::trackShootEntOrPos(); + + wait( timeleft ); + + self StopAnimScripted(); + + //self clearAnim( popupAnim, 0.1 ); + + if ( self isSniper() ) + { + thread animscripts\shoot_behavior::sniper_glint_behavior(); + } + + self.changingCoverPos = false; + self.coverPosEstablishedTime = gettime(); + + self notify( "stop_popup_donotetracks" ); + + return true; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_setup_additive_aim( transTime ) +{ + if ( self.a.coverMode == "left" || self.a.coverMode == "right" ) + aimCoverMode = "crouch"; + else + aimCoverMode = self.a.coverMode; + + self setAnimKnobAll( animArray( aimCoverMode + "_aim" ), %body, 1, transTime ); + self setanimlimited( animArray( aimCoverMode + "_aim2" ), 1, 0 ); + self setanimlimited( animArray( aimCoverMode + "_aim4" ), 1, 0 ); + self setanimlimited( animArray( aimCoverMode + "_aim6" ), 1, 0 ); + self setanimlimited( animArray( aimCoverMode + "_aim8" ), 1, 0 ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_aim_idle() +{ + self endon( "killanimscript" ); + self endon( "end_aim_idle_thread" ); + + mode = self.a.coverMode; + + if ( isdefined( self.a.aimIdleThread ) ) + return; + self.a.aimIdleThread = true; + + // wait a bit before starting idle since firing will end the idle thread + wait 0.1; + + // this used to be setAnim, but it caused problems with turning on its parent nodes when they were supposed to be off (like during pistol pullout). + self setAnimLimited( %add_idle, 1, .2 ); + + for ( i = 0; ; i++ ) + { + flagname = "idle" + i; + idleanim = animArrayPickRandom( mode + "_idle" ); + self setFlaggedAnimKnobLimitedRestart( flagname, idleanim, 1, 0.2 ); + self waittillmatch( flagname, "end" ); + } + + self clearAnim( %add_idle, .1 ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_corner_think( direction, nodeAngleOffset ) +{ + self.animArrayFuncs[ "exposed" ][ "stand" ] = animscripts\corner::set_standing_animarray_aiming; + self.animArrayFuncs[ "exposed" ][ "crouch" ] = animscripts\corner::set_crouching_animarray_aiming; + + if ( IsDefined( self.customAnimFunc ) && IsDefined( self.customAnimFunc[ "corner_exposed" ] ) ) + { + if ( IsDefined( self.customAnimFunc[ "corner_exposed" ][ "stand" ] ) ) + { + self.animArrayFuncs[ "exposed" ][ "stand" ] = self.customAnimFunc[ "corner_exposed" ][ "stand" ]; + } + + if ( IsDefined( self.customAnimFunc[ "corner_exposed" ][ "crouch" ] ) ) + { + self.animArrayFuncs[ "exposed" ][ "crouch" ] = self.customAnimFunc[ "corner_exposed" ][ "crouch" ]; + } + } + + self.cornerDirection = direction; + self.a.cornerMode = "unknown"; + + self.a.aimIdleThread = undefined; + + animscripts\cover_behavior::turnToMatchNodeDirection( nodeAngleOffset ); + + animscripts\corner::set_corner_anim_array(); + + self.isshooting = false; + self.tracking = false; + + self.cornerAiming = false; + + animscripts\shared::setAnimAimWeight( 0 ); + + self.haveGoneToCover = false; + + behaviorCallbacks = spawnstruct(); + + if ( !self.fixedNode ) + behaviorCallbacks.moveToNearByCover = animscripts\cover_behavior::moveToNearbyCover; + + behaviorCallbacks.mainLoopStart = ::locked_corner_mainLoopStart; + behaviorCallbacks.reload = animscripts\corner::cornerReload; + behaviorCallbacks.leaveCoverAndShoot = animscripts\corner::stepOutAndShootEnemy; + behaviorCallbacks.look = animscripts\corner::lookForEnemy; + behaviorCallbacks.fastlook = animscripts\corner::fastlook; + behaviorCallbacks.idle = animscripts\corner::idle; + behaviorCallbacks.grenade = ::return_false; + behaviorCallbacks.grenadehidden = ::return_false; + behaviorCallbacks.blindfire = animscripts\corner::blindfire; + + animscripts\cover_behavior::main( behaviorCallbacks ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_corner_mainLoopStart() +{ + desiredStance = "stand"; + + /# + if ( getdvarint( "scr_cornerforcecrouch" ) == 1 ) + desiredStance = "crouch"; + #/ + + self animscripts\corner::transitionToStance( desiredStance ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_moveToNearbyCover() +{ + return false; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_reload() +{ + return Reload( 2.0, animArray( "reload" ) ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_look( lookTime ) +{ + if ( !isdefined( self.a.array[ "hide_to_look" ] ) ) + return false; + + if ( !locked_peekOut() ) + return false; + + animscripts\shared::playLookAnimation( animArray( "look_idle" ), lookTime );// TODO: replace + + lookanim = undefined; + if ( self isSuppressedWrapper() ) + lookanim = animArray( "look_to_hide_fast" ); + else + lookanim = animArray( "look_to_hide" ); + + self setflaggedanimknoballrestart( "looking_end", lookanim, %body, 1, .1 ); + animscripts\shared::DoNoteTracks( "looking_end" ); + + return true; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_peekOut() +{ + if ( isdefined( self.coverNode.script_dontpeek ) ) + return false; + + // assuming no delta, so no maymovetopoint check + + self setFlaggedAnimKnobAll( "looking_start", animArray( "hide_to_look" ), %body, 1, .2 ); + animscripts\shared::DoNoteTracks( "looking_start" ); + + return true; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_fastlook() +{ + self setFlaggedAnimKnobAllRestart( "look", animArrayPickRandom( "look" ), %body, 1, .1 ); + self animscripts\shared::DoNoteTracks( "look" ); + + return true; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_idle() +{ + self endon( "end_idle" ); + + while ( 1 ) + { + useTwitch = ( randomint( 2 ) == 0 && animArrayAnyExist( "hide_idle_twitch" ) ); + if ( useTwitch ) + idleanim = animArrayPickRandom( "hide_idle_twitch" ); + else + idleanim = animarray( "hide_idle" ); + + locked_playIdleAnimation( idleAnim, useTwitch ); + } +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_playIdleAnimation( idleAnim, needsRestart ) +{ + if ( needsRestart ) + self setFlaggedAnimKnobAllRestart( "idle", idleAnim, %body, 1, 0.25, 1 ); + else + self setFlaggedAnimKnobAll( "idle", idleAnim, %body, 1, 0.25, 1 ); + + self.a.coverMode = "hide"; + + self animscripts\shared::DoNoteTracks( "idle" ); +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_flinch() +{ + if ( !animArrayAnyExist( "hide_idle_flinch" ) ) + return false; + + forward = anglesToForward( self.angles ); + stepto = self.origin + vector_multiply( forward, -16 ); + + if ( !self mayMoveToPoint( stepto ) ) + return false; + + self animmode( "point_relative" ); + self.keepClaimedNodeIfValid = true; + + flinchanim = animArrayPickRandom( "hide_idle_flinch" ); + locked_playIdleAnimation( flinchanim, true ); + + self.keepClaimedNodeIfValid = false; + + return true; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_blindfire() +{ + if ( !animArrayAnyExist( "blind_fire" ) ) + return false; + + self animMode( "point_relative" ); + self.keepClaimedNodeIfValid = true; + + self setFlaggedAnimKnobAllRestart( "blindfire", animArrayPickRandom( "blind_fire" ), %body, 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "blindfire" ); + + self.keepClaimedNodeIfValid = false; + + return true; +} + +//******************************************************************* +// * +// * +//******************************************************************* +return_false( param1 ) +{ + return false; +} + +//******************************************************************* +// * +// * +//******************************************************************* +locked_grenade( throwAt, safe ) +{ + if ( !isPlayer( throwAt ) ) // Only throw grenades at players for now + { + return false; + } + + if ( isdefined( self.dontEverShoot ) || isdefined( throwAt.dontAttackMe ) ) + return false; + + // We want to only throw locked_combat grenades if the player is trying to hide + if ( !(throwAt should_grenade_player()) ) + { + return false; + } + + theanim = undefined; + if ( isdefined( safe ) && safe ) + theanim = animArrayPickRandom( "grenade_safe" ); + else + theanim = animArrayPickRandom( "grenade_exposed" ); + + threwGrenade = TryGrenade( throwAt, theanim ); + + return threwGrenade; +} + +should_grenade_player() +{ + assert( isPlayer( self ) ); + assert( isDefined( self.locked_shouldGrenade ) ); + return self.locked_shouldGrenade; +} diff --git a/animscripts/lunar/animset.gsc b/animscripts/lunar/animset.gsc new file mode 100644 index 0000000..b4ea849 --- /dev/null +++ b/animscripts/lunar/animset.gsc @@ -0,0 +1,1627 @@ +#include animscripts\Utility; +#include common_scripts\Utility; + +#using_animtree( "generic_human" ); + +//The LUNAR version of animset. + +//////////////////////////////////////////// +// Initialize anim sets +// +// anim.initAnimSet is used as a temporary buffer, because variables, including arrays, can't be passed by reference +// Set it up in each init_animset_* function and then store it in anim.animset.* +// This allows using helpers such as "set_animarray_stance_change" for different sets +//////////////////////////////////////////// + +init_anim_sets() +{ + anim.animsets = spawnstruct(); + anim.animsets.move = []; + anim.animSetLoaded = "lunar"; + + // combat stand + init_animset_default_stand(); + init_animset_cqb_stand(); + init_animset_pistol_stand(); + init_animset_rpg_stand(); + init_animset_shotgun_stand(); + init_animset_heat_stand(); + + // combat crouch + init_animset_default_crouch(); + init_animset_rpg_crouch(); + init_animset_shotgun_crouch(); + + // combat prone + init_animset_default_prone(); + + // move + init_animset_run_move(); + init_animset_walk_move(); + init_animset_cqb_move(); + init_animset_heat_run_move(); + + // move aim + init_animset_run_aim_tracking(); + + init_moving_turn_animations(); + + //death + init_animset_death(); + + //pain + init_animset_pain(); + + //wall cover anims + init_animset_crouch_wall(); + init_animset_stand_wall(); + + // Melee + init_animset_melee(); + + //gib + init_animset_gib(); + + init_animset_run_n_gun(); + + init_ambush_sidestep_anims(); + + init_heat_reload_function(); + + //left cover anims + init_animarray_standing_left(); + init_animarray_crouching_left(); + + //right cover anims + init_animarray_standing_right(); + init_animarray_crouching_right(); + + initGrenades(); + + init_noder_anims(); + + //aim anims + init_standing_animarray_aiming(); + init_crouching_animarray_aiming(); + + //reactions + init_reaction_anims(); +} + +initGrenades() +{ + for ( i = 0; i < level._players.size; i++ ) + { + player = level._players[ i ]; + player.grenadeTimers[ "fraggrenade" ] = randomIntRange( 1000, 20000 ); + player.grenadeTimers[ "moon_grenade" ] = randomIntRange( 1000, 20000 ); + player.grenadeTimers[ "flash_grenade" ] = randomIntRange( 1000, 20000 ); + player.grenadeTimers[ "double_grenade" ] = randomIntRange( 1000, 60000 ); + player.numGrenadesInProgressTowardsPlayer = 0; + player.lastGrenadeLandedNearPlayerTime = -1000000; + player.lastFragGrenadeToPlayerStart = -1000000; + player thread animscripts\init_common::setNextPlayerGrenadeTime(); + } + anim.grenadeTimers[ "AI_fraggrenade" ] = randomIntRange( 0, 20000 ); + anim.grenadeTimers[ "AI_moon_grenade" ] = randomIntRange( 0, 20000 ); + anim.grenadeTimers[ "AI_flash_grenade" ] = randomIntRange( 0, 20000 ); + anim.grenadeTimers[ "AI_smoke_grenade_american" ] = randomIntRange( 0, 20000 ); + + /# + thread animscripts\combat_utility::grenadeTimerDebug(); + #/ + + initGrenadeThrowAnims(); +} + +init_animset_run_aim_tracking() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_down" ] = %walk_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %walk_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %walk_aim_6; + anim.initAnimSet[ "add_aim_up" ] = %walk_aim_8; + + assert( !isdefined( anim.animsets.runAimTracking ) ); + anim.animsets.runAimTracking = anim.initAnimSet; +} + +init_animset_melee() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "attack" ] = %tp_moon_melee_attack; + anim.initAnimSet[ "charge" ] = %tp_moon_melee_charge; + anim.initAnimSet[ "run" ] = %tp_moon_melee_run; + anim.initAnimSet[ "stand_to_melee" ] = %tp_moon_melee_stand_2_melee_1; + + assert( !isdefined( anim.animsets.melee ) ); + anim.animsets.melee = anim.initAnimSet; +} + + +init_animset_run_move() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "sprint" ] = %tp_moon_sprint_straight; + anim.initAnimSet[ "sprint_short" ] = %tp_moon_sprint_straight; + anim.initAnimSet[ "prone" ] = %prone_crawl; + + anim.initAnimSet[ "straight" ] = %tp_moon_run_straight; + + anim.initAnimSet[ "move_f" ] = %tp_moon_move_f; + anim.initAnimSet[ "move_l" ] = %tp_moon_move_l; + anim.initAnimSet[ "move_r" ] = %tp_moon_move_r; + anim.initAnimSet[ "move_b" ] = %tp_moon_move_b; //this looks too fast to be natural + + anim.initAnimSet[ "crouch" ] = %tp_moon_crouchwalk_straight; + anim.initAnimSet[ "crouch_l" ] = %tp_moon_crouchwalk_left; + anim.initAnimSet[ "crouch_r" ] = %tp_moon_crouchwalk_right; + anim.initAnimSet[ "crouch_b" ] = %tp_moon_crouchwalk_back; + + anim.initAnimSet[ "stairs_up" ] = %tp_moon_stairs_up; + anim.initAnimSet[ "stairs_down" ] = %tp_moon_stairs_down; + + assert( !isdefined( anim.animsets.move[ "run" ] ) ); + anim.animsets.move[ "run" ] = anim.initAnimSet; +} + + +init_animset_heat_run_move() +{ + assert( isdefined( anim.animsets.move[ "run" ] ) ); + anim.initAnimSet = anim.animsets.move[ "run" ]; + + anim.initAnimSet[ "straight" ] = %heat_run_loop; + + assert( !isdefined( anim.animsets.move[ "heat_run" ] ) ); + anim.animsets.move[ "heat_run" ] = anim.initAnimSet; +} + + +init_animset_walk_move() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "sprint" ] = %tp_moon_sprint_straight; + anim.initAnimSet[ "sprint_short" ] = %tp_moon_sprint_straight; + anim.initAnimSet[ "prone" ] = %prone_crawl; + + anim.initAnimSet[ "straight" ] = %tp_moon_run_straight; + + anim.initAnimSet[ "move_f" ] = %tp_moon_move_f; + anim.initAnimSet[ "move_l" ] = %tp_moon_move_l; + anim.initAnimSet[ "move_r" ] = %tp_moon_move_r; + anim.initAnimSet[ "move_b" ] = %tp_moon_move_b; + + anim.initAnimSet[ "crouch" ] = %tp_moon_crouchwalk_straight; + anim.initAnimSet[ "crouch_l" ] = %tp_moon_crouchwalk_left; + anim.initAnimSet[ "crouch_r" ] = %tp_moon_crouchwalk_right; + anim.initAnimSet[ "crouch_b" ] = %tp_moon_crouchwalk_back; + + anim.initAnimSet[ "stairs_up" ] = %tp_moon_stairs_up; + anim.initAnimSet[ "stairs_down" ] = %tp_moon_stairs_down; + + assert( !isdefined( anim.animsets.move[ "walk" ] ) ); + anim.animsets.move[ "walk" ] = anim.initAnimSet; +} + + +init_animset_cqb_move() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "sprint" ] = %tp_moon_sprint_straight; + anim.initAnimSet[ "sprint_short" ] = %tp_moon_sprint_straight; + anim.initAnimSet[ "straight" ] = %tp_moon_cqb_straight; // %run_CQB_F_search_v2 + anim.initAnimSet[ "straight_variation" ] = %tp_moon_cqb_straight; + + anim.initAnimSet[ "move_f" ] = %tp_moon_move_f; + anim.initAnimSet[ "move_l" ] = %tp_moon_move_l; + anim.initAnimSet[ "move_r" ] = %tp_moon_move_r; + anim.initAnimSet[ "move_b" ] = %tp_moon_move_b; + + anim.initAnimSet[ "stairs_up" ] = %tp_moon_stairs_up; + anim.initAnimSet[ "stairs_down" ] = %tp_moon_stairs_down; + + assert( !isdefined( anim.animsets.move[ "cqb" ] ) ); + anim.animsets.move[ "cqb" ] = anim.initAnimSet; +} + + +init_animset_pistol_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %pistol_stand_aim_8_add; + anim.initAnimSet[ "add_aim_down" ] = %pistol_stand_aim_2_add; + anim.initAnimSet[ "add_aim_left" ] = %pistol_stand_aim_4_add; + anim.initAnimSet[ "add_aim_right" ] = %pistol_stand_aim_6_add; + anim.initAnimSet[ "straight_level" ] = %pistol_stand_aim_5; + + anim.initAnimSet[ "fire" ] = %pistol_stand_fire_A; + anim.initAnimSet[ "single" ] = array( %pistol_stand_fire_A ); + + anim.initAnimSet[ "reload" ] = array( %pistol_stand_reload_A ); + anim.initAnimSet[ "reload_crouchhide" ] = array(); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + set_animarray_standing_turns_pistol(); + + anim.initAnimSet[ "add_turn_aim_up" ] = %pistol_stand_aim_8_alt; + anim.initAnimSet[ "add_turn_aim_down" ] = %pistol_stand_aim_2_alt; + anim.initAnimSet[ "add_turn_aim_left" ] = %pistol_stand_aim_4_alt; + anim.initAnimSet[ "add_turn_aim_right" ] = %pistol_stand_aim_6_alt; + + assert( !isdefined( anim.animsets.pistolStand ) ); + anim.animsets.pistolStand = anim.initAnimSet; +} + +init_animset_rpg_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %RPG_stand_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %RPG_stand_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %RPG_stand_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %RPG_stand_aim_6; + anim.initAnimSet[ "straight_level" ] = %RPG_stand_aim_5; + + anim.initAnimSet[ "fire" ] = %RPG_stand_fire; + anim.initAnimSet[ "single" ] = array( %exposed_shoot_semi1 ); + + anim.initAnimSet[ "reload" ] = array( %RPG_stand_reload ); + anim.initAnimSet[ "reload_crouchhide" ] = array(); + + anim.initAnimSet[ "exposed_idle" ] = array( %RPG_stand_idle ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.rpgStand ) ); + anim.animsets.rpgStand = anim.initAnimSet; +} + +init_animset_shotgun_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %shotgun_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %shotgun_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %shotgun_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %shotgun_aim_6; + anim.initAnimSet[ "straight_level" ] = %shotgun_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_shoot_auto_v3; + anim.initAnimSet[ "single" ] = array( %shotgun_stand_fire_1A, %shotgun_stand_fire_1B ); + + set_animarray_burst_and_semi_fire_stand(); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "reload" ] = array( %shotgun_stand_reload_A, %shotgun_stand_reload_B, %shotgun_stand_reload_C, %shotgun_stand_reload_C, %shotgun_stand_reload_C );// ( C is standing, want it more often ) + anim.initAnimSet[ "reload_crouchhide" ] = array( %shotgun_stand_reload_A, %shotgun_stand_reload_B ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.shotgunStand ) ); + anim.animsets.shotgunStand = anim.initAnimSet; +} + +init_animset_cqb_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %tp_moon_exposed_stand_add_aim_up; + anim.initAnimSet[ "add_aim_down" ] = %tp_moon_exposed_stand_add_aim_down; + anim.initAnimSet[ "add_aim_left" ] = %tp_moon_exposed_stand_add_aim_left; + anim.initAnimSet[ "add_aim_right" ] = %tp_moon_exposed_stand_add_aim_right; + + anim.initAnimSet[ "straight_level" ] = %tp_moon_exposed_stand_aim_straight; + + anim.initAnimSet[ "fire" ] = %tp_moon_exposed_stand_fire; + anim.initAnimSet[ "single" ] = [ %tp_moon_exposed_stand_single_01 ]; + set_animarray_burst_and_semi_fire_stand(); + + anim.initAnimSet[ "exposed_idle" ] = [ %tp_moon_exposed_stand_idle ]; + + anim.initAnimSet[ "reload" ] = [ %CQB_stand_reload_steady ]; + anim.initAnimSet[ "reload_crouchhide" ] = [ %CQB_stand_reload_knee ]; + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.cqbStand ) ); + anim.animsets.cqbStand = anim.initAnimSet; +} + +init_animset_heat_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %heat_stand_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %heat_stand_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %heat_stand_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %heat_stand_aim_6; + + anim.initAnimSet[ "straight_level" ] = %heat_stand_aim_5; + + anim.initAnimSet[ "fire" ] = %heat_stand_fire_auto; + anim.initAnimSet[ "single" ] = array( %heat_stand_fire_single ); + animscripts\init_common::set_animarray_custom_burst_and_semi_fire_stand( %heat_stand_fire_burst ); + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "exposed_idle" ] = array( %heat_stand_idle, /*%heat_stand_twitchA, %heat_stand_twitchB, %heat_stand_twitchC,*/ %heat_stand_scanA, %heat_stand_scanB ); + //heat_stand_scanA + //heat_stand_scanB + + anim.initAnimSet[ "reload" ] = array( %heat_exposed_reload ); + anim.initAnimSet[ "reload_crouchhide" ] = array(); + + set_animarray_stance_change(); + + anim.initAnimSet[ "turn_left_45" ] = %heat_stand_turn_L; + anim.initAnimSet[ "turn_left_90" ] = %heat_stand_turn_L; + anim.initAnimSet[ "turn_left_135" ] = %heat_stand_turn_180; + anim.initAnimSet[ "turn_left_180" ] = %heat_stand_turn_180; + anim.initAnimSet[ "turn_right_45" ] = %heat_stand_turn_R; + anim.initAnimSet[ "turn_right_90" ] = %heat_stand_turn_R; + anim.initAnimSet[ "turn_right_135" ] = %heat_stand_turn_180; + anim.initAnimSet[ "turn_right_180" ] = %heat_stand_turn_180; + + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.heatStand ) ); + anim.animsets.heatStand = anim.initAnimSet; +} + +init_animset_default_stand() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %tp_moon_exposed_stand_add_aim_up; + anim.initAnimSet[ "add_aim_down" ] = %tp_moon_exposed_stand_add_aim_down; + anim.initAnimSet[ "add_aim_left" ] = %tp_moon_exposed_stand_add_aim_left; + anim.initAnimSet[ "add_aim_right" ] = %tp_moon_exposed_stand_add_aim_right; + + anim.initAnimSet[ "straight_level" ] = %tp_moon_exposed_stand_aim_straight; + + anim.initAnimSet[ "fire" ] = %tp_moon_exposed_stand_fire; + anim.initAnimSet[ "single" ] = [ %tp_moon_exposed_stand_single_01 ]; + set_animarray_burst_and_semi_fire_stand(); + + anim.initAnimSet[ "exposed_idle" ] = [ %tp_moon_exposed_stand_idle ]; + anim.initAnimSet[ "exposed_grenade" ] = array( %exposed_grenadeThrowB, %exposed_grenadeThrowC ); + + anim.initAnimSet[ "reload" ] = array( %exposed_reload );// %exposed_reloadb, %exposed_reloadc + anim.initAnimSet[ "reload_crouchhide" ] = array( %exposed_reloadb ); + + set_animarray_stance_change(); + set_animarray_standing_turns(); + set_animarray_add_turn_aims_stand(); + + assert( !isdefined( anim.animsets.defaultStand ) ); + anim.animsets.defaultStand = anim.initAnimSet; +} + + +init_animset_default_crouch() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %exposed_crouch_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %exposed_crouch_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %exposed_crouch_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %exposed_crouch_aim_6; + anim.initAnimSet[ "straight_level" ] = %exposed_crouch_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_crouch_shoot_auto_v2; + anim.initAnimSet[ "single" ] = array( %exposed_crouch_shoot_semi1 ); + set_animarray_burst_and_semi_fire_crouch(); + + anim.initAnimSet[ "reload" ] = array( %exposed_crouch_reload ); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ); + + set_animarray_stance_change(); + set_animarray_crouching_turns(); + set_animarray_add_turn_aims_crouch(); + + assert( !isdefined( anim.animsets.defaultCrouch ) ); + anim.animsets.defaultCrouch = anim.initAnimSet; +} + +init_animset_rpg_crouch() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %RPG_crouch_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %RPG_crouch_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %RPG_crouch_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %RPG_crouch_aim_6; + anim.initAnimSet[ "straight_level" ] = %RPG_crouch_aim_5; + + anim.initAnimSet[ "fire" ] = %RPG_crouch_fire; + anim.initAnimSet[ "single" ] = array( %RPG_crouch_fire ); + + anim.initAnimSet[ "reload" ] = array( %RPG_crouch_reload ); + + anim.initAnimSet[ "exposed_idle" ] = array( %RPG_crouch_idle ); + + set_animarray_stance_change(); + set_animarray_crouching_turns(); + set_animarray_add_turn_aims_crouch(); + + assert( !isdefined( anim.animsets.rpgCrouch ) ); + anim.animsets.rpgCrouch = anim.initAnimSet; +} + + +init_animset_shotgun_crouch() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %exposed_crouch_aim_8; + anim.initAnimSet[ "add_aim_down" ] = %exposed_crouch_aim_2; + anim.initAnimSet[ "add_aim_left" ] = %exposed_crouch_aim_4; + anim.initAnimSet[ "add_aim_right" ] = %exposed_crouch_aim_6; + anim.initAnimSet[ "straight_level" ] = %exposed_crouch_aim_5; + + anim.initAnimSet[ "fire" ] = %exposed_crouch_shoot_auto_v2; + anim.initAnimSet[ "single" ] = array( %shotgun_crouch_fire ); + set_animarray_burst_and_semi_fire_crouch(); + + anim.initAnimSet[ "reload" ] = array( %shotgun_crouch_reload ); + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ); + + set_animarray_stance_change(); + set_animarray_crouching_turns(); + set_animarray_add_turn_aims_crouch(); + + assert( !isdefined( anim.animsets.shotgunCrouch ) ); + anim.animsets.shotgunCrouch = anim.initAnimSet; +} + + +init_animset_default_prone() +{ + anim.initAnimSet = []; + anim.initAnimSet[ "add_aim_up" ] = %prone_aim_8_add; + anim.initAnimSet[ "add_aim_down" ] = %prone_aim_2_add; + anim.initAnimSet[ "add_aim_left" ] = %prone_aim_4_add; + anim.initAnimSet[ "add_aim_right" ] = %prone_aim_6_add; + + anim.initAnimSet[ "straight_level" ] = %prone_aim_5; + anim.initAnimSet[ "fire" ] = %prone_fire_1; + + anim.initAnimSet[ "single" ] = array( %prone_fire_1 ); + anim.initAnimSet[ "reload" ] = array( %prone_reload ); + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "burst2" ] = %prone_fire_burst; + anim.initAnimSet[ "burst3" ] = %prone_fire_burst; + anim.initAnimSet[ "burst4" ] = %prone_fire_burst; + anim.initAnimSet[ "burst5" ] = %prone_fire_burst; + anim.initAnimSet[ "burst6" ] = %prone_fire_burst; + + anim.initAnimSet[ "semi2" ] = %prone_fire_burst; + anim.initAnimSet[ "semi3" ] = %prone_fire_burst; + anim.initAnimSet[ "semi4" ] = %prone_fire_burst; + anim.initAnimSet[ "semi5" ] = %prone_fire_burst; + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ); + + set_animarray_stance_change(); + + assert( !isdefined( anim.animsets.defaultProne ) ); + anim.animsets.defaultProne = anim.initAnimSet; +} + +init_animset_death() +{ + anim.initAnimSet = []; + + //special deaths + anim.initAnimSet[ "cover_right_stand" ] = [ %tp_moon_death_cover_right_stand_01 ]; + anim.initAnimSet[ "cover_right_crouch_head_neck" ] = [ %tp_moon_death_cover_right_crouch_head_neck_01 ]; + anim.initAnimSet[ "cover_right_crouch" ] = [ %tp_moon_death_cover_right_crouch_01 ]; + anim.initAnimSet[ "cover_left_stand" ] = [ %tp_moon_death_cover_left_stand_01 ]; + anim.initAnimSet[ "cover_left_crouch" ] = [ %tp_moon_death_cover_left_crouch_01 ]; + anim.initAnimSet[ "cover_stand" ] = [ %tp_moon_death_cover_stand_01 ]; + anim.initAnimSet[ "cover_crouch_head" ] = [ %tp_moon_death_cover_crouch_head_01 ]; + anim.initAnimSet[ "cover_crouch_back" ] = [ %tp_moon_death_cover_crouch_head_01 ]; + anim.initAnimSet[ "cover_crouch" ] = [ %tp_moon_death_cover_crouch_01 ]; + + anim.initAnimSet[ "saw_stand" ] = array( %saw_gunner_death ); + anim.initAnimSet[ "saw_crouch" ] = array( %saw_gunner_lowwall_death ); + anim.initAnimSet[ "saw_prone" ] = array( %saw_gunner_prone_death ); + anim.initAnimSet[ "crawl_crouch" ] = array( %dying_back_death_v2, %dying_back_death_v3, %dying_back_death_v4); + anim.initAnimSet[ "crawl_prone" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2 ); + + //stong bullet deaths + anim.initAnimSet[ "strong_leg_front" ] = array( %death_shotgun_legs, %death_stand_sniper_leg ); + anim.initAnimSet[ "strong_lower_torso_front" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2, %death_shotgun_back_v1, %exposed_death_blowback, %death_stand_sniper_chest1, %death_stand_sniper_chest2, %death_stand_sniper_spin1 ); + anim.initAnimSet[ "strong_torso_front" ] = array( %death_shotgun_back_v1, %exposed_death_blowback, %death_stand_sniper_chest1, %death_stand_sniper_chest2, %death_stand_sniper_spin1 ); + anim.initAnimSet[ "strong_left" ] = array( %death_shotgun_spinL, %death_stand_sniper_spin1, %death_stand_sniper_chest1, %death_stand_sniper_chest2 ); + anim.initAnimSet[ "strong_right" ] = array( %death_shotgun_spinR, %death_stand_sniper_spin2, %death_stand_sniper_chest1, %death_stand_sniper_chest2 ); + + //running forward deaths + anim.initAnimSet[ "running_forward" ] = [ %tp_moon_death_running_forward_01 ]; + + //stand pistol deaths + anim.initAnimSet[ "stand_pistol_back" ] = %pistol_death_2; + anim.initAnimSet[ "stand_pistol_legs" ] = %pistol_death_3; + anim.initAnimSet[ "stand_pistol_chest" ] = %pistol_death_4; + anim.initAnimSet[ "stand_pistol_head" ] = %pistol_death_1; + + //stand deaths + anim.initAnimSet[ "stand_legs" ] = [ %tp_moon_death_stand_legs_01 ]; + anim.initAnimSet[ "stand_legs_extended" ] = [ %tp_moon_death_stand_legs_extended_01 ]; + anim.initAnimSet[ "stand_head" ] = [ %tp_moon_death_stand_head_01 ]; + anim.initAnimSet[ "stand_neck" ] = [ %tp_moon_death_stand_neck_01 ]; + anim.initAnimSet[ "stand_upper_torso" ] = [ %tp_moon_death_stand_upper_torso_01 ]; + anim.initAnimSet[ "stand_upper_torso_extended" ] = [ %tp_moon_death_stand_upper_torso_extended_01 ]; + anim.initAnimSet[ "stand_upper_left" ] = [ %tp_moon_death_stand_upper_left_01 ]; + anim.initAnimSet[ "stand_front_head" ] = [ %tp_moon_death_stand_front_head_01 ]; + anim.initAnimSet[ "stand_front_head_extended" ] = [ %tp_moon_death_stand_front_head_extended_01 ]; + anim.initAnimSet[ "stand_front_torso" ] = [ %tp_moon_death_stand_generic_01 ]; + anim.initAnimSet[ "stand_front_torso_extended" ] = [ %tp_moon_death_stand_front_torso_extended_01 ]; + anim.initAnimSet[ "stand_back" ] = [ %tp_moon_death_stand_back_01 ]; + anim.initAnimSet[ "stand_firing" ] = [ %tp_moon_death_stand_firing_01 ]; + anim.initAnimSet[ "stand_generic" ] = [ %tp_moon_death_stand_generic_01 ]; + anim.initAnimSet[ "stand_exposed" ] = [ %tp_moon_death_stand_exposed_01 ]; + + //crouch deaths + anim.initAnimSet[ "crouch_head" ] = %tp_moon_death_crouch_head; + anim.initAnimSet[ "crouch_torso" ] = %tp_moon_death_crouch_generic; + anim.initAnimSet[ "crouch_twist" ] = %tp_moon_death_crouch_twist; + anim.initAnimSet[ "crouch_generic" ] = %tp_moon_death_crouch_generic; + + //prone + anim.initAnimSet[ "prone_aiming" ] = %prone_death_quickdeath; + anim.initAnimSet[ "prone" ] = %dying_crawl_death_v1; + + //back + anim.initAnimSet[ "back" ] = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3, %dying_back_death_v4 ); + + //explosions! + anim.initAnimSet[ "explode_stand_front" ] = [ %tp_moon_death_explode_stand_front_01 ]; + anim.initAnimSet[ "explode_stand_right" ] = [ %tp_moon_death_explode_stand_right_01 ]; + anim.initAnimSet[ "explode_stand_left" ] = [ %tp_moon_death_explode_stand_left_01 ]; + anim.initAnimSet[ "explode_stand_back" ] = [ %tp_moon_death_explode_stand_back_01 ]; + anim.initAnimSet[ "explode_run_front" ] = [ %tp_moon_death_explode_stand_front_01 ]; + anim.initAnimSet[ "explode_run_right" ] = [ %tp_moon_death_explode_stand_right_01 ]; + anim.initAnimSet[ "explode_run_left" ] = [ %tp_moon_death_explode_stand_left_01 ]; + anim.initAnimSet[ "explode_run_back" ] = [ %tp_moon_death_explode_stand_back_01 ]; + + + assert( !isdefined( anim.animsets.deathAnimSet ) ); + anim.animsets.deathAnimSet = anim.initAnimSet; +} + +init_animset_pain() +{ + anim.initAnimSet = []; + + //special pain + anim.initAnimSet[ "shield" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "cover_left_stand" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "cover_left_crouch" ] = array( %CornerCrL_painB ); + anim.initAnimSet[ "cover_right_stand" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "cover_right_crouch" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "cover_right_stand_B" ] = array( %corner_standR_pain_B_2_alert ); + anim.initAnimSet[ "cover_left_stand_A" ] = array( %corner_standL_pain_A_2_alert ); + anim.initAnimSet[ "cover_left_stand_B" ] = array( %corner_standL_pain_B_2_alert ); + anim.initAnimSet[ "cover_crouch" ] = array( %covercrouch_pain_right, %covercrouch_pain_front, %covercrouch_pain_left_3 ); + anim.initAnimSet[ "cover_stand" ] = array( %coverstand_pain_groin, %coverstand_pain_leg ); + anim.initAnimSet[ "cover_stand_aim" ] = array( %coverstand_pain_aim_2_hide_01, %coverstand_pain_aim_2_hide_02 ); + anim.initAnimSet[ "cover_crouch_aim" ] = array( %covercrouch_pain_aim_2_hide_01 ); + anim.initAnimSet[ "saw_stand" ] = %saw_gunner_pain; + anim.initAnimSet[ "saw_crouch" ] = %saw_gunner_lowwall_pain_02; + anim.initAnimSet[ "saw_prone" ] = %saw_gunner_prone_pain; + + + //run pains + anim.initAnimSet[ "long_run" ] = [ %tp_moon_pain_med_run_01 ]; + anim.initAnimSet[ "med_run" ] = [ %tp_moon_pain_med_run_01 ]; + anim.initAnimSet[ "short_run" ] = [ %tp_moon_pain_med_run_01 ]; + + //pistol pain + anim.initAnimSet[ "pistol_torso" ] = %pistol_stand_pain_chest; + anim.initAnimSet[ "pistol_legs" ] = %pistol_stand_pain_groin; + anim.initAnimSet[ "pistol_head" ] = %pistol_stand_pain_head; + anim.initAnimSet[ "pistol_left_arm" ] = %pistol_stand_pain_leftshoulder; + anim.initAnimSet[ "pistol_right_arm" ] = %pistol_stand_pain_rightshoulder; + + //stand pain + anim.initAnimSet[ "stand_torso_extended" ] = array( %stand_exposed_extendedpain_gut, %stand_exposed_extendedpain_stomach ); + anim.initAnimSet[ "stand_head" ] = array( %exposed_pain_face, %stand_exposed_extendedpain_neck ); + anim.initAnimSet[ "stand_head_extended" ] = array( %stand_exposed_extendedpain_head_2_crouch ); + anim.initAnimSet[ "stand_right_arm" ] = array( %exposed_pain_right_arm ); + anim.initAnimSet[ "stand_left_arm" ] = array( %stand_exposed_extendedpain_shoulderswing ); + anim.initAnimSet[ "stand_left_arm_extended" ] = array( %stand_exposed_extendedpain_shoulder_2_crouch ); + anim.initAnimSet[ "stand_legs" ] = array( %exposed_pain_groin, %stand_exposed_extendedpain_hip ); + anim.initAnimSet[ "stand_legs_extended" ] = array( %stand_exposed_extendedpain_hip_2_crouch, %stand_exposed_extendedpain_feet_2_crouch, %stand_exposed_extendedpain_stomach ); + anim.initAnimSet[ "stand_feet" ] = array( %stand_exposed_extendedpain_thigh ); + anim.initAnimSet[ "stand_feet_extended" ] = array( %stand_exposed_extendedpain_feet_2_crouch ); + anim.initAnimSet[ "stand_generic_long_death" ] = array( %exposed_pain_2_crouch, %stand_extendedpainB ); + anim.initAnimSet[ "stand_generic" ] = array( %exposed_pain_right_arm, %exposed_pain_face, %exposed_pain_groin ); + anim.initAnimSet[ "stand_generic_extended" ] = array( %stand_extendedpainC, %stand_exposed_extendedpain_chest ); + + //crouch pain + anim.initAnimSet[ "crouch_generic" ] = array( %exposed_crouch_extendedpainA ); + anim.initAnimSet[ "crouch_exposed" ] = array( %exposed_crouch_pain_chest, %exposed_crouch_pain_headsnap, %exposed_crouch_pain_flinch ); + anim.initAnimSet[ "crouch_left_arm" ] = array( %exposed_crouch_pain_left_arm ); + anim.initAnimSet[ "crouch_right_arm" ] = array( %exposed_crouch_pain_right_arm ); + + //prone pain + anim.initAnimSet[ "prone" ] = array( %prone_reaction_A, %prone_reaction_B ); + + anim.initAnimSet[ "crawl_trans_stand" ] = array( %dying_stand_2_back_v1, %dying_stand_2_back_v2 ); + anim.initAnimSet[ "crawl_trans_crouch" ] = array( %dying_crouch_2_back ); + anim.initAnimSet[ "crawl_trans_prone" ] = array( %dying_crawl_2_back ); + + //pistol crawl + anim.initAnimSet[ "stand_2_crawl" ] = array( %dying_stand_2_crawl_v1, %dying_stand_2_crawl_v2, %dying_stand_2_crawl_v3 ); + anim.initAnimSet[ "crouch_2_crawl" ] = array( %dying_crouch_2_crawl ); + anim.initAnimSet[ "crawl" ] = %dying_crawl; + anim.initAnimSet[ "death" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2 ); + anim.initAnimSet[ "back_idle" ] = %dying_back_idle; + anim.initAnimSet[ "back_idle_twitch" ] = array( %dying_back_twitch_A, %dying_back_twitch_B ); + anim.initAnimSet[ "back_crawl" ] = %dying_crawl_back; + anim.initAnimSet[ "back_fire" ] = %dying_back_fire; + anim.initAnimSet[ "back_death" ] = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3 ); + + //additive pain + anim.initAnimSet[ "add_generic" ] = array( %pain_add_standing_belly, %pain_add_standing_left_arm, %pain_add_standing_right_arm ); + anim.initAnimSet[ "add_left_arm" ] = array( %pain_add_standing_left_arm ); + anim.initAnimSet[ "add_right_arm" ] = array( %pain_add_standing_right_arm ); + anim.initAnimSet[ "add_left_leg" ] = array( %pain_add_standing_left_leg ); + anim.initAnimSet[ "add_right_leg" ] = array( %pain_add_standing_right_leg ); + + anim.initAnimSet[ "dying_back_aim_left" ] = %dying_back_aim_4; + anim.initAnimSet[ "dying_back_aim_right" ] = %dying_back_aim_6; + + //anim.initAnimSet[ "taser_stand" ] = array( %tp_moon_react_tased_stand_opfor_01 ); + //anim.initAnimSet[ "taser_crouch" ] = array( %tp_moon_react_tased_crouch_opfor_01 ); + //anim.initAnimSet[ "taser_stand_ally" ] = array( %tp_moon_react_tased_stand_ally_01 ); + //anim.initAnimSet[ "taser_crouch_ally" ] = array( %tp_moon_react_tased_crouch_ally_01 ); + + assert( !isdefined( anim.animsets.painAnimSet ) ); + anim.animsets.painAnimSet = anim.initAnimSet; +} + +init_animset_gib() +{ + anim.initAnimSet = []; + + //TEMP + anim.initAnimSet[ "gib_right_arm_front_start" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_arm_back_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_arm_front_loop" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_arm_back_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_arm_front_end" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_arm_back_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + + anim.initAnimSet[ "gib_left_arm_front_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_arm_back_start" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_arm_front_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_arm_back_loop" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_arm_front_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_arm_back_end" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + + anim.initAnimSet[ "gib_right_leg_front_start" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_leg_back_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_leg_front_loop" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_leg_back_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_right_leg_front_end" ] = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch ); + anim.initAnimSet[ "gib_right_leg_back_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + + anim.initAnimSet[ "gib_left_leg_front_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_leg_back_start" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_leg_front_loop" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_leg_back_loop" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_left_leg_front_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_left_leg_back_end" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + + anim.initAnimSet[ "gib_no_legs_start" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + anim.initAnimSet[ "gib_no_legs_loop" ] = array( %CornerCrR_alert_painA, %CornerCrR_alert_painC ); + anim.initAnimSet[ "gib_no_legs_end" ] = array( %corner_standl_painB, %corner_standl_painC, %corner_standl_painD, %corner_standl_painE ); + + anim.initAnimSet[ "gib_shoulder_back" ] = %stand_death_shoulderback; + anim.initAnimSet[ "gib_shoulder_spin" ] = %stand_death_shoulder_spin; + anim.initAnimSet[ "gib_shoulder_twist" ] = %exposed_death_twist; + + assert( !isdefined( anim.animsets.gibAnimSet ) ); + anim.animsets.gibAnimSet = anim.initAnimSet; +} + +init_animset_crouch_wall() +{ + anim.initAnimSet = []; + + anim.initAnimSet[ "hide_idle" ] = %covercrouch_hide_idle; + anim.initAnimSet[ "hide_idle_twitch" ] = array( + %covercrouch_twitch_1, + %covercrouch_twitch_2, + %covercrouch_twitch_3, + %covercrouch_twitch_4 + ); + + anim.initAnimSet[ "hide_idle_flinch" ] = array(); + + anim.initAnimSet[ "hide_2_crouch" ] = %covercrouch_hide_2_aim; + anim.initAnimSet[ "hide_2_stand" ] = %covercrouch_hide_2_stand; + anim.initAnimSet[ "hide_2_lean" ] = %covercrouch_hide_2_lean; + anim.initAnimSet[ "hide_2_right" ] = %covercrouch_hide_2_right; + anim.initAnimSet[ "hide_2_left" ] = %covercrouch_hide_2_left; + + anim.initAnimSet[ "crouch_2_hide" ] = %covercrouch_aim_2_hide; + anim.initAnimSet[ "stand_2_hide" ] = %covercrouch_stand_2_hide; + anim.initAnimSet[ "lean_2_hide" ] = %covercrouch_lean_2_hide; + anim.initAnimSet[ "right_2_hide" ] = %covercrouch_right_2_hide; + anim.initAnimSet[ "left_2_hide" ] = %covercrouch_left_2_hide; + + + anim.initAnimSet[ "crouch_aim" ] = %covercrouch_aim5; + anim.initAnimSet[ "stand_aim" ] = %exposed_aim_5; + anim.initAnimSet[ "lean_aim" ] = %covercrouch_lean_aim5; + + anim.initAnimSet[ "fire" ] = %exposed_shoot_auto_v2; + anim.initAnimSet[ "semi2" ] = %exposed_shoot_semi2; + anim.initAnimSet[ "semi3" ] = %exposed_shoot_semi3; + anim.initAnimSet[ "semi4" ] = %exposed_shoot_semi4; + anim.initAnimSet[ "semi5" ] = %exposed_shoot_semi5; + + anim.initAnimSet[ "burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + anim.initAnimSet[ "burst3" ] = %exposed_shoot_burst3; + anim.initAnimSet[ "burst4" ] = %exposed_shoot_burst4; + anim.initAnimSet[ "burst5" ] = %exposed_shoot_burst5; + anim.initAnimSet[ "burst6" ] = %exposed_shoot_burst6; + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "blind_fire" ] = array( %covercrouch_blindfire_1, %covercrouch_blindfire_2, %covercrouch_blindfire_3, %covercrouch_blindfire_4 ); + + anim.initAnimSet[ "reload" ] = %covercrouch_reload_hide; + + anim.initAnimSet[ "grenade_safe" ] = array( %covercrouch_grenadeA, %covercrouch_grenadeB ); + anim.initAnimSet[ "grenade_exposed" ] = array( %covercrouch_grenadeA, %covercrouch_grenadeB ); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "look" ] = array( %covercrouch_hide_look ); + + anim.initAnimSet[ "shotgun_lean_single" ] = array( %shotgun_stand_fire_1A ); + anim.initAnimSet[ "shotgun_over_single" ] = array( %shotgun_crouch_fire ); + anim.initAnimSet[ "normal_single" ] = array( %exposed_shoot_semi1 ); + + assert( !isdefined( anim.animsets.crouchWallAnimSet ) ); + anim.animsets.crouchWallAnimSet = anim.initAnimSet; +} + +init_animset_stand_wall() +{ + anim.initAnimSet = []; + + anim.initAnimSet[ "hide_idle" ] = %coverstand_hide_idle; + anim.initAnimSet[ "hide_idle_twitch" ] = array( + %coverstand_hide_idle_twitch01, + %coverstand_hide_idle_twitch02, + %coverstand_hide_idle_twitch03, + %coverstand_hide_idle_twitch04, + %coverstand_hide_idle_twitch05 + ); + + anim.initAnimSet[ "hide_idle_flinch" ] = array( + %coverstand_react01, + %coverstand_react02, + %coverstand_react03, + %coverstand_react04 + ); + + anim.initAnimSet[ "hide_2_stand" ] = %coverstand_hide_2_aim; + anim.initAnimSet[ "stand_2_hide" ] = %coverstand_aim_2_hide; + + anim.initAnimSet[ "hide_2_over" ] = %coverstand_2_coverstandaim; + anim.initAnimSet[ "over_2_hide" ] = %coverstandaim_2_coverstand; + + anim.initAnimSet[ "over_aim" ] = %coverstandaim_aim5; + + anim.initAnimSet[ "over_fire" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_semi2" ] = %coverstandaim_fire; + anim.initAnimSet[ "over_semi3" ] = %coverstandaim_fire; + anim.initAnimSet[ "over_semi4" ] = %coverstandaim_fire; + anim.initAnimSet[ "over_semi5" ] = %coverstandaim_fire; + + anim.initAnimSet[ "over_single" ] = array( %coverstandaim_fire ); + + anim.initAnimSet[ "over_burst2" ] = %coverstandaim_autofire;// ( will be limited to 2 shots ) + anim.initAnimSet[ "over_burst3" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_burst4" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_burst5" ] = %coverstandaim_autofire; + anim.initAnimSet[ "over_burst6" ] = %coverstandaim_autofire; + + anim.initAnimSet[ "over_continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet[ "stand_aim" ] = %exposed_aim_5; + + anim.initAnimSet[ "stand_fire" ] = %exposed_shoot_auto_v2; + anim.initAnimSet[ "stand_semi2" ] = %exposed_shoot_semi2; + anim.initAnimSet[ "stand_semi3" ] = %exposed_shoot_semi3; + anim.initAnimSet[ "stand_semi4" ] = %exposed_shoot_semi4; + anim.initAnimSet[ "stand_semi5" ] = %exposed_shoot_semi5; + + anim.initAnimSet[ "stand_shotgun_single" ] = array( %shotgun_stand_fire_1A ); + anim.initAnimSet[ "stand_normal_single" ] = array( %exposed_shoot_semi1 ); + + anim.initAnimSet[ "stand_burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + anim.initAnimSet[ "stand_burst3" ] = %exposed_shoot_burst3; + anim.initAnimSet[ "stand_burst4" ] = %exposed_shoot_burst4; + anim.initAnimSet[ "stand_burst5" ] = %exposed_shoot_burst5; + anim.initAnimSet[ "stand_burst6" ] = %exposed_shoot_burst6; + + anim.initAnimSet[ "stand_continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + anim.initAnimSet["blind_fire"] = array( %coverstand_blindfire_1, %coverstand_blindfire_2 ); // #3 looks silly + + anim.initAnimSet[ "reload" ] = %coverstand_reloadA; + + anim.initAnimSet[ "look" ] = array( %coverstand_look_quick, %coverstand_look_quick_v2 ); + + anim.initAnimSet[ "grenade_safe" ] = array( %coverstand_grenadeA, %coverstand_grenadeB ); + anim.initAnimSet[ "grenade_exposed" ] = array( %coverstand_grenadeA, %coverstand_grenadeB ); + + anim.initAnimSet[ "exposed_idle" ] = array( %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ); + + anim.initAnimSet[ "hide_to_look" ] = %coverstand_look_moveup; + anim.initAnimSet[ "look_idle" ] = %coverstand_look_idle; + anim.initAnimSet[ "look_to_hide" ] = %coverstand_look_movedown; + anim.initAnimSet[ "look_to_hide_fast" ] = %coverstand_look_movedown_fast; + + assert( !isdefined( anim.animsets.standWallAnimSet ) ); + anim.animsets.standWallAnimSet = anim.initAnimSet; +} + +init_animset_complete_custom_stand( completeSet ) +{ + self.combatStandAnims = completeSet; +} + +init_animset_complete_custom_crouch( completeSet ) +{ + self.combatCrouchAnims = completeSet; +} + +init_animset_custom_crouch( fireAnim, idleAnim, reloadAnim ) +{ + assert( isdefined( anim.animsets ) && isdefined( anim.animsets.defaultCrouch ) ); + + anim.initAnimSet = anim.animsets.defaultCrouch; + + if ( isdefined( fireAnim ) ) + { + anim.initAnimSet[ "fire" ] = fireAnim; + anim.initAnimSet[ "single" ] = array( fireAnim ); + set_animarray_custom_burst_and_semi_fire_crouch( fireAnim ); + } + + if ( isdefined( idleAnim ) ) + anim.initAnimSet[ "exposed_idle" ] = array( idleAnim ); + + if ( isdefined( reloadAnim ) ) + anim.initAnimSet[ "reload" ] = array( reloadAnim ); + + self.combatCrouchAnims = anim.initAnimSet; +} + + +clear_custom_animset() +{ + self.customMoveAnimSet = undefined; + self.customIdleAnimSet = undefined; + + self.combatStandAnims = undefined; + self.combatCrouchAnims = undefined; + self.combatStandCQBAnims = undefined; + + self.customTurnAnimSet = undefined; + + self.customAnimFunc = undefined; + + self.customCoverEnterTrans = undefined; + self.customCoverExitTrans = undefined; + + self.customDeathAnimSet = undefined; + + self.customPainAnimSet = undefined; +} + + +//////////////////////////////////////////// +// Helpers for the above init_* +//////////////////////////////////////////// + +set_animarray_standing_turns_pistol( animArray ) +{ + anim.initAnimSet[ "turn_left_45" ] = %pistol_stand_turn45L; + anim.initAnimSet[ "turn_left_90" ] = %pistol_stand_turn90L; + anim.initAnimSet[ "turn_left_135" ] = %pistol_stand_turn90L; + anim.initAnimSet[ "turn_left_180" ] = %pistol_stand_turn180L; + anim.initAnimSet[ "turn_right_45" ] = %pistol_stand_turn45R; + anim.initAnimSet[ "turn_right_90" ] = %pistol_stand_turn90R; + anim.initAnimSet[ "turn_right_135" ] = %pistol_stand_turn90R; + anim.initAnimSet[ "turn_right_180" ] = %pistol_stand_turn180L; +} + +set_animarray_standing_turns() +{ + anim.initAnimSet[ "turn_left_45" ] = %exposed_tracking_turn45L; + anim.initAnimSet[ "turn_left_90" ] = %exposed_tracking_turn90L; + anim.initAnimSet[ "turn_left_135" ] = %exposed_tracking_turn135L; + anim.initAnimSet[ "turn_left_180" ] = %exposed_tracking_turn180L; + anim.initAnimSet[ "turn_right_45" ] = %exposed_tracking_turn45R; + anim.initAnimSet[ "turn_right_90" ] = %exposed_tracking_turn90R; + anim.initAnimSet[ "turn_right_135" ] = %exposed_tracking_turn135R; + anim.initAnimSet[ "turn_right_180" ] = %exposed_tracking_turn180R; +} + +set_animarray_crouching_turns() +{ + anim.initAnimSet[ "turn_left_45" ] = %exposed_crouch_turn_90_left; + anim.initAnimSet[ "turn_left_90" ] = %exposed_crouch_turn_90_left; + anim.initAnimSet[ "turn_left_135" ] = %exposed_crouch_turn_180_left; + anim.initAnimSet[ "turn_left_180" ] = %exposed_crouch_turn_180_left; + anim.initAnimSet[ "turn_right_45" ] = %exposed_crouch_turn_90_right; + anim.initAnimSet[ "turn_right_90" ] = %exposed_crouch_turn_90_right; + anim.initAnimSet[ "turn_right_135" ] = %exposed_crouch_turn_180_right; + anim.initAnimSet[ "turn_right_180" ] = %exposed_crouch_turn_180_right; +} + + +set_animarray_stance_change() +{ + anim.initAnimSet[ "crouch_2_stand" ] = %exposed_crouch_2_stand; + anim.initAnimSet[ "crouch_2_prone" ] = %crouch_2_prone; + anim.initAnimSet[ "stand_2_crouch" ] = %exposed_stand_2_crouch; + anim.initAnimSet[ "stand_2_prone" ] = %stand_2_prone; + anim.initAnimSet[ "prone_2_crouch" ] = %prone_2_crouch; + anim.initAnimSet[ "prone_2_stand" ] = %prone_2_stand; +} + +set_animarray_burst_and_semi_fire_stand() +{ + anim.initAnimSet[ "burst2" ] = %exposed_shoot_burst3;// ( will be stopped after second bullet ) + anim.initAnimSet[ "burst3" ] = %exposed_shoot_burst3; + anim.initAnimSet[ "burst4" ] = %exposed_shoot_burst4; + anim.initAnimSet[ "burst5" ] = %exposed_shoot_burst5; + anim.initAnimSet[ "burst6" ] = %exposed_shoot_burst6; + + anim.initAnimSet[ "semi2" ] = %exposed_shoot_semi2; + anim.initAnimSet[ "semi3" ] = %exposed_shoot_semi3; + anim.initAnimSet[ "semi4" ] = %exposed_shoot_semi4; + anim.initAnimSet[ "semi5" ] = %exposed_shoot_semi5; + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); +} + +set_animarray_burst_and_semi_fire_crouch() +{ + anim.initAnimSet[ "burst2" ] = %exposed_crouch_shoot_burst3; + anim.initAnimSet[ "burst3" ] = %exposed_crouch_shoot_burst3; + anim.initAnimSet[ "burst4" ] = %exposed_crouch_shoot_burst4; + anim.initAnimSet[ "burst5" ] = %exposed_crouch_shoot_burst5; + anim.initAnimSet[ "burst6" ] = %exposed_crouch_shoot_burst6; + + anim.initAnimSet[ "semi2" ] = %exposed_crouch_shoot_semi2; + anim.initAnimSet[ "semi3" ] = %exposed_crouch_shoot_semi3; + anim.initAnimSet[ "semi4" ] = %exposed_crouch_shoot_semi4; + anim.initAnimSet[ "semi5" ] = %exposed_crouch_shoot_semi5; + + anim.initAnimSet[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); +} + +set_animarray_custom_burst_and_semi_fire_crouch( fireAnim ) +{ + anim.initAnimSet[ "burst2" ] = fireAnim; + anim.initAnimSet[ "burst3" ] = fireAnim; + anim.initAnimSet[ "burst4" ] = fireAnim; + anim.initAnimSet[ "burst5" ] = fireAnim; + anim.initAnimSet[ "burst6" ] = fireAnim; + + anim.initAnimSet[ "semi2" ] = fireAnim; + anim.initAnimSet[ "semi3" ] = fireAnim; + anim.initAnimSet[ "semi4" ] = fireAnim; + anim.initAnimSet[ "semi5" ] = fireAnim; +} + + +set_animarray_add_turn_aims_stand() +{ + anim.initAnimSet[ "add_turn_aim_up" ] = %exposed_turn_aim_8; + anim.initAnimSet[ "add_turn_aim_down" ] = %exposed_turn_aim_2; + anim.initAnimSet[ "add_turn_aim_left" ] = %exposed_turn_aim_4; + anim.initAnimSet[ "add_turn_aim_right" ] = %exposed_turn_aim_6; +} + +set_animarray_add_turn_aims_crouch() +{ + anim.initAnimSet[ "add_turn_aim_up" ] = %exposed_crouch_turn_aim_8; + anim.initAnimSet[ "add_turn_aim_down" ] = %exposed_crouch_turn_aim_2; + anim.initAnimSet[ "add_turn_aim_left" ] = %exposed_crouch_turn_aim_4; + anim.initAnimSet[ "add_turn_aim_right" ] = %exposed_crouch_turn_aim_6; +} + + +//////////////////////////////////////////// +// Stand +//////////////////////////////////////////// + +set_animarray_standing() +{ + if ( usingSidearm() ) + { + self.a.array = anim.animsets.pistolStand; + } + else if ( isdefined( self.combatStandAnims ) ) + { + assert( isArray( self.combatStandAnims ) ); + self.a.array = self.combatStandAnims; + } + else if ( isdefined( self.heat ) ) + { + self.a.array = anim.animsets.heatStand; + } + else if ( usingRocketLauncher() ) + { + self.a.array = anim.animsets.rpgStand; + } + else if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() ) + { + self.a.array = anim.animsets.shotgunStand; + } + else if ( self isCQBWalking() ) + { + if ( isdefined( self.combatStandCQBAnims ) ) + { + assert( isArray( self.combatStandCQBAnims ) ); + self.a.array = self.combatStandCQBAnims; + } + else + { + self.a.array = anim.animsets.cqbStand; + } + } + else + { + self.a.array = anim.animsets.defaultStand; + } +} + + +//////////////////////////////////////////// +// Crouch +//////////////////////////////////////////// + +set_animarray_crouching() +{ + if ( usingSidearm() ) + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); + + if ( isdefined( self.combatCrouchAnims ) ) + { + assert( isArray( self.combatCrouchAnims ) ); + self.a.array = self.combatCrouchAnims; + } + else if ( usingRocketLauncher() ) + { + self.a.array = anim.animsets.rpgCrouch; + } + else if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() ) + { + self.a.array = anim.animsets.shotgunCrouch; + } + else + { + self.a.array = anim.animsets.defaultCrouch; + } +} + + + +//////////////////////////////////////////// +// Prone +//////////////////////////////////////////// + +set_animarray_prone() +{ + if ( usingSidearm() ) + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); + + self.a.array = anim.animsets.defaultProne; +} + + +//////////////////////////////////////////// +// Moving turn +//////////////////////////////////////////// + +init_moving_turn_animations() +{ + anim.runTurnAnims[ "L90" ] = %tp_moon_run_turn_L90; + anim.runTurnAnims[ "R90" ] = %tp_moon_run_turn_R90; + anim.runTurnAnims[ "L45" ] = %tp_moon_run_turn_L45; + anim.runTurnAnims[ "R45" ] = %tp_moon_run_turn_R45; + anim.runTurnAnims[ "L135" ] = %tp_moon_run_turn_L135; + anim.runTurnAnims[ "R135" ] = %tp_moon_run_turn_R135; + anim.runTurnAnims[ "180" ] = %tp_moon_run_turn_180; + + anim.cqbTurnAnims[ "L90" ] = %tp_moon_cqb_turn_L90; + anim.cqbTurnAnims[ "R90" ] = %tp_moon_cqb_turn_R90; + anim.cqbTurnAnims[ "L45" ] = %tp_moon_cqb_turn_L45; + anim.cqbTurnAnims[ "R45" ] = %tp_moon_cqb_turn_R45; + anim.cqbTurnAnims[ "L135" ] = %tp_moon_cqb_turn_L135; + anim.cqbTurnAnims[ "R135" ] = %tp_moon_cqb_turn_R135; + anim.cqbTurnAnims[ "180" ] = %tp_moon_cqb_turn_180; +} + + +//////////////////////////////////////////// +// Misc +//////////////////////////////////////////// + +init_animset_run_n_gun() +{ + anim.runNGunAnims[ "F" ] = %run_n_gun_F; + anim.runNGunAnims[ "L" ] = %run_n_gun_L; + anim.runNGunAnims[ "R" ] = %run_n_gun_R; + anim.runNGunAnims[ "LB" ] = %run_n_gun_L_120; + anim.runNGunAnims[ "RB" ] = %run_n_gun_R_120; +} + + +init_ambush_sidestep_anims() +{ + anim.moveAnimSet[ "move_l" ] = %combatwalk_L; + anim.moveAnimSet[ "move_r" ] = %combatwalk_R; + anim.moveAnimSet[ "move_b" ] = %combatwalk_B; +} + +init_heat_reload_function() +{ + anim.heat_reload_anim_func = ::heat_reload_anim; +} + + +heat_reload_anim() +{ + if ( self.weapon != self.primaryweapon ) + return animArrayPickRandom( "reload" ); + + if ( isdefined( self.node ) ) + { + if ( self nearClaimNodeAndAngle() ) + { + coverReloadAnim = undefined; + if ( self.node.type == "Cover Left" ) + coverReloadAnim = %heat_cover_reload_R; + else if ( self.node.type == "Cover Right" ) + coverReloadAnim = %heat_cover_reload_L; + + if ( isdefined( coverReloadAnim ) ) + { + //self mayMoveToPoint( reloadAnimPos ); + return coverReloadAnim; + } + } + } + + return %heat_exposed_reload; +} + +init_animarray_standing_left() /* void */ +{ + array = []; + + array[ "alert_idle" ] = %tp_moon_coverL_stand_alert_idle; + array[ "alert_idle_twitch" ] = [ %tp_moon_coverL_stand_alert_twitch_01 ]; + array[ "alert_idle_flinch" ] = [ %tp_moon_coverL_stand_alert_flinch_01 ]; + + array[ "alert_to_A" ] = [ %tp_moon_coverL_stand_alert_2_A_01 ]; + array[ "alert_to_B" ] = [ %tp_moon_coverL_stand_alert_2_B_01 ]; + array[ "A_to_alert" ] = [ %tp_moon_coverL_stand_A_2_alert_01 ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %tp_moon_coverL_stand_A_2_B_01 ]; + array[ "B_to_alert" ] = [ %tp_moon_coverL_stand_B_2_alert_01 ]; + array[ "B_to_alert_reload" ] = [ ]; + array[ "B_to_A" ] = [ %tp_moon_coverL_stand_B_2_A_01 ]; + array[ "lean_to_alert" ] = [ %tp_moon_coverL_stand_lean_2_alert_01 ]; + array[ "alert_to_lean" ] = [ %tp_moon_coverL_stand_alert_2_lean_01 ]; + array[ "look" ] = %corner_standL_look; + array[ "reload" ] = [ %tp_moon_coverL_stand_reload_01 ];// , %corner_standL_reload_v2 ); + array[ "grenade_exposed" ] = %tp_moon_coverL_stand_grenade_exposed; + array[ "grenade_safe" ] = %tp_moon_coverL_stand_grenade_exposed; + + array[ "blind_fire" ] = [ %tp_moon_coverL_stand_blindfire_01 ]; + + array[ "alert_to_look" ] = %corner_standL_alert_2_look; + array[ "look_to_alert" ] = %corner_standL_look_2_alert; + array[ "look_to_alert_fast" ] = %corner_standl_look_2_alert_fast_v1; + array[ "look_idle" ] = %corner_standL_look_idle; + array[ "stance_change" ] = %tp_moon_coverL_stand_stand_2_alert; + + array[ "lean_aim_down" ] = %tp_moon_coverL_stand_lean_aim_down; + array[ "lean_aim_left" ] = %tp_moon_coverL_stand_lean_aim_left; + array[ "lean_aim_straight" ] = %tp_moon_coverL_stand_lean_aim_straight; + array[ "lean_aim_right" ] = %tp_moon_coverL_stand_lean_aim_right; + array[ "lean_aim_up" ] = %tp_moon_coverL_stand_lean_aim_up; + array[ "lean_reload" ] = %tp_moon_coverL_stand_lean_reload; + + array[ "lean_idle" ] = [ %tp_moon_coverL_stand_lean_idle_01 ]; + + array[ "lean_single" ] = %tp_moon_coverL_stand_lean_fire_single; + //array["lean_burst"] = %CornerStndL_lean_autoburst; + array[ "lean_fire" ] = %tp_moon_coverL_stand_lean_fire_auto; + + if ( isDefined( anim.ramboAnims ) ) + { + //array[ "rambo" ] = [ %corner_standL_rambo_set, %corner_standL_rambo_jam ]; + array[ "rambo90" ] = anim.ramboAnims.coverleft90; + array[ "rambo45" ] = anim.ramboAnims.coverleft45; + array[ "grenade_rambo" ] = anim.ramboAnims.coverleftgrenade; + } + + anim.coverLeftStand = array; +} + + +init_animarray_crouching_left() +{ + array = []; + + array[ "alert_idle" ] = %CornerCrL_alert_idle; + array[ "alert_idle_twitch" ] = []; + array[ "alert_idle_flinch" ] = []; + + array[ "alert_to_A" ] = [ %CornerCrL_trans_alert_2_A ]; + array[ "alert_to_B" ] = [ %CornerCrL_trans_alert_2_B ]; + array[ "A_to_alert" ] = [ %CornerCrL_trans_A_2_alert ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %CornerCrL_trans_A_2_B ]; + array[ "B_to_alert" ] = [ %CornerCrL_trans_B_2_alert ]; + array[ "B_to_alert_reload" ] = []; + array[ "B_to_A" ] = [ %CornerCrL_trans_B_2_A ]; + array[ "lean_to_alert" ] = [ %CornerCrL_lean_2_alert ]; + array[ "alert_to_lean" ] = [ %CornerCrL_alert_2_lean ]; + + array[ "look" ] = %CornerCrL_look_fast; + array[ "reload" ] = [ %CornerCrL_reloadA, %CornerCrL_reloadB ]; + array[ "grenade_safe" ] = %CornerCrL_grenadeA; //when replacing these, please generate offsets for initGrenadeThrowAnims + array[ "grenade_exposed" ] = %CornerCrL_grenadeB; + + array[ "alert_to_over" ] = [ %CornerCrL_alert_2_over ]; + array[ "over_to_alert" ] = [ %CornerCrL_over_2_alert ]; + array[ "over_to_alert_reload" ] = []; + array[ "blind_fire" ] = []; + + array[ "rambo90" ] = []; + array[ "rambo45" ] = []; + + array[ "stance_change" ] = %CornerCrL_alert_2_stand; + + array[ "lean_aim_down" ] = %CornerCrL_lean_aim_2; + array[ "lean_aim_left" ] = %CornerCrL_lean_aim_4; + array[ "lean_aim_straight" ] = %CornerCrL_lean_aim_5; + array[ "lean_aim_right" ] = %CornerCrL_lean_aim_6; + array[ "lean_aim_up" ] = %CornerCrL_lean_aim_8; + + array[ "lean_idle" ] = [ %CornerCrL_lean_idle ]; + + array[ "lean_single" ] = %CornerCrL_lean_fire; + array[ "lean_fire" ] = %CornerCrL_lean_auto; + + anim.coverLeftCrouch = array; +} + +init_animarray_standing_right() /* void */ +{ + array = []; + + array[ "alert_idle" ] = %tp_moon_coverR_stand_alert_idle; + array[ "alert_idle_twitch" ] = [ %tp_moon_coverR_stand_alert_twitch_01 ]; + array[ "alert_idle_flinch" ] = [ %tp_moon_coverR_stand_alert_flinch_01 ]; + + array[ "alert_to_A" ] = [ %tp_moon_coverR_stand_alert_2_A_01 ]; + array[ "alert_to_B" ] = [ %tp_moon_coverR_stand_alert_2_B_01 ]; + array[ "A_to_alert" ] = [ %tp_moon_coverR_stand_A_2_alert_01 ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %tp_moon_coverR_stand_A_2_B_01 ]; + array[ "B_to_alert" ] = [ %tp_moon_coverR_stand_B_2_alert_01 ]; + array[ "B_to_alert_reload" ] = [ %tp_moon_coverR_stand_B_2_alert_reload ]; + array[ "B_to_A" ] = [ %tp_moon_coverR_stand_B_2_A_01 ]; + array[ "lean_to_alert" ] = [ %tp_moon_coverR_stand_lean_2_alert_01 ]; + array[ "alert_to_lean" ] = [ %tp_moon_coverR_stand_alert_2_lean_01 ]; + array[ "look" ] = %tp_moon_coverR_stand_look; + array[ "reload" ] = [ %tp_moon_coverR_stand_reload_01 ]; + array[ "grenade_exposed" ] = %tp_moon_coverR_stand_grenade_exposed; + array[ "grenade_safe" ] = %tp_moon_coverR_stand_grenade_safe; + + array[ "blind_fire" ] = [ %tp_moon_coverR_stand_blindfire_01 ]; + + array[ "alert_to_look" ] = %tp_moon_coverR_stand_alert_2_look; + array[ "look_to_alert" ] = %tp_moon_coverR_stand_look_2_alert; + array[ "look_to_alert_fast" ] = %tp_moon_coverR_stand_look_2_alert_fast; + array[ "look_idle" ] = %tp_moon_coverR_stand_look_idle; + array[ "stance_change" ] = %tp_moon_coverR_stand_stand_2_alert; + + array[ "lean_aim_down" ] = %tp_moon_coverR_stand_lean_aim_down; + array[ "lean_aim_left" ] = %tp_moon_coverR_stand_lean_aim_left; + array[ "lean_aim_straight" ] = %tp_moon_coverR_stand_lean_aim_straight; + array[ "lean_aim_right" ] = %tp_moon_coverR_stand_lean_aim_right; + array[ "lean_aim_up" ] = %tp_moon_coverR_stand_lean_aim_up; + array[ "lean_reload" ] = %tp_moon_coverR_stand_lean_reload; + + array[ "lean_idle" ] = [ %CornerStndR_lean_idle ]; + + array[ "lean_single" ] = %tp_moon_coverR_stand_lean_fire_single; + array[ "lean_fire" ] = %tp_moon_coverR_stand_lean_fire_auto; + + if ( isDefined( anim.ramboAnims ) ) + { + array[ "rambo90" ] = anim.ramboAnims.coverright90; + array[ "rambo45" ] = anim.ramboAnims.coverright45; + array[ "grenade_rambo" ] = anim.ramboAnims.coverrightgrenade; + } + + anim.coverRightStand = array; +} + +init_animarray_crouching_right() +{ + array = []; + + array[ "alert_idle" ] = %CornerCrR_alert_idle; + array[ "alert_idle_twitch" ] = [ + %CornerCrR_alert_twitch_v1, + %CornerCrR_alert_twitch_v2, + %CornerCrR_alert_twitch_v3 + ]; + array[ "alert_idle_flinch" ] = []; + + array[ "alert_to_A" ] = [ %CornerCrR_trans_alert_2_A ]; + array[ "alert_to_B" ] = [ %CornerCrR_trans_alert_2_B ]; + array[ "A_to_alert" ] = [ %CornerCrR_trans_A_2_alert ]; + array[ "A_to_alert_reload" ] = []; + array[ "A_to_B" ] = [ %CornerCrR_trans_A_2_B ]; + array[ "B_to_alert" ] = [ %CornerCrR_trans_B_2_alert ]; + array[ "B_to_alert_reload" ] = []; + array[ "B_to_A" ] = [ %CornerCrR_trans_B_2_A ]; + array[ "lean_to_alert" ] = [ %CornerCrR_lean_2_alert ]; + array[ "alert_to_lean" ] = [ %CornerCrR_alert_2_lean ]; + array[ "reload" ] = [ %CornerCrR_reloadA, %CornerCrR_reloadB ]; + array[ "grenade_exposed" ] = %CornerCrR_grenadeA;//when replacing these, please generate offsets for initGrenadeThrowAnims + array[ "grenade_safe" ] = %CornerCrR_grenadeA;// TODO: need a unique animation for this; use the exposed throw because not having it limits the options of the AI too much + + array[ "alert_to_over" ] = [ %CornerCrR_alert_2_over ]; + array[ "over_to_alert" ] = [ %CornerCrR_over_2_alert ]; + array[ "over_to_alert_reload" ] = []; + + array[ "blind_fire" ] = []; + + array[ "rambo90" ] = []; + array[ "rambo45" ] = []; + + array[ "alert_to_look" ] = %CornerCrR_alert_2_look; + array[ "look_to_alert" ] = %CornerCrR_look_2_alert; + array[ "look_to_alert_fast" ] = %CornerCrR_look_2_alert_fast;// there's a v2 we could use for this also if we want + array[ "look_idle" ] = %CornerCrR_look_idle; + array[ "stance_change" ] = %CornerCrR_alert_2_stand; + + array[ "lean_aim_down" ] = %CornerCrR_lean_aim_2; + array[ "lean_aim_left" ] = %CornerCrR_lean_aim_4; + array[ "lean_aim_straight" ] = %CornerCrR_lean_aim_5; + array[ "lean_aim_right" ] = %CornerCrR_lean_aim_6; + array[ "lean_aim_up" ] = %CornerCrR_lean_aim_8; + + array[ "lean_idle" ] = [ %CornerCrR_lean_idle ]; + + array[ "lean_single" ] = %CornerCrR_lean_fire; + array[ "lean_fire" ] = %CornerCrR_lean_auto; + + anim.coverRightCrouch = array; +} + +// generated with scr_testgrenadethrows in combat.gsc +initGrenadeThrowAnims() +{ + //lunar anims, as of 6/28/11 these need to have offsets generated by scr_testgrenaethrows + animscripts\init_common::addGrenadeThrowAnimOffset( %tp_moon_coverL_stand_grenade_exposed, ( 0, 0, 64 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %tp_moon_coverL_stand_grenade_safe, ( 0, 0, 64 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %tp_moon_coverR_stand_grenade_exposed, ( 0, 0, 64 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %tp_moon_coverR_stand_grenade_safe, ( 0, 0, 64 ) ); + + //non-lunar grenade throws not yet replaced (and may not be) + animscripts\init_common::addGrenadeThrowAnimOffset( %exposed_grenadethrowb, ( 41.5391, 7.28883, 72.2128 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %exposed_grenadethrowc, ( 34.8849, -4.77048, 74.0488 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %cornercrl_grenadea, ( 25.8988, -10.2811, 30.4813 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %cornercrl_grenadeb, ( 24.688, 45.0702, 64.377 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %cornercrr_grenadea, ( 39.8857, 5.92472, 24.5878 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %covercrouch_grenadea, ( -1.6363, -0.693674, 60.1009 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %covercrouch_grenadeb, ( -1.6363, -0.693674, 60.1009 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %coverstand_grenadea, ( 10.8573, 7.12614, 77.2356 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %coverstand_grenadeb, ( 19.1804, 5.68214, 73.2278 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %prone_grenade_a, ( 12.2859, -1.3019, 33.4307 ) ); + animscripts\init_common::addGrenadeThrowAnimOffset( %CQB_stand_grenade_throw, ( 35.7494, 26.6052, 37.7086 ) ); +} + +init_noder_anims() +{ + array = []; + array[ "node_cover_left" ][ 0 ] = %CornerCrL_reloadA; + array[ "node_cover_left" ][ 1 ] = %CornerCrL_look_fast; + array[ "node_cover_left" ][ 2 ] = %tp_moon_coverL_stand_grenade_exposed; + array[ "node_cover_left" ][ 3 ] = %tp_moon_coverL_stand_alert_flinch_01; + array[ "node_cover_left" ][ 4 ] = %corner_standL_look_idle; + array[ "node_cover_left" ][ 5 ] = %corner_standL_look_2_alert; + + array[ "node_cover_right" ][ 0 ] = %CornerCrR_reloadA; + array[ "node_cover_right" ][ 1 ] = %tp_moon_coverR_stand_grenade_safe; + array[ "node_cover_right" ][ 2 ] = %tp_moon_coverR_stand_alert_flinch_01; + array[ "node_cover_right" ][ 3 ] = %tp_moon_coverR_stand_look_idle; + array[ "node_cover_right" ][ 4 ] = %tp_moon_coverR_stand_look_2_alert; + + array[ "node_cover_crouch" ][ 0 ] = %covercrouch_hide_idle; + array[ "node_cover_crouch" ][ 1 ] = %covercrouch_twitch_1; + array[ "node_cover_crouch" ][ 2 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch" ][ 3 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch" ][ 4 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch" ][ 5 ] = %covercrouch_hide_look; + + array[ "node_cover_crouch_window" ][ 0 ] = %covercrouch_hide_idle; + array[ "node_cover_crouch_window" ][ 1 ] = %covercrouch_twitch_1; + array[ "node_cover_crouch_window" ][ 2 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch_window" ][ 3 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch_window" ][ 4 ] = %covercrouch_hide_2_aim; + array[ "node_cover_crouch_window" ][ 5 ] = %covercrouch_hide_look; + + array[ "node_cover_prone" ][ 0 ] = %crouch_2_prone_firing; + array[ "node_cover_prone" ][ 1 ] = %prone_2_crouch; + array[ "node_cover_prone" ][ 2 ] = %prone_reload; + + array[ "node_cover_stand" ][ 0 ] = %coverstand_reloadA; + + array[ "node_concealment_crouch" ][ 0 ] = %covercrouch_hide_idle; + array[ "node_concealment_crouch" ][ 1 ] = %covercrouch_twitch_1; + array[ "node_concealment_crouch" ][ 2 ] = %covercrouch_hide_2_aim; + array[ "node_concealment_crouch" ][ 3 ] = %covercrouch_hide_2_aim; + array[ "node_concealment_crouch" ][ 4 ] = %covercrouch_hide_2_aim; + array[ "node_concealment_crouch" ][ 5 ] = %covercrouch_hide_look; + + array[ "node_concealment_prone" ][ 0 ] = %crouch_2_prone_firing; + array[ "node_concealment_prone" ][ 1 ] = %prone_2_crouch; + array[ "node_concealment_prone" ][ 2 ] = %prone_reload; + + array[ "node_concealment_stand" ][ 0 ] = %coverstand_reloadA; + + anim.noderAnims = array; +} + +init_standing_animarray_aiming() +{ + array = []; + array[ "add_aim_up" ] = %tp_moon_exposed_stand_add_aim_up; + array[ "add_aim_down" ] = %tp_moon_exposed_stand_add_aim_down; + array[ "add_aim_left" ] = %tp_moon_exposed_stand_add_aim_left; + array[ "add_aim_right" ] = %tp_moon_exposed_stand_add_aim_right; + array[ "add_turn_aim_up" ] = %exposed_turn_aim_8; + array[ "add_turn_aim_down" ] = %exposed_turn_aim_2; + array[ "add_turn_aim_left" ] = %exposed_turn_aim_4; + array[ "add_turn_aim_right" ] = %exposed_turn_aim_6; + array[ "straight_level" ] = %tp_moon_exposed_stand_aim_straight; + + array[ "fire" ] = %tp_moon_exposed_stand_fire; + array[ "semi2" ] = %exposed_shoot_semi2; + array[ "semi3" ] = %exposed_shoot_semi3; + array[ "semi4" ] = %exposed_shoot_semi4; + array[ "semi5" ] = %exposed_shoot_semi5; + + array[ "shotgun_single" ] = [ %shotgun_stand_fire_1A ]; //used to replace single with this one when pump action. + array[ "single" ] = [ %tp_moon_exposed_stand_single_01 ]; + + array[ "burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + array[ "burst3" ] = %exposed_shoot_burst3; + array[ "burst4" ] = %exposed_shoot_burst4; + array[ "burst5" ] = %exposed_shoot_burst5; + array[ "burst6" ] = %exposed_shoot_burst6; + + array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + + array[ "exposed_idle" ] = [ %tp_moon_exposed_stand_idle ]; + + anim.standingAiming = array; +} + +init_crouching_animarray_aiming() +{ + array = []; + array[ "add_aim_up" ] = %covercrouch_aim8_add; + array[ "add_aim_down" ] = %covercrouch_aim2_add; + array[ "add_aim_left" ] = %covercrouch_aim4_add; + array[ "add_aim_right" ] = %covercrouch_aim6_add; + array[ "straight_level" ] = %covercrouch_aim5; + + array[ "fire" ] = %exposed_shoot_auto_v2; + array[ "semi2" ] = %exposed_shoot_semi2; + array[ "semi3" ] = %exposed_shoot_semi3; + array[ "semi4" ] = %exposed_shoot_semi4; + array[ "semi5" ] = %exposed_shoot_semi5; + + array[ "burst2" ] = %exposed_shoot_burst3;// ( will be limited to 2 shots ) + array[ "burst3" ] = %exposed_shoot_burst3; + array[ "burst4" ] = %exposed_shoot_burst4; + array[ "burst5" ] = %exposed_shoot_burst5; + array[ "burst6" ] = %exposed_shoot_burst6; + + array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + + array[ "shotgun_single" ] = [ %shotgun_crouch_fire ]; + array[ "single" ] = [ %exposed_shoot_semi1 ]; + array[ "exposed_idle" ] = [ %exposed_idle_alert_v1, %exposed_idle_alert_v2, %exposed_idle_alert_v3 ]; + + anim.crouchingAimingOver = array; + + + + array = []; + array[ "fire" ] = %exposed_crouch_shoot_auto_v2; + array[ "semi2" ] = %exposed_crouch_shoot_semi2; + array[ "semi3" ] = %exposed_crouch_shoot_semi3; + array[ "semi4" ] = %exposed_crouch_shoot_semi4; + array[ "semi5" ] = %exposed_crouch_shoot_semi5; + + array[ "continuous" ] = [ %nx_tp_stand_exposed_stream_01 ]; + + array[ "shotgun_single" ] = [ %shotgun_crouch_fire ]; + array[ "single" ] = [ %exposed_crouch_shoot_semi1 ]; + + array[ "burst2" ] = %exposed_crouch_shoot_burst3;// ( will be limited to 2 shots ) + array[ "burst3" ] = %exposed_crouch_shoot_burst3; + array[ "burst4" ] = %exposed_crouch_shoot_burst4; + array[ "burst5" ] = %exposed_crouch_shoot_burst5; + array[ "burst6" ] = %exposed_crouch_shoot_burst6; + + array[ "add_aim_up" ] = %exposed_crouch_aim_8; + array[ "add_aim_down" ] = %exposed_crouch_aim_2; + array[ "add_aim_left" ] = %exposed_crouch_aim_4; + array[ "add_aim_right" ] = %exposed_crouch_aim_6; + array[ "add_turn_aim_up" ] = %exposed_crouch_turn_aim_8; + array[ "add_turn_aim_down" ] = %exposed_crouch_turn_aim_2; + array[ "add_turn_aim_left" ] = %exposed_crouch_turn_aim_4; + array[ "add_turn_aim_right" ] = %exposed_crouch_turn_aim_6; + array[ "straight_level" ] = %exposed_crouch_aim_5; + + array[ "exposed_idle" ] = [ %exposed_crouch_idle_alert_v1, %exposed_crouch_idle_alert_v2, %exposed_crouch_idle_alert_v3 ]; + + anim.crouchingAiming = array; +} + +init_reaction_anims() +{ + anim.runningReactToBullets = []; + anim.runningReactToBullets = array( %run_react_duck, %run_react_flinch, %run_react_stumble ); + + anim.lastRunningReactAnim = 0; + + anim.reactionAnimArray = []; + anim.reactionAnimArray[ "cover_stand" ] = array( %stand_cover_reaction_A, %stand_cover_reaction_B ); + anim.reactionAnimArray[ "cover_crouch" ] = array( %crouch_cover_reaction_A, %crouch_cover_reaction_B ); + anim.reactionAnimArray[ "cover_left" ] = array( %CornerStndL_react_A ); + anim.reactionAnimArray[ "cover_right" ] = array( %CornerStndR_react_A ); + anim.reactionAnimArray[ "wizby_idle" ] = array( %exposed_idle_reactA, %exposed_idle_reactB, %exposed_idle_twitch, %exposed_idle_twitch_v4); + anim.reactionAnimArray[ "wizby_dive" ] = array( %exposed_dive_grenade_B, %exposed_dive_grenade_F ); + anim.reactionAnimArray[ "wizby_twitch" ] = array( %exposed_crouch_idle_twitch_v2, %exposed_crouch_idle_twitch_v3 ); + anim.reactionAnimArray[ "wizby_crouch" ] = %exposed_stand_2_crouch; + anim.reactionAnimArray[ "wizby_turn" ] = array( %exposed_crouch_turn_180_left, %exposed_crouch_turn_180_right ); + + anim.reactionAnimArray[ "stand" ] = array( %exposed_backpedal, %exposed_idle_reactB ); + anim.reactionAnimArray[ "crouch" ] = array( %crouch_cover_reaction_A, %crouch_cover_reaction_B ); + anim.reactionAnimArray[ "stealth" ] = %exposed_idle_reactB; + anim.reactionAnimArray[ "stealth_backpedal" ] = %exposed_backpedal; + anim.reactionAnimArray[ "stealth_backpedal2" ] = %exposed_backpedal_v2; + anim.reactionAnimArray[ "surprise" ] = %surprise_stop_v1; +} \ No newline at end of file diff --git a/animscripts/lunar/init.gsc b/animscripts/lunar/init.gsc new file mode 100644 index 0000000..e4cb310 --- /dev/null +++ b/animscripts/lunar/init.gsc @@ -0,0 +1,35 @@ + +#include animscripts\init_common; + +main() +{ + prof_begin( "animscript_init" ); + + pre_first_init(); + firstInit(); + post_first_init(); + + prof_end( "animscript_init" ); +} + +firstInit() +{ + // Initialization that should happen once per level + if ( isDefined( anim.NotFirstTime ) )// Use this to trigger the first init + { + return false; + } + + pre_anim_init(); + + animscripts\lunar\animset::init_anim_sets();//lunar version + animscripts\lunar\init_move_transitions::initMoveStartStopTransitions(); + + anim.lastGibTime = 0; + anim.gibDelay = 3 * 1000; // 3 seconds + anim.minGibs = 2; + anim.maxGibs = 4; + anim.totalGibs = RandomIntRange( anim.minGibs, anim.maxGibs ); + + post_anim_init(); +} diff --git a/animscripts/lunar/init_move_transitions.gsc b/animscripts/lunar/init_move_transitions.gsc new file mode 100644 index 0000000..5709f57 --- /dev/null +++ b/animscripts/lunar/init_move_transitions.gsc @@ -0,0 +1,982 @@ +#include animscripts\Utility; +#include maps\_utility; +#include animscripts\Combat_utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + +//The LUNAR version. + +init_move_transition_arrays() +{ + if ( isdefined( anim.move_transition_arrays ) ) + return; + + anim.move_transition_arrays = 1; + + anim.coverTrans = []; + anim.coverExit = []; + anim.maxDirections = []; + anim.excludeDir = []; + + anim.traverseInfo = []; + + anim.coverTransLongestDist = []; + anim.coverTransDist = []; + anim.coverExitDist = []; + + // this is the distance moved to get around corner for 7, 8, 9 directions + anim.coverExitPostDist = []; + + // this is the distance moved to get around corner for 7, 8, 9 directions + anim.coverTransPreDist = []; + + anim.coverTransAngles = []; + anim.coverExitAngles = []; + + anim.coverExitSplit = []; + anim.coverTransSplit = []; + + anim.arrivalEndStance = []; +} + + +initMoveStartStopTransitions() +{ + init_move_transition_arrays(); + + // Move transition points + init_moon_transition_points(); + + // TEMP, remove this flag + level._newArrivals = true; + + transTypes = []; + transTypes[ 0 ] = "left"; + transTypes[ 1 ] = "right"; + transTypes[ 2 ] = "left_crouch"; + transTypes[ 3 ] = "right_crouch"; + transTypes[ 4 ] = "crouch"; + transTypes[ 5 ] = "stand"; + transTypes[ 6 ] = "exposed"; + transTypes[ 7 ] = "exposed_crouch"; + transTypes[ 8 ] = "stand_saw"; + transTypes[ 9 ] = "prone_saw"; + transTypes[ 10 ] = "crouch_saw"; + transTypes[ 11 ] = "wall_over_40"; + transTypes[ 12 ] = "right_cqb"; + transTypes[ 13 ] = "right_crouch_cqb"; + transTypes[ 14 ] = "left_cqb"; + transTypes[ 15 ] = "left_crouch_cqb"; + transTypes[ 16 ] = "exposed_cqb"; + transTypes[ 17 ] = "exposed_crouch_cqb"; + transTypes[ 18 ] = "heat"; + transTypes[ 19 ] = "heat_left"; + transTypes[ 20 ] = "heat_right"; + + + lastCoverTrans = 6; + + // tagJW: Non-lunar movements do not use move transition notes + // Moon_actor's main is called before load, so don't override these if they exist + if ( !isdefined( anim.run_transition_notes ) ) + { + anim.cqb_transition_notes = []; + anim.cqb_transition_points = []; + + anim.run_transition_notes = []; + anim.run_transition_points = []; + } + + anim.approach_types = []; + + anim.approach_types[ "Cover Left" ] = []; + anim.approach_types[ "Cover Left" ][ "stand" ] = "left"; + anim.approach_types[ "Cover Left" ][ "crouch" ] = "left_crouch"; + anim.maxDirections[ "Cover Left" ] = 9; + anim.excludeDir[ "Cover Left" ] = 9; + + anim.approach_types[ "Cover Right" ] = []; + anim.approach_types[ "Cover Right" ][ "stand" ] = "right"; + anim.approach_types[ "Cover Right" ][ "crouch" ] = "right_crouch"; + anim.maxDirections[ "Cover Right" ] = 9; + anim.excludeDir[ "Cover Right" ] = 7; + + anim.approach_types[ "Cover Crouch" ] = []; + anim.approach_types[ "Cover Crouch" ][ "stand" ] = "crouch"; + anim.approach_types[ "Cover Crouch" ][ "crouch" ] = "crouch"; + anim.approach_types[ "Conceal Crouch" ] = anim.approach_types[ "Cover Crouch" ]; + anim.approach_types[ "Cover Crouch Window" ] = anim.approach_types[ "Cover Crouch" ]; + anim.maxDirections[ "Cover Crouch" ] = 6; + anim.excludeDir[ "Cover Crouch" ] = -1; + anim.maxDirections[ "Conceal Crouch" ] = 6; + anim.excludeDir[ "Conceal Crouch" ] = -1; + + anim.approach_types[ "Cover Stand" ] = []; + anim.approach_types[ "Cover Stand" ][ "stand" ] = "stand"; + anim.approach_types[ "Cover Stand" ][ "crouch" ] = "stand"; + anim.approach_types[ "Conceal Stand" ] = anim.approach_types[ "Cover Stand" ]; + anim.maxDirections[ "Cover Stand" ] = 6; + anim.excludeDir[ "Cover Stand" ] = -1; + anim.maxDirections[ "Conceal Stand" ] = 6; + anim.excludeDir[ "Conceal Stand" ] = -1; + + anim.approach_types[ "Cover Prone" ] = []; + anim.approach_types[ "Cover Prone" ][ "stand" ] = "exposed"; + anim.approach_types[ "Cover Prone" ][ "crouch" ] = "exposed"; + anim.approach_types[ "Conceal Prone" ] = anim.approach_types[ "Cover Prone" ]; + anim.excludeDir[ "Conceal Prone" ] = -1; + + anim.approach_types[ "Path" ] = []; + anim.approach_types[ "Path" ][ "stand" ] = "exposed"; + anim.approach_types[ "Path" ][ "crouch" ] = "exposed_crouch"; + anim.approach_types[ "Guard" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Ambush" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Scripted" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Custom" ] = anim.approach_types[ "Path" ]; + anim.approach_types[ "Exposed" ] = anim.approach_types[ "Path" ]; + + anim.isCombatPathNode[ "Guard" ] = true; + anim.isCombatPathNode[ "Ambush" ] = true; + anim.isCombatPathNode[ "Exposed" ] = true; + + // used by level script to orient AI in certain ways at a node + anim.isCombatScriptNode[ "Guard" ] = true; + anim.isCombatScriptNode[ "Exposed" ] = true; + + // CORNER TRANSITIONS ANIMS + // indicies indicate the keyboard numpad directions (8 is forward) + // 7 8 9 + // 4 6 <- 5 is invalid + // 1 2 3 + + /************************************************* + * Entrance Animations + *************************************************/ + + anim.coverTrans[ "right" ][ 1 ] = %tp_moon_coverR_run_2_stand_BL; + anim.coverTrans[ "right" ][ 2 ] = %tp_moon_coverR_run_2_stand_B; + anim.coverTrans[ "right" ][ 3 ] = %tp_moon_coverR_run_2_stand_BR; + anim.coverTrans[ "right" ][ 4 ] = %tp_moon_coverR_run_2_stand_L; + anim.coverTrans[ "right" ][ 6 ] = %tp_moon_coverR_run_2_stand_R; + //im.coverTrans[ "right" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right" ][ 8 ] = %tp_moon_coverR_run_2_stand_F; + anim.coverTrans[ "right" ][ 9 ] = %tp_moon_coverR_run_2_stand_FR; + + anim.coverTrans[ "right_crouch" ][ 1 ] = %tp_moon_coverR_run_2_crouch_BL; + anim.coverTrans[ "right_crouch" ][ 2 ] = %tp_moon_coverR_run_2_crouch_B; + anim.coverTrans[ "right_crouch" ][ 3 ] = %tp_moon_coverR_run_2_crouch_BR; + anim.coverTrans[ "right_crouch" ][ 4 ] = %tp_moon_coverR_run_2_crouch_L; + anim.coverTrans[ "right_crouch" ][ 6 ] = %tp_moon_coverR_run_2_crouch_R; + //im.coverTrans[ "right_crouch" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right_crouch" ][ 8 ] = %tp_moon_coverR_run_2_crouch_F; + anim.coverTrans[ "right_crouch" ][ 9 ] = %tp_moon_coverR_run_2_crouch_FR; + + anim.coverTrans[ "right_cqb" ][ 1 ] = %tp_moon_coverR_run_2_stand_BL; + anim.coverTrans[ "right_cqb" ][ 2 ] = %tp_moon_coverR_run_2_stand_B; + anim.coverTrans[ "right_cqb" ][ 3 ] = %tp_moon_coverR_run_2_stand_BR; + anim.coverTrans[ "right_cqb" ][ 4 ] = %tp_moon_coverR_run_2_stand_L; + anim.coverTrans[ "right_cqb" ][ 6 ] = %tp_moon_coverR_run_2_stand_R; + //im.coverTrans[ "right_cqb" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right_cqb" ][ 8 ] = %tp_moon_coverR_run_2_stand_F; + anim.coverTrans[ "right_cqb" ][ 9 ] = %tp_moon_coverR_run_2_stand_FR; + + anim.coverTrans[ "right_crouch_cqb" ][ 1 ] = %tp_moon_cqb_coverR_run_2_crouch_BL; + anim.coverTrans[ "right_crouch_cqb" ][ 2 ] = %tp_moon_cqb_coverR_run_2_crouch_B; + anim.coverTrans[ "right_crouch_cqb" ][ 3 ] = %tp_moon_cqb_coverR_run_2_crouch_BR; + anim.coverTrans[ "right_crouch_cqb" ][ 4 ] = %tp_moon_cqb_coverR_run_2_crouch_L; + anim.coverTrans[ "right_crouch_cqb" ][ 6 ] = %tp_moon_cqb_coverR_run_2_crouch_R; + //im.coverTrans[ "right_crouch_cqb" ][ 7 ] = can't approach from this direction; + anim.coverTrans[ "right_crouch_cqb" ][ 8 ] = %tp_moon_cqb_coverR_run_2_crouch_F; + anim.coverTrans[ "right_crouch_cqb" ][ 9 ] = %tp_moon_cqb_coverR_run_2_crouch_FR; + + anim.coverTrans[ "left" ][ 1 ] = %tp_moon_coverL_run_2_stand_BL; + anim.coverTrans[ "left" ][ 2 ] = %tp_moon_coverL_run_2_stand_B; + anim.coverTrans[ "left" ][ 3 ] = %tp_moon_coverL_run_2_stand_BR; + anim.coverTrans[ "left" ][ 4 ] = %tp_moon_coverL_run_2_stand_L; + anim.coverTrans[ "left" ][ 6 ] = %tp_moon_coverL_run_2_stand_R; + anim.coverTrans[ "left" ][ 7 ] = %tp_moon_coverL_run_2_stand_FL; + anim.coverTrans[ "left" ][ 8 ] = %tp_moon_coverL_run_2_stand_F; + //im.coverTrans[ "left" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "left_crouch" ][ 1 ] = %tp_moon_coverL_run_2_crouch_BL; + anim.coverTrans[ "left_crouch" ][ 2 ] = %tp_moon_coverL_run_2_crouch_B; + anim.coverTrans[ "left_crouch" ][ 3 ] = %tp_moon_coverL_run_2_crouch_BR; + anim.coverTrans[ "left_crouch" ][ 4 ] = %tp_moon_coverL_run_2_crouch_L; + anim.coverTrans[ "left_crouch" ][ 6 ] = %tp_moon_coverL_run_2_crouch_R; + anim.coverTrans[ "left_crouch" ][ 7 ] = %tp_moon_coverL_run_2_crouch_FL; + anim.coverTrans[ "left_crouch" ][ 8 ] = %tp_moon_coverL_run_2_crouch_F; + //im.coverTrans[ "left_crouch" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "left_cqb" ][ 1 ] = %tp_moon_coverL_run_2_stand_BL; + anim.coverTrans[ "left_cqb" ][ 2 ] = %tp_moon_coverL_run_2_stand_B; + anim.coverTrans[ "left_cqb" ][ 3 ] = %tp_moon_coverL_run_2_stand_BR; + anim.coverTrans[ "left_cqb" ][ 4 ] = %tp_moon_coverL_run_2_stand_L; + anim.coverTrans[ "left_cqb" ][ 6 ] = %tp_moon_coverL_run_2_stand_R; + anim.coverTrans[ "left_cqb" ][ 7 ] = %tp_moon_coverL_run_2_stand_FL; + anim.coverTrans[ "left_cqb" ][ 8 ] = %tp_moon_coverL_run_2_stand_F; + //im.coverTrans[ "left_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "left_crouch_cqb" ][ 1 ] = %tp_moon_cqb_coverL_run_2_crouch_BL; + anim.coverTrans[ "left_crouch_cqb" ][ 2 ] = %tp_moon_cqb_coverL_run_2_crouch_B; + anim.coverTrans[ "left_crouch_cqb" ][ 3 ] = %tp_moon_cqb_coverL_run_2_crouch_BR; + anim.coverTrans[ "left_crouch_cqb" ][ 4 ] = %tp_moon_cqb_coverL_run_2_crouch_L; + anim.coverTrans[ "left_crouch_cqb" ][ 6 ] = %tp_moon_cqb_coverL_run_2_crouch_R; + anim.coverTrans[ "left_crouch_cqb" ][ 7 ] = %tp_moon_cqb_coverL_run_2_crouch_FL; + anim.coverTrans[ "left_crouch_cqb" ][ 8 ] = %tp_moon_cqb_coverL_run_2_crouch_F; + //im.coverTrans[ "left_crouch_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "crouch" ][ 1 ] = %tp_moon_coverF_run_2_crouch_BL; + anim.coverTrans[ "crouch" ][ 2 ] = %tp_moon_coverF_run_2_crouch_B; + anim.coverTrans[ "crouch" ][ 3 ] = %tp_moon_coverF_run_2_crouch_BR; + anim.coverTrans[ "crouch" ][ 4 ] = %tp_moon_coverF_run_2_crouch_L; + anim.coverTrans[ "crouch" ][ 6 ] = %tp_moon_coverF_run_2_crouch_R; + //im.coverTrans[ "crouch" ][ 7 ] = can't approach from this direction; + //im.coverTrans[ "crouch" ][ 8 ] = can't approach from this direction; + //im.coverTrans[ "crouch" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "stand" ][ 1 ] = %tp_moon_coverF_run_2_stand_BL; + anim.coverTrans[ "stand" ][ 2 ] = %tp_moon_coverF_run_2_stand_B; + anim.coverTrans[ "stand" ][ 3 ] = %tp_moon_coverF_run_2_stand_BR; + anim.coverTrans[ "stand" ][ 4 ] = %tp_moon_coverF_run_2_stand_L; + anim.coverTrans[ "stand" ][ 6 ] = %tp_moon_coverF_run_2_stand_R; + //im.coverTrans[ "stand" ][ 7 ] = can't approach from this direction; + //im.coverTrans[ "stand" ][ 8 ] = can't approach from this direction; + //im.coverTrans[ "stand" ][ 9 ] = can't approach from this direction; + + anim.coverTrans[ "stand_saw" ][ 1 ] = %saw_gunner_runin_ML; + anim.coverTrans[ "stand_saw" ][ 2 ] = %saw_gunner_runin_M; + anim.coverTrans[ "stand_saw" ][ 3 ] = %saw_gunner_runin_MR; + anim.coverTrans[ "stand_saw" ][ 4 ] = %saw_gunner_runin_L; + anim.coverTrans[ "stand_saw" ][ 6 ] = %saw_gunner_runin_R; + + anim.coverTrans[ "crouch_saw" ][ 1 ] = %saw_gunner_lowwall_runin_ML; + anim.coverTrans[ "crouch_saw" ][ 2 ] = %saw_gunner_lowwall_runin_M; + anim.coverTrans[ "crouch_saw" ][ 3 ] = %saw_gunner_lowwall_runin_MR; + anim.coverTrans[ "crouch_saw" ][ 4 ] = %saw_gunner_lowwall_runin_L; + anim.coverTrans[ "crouch_saw" ][ 6 ] = %saw_gunner_lowwall_runin_R; + + anim.coverTrans[ "prone_saw" ][ 1 ] = %saw_gunner_prone_runin_ML; + anim.coverTrans[ "prone_saw" ][ 2 ] = %saw_gunner_prone_runin_M; + anim.coverTrans[ "prone_saw" ][ 3 ] = %saw_gunner_prone_runin_MR; + + // we need 45 degree angle approaches for exposed... + anim.coverTrans[ "exposed" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed" ][ 1 ] = %tp_moon_exposed_run_2_stand_BL; + anim.coverTrans[ "exposed" ][ 2 ] = %tp_moon_exposed_run_2_stand_B; + anim.coverTrans[ "exposed" ][ 3 ] = %tp_moon_exposed_run_2_stand_BR; + anim.coverTrans[ "exposed" ][ 4 ] = %tp_moon_exposed_run_2_stand_L; + anim.coverTrans[ "exposed" ][ 6 ] = %tp_moon_exposed_run_2_stand_R; + anim.coverTrans[ "exposed" ][ 7 ] = %tp_moon_exposed_run_2_stand_FL; + anim.coverTrans[ "exposed" ][ 8 ] = %tp_moon_exposed_run_2_stand_F; + anim.coverTrans[ "exposed" ][ 9 ] = %tp_moon_exposed_run_2_stand_FR; + + anim.coverTrans[ "exposed_crouch" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed_crouch" ][ 1 ] = %tp_moon_exposed_run_2_crouch_BL; + anim.coverTrans[ "exposed_crouch" ][ 2 ] = %tp_moon_exposed_run_2_crouch_B; + anim.coverTrans[ "exposed_crouch" ][ 3 ] = %tp_moon_exposed_run_2_crouch_BR; + anim.coverTrans[ "exposed_crouch" ][ 4 ] = %tp_moon_exposed_run_2_crouch_L; + anim.coverTrans[ "exposed_crouch" ][ 6 ] = %tp_moon_exposed_run_2_crouch_R; + anim.coverTrans[ "exposed_crouch" ][ 7 ] = %tp_moon_exposed_run_2_crouch_FL; + anim.coverTrans[ "exposed_crouch" ][ 8 ] = %tp_moon_exposed_run_2_crouch_F; + anim.coverTrans[ "exposed_crouch" ][ 9 ] = %tp_moon_exposed_run_2_crouch_FR; + + anim.coverTrans[ "exposed_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed_cqb" ][ 1 ] = %tp_moon_cqb_exposed_run_2_stand_BL; + anim.coverTrans[ "exposed_cqb" ][ 2 ] = %tp_moon_cqb_exposed_run_2_stand_B; + anim.coverTrans[ "exposed_cqb" ][ 3 ] = %tp_moon_cqb_exposed_run_2_stand_BR; + anim.coverTrans[ "exposed_cqb" ][ 4 ] = %tp_moon_cqb_exposed_run_2_stand_L; + anim.coverTrans[ "exposed_cqb" ][ 6 ] = %tp_moon_cqb_exposed_run_2_stand_R; + anim.coverTrans[ "exposed_cqb" ][ 7 ] = %tp_moon_cqb_exposed_run_2_stand_FL; + anim.coverTrans[ "exposed_cqb" ][ 8 ] = %tp_moon_cqb_exposed_run_2_stand_F; + anim.coverTrans[ "exposed_cqb" ][ 9 ] = %tp_moon_cqb_exposed_run_2_stand_FR; + + anim.coverTrans[ "exposed_crouch_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "exposed_crouch_cqb" ][ 1 ] = %tp_moon_exposed_run_2_crouch_BL; + anim.coverTrans[ "exposed_crouch_cqb" ][ 2 ] = %tp_moon_exposed_run_2_crouch_B; + anim.coverTrans[ "exposed_crouch_cqb" ][ 3 ] = %tp_moon_exposed_run_2_crouch_BR; + anim.coverTrans[ "exposed_crouch_cqb" ][ 4 ] = %tp_moon_exposed_run_2_crouch_L; + anim.coverTrans[ "exposed_crouch_cqb" ][ 6 ] = %tp_moon_exposed_run_2_crouch_R; + anim.coverTrans[ "exposed_crouch_cqb" ][ 7 ] = %tp_moon_exposed_run_2_crouch_FL; + anim.coverTrans[ "exposed_crouch_cqb" ][ 8 ] = %tp_moon_exposed_run_2_crouch_F; + anim.coverTrans[ "exposed_crouch_cqb" ][ 9 ] = %tp_moon_exposed_run_2_crouch_FR; + + anim.coverTrans[ "heat" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverTrans[ "heat" ][ 1 ] = %heat_approach_1; + anim.coverTrans[ "heat" ][ 2 ] = %heat_approach_2; + anim.coverTrans[ "heat" ][ 3 ] = %heat_approach_3; + anim.coverTrans[ "heat" ][ 4 ] = %heat_approach_4; + anim.coverTrans[ "heat" ][ 6 ] = %heat_approach_6; + //anim.coverTrans[ "heat" ][ 7 ] = %heat_approach_8; + anim.coverTrans[ "heat" ][ 8 ] = %heat_approach_8; + //anim.coverTrans[ "heat" ][ 9 ] = %heat_approach_8; + + anim.coverTrans[ "heat_left" ] = []; + anim.coverTrans[ "heat_right" ] = []; + + /************************************************* + * Step in position Animations + *************************************************/ + + anim.coverStepInAnim = []; + anim.coverStepInAnim[ "right" ] = %corner_standR_trans_B_2_alert; + anim.coverStepInAnim[ "right_crouch" ] = %CornerCrR_trans_B_2_alert; + anim.coverStepInAnim[ "left" ] = %corner_standL_trans_B_2_alert_v2; + anim.coverStepInAnim[ "left_crouch" ] = %CornerCrL_trans_B_2_alert; + anim.coverStepInAnim[ "crouch" ] = %covercrouch_aim_2_hide; + anim.coverStepInAnim[ "stand" ] = %coverstand_aim_2_hide; + + anim.coverStepInOffsets = []; + anim.coverStepInAngles = []; + + for( i = 0; i < lastCoverTrans; i++ ) + { + trans = transTypes[i]; + anim.coverStepInOffsets[ trans ] = getMoveDelta( anim.coverStepInAnim[ trans ], 0, 1 ); + anim.coverStepInAngles[ trans ] = getAngleDelta( anim.coverStepInAnim[ trans ], 0, 1 ); + } + + anim.coverStepInAngles[ "right" ] += 90; + anim.coverStepInAngles[ "right_crouch" ] += 90; + anim.coverStepInAngles[ "left" ] -= 90; + anim.coverStepInAngles[ "left_crouch" ] -= 90; + + /************************************************* + * Traverse Animations + *************************************************/ + + anim.coverTrans[ "wall_over_96" ][ 1 ] = %traverse90_IN_ML; + anim.coverTrans[ "wall_over_96" ][ 2 ] = %traverse90_IN_M; + anim.coverTrans[ "wall_over_96" ][ 3 ] = %traverse90_IN_MR; + anim.traverseInfo[ "wall_over_96" ][ "height" ] = 96; + + anim.coverTrans[ "wall_over_40" ][ 1 ] = %traverse_window_M_2_run; + anim.coverTrans[ "wall_over_40" ][ 2 ] = %traverse_window_M_2_run; + anim.coverTrans[ "wall_over_40" ][ 3 ] = %traverse_window_M_2_run; + + /* + anim.coverTrans["wall_over_40"][1] = %traverse40_IN_ML; + anim.coverTrans["wall_over_40"][2] = %traverse40_IN_M; + anim.coverTrans["wall_over_40"][3] = %traverse40_IN_MR; + */ + + + + + /************************************************* + * Exit Animations + *************************************************/ + + anim.coverExit[ "right" ][ 1 ] = %tp_moon_coverR_stand_2_run_BL; + anim.coverExit[ "right" ][ 2 ] = %tp_moon_coverR_stand_2_run_B; + anim.coverExit[ "right" ][ 3 ] = %tp_moon_coverR_stand_2_run_BR; + anim.coverExit[ "right" ][ 4 ] = %tp_moon_coverR_stand_2_run_L; + anim.coverExit[ "right" ][ 6 ] = %tp_moon_coverR_stand_2_run_R; + //im.coverExit[ "right" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right" ][ 8 ] = %tp_moon_coverR_stand_2_run_F; + anim.coverExit[ "right" ][ 9 ] = %tp_moon_coverR_stand_2_run_FR; + + anim.coverExit[ "right_crouch" ][ 1 ] = %tp_moon_coverR_crouch_2_run_BL; + anim.coverExit[ "right_crouch" ][ 2 ] = %tp_moon_coverR_crouch_2_run_B; + anim.coverExit[ "right_crouch" ][ 3 ] = %tp_moon_coverR_crouch_2_run_BR; + anim.coverExit[ "right_crouch" ][ 4 ] = %tp_moon_coverR_crouch_2_run_L; + anim.coverExit[ "right_crouch" ][ 6 ] = %tp_moon_coverR_crouch_2_run_R; + //im.coverExit[ "right_crouch" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right_crouch" ][ 8 ] = %tp_moon_coverR_crouch_2_run_F; + anim.coverExit[ "right_crouch" ][ 9 ] = %tp_moon_coverR_crouch_2_run_FR; + + anim.coverExit[ "right_cqb" ][ 1 ] = %tp_moon_coverR_stand_2_run_BL; + anim.coverExit[ "right_cqb" ][ 2 ] = %tp_moon_coverR_stand_2_run_B; + anim.coverExit[ "right_cqb" ][ 3 ] = %tp_moon_coverR_stand_2_run_BR; + anim.coverExit[ "right_cqb" ][ 4 ] = %tp_moon_coverR_stand_2_run_L; + anim.coverExit[ "right_cqb" ][ 6 ] = %tp_moon_coverR_stand_2_run_R; + //im.coverExit[ "right_cqb" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right_cqb" ][ 8 ] = %tp_moon_coverR_stand_2_run_F; + anim.coverExit[ "right_cqb" ][ 9 ] = %tp_moon_coverR_stand_2_run_FR; + + anim.coverExit[ "right_crouch_cqb" ][ 1 ] = %tp_moon_cqb_coverR_crouch_2_run_BL; + anim.coverExit[ "right_crouch_cqb" ][ 2 ] = %tp_moon_cqb_coverR_crouch_2_run_B; + anim.coverExit[ "right_crouch_cqb" ][ 3 ] = %tp_moon_cqb_coverR_crouch_2_run_BR; + anim.coverExit[ "right_crouch_cqb" ][ 4 ] = %tp_moon_cqb_coverR_crouch_2_run_L; + anim.coverExit[ "right_crouch_cqb" ][ 6 ] = %tp_moon_cqb_coverR_crouch_2_run_R; + //im.coverExit[ "right_crouch_cqb" ][ 7 ] = can't approach from this direction; + anim.coverExit[ "right_crouch_cqb" ][ 8 ] = %tp_moon_cqb_coverR_crouch_2_run_F; + anim.coverExit[ "right_crouch_cqb" ][ 9 ] = %tp_moon_cqb_coverR_crouch_2_run_FR; + + + anim.coverExit[ "left" ][ 1 ] = %tp_moon_coverL_stand_2_run_BL; + anim.coverExit[ "left" ][ 2 ] = %tp_moon_coverL_stand_2_run_B; + anim.coverExit[ "left" ][ 3 ] = %tp_moon_coverL_stand_2_run_BR; + anim.coverExit[ "left" ][ 4 ] = %tp_moon_coverL_stand_2_run_L; + anim.coverExit[ "left" ][ 6 ] = %tp_moon_coverL_stand_2_run_R; + anim.coverExit[ "left" ][ 7 ] = %tp_moon_coverL_stand_2_run_FL; + anim.coverExit[ "left" ][ 8 ] = %tp_moon_coverL_stand_2_run_F; + //im.coverExit[ "left" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "left_crouch" ][ 1 ] = %tp_moon_coverL_crouch_2_run_BL; + anim.coverExit[ "left_crouch" ][ 2 ] = %tp_moon_coverL_crouch_2_run_B; + anim.coverExit[ "left_crouch" ][ 3 ] = %tp_moon_coverL_crouch_2_run_BR; + anim.coverExit[ "left_crouch" ][ 4 ] = %tp_moon_coverL_crouch_2_run_L; + anim.coverExit[ "left_crouch" ][ 6 ] = %tp_moon_coverL_crouch_2_run_R; + anim.coverExit[ "left_crouch" ][ 7 ] = %tp_moon_coverL_crouch_2_run_FL; + anim.coverExit[ "left_crouch" ][ 8 ] = %tp_moon_coverL_crouch_2_run_F; + //im.coverExit[ "left_crouch" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "left_cqb" ][ 1 ] = %tp_moon_coverL_stand_2_run_BL; + anim.coverExit[ "left_cqb" ][ 2 ] = %tp_moon_coverL_stand_2_run_B; + anim.coverExit[ "left_cqb" ][ 3 ] = %tp_moon_coverL_stand_2_run_BR; + anim.coverExit[ "left_cqb" ][ 4 ] = %tp_moon_coverL_stand_2_run_L; + anim.coverExit[ "left_cqb" ][ 6 ] = %tp_moon_coverL_stand_2_run_R; + anim.coverExit[ "left_cqb" ][ 7 ] = %tp_moon_coverL_stand_2_run_FL; + anim.coverExit[ "left_cqb" ][ 8 ] = %tp_moon_coverL_stand_2_run_F; + //im.coverExit[ "left_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "left_crouch_cqb" ][ 1 ] = %tp_moon_cqb_coverL_crouch_2_run_BL; + anim.coverExit[ "left_crouch_cqb" ][ 2 ] = %tp_moon_cqb_coverL_crouch_2_run_B; + anim.coverExit[ "left_crouch_cqb" ][ 3 ] = %tp_moon_cqb_coverL_crouch_2_run_BR; + anim.coverExit[ "left_crouch_cqb" ][ 4 ] = %tp_moon_cqb_coverL_crouch_2_run_L; + anim.coverExit[ "left_crouch_cqb" ][ 6 ] = %tp_moon_cqb_coverL_crouch_2_run_R; + anim.coverExit[ "left_crouch_cqb" ][ 7 ] = %tp_moon_cqb_coverL_crouch_2_run_FL; + anim.coverExit[ "left_crouch_cqb" ][ 8 ] = %tp_moon_cqb_coverL_crouch_2_run_F; + //im.coverExit[ "left_crouch_cqb" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "crouch" ][ 1 ] = %tp_moon_coverF_crouch_2_run_BL; + anim.coverExit[ "crouch" ][ 2 ] = %tp_moon_coverF_crouch_2_run_B; + anim.coverExit[ "crouch" ][ 3 ] = %tp_moon_coverF_crouch_2_run_BR; + anim.coverExit[ "crouch" ][ 4 ] = %tp_moon_coverF_crouch_2_run_L; + anim.coverExit[ "crouch" ][ 6 ] = %tp_moon_coverF_crouch_2_run_R; + //im.coverExit[ "crouch" ][ 7 ] = can't approach from this direction; + //im.coverExit[ "crouch" ][ 8 ] = can't approach from this direction; + //im.coverExit[ "crouch" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "stand" ][ 1 ] = %tp_moon_coverF_stand_2_run_BL; + anim.coverExit[ "stand" ][ 2 ] = %tp_moon_coverF_stand_2_run_B; + anim.coverExit[ "stand" ][ 3 ] = %tp_moon_coverF_stand_2_run_BR; + anim.coverExit[ "stand" ][ 4 ] = %tp_moon_coverF_stand_2_run_L; + anim.coverExit[ "stand" ][ 6 ] = %tp_moon_coverF_stand_2_run_R; + //im.coverExit[ "stand" ][ 7 ] = can't approach from this direction; + //im.coverExit[ "stand" ][ 8 ] = can't approach from this direction; + //im.coverExit[ "stand" ][ 9 ] = can't approach from this direction; + + anim.coverExit[ "stand_saw" ][ 1 ] = %saw_gunner_runout_ML; + anim.coverExit[ "stand_saw" ][ 2 ] = %saw_gunner_runout_M; + anim.coverExit[ "stand_saw" ][ 3 ] = %saw_gunner_runout_MR; + anim.coverExit[ "stand_saw" ][ 4 ] = %saw_gunner_runout_L; + anim.coverExit[ "stand_saw" ][ 6 ] = %saw_gunner_runout_R; + +// anim.coverExit["prone_saw" ][1] = %saw_gunner_prone_runout_ML; + anim.coverExit[ "prone_saw" ][ 2 ] = %saw_gunner_prone_runout_M; +// anim.coverExit["prone_saw" ][3] = %saw_gunner_prone_runout_MR; + anim.coverExit[ "prone_saw" ][ 4 ] = %saw_gunner_prone_runout_L; + anim.coverExit[ "prone_saw" ][ 6 ] = %saw_gunner_prone_runout_R; +// anim.coverExit["prone_saw" ][7] = %saw_gunner_prone_runout_F; // need this anim or a way to exclude it + anim.coverExit[ "prone_saw" ][ 8 ] = %saw_gunner_prone_runout_F; + + anim.coverExit[ "crouch_saw" ][ 1 ] = %saw_gunner_lowwall_runout_ML; + anim.coverExit[ "crouch_saw" ][ 2 ] = %saw_gunner_lowwall_runout_M; + anim.coverExit[ "crouch_saw" ][ 3 ] = %saw_gunner_lowwall_runout_MR; + anim.coverExit[ "crouch_saw" ][ 4 ] = %saw_gunner_lowwall_runout_L; + anim.coverExit[ "crouch_saw" ][ 6 ] = %saw_gunner_lowwall_runout_R; + + // we need 45 degree angle exits for exposed... + anim.coverExit[ "exposed" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed" ][ 1 ] = %tp_moon_exposed_stand_2_run_BL; + anim.coverExit[ "exposed" ][ 2 ] = %tp_moon_exposed_stand_2_run_B; + anim.coverExit[ "exposed" ][ 3 ] = %tp_moon_exposed_stand_2_run_BR; + anim.coverExit[ "exposed" ][ 4 ] = %tp_moon_exposed_stand_2_run_L; + anim.coverExit[ "exposed" ][ 6 ] = %tp_moon_exposed_stand_2_run_R; + anim.coverExit[ "exposed" ][ 7 ] = %tp_moon_exposed_stand_2_run_FL; + anim.coverExit[ "exposed" ][ 8 ] = %tp_moon_exposed_stand_2_run_F; // %stand_2_run_F_2; + anim.coverExit[ "exposed" ][ 9 ] = %tp_moon_exposed_stand_2_run_FR; + + anim.coverExit[ "exposed_crouch" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed_crouch" ][ 1 ] = %tp_moon_exposed_crouch_2_run_BL; + anim.coverExit[ "exposed_crouch" ][ 2 ] = %tp_moon_exposed_crouch_2_run_B; + anim.coverExit[ "exposed_crouch" ][ 3 ] = %tp_moon_exposed_crouch_2_run_BR; + anim.coverExit[ "exposed_crouch" ][ 4 ] = %tp_moon_exposed_crouch_2_run_L; + anim.coverExit[ "exposed_crouch" ][ 6 ] = %tp_moon_exposed_crouch_2_run_R; + anim.coverExit[ "exposed_crouch" ][ 7 ] = %tp_moon_exposed_crouch_2_run_FL; + anim.coverExit[ "exposed_crouch" ][ 8 ] = %tp_moon_exposed_crouch_2_run_F; + anim.coverExit[ "exposed_crouch" ][ 9 ] = %tp_moon_exposed_crouch_2_run_FR; + + anim.coverExit[ "exposed_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed_cqb" ][ 1 ] = %tp_moon_cqb_exposed_stand_2_run_BL; + anim.coverExit[ "exposed_cqb" ][ 2 ] = %tp_moon_cqb_exposed_stand_2_run_B; + anim.coverExit[ "exposed_cqb" ][ 3 ] = %tp_moon_cqb_exposed_stand_2_run_BR; + anim.coverExit[ "exposed_cqb" ][ 4 ] = %tp_moon_cqb_exposed_stand_2_run_L; + anim.coverExit[ "exposed_cqb" ][ 6 ] = %tp_moon_cqb_exposed_stand_2_run_R; + anim.coverExit[ "exposed_cqb" ][ 7 ] = %tp_moon_cqb_exposed_stand_2_run_FL; + anim.coverExit[ "exposed_cqb" ][ 8 ] = %tp_moon_cqb_exposed_stand_2_run_F; + anim.coverExit[ "exposed_cqb" ][ 9 ] = %tp_moon_cqb_exposed_stand_2_run_FR; + + anim.coverExit[ "exposed_crouch_cqb" ] = [];// need this or it chokes on the next line due to assigning undefined... + anim.coverExit[ "exposed_crouch_cqb" ][ 1 ] = %tp_moon_exposed_crouch_2_run_BL; + anim.coverExit[ "exposed_crouch_cqb" ][ 2 ] = %tp_moon_exposed_crouch_2_run_B; + anim.coverExit[ "exposed_crouch_cqb" ][ 3 ] = %tp_moon_exposed_crouch_2_run_BR; + anim.coverExit[ "exposed_crouch_cqb" ][ 4 ] = %tp_moon_exposed_crouch_2_run_L; + anim.coverExit[ "exposed_crouch_cqb" ][ 6 ] = %tp_moon_exposed_crouch_2_run_R; + anim.coverExit[ "exposed_crouch_cqb" ][ 7 ] = %tp_moon_exposed_crouch_2_run_FL; + anim.coverExit[ "exposed_crouch_cqb" ][ 8 ] = %tp_moon_exposed_crouch_2_run_F; + anim.coverExit[ "exposed_crouch_cqb" ][ 9 ] = %tp_moon_exposed_crouch_2_run_FR; + + anim.coverExit[ "heat" ] = []; + anim.coverExit[ "heat" ][ 1 ] = %heat_exit_1; + anim.coverExit[ "heat" ][ 2 ] = %heat_exit_2; + anim.coverExit[ "heat" ][ 3 ] = %heat_exit_3; + anim.coverExit[ "heat" ][ 4 ] = %heat_exit_4; + anim.coverExit[ "heat" ][ 6 ] = %heat_exit_6; //%heat_exit_6a + anim.coverExit[ "heat" ][ 7 ] = %heat_exit_7; + anim.coverExit[ "heat" ][ 8 ] = %heat_exit_8; //%heat_exit_8a + anim.coverExit[ "heat" ][ 9 ] = %heat_exit_9; + + anim.coverExit[ "heat_left" ] = []; + anim.coverExit[ "heat_left" ][ 1 ] = %heat_exit_1; + anim.coverExit[ "heat_left" ][ 2 ] = %heat_exit_2; + anim.coverExit[ "heat_left" ][ 3 ] = %heat_exit_3; + anim.coverExit[ "heat_left" ][ 4 ] = %heat_exit_4; + anim.coverExit[ "heat_left" ][ 6 ] = %heat_exit_6; + anim.coverExit[ "heat_left" ][ 7 ] = %heat_exit_8L; + anim.coverExit[ "heat_left" ][ 8 ] = %heat_exit_8L; + anim.coverExit[ "heat_left" ][ 9 ] = %heat_exit_8R; + + anim.coverExit[ "heat_right" ] = []; + anim.coverExit[ "heat_right" ][ 1 ] = %heat_exit_1; + anim.coverExit[ "heat_right" ][ 2 ] = %heat_exit_2; + anim.coverExit[ "heat_right" ][ 3 ] = %heat_exit_3; + anim.coverExit[ "heat_right" ][ 4 ] = %heat_exit_4; + anim.coverExit[ "heat_right" ][ 6 ] = %heat_exit_6; + anim.coverExit[ "heat_right" ][ 7 ] = %heat_exit_8L; + anim.coverExit[ "heat_right" ][ 8 ] = %heat_exit_8R; + anim.coverExit[ "heat_right" ][ 9 ] = %heat_exit_8R; + + + for ( i = 1; i <= 6; i++ ) + { + if ( i == 5 ) + continue; + + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + if ( isdefined( anim.coverTrans[ trans ][ i ] ) ) + { + anim.coverTransDist [ trans ][ i ] = getMoveDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + anim.coverTransAngles[ trans ][ i ] = getAngleDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + } + + if ( isdefined( anim.coverExit [ trans ] ) && isdefined( anim.coverExit [ trans ][ i ] ) ) + { + // get exit dist only to code_move + if ( animHasNotetrack( anim.coverExit[ trans ][ i ], "code_move" ) ) + codeMoveTime = getNotetrackTimes( anim.coverExit[ trans ][ i ], "code_move" )[ 0 ]; + else + codeMoveTime = 1; + + anim.coverExitDist [ trans ][ i ] = getMoveDelta( anim.coverExit [ trans ][ i ], 0, codeMoveTime ); + anim.coverExitAngles [ trans ][ i ] = getAngleDelta( anim.coverExit [ trans ][ i ], 0, 1 ); + } + } + } + + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + anim.coverTransLongestDist[ trans ] = 0; + + for ( i = 1; i <= 6; i++ ) + { + if ( i == 5 || !isdefined( anim.coverTrans[ trans ][ i ] ) ) + continue; + + lengthSq = lengthSquared( anim.coverTransDist[ trans ][ i ] ); + if ( anim.coverTransLongestDist[ trans ] < lengthSq ) + anim.coverTransLongestDist[ trans ] = lengthSq; + } + + anim.coverTransLongestDist[ trans ] = sqrt( anim.coverTransLongestDist[ trans ] ); + } + + anim.exposedTransition[ "exposed" ] = true; + anim.exposedTransition[ "exposed_crouch" ] = true; + anim.exposedTransition[ "exposed_cqb" ] = true; + anim.exposedTransition[ "exposed_crouch_cqb" ] = true; + anim.exposedTransition[ "heat" ] = true; + + anim.longestExposedApproachDist = 0; + + foreach ( trans, transType in anim.exposedTransition ) + { + for ( i = 7; i <= 9; i++ ) + { + if ( isdefined( anim.coverTrans[ trans ][ i ] ) ) + { + anim.coverTransDist [ trans ][ i ] = getMoveDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + anim.coverTransAngles[ trans ][ i ] = getAngleDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + } + + if ( isdefined( anim.coverExit [ trans ][ i ] ) ) + { + // get exit dist only to code_move + assert( animHasNotetrack( anim.coverExit[ trans ][ i ], "code_move" ) ); + codeMoveTime = getNotetrackTimes( anim.coverExit[ trans ][ i ], "code_move" )[ 0 ]; + + anim.coverExitDist [ trans ][ i ] = getMoveDelta( anim.coverExit [ trans ][ i ], 0, codeMoveTime ); + anim.coverExitAngles [ trans ][ i ] = getAngleDelta( anim.coverExit [ trans ][ i ], 0, 1 ); + } + } + + for ( i = 1; i <= 9; i++ ) + { + if ( !isdefined( anim.coverTrans[ trans ][ i ] ) ) + continue; + + len = length( anim.coverTransDist[ trans ][ i ] ); + if ( len > anim.longestExposedApproachDist ) + anim.longestExposedApproachDist = len; + } + } + + + // the FindBestSplitTime calls below are used to find these values. + // all of this is for corner nodes. + + anim.coverTransSplit[ "left" ][ 7 ] = 0.5;// delta of( 35.5356, 3.27114, 0 ) + anim.coverTransSplit[ "left_crouch" ][ 7 ] = 0.448; // delta of (32.2281, 0.356673, 0) + anim.coverTransSplit[ "left_cqb" ][ 7 ] = 0.5; // delta of (33.1115, 1.05645, 0) + anim.coverTransSplit[ "left_crouch_cqb" ][ 7 ] = 0.448; // delta of (34.2986, 2.32586, 0) + anim.coverExitSplit[ "left" ][ 7 ] = 0.52;// delta of( 37.5652, 5.61999, 0 ) + anim.coverExitSplit[ "left_crouch" ][ 7 ] = 0.55;// delta of( 35.9166, 3.88091, 0 ) + anim.coverExitSplit[ "left_cqb" ][ 7 ] = 0.52; // delta of (32.9692, 0.881301, 0) + anim.coverExitSplit[ "left_crouch_cqb" ][ 7 ] = 0.55; // delta of (33.6642, 1.70904, 0) + anim.coverExitSplit[ "heat_left" ][ 7 ] = 0.42; + + anim.coverTransSplit[ "left" ][ 8 ] = 0.38;// delta of( 32.9863, 0.925748, 0 ) + anim.coverTransSplit[ "left_crouch" ][ 8 ] = 0.5;// delta of( 38.4125, 6.445, 0 ) + anim.coverTransSplit[ "left_cqb" ][ 8 ] = 0.38; // delta of (33.892, 1.86121, 0) + anim.coverTransSplit[ "left_crouch_cqb" ][ 8 ] = 0.5; // delta of (35.8107, 3.70985, 0) + anim.coverExitSplit[ "left" ][ 8 ] = 0.442;// delta of( 34.298, 2.26239, 0 ) + anim.coverExitSplit[ "left_crouch" ][ 8 ] = 0.6; // delta of (33.0388, 0.964628, 0) + anim.coverExitSplit[ "left_cqb" ][ 8 ] = 0.442; // delta of (35.6563, 3.64754, 0)) + anim.coverExitSplit[ "left_crouch_cqb" ][ 8 ] = 0.6; // delta of (33.0797, 1.14774, 0) + anim.coverExitSplit[ "heat_left" ][ 8 ] = 0.42; + + anim.coverTransSplit[ "right" ][ 8 ] = 0.561562;// delta of( 35.6571, 3.63511, 0 ) + anim.coverTransSplit[ "right_crouch" ][ 8 ] = 0.521522; // delta of (34.6368, 2.67554, 0) + anim.coverTransSplit[ "right_cqb" ][ 8 ] = 0.561562; // delta of (35.6571, 3.63511, 0) + anim.coverTransSplit[ "right_crouch_cqb" ][ 8 ] = 0.521522; // delta of (34.2736, 2.32471, 0) + anim.coverExitSplit[ "right" ][ 8 ] = 0.316316;// delta of( 36.3085, 4.34586, 0 ) + anim.coverExitSplit[ "right_crouch" ][ 8 ] = 0.386386; // delta of (33.1181, 1.14301, -0.0001) + anim.coverExitSplit[ "right_cqb" ][ 8 ] = 0.316316; // delta of (33.0089, 1.0005, 0) + anim.coverExitSplit[ "right_crouch_cqb" ][ 8 ] = 0.386386; // delta of (34.7739, 2.41176, 0) + anim.coverExitSplit[ "heat_right" ][ 8 ] = 0.4; + + anim.coverTransSplit[ "right" ][ 9 ] = 0.41041;// delta of( 37.7732, 5.76641, 0 ) + anim.coverTransSplit[ "right_crouch" ][ 9 ] = 0.518519; // delta of (36.3871, 4.39434, 0) + anim.coverTransSplit[ "right_cqb" ][ 9 ] = 0.41041;// delta of( 37.7732, 5.76641, 0 ) + anim.coverTransSplit[ "right_crouch_cqb" ][ 9 ] = 0.232232518519; // delta of (35.8102, 3.81592, 0) + anim.coverExitSplit[ "right" ][ 9 ] = 0.365365; // delta of (35.251, 3.31115, 0) + anim.coverExitSplit[ "right_crouch" ][ 9 ] = 0.376376; // delta of (34.4959, 2.45688, -0.0001) + anim.coverExitSplit[ "right_cqb" ][ 9 ] = 0.365365; // delta of (35.4487, 3.42926, 0) + anim.coverExitSplit[ "right_crouch_cqb" ][ 9 ] = 0.376376; // delta of (35.4592, 1.47273, 0) + anim.coverExitSplit[ "heat_right" ][ 9 ] = 0.4; + + /# + setDvarIfUninitialized( "scr_findsplittimes", "0" ); + #/ + + splitArrivals = []; + splitArrivals[ "left" ] = 1; + splitArrivals[ "left_crouch" ] = 1; + splitArrivals[ "left_crouch_cqb" ] = 1; + splitArrivals[ "left_cqb" ] = 1; + + splitExits = []; + splitExits[ "left" ] = 1; + splitExits[ "left_crouch" ] = 1; + splitExits[ "left_crouch_cqb" ] = 1; + splitExits[ "left_cqb" ] = 1; + splitExits[ "heat_left" ] = 1; + + GetSplitTimes( 7, 8, false, splitArrivals, splitExits ); + + + splitArrivals = []; + splitArrivals[ "right" ] = 1; + splitArrivals[ "right_crouch" ] = 1; + splitArrivals[ "right_cqb" ] = 1; + splitArrivals[ "right_crouch_cqb" ] = 1; + + splitExits = []; + splitExits[ "right" ] = 1; + splitExits[ "right_crouch" ] = 1; + splitExits[ "right_cqb" ] = 1; + splitExits[ "right_crouch_cqb" ] = 1; + splitExits[ "heat_right" ] = 1; + + GetSplitTimes( 8, 9, true, splitArrivals, splitExits ); + + + /# + //thread checkApproachAngles( transTypes ); + #/ + + anim.arrivalEndStance["left"] = "stand"; + anim.arrivalEndStance["left_cqb"] = "stand"; + anim.arrivalEndStance["right"] = "stand"; + anim.arrivalEndStance["right_cqb"] = "stand"; + anim.arrivalEndStance["stand"] = "stand"; + anim.arrivalEndStance["stand_saw"] = "stand"; + anim.arrivalEndStance["exposed"] = "stand"; + anim.arrivalEndStance["exposed_cqb"] = "stand"; + anim.arrivalEndStance["heat"] = "stand"; + anim.arrivalEndStance["left_crouch"] = "crouch"; + anim.arrivalEndStance["left_crouch_cqb"] = "crouch"; + anim.arrivalEndStance["right_crouch"] = "crouch"; + anim.arrivalEndStance["right_crouch_cqb"] = "crouch"; + anim.arrivalEndStance["crouch_saw"] = "crouch"; + anim.arrivalEndStance["crouch"] = "crouch"; + anim.arrivalEndStance["exposed_crouch"] = "crouch"; + anim.arrivalEndStance["exposed_crouch_cqb"] = "crouch"; + anim.arrivalEndStance["prone_saw"] = "prone"; + + anim.requiredExitStance[ "Cover Stand" ] = "stand"; + anim.requiredExitStance[ "Conceal Stand" ] = "stand"; + anim.requiredExitStance[ "Cover Crouch" ] = "crouch"; + anim.requiredExitStance[ "Conceal Crouch" ] = "crouch"; +} + + +GetSplitTimes( begin, end, isRightSide, splitArrivals, splitExits ) +{ + for ( i = begin; i <= end; i++ ) + { + foreach ( type, val in splitArrivals ) + { + anim.coverTransPreDist[ type ][ i ] = getMoveDelta( anim.coverTrans[ type ][ i ], 0, getTransSplitTime( type, i ) ); + anim.coverTransDist [ type ][ i ] = getMoveDelta( anim.coverTrans[ type ][ i ], 0, 1 ) - anim.coverTransPreDist[ type ][ i ]; + anim.coverTransAngles [ type ][ i ] = getAngleDelta( anim.coverTrans[ type ][ i ], 0, 1 ); + } + + foreach ( type, val in splitExits ) + { + anim.coverExitDist [ type ][ i ] = getMoveDelta( anim.coverExit [ type ][ i ], 0, getExitSplitTime( type, i ) ); + anim.coverExitPostDist[ type ][ i ] = getMoveDelta( anim.coverExit [ type ][ i ], 0, 1 ) - anim.coverExitDist[ type ][ i ]; + anim.coverExitAngles [ type ][ i ] = getAngleDelta( anim.coverExit [ type ][ i ], 0, 1 ); + } + + /# + if ( getdebugdvar( "scr_findsplittimes" ) != "0" ) + { + foreach ( type, val in splitArrivals ) + { + if ( isSubStr( type, "heat" ) ) + continue; + + FindBestSplitTime( anim.coverTrans[ type ][ i ], true, isRightSide, "anim.coverTransSplit[ \"" + type + "\" ][ " + i + " ]", type + " arrival in dir " + i ); + AssertIsValidSplitDelta( DeltaRotate( anim.coverTransDist[ type ][ i ], 180 - anim.coverTransAngles[ type ][ i ] ), isRightSide, type + " arrival in dir " + i ); + } + + foreach ( type, val in splitExits ) + { + if ( isSubStr( type, "heat" ) ) + continue; + + FindBestSplitTime( anim.coverExit [ type ][ i ], false, isRightSide, "anim.coverExitSplit[ \"" + type + "\" ][ " + i + " ]", type + " exit in dir " + i ); + AssertIsValidSplitDelta( anim.coverExitDist[ type ][ i ], isRightSide, type + " exit in dir " + i ); + } + } + #/ + } +} + +/# +FindBestSplitTime( exitanim, isapproach, isright, arrayname, debugname ) +{ + angleDelta = getAngleDelta( exitanim, 0, 1 ); + fullDelta = getMoveDelta( exitanim, 0, 1 ); + numiter = 1000; + + bestsplit = -1; + bestvalue = -100000000; + bestdelta = ( 0, 0, 0 ); + + for ( i = 0; i < numiter; i++ ) + { + splitTime = 1.0 * i / ( numiter - 1 ); + + delta = getMoveDelta( exitanim, 0, splitTime ); + if ( isapproach ) + delta = DeltaRotate( fullDelta - delta, 180 - angleDelta ); + if ( isright ) + delta = ( delta[ 0 ], 0 - delta[ 1 ], delta[ 2 ] ); + + val = min( delta[ 0 ] - 32, delta[ 1 ] ); + + if ( val > bestvalue || bestsplit < 0 ) + { + bestvalue = val; + bestsplit = splitTime; + bestdelta = delta; + } + } + + if ( bestdelta[ 0 ] < 32 || bestdelta[ 1 ] < 0 ) + { + println( "^0 ^1" + debugname + " has no valid split time available! Best was at " + bestsplit + ", delta of " + bestdelta ); + return; + } + //println("^0 ^2" + debugname + " has best split time at " + bestsplit + ", delta of " + bestdelta ); + println( "^0 ^2" + arrayname + " = " + bestsplit + "; // delta of " + bestdelta ); +} + + +DeltaRotate( delta, yaw ) +{ + cosine = cos( yaw ); + sine = sin( yaw ); + return( delta[ 0 ] * cosine - delta[ 1 ] * sine, delta[ 1 ] * cosine + delta[ 0 ] * sine, 0 ); +} + +AssertIsValidSplitDelta( delta, isRightSide, debugname ) +{ + if ( isRightSide ) + delta = ( delta[ 0 ], 0 - delta[ 1 ], delta[ 2 ] ); + + // in a delta, x is forward and y is left + + // assert the delta goes out far enough from the node + if ( delta[ 0 ] < 32 ) + println( "^0 ^1" + debugname + " doesn't go out from the node far enough in the given split time (delta = " + delta + ")" ); + + // assert the delta doesn't go into the wall + if ( delta[ 1 ] < 0 ) + println( "^0 ^1" + debugname + " goes into the wall during the given split time (delta = " + delta + ")" ); +} + +checkApproachAngles( transTypes ) +{ + idealTransAngles[ 1 ] = 45; + idealTransAngles[ 2 ] = 0; + idealTransAngles[ 3 ] = -45; + idealTransAngles[ 4 ] = 90; + idealTransAngles[ 6 ] = -90; + idealTransAngles[ 7 ] = 135; + idealTransAngles[ 8 ] = 180; + idealTransAngles[ 9 ] = -135; + + wait .05; + + for ( i = 1; i <= 9; i++ ) + { + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + idealAdd = 0; + if ( trans == "left" || trans == "left_crouch" ) + idealAdd = 90; + else if ( trans == "right" || trans == "right_crouch" ) + idealAdd = -90; + + if ( isdefined( anim.coverTransAngles[ trans ][ i ] ) ) + { + correctAngle = AngleClamp180( idealTransAngles[ i ] + idealAdd ); + actualAngle = AngleClamp180( anim.coverTransAngles[ trans ][ i ] ); + if ( AbsAngleClamp180( actualAngle - correctAngle ) > 7 ) + { + println( "^1Cover approach animation has bad yaw delta: anim.coverTrans[\"" + trans + "\"][" + i + "]; is ^2" + actualAngle + "^1, should be closer to ^2" + correctAngle + "^1." ); + } + } + } + } + + for ( i = 1; i <= 9; i++ ) + { + for ( j = 0; j < transTypes.size; j++ ) + { + trans = transTypes[ j ]; + + idealAdd = 0; + if ( trans == "left" || trans == "left_crouch" ) + idealAdd = 90; + else if ( trans == "right" || trans == "right_crouch" ) + idealAdd = -90; + + if ( isdefined( anim.coverExitAngles[ trans ][ i ] ) ) + { + correctAngle = AngleClamp180( -1 * ( idealTransAngles[ i ] + idealAdd + 180 ) ); + actualAngle = AngleClamp180( anim.coverExitAngles[ trans ][ i ] ); + if ( AbsAngleClamp180( actualAngle - correctAngle ) > 7 ) + { + println( "^1Cover exit animation has bad yaw delta: anim.coverTrans[\"" + trans + "\"][" + i + "]; is ^2" + actualAngle + "^1, should be closer to ^2" + correctAngle + "^1." ); + } + } + } + } +} +#/ + +getExitSplitTime( approachType, dir ) +{ + exitAnim = anim.coverExit[ approachType ][ dir ]; + if ( animHasNotetrack( exitAnim, "split_time" ) ) + { + exitAlignTimes = getNotetrackTimes( exitAnim, "split_time" ); + + assert( exitAlignTimes.size == 1 ); + if ( exitAlignTimes.size == 0 ) + { + return anim.coverExitSplit[ approachType ][ dir ]; + } + + return exitAlignTimes[0]; + } + + return anim.coverExitSplit[ approachType ][ dir ]; +} + +getTransSplitTime( approachType, dir ) +{ + arrivalAnim = anim.coverTrans[ approachType ][ dir ]; + if ( animHasNotetrack( arrivalAnim, "split_time" ) ) + { + arrivalSplitTimes = getNotetrackTimes( arrivalAnim, "split_time" ); + + assert( arrivalSplitTimes.size == 1 ); + if ( arrivalSplitTimes.size == 0 ) + { + return anim.coverTransSplit[ approachType ][ dir ]; + } + + return arrivalSplitTimes[0]; + } + + return anim.coverTransSplit[ approachType ][ dir ]; +} + +init_moon_transition_points() +{ + anim.run_transition_notes = []; + anim.run_transition_points = []; + + anim.run_transition_notes[ anim.run_transition_notes.size ] = "move_transition_run_r_down"; + anim.run_transition_points[ anim.run_transition_points.size ] = 0.2537; + + anim.run_transition_notes[ anim.run_transition_notes.size ] = "move_transition_run_l_down"; + anim.run_transition_points[ anim.run_transition_points.size ] = 0.5074; + + anim.cqb_transition_notes = []; + anim.cqb_transition_points = []; + + //entrance for starting the loop at a run with the right foot down + anim.cqb_transition_notes[ anim.cqb_transition_notes.size ] = "move_transition_run_r_down"; + anim.cqb_transition_points[ anim.cqb_transition_points.size ] = 0.48; + + anim.cqb_transition_notes[ anim.cqb_transition_notes.size ] = "move_transition_run_l_down"; + anim.cqb_transition_points[ anim.cqb_transition_points.size ] = 0.24; + + anim.cqb_transition_notes[ anim.cqb_transition_notes.size ] = "move_transition_start 2"; + anim.cqb_transition_points[ anim.cqb_transition_points.size ] = 0.6; +} + diff --git a/animscripts/melee.gsc b/animscripts/melee.gsc new file mode 100644 index 0000000..83c6bd7 --- /dev/null +++ b/animscripts/melee.gsc @@ -0,0 +1,1748 @@ +#include animscripts\Utility; +#include animscripts\SetPoseMovement; +#include animscripts\Combat_Utility; +#include common_scripts\Utility; +#include maps\_utility; +#using_animtree( "generic_human" ); + +// =========================================================== +// AI vs Player melee +// =========================================================== + +sqr8 = 8 * 8; +sqr16 = 16 * 16; +sqr32 = 32 * 32; +sqr36 = 36 * 36; +sqr64 = 64 * 64; + +MELEE_RANGE = 64; +MELEE_RANGE_SQ = sqr64; +MELEE_ACTOR_BOUNDS_RADIUS = 32; // a little bigger than twice the radius of an actor's bounding box +MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON = (MELEE_ACTOR_BOUNDS_RADIUS-0.1); // used for asserts + +CHARGE_RANGE_SQ = 160 * 160; +CHARGE_RANGE_SQ_VS_PLAYER = 200 * 200; + +FAILED_INIT_NEXT_MELEE_TIME = 150; // basic IsValid() falure +FAILED_CHARGE_NEXT_MELEE_TIME = 1500; // charge failures (both standard/aiVSai) +FAILED_STANDARD_NEXT_MELEE_TIME = 2500; // standard melee failure + +NOTETRACK_SYNC = "sync"; +NOTETRACK_UNSYNC = "unsync"; +NOTETRACK_ATTACHKNIFE = "attach_knife"; +NOTETRACK_DETACTKNIFE = "detach_knife"; +NOTETRACK_STAB = "stab"; +NOTETRACK_DEATH = "melee_death"; +NOTETRACK_INTERACT = "melee_interact"; + +KNIFE_ATTACK_MODEL = "weapon_parabolic_knife"; +KNIFE_ATTACK_TAG = "TAG_INHAND"; +KNIFE_ATTACK_SOUND = "melee_knife_hit_body"; +KNIFE_ATTACK_FX_NAME = "melee_knife_ai"; +KNIFE_ATTACK_FX_PATH = "impacts/flesh_hit_knife"; +KNIFE_ATTACK_FX_TAG = "TAG_KNIFE_FX"; + + +Melee_Init() +{ + precacheModel( KNIFE_ATTACK_MODEL ); + level._effect[ KNIFE_ATTACK_FX_NAME ] = loadfx( KNIFE_ATTACK_FX_PATH ); +} + +Melee_StealthCheck() +{ + if ( !isdefined( self._stealth ) ) + return false; + + if ( isdefined( self.ent_flag ) && isdefined( self.ent_flag[ "_stealth_enabled" ] ) && self.ent_flag[ "_stealth_enabled" ] ) + if ( isdefined( self.ent_flag[ "_stealth_attack" ] ) && !self.ent_flag[ "_stealth_attack" ] ) + return true; + + return false; +} + + +Melee_TryExecuting() +{ + // Must have a valid enemy before we try anything + if ( !isDefined( self.enemy ) ) + return false; + + if ( isdefined( self.dontmelee ) ) + return false; + + if ( Melee_StealthCheck() ) + return false; + + if ( !Melee_AcquireMutex( self.enemy ) ) + return false; + + Melee_ResetAction(); + if ( !Melee_ChooseAction() ) + { + Melee_ReleaseMutex( self.enemy ); + return false; + } + + self animcustom( ::Melee_MainLoop, ::Melee_EndScript ); +} + + +// Setup internal melee structure for sanity/cache tracking +Melee_ResetAction() +{ + assert( isDefined( self.melee ) ); + assert( isDefined( self.enemy.melee ) ); + + self.melee.target = self.enemy; + self.melee.initiated = false; + self.melee.inProgress = false; +} + + +// After succesfully checking for melee, initialize our move +Melee_ChooseAction() +{ + if ( !Melee_IsValid() ) + return false; + + self.melee.initiated = true; + + if ( Melee_AIvsAI_ChooseAction() ) + { + self.melee.func = ::Melee_AIvsAI_Main; + return true; + } + + if ( Melee_Standard_ChooseAction() ) + { + if ( isdefined( self.specialMelee_Standard ) ) + self.melee.func = self.specialMelee_Standard; + else + self.melee.func = ::Melee_Standard_Main; + return true; + } + + // Don't try again for a while since we can't start + self.melee.func = undefined; + self.nextMeleeCheckTime = gettime() + FAILED_INIT_NEXT_MELEE_TIME; + self.nextMeleeCheckTarget = self.melee.target; + return false; +} + + +Melee_UpdateAndValidateStartPos() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.startPos ) ); + assert( isDefined( self.melee.target ) ); + + ignoreActors = true; + + // If the target is too close from the start pos, move the start pos in a way that our traces will succeed. + distFromTarget2d = distance2d( self.melee.startPos, self.melee.target.origin ); + if ( distFromTarget2d < MELEE_ACTOR_BOUNDS_RADIUS ) + { + // Calculate the direction from the target to the start pos, and push that start pos a bit + dirToStartPos2d = vectorNormalize( (self.melee.startPos[0] - self.melee.target.origin[0], self.melee.startPos[1] - self.melee.target.origin[1], 0) ); + self.melee.startPos += dirToStartPos2d * (MELEE_ACTOR_BOUNDS_RADIUS - distFromTarget2d); + assertex( distance2d( self.melee.startPos, self.melee.target.origin ) >= (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON), "Invalid distance to target: " + distance2d( self.melee.startPos, self.melee.target.origin ) + ", should be more than " + (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON) ); + ignoreActors = false; + } + + // Height-based checks + floorPos = self getDropToFloorPosition( self.melee.startPos ); + if ( !isDefined( floorPos ) ) + return false; + + // Point is so far from the ground that we can't reach it, fail + if ( abs( self.melee.startPos[2] - floorPos[2] ) > (MELEE_RANGE * 0.80) ) + return false; + + // Point is on another floor / platform, can't get that high + if ( abs( self.origin[2] - floorPos[2] ) > (MELEE_RANGE * 0.80) ) + return false; + + // If the point is fine, update its value + self.melee.startPos = floorPos; + assertex( distance2d( self.melee.startPos, self.melee.target.origin ) >= (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON), "Invalid distance to target: " + distance2d( self.melee.startPos, self.melee.target.origin ) + ", should be more than " + (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON) ); + + // Now check whether movement is possible to that point + + // First check to see if we can reach our start pos + if ( !self mayMoveToPoint( self.melee.startPos, true, ignoreActors ) ) + return false; + + // Compute a point that's just outside of the target's bounds. Do a first trace to that point which doesn't + // ignore actors, and then a second trace which does + + // if we're going around a corner, the two traces will pick a point to form a 90 angle. + // otherwise we pick a point right outside of the target's box + if ( isDefined( self.melee.startToTargetCornerAngles ) ) + { + // first find the corner based on the angles + targetToStartPos = self.melee.startPos - self.melee.target.origin; + cornerDir = anglesToForward( self.melee.startToTargetCornerAngles ); + cornerDirLen = vectorDot( cornerDir, targetToStartPos ); + mayMoveTargetOrigin = self.melee.startPos - (cornerDir * cornerDirLen); + + // push it out a bit if it's too close to the target + cornerToTarget = self.melee.target.origin - mayMoveTargetOrigin; + cornerToTargetLen = distance2d( self.melee.target.origin, mayMoveTargetOrigin ); + if ( cornerToTargetLen < MELEE_ACTOR_BOUNDS_RADIUS ) + mayMoveTargetOrigin -= cornerToTarget * ((MELEE_ACTOR_BOUNDS_RADIUS-cornerToTargetLen)/MELEE_ACTOR_BOUNDS_RADIUS); + } + else + { + dirToStartPos2d = vectorNormalize( (self.melee.startPos[0] - self.melee.target.origin[0], self.melee.startPos[1] - self.melee.target.origin[1], 0) ); + mayMoveTargetOrigin = self.melee.target.origin + dirToStartPos2d * MELEE_ACTOR_BOUNDS_RADIUS; + } + + assert( isDefined( mayMoveTargetOrigin ) ); + + if ( !self mayMoveFromPointToPoint( self.melee.startPos, mayMoveTargetOrigin, true, false ) ) + return false; + + if ( !self mayMoveFromPointToPoint( mayMoveTargetOrigin, self.melee.target.origin, true, true ) ) + return false; + + return true; +} + + +// Checks for various self / target conditions. Does not check for pathing issues. +Melee_IsValid() +{ + // Must have a target still + if ( !isDefined( self.melee.target ) ) + return false; + + target = self.melee.target; + + if ( isdefined( target.dontMelee ) ) + return false; + + // Distance check should usually fail + enemyDistanceSq = distanceSquared( self.origin, target.origin ); + + if ( isdefined( self.meleeChargeDistSq ) ) + chargeDistSq = self.meleeChargeDistSq; + else if ( isplayer( target ) ) + chargeDistSq = CHARGE_RANGE_SQ_VS_PLAYER; + else + chargeDistSq = CHARGE_RANGE_SQ; + + // Enemy isn't even close enough to initiate + if ( !self.melee.initiated && (enemyDistanceSq > chargeDistSq) ) + return false; + + // + // Self Checks + // + + // Don't charge if we're about to die + if ( !isAlive( self ) ) + return false; + + // Don't melee on the first frame ... + if ( isDefined( self.a.noFirstFrameMelee ) && (self.a.scriptStartTime >= gettime() + 50) ) + return false; + + // Prevent doing checks too often on the same target + if ( isDefined( self.nextMeleeCheckTime ) && isDefined( self.nextMeleeCheckTarget ) && (gettime() < self.nextMeleeCheckTime) && ( self.nextMeleeCheckTarget == target ) ) + return false; + + // Can't melee if we're not standing or crouching + if ( isdefined( self.a.onback ) || (self.a.pose == "prone") ) + return false; + + // can't melee while sidearm is out. need animations for this. + // we rely on main loop to put away sidearm if necessary. + if ( usingSidearm() ) + return false; + + // don't melee charge with a grenade in range, unless you have a shield + if ( isDefined( self.grenade ) && ( self.frontShieldAngleCos == 1 ) ) + return false; + + // + // Enemy checks + // + + if ( !isAlive( target ) ) + return false; + + // no melee on enemies that are flagged as such + if ( isDefined( target.dontAttackMe ) || (isDefined( target.ignoreMe ) && target.ignoreMe) ) + return false; + + // no meleeing virtual targets + if ( !isAI( target ) && !isPlayer( target ) ) + return false; + + if ( isAI( target ) ) + { + // special state, can't allow meleeing + if ( target isInScriptedState() ) + return false; + + // crawling/dying + if ( target doingLongDeath() || target.delayedDeath ) + return false; + } + + // Check if our enemy is in a proper pose to get melee'd + if ( isPlayer( target ) ) + enemyPose = target getStance(); + else + enemyPose = target.a.pose; + + if ( (enemyPose != "stand") && (enemyPose != "crouch") ) + return false; + + // Disable melee completely when both targets are invulnerable + if ( isDefined( self.magic_bullet_shield ) && isDefined( target.magic_bullet_shield ) ) + return false; + + // don't melee charge with a grenade in range of the enemy + if ( isDefined( target.grenade ) ) + return false; + + // + // Position Checks + // + + // Have extra tolerance when already in progress, since some animations twist the origin quite a bit ( for example standard melee ) + if ( self.melee.inProgress ) + yawThreshold = 110; + else + yawThreshold = 60; + + yawToEnemy = AngleClamp180( self.angles[ 1 ] - GetYaw( target.origin ) ); + if ( abs( yawToEnemy ) > yawThreshold ) + return false; + + // Enemy is already close enough to melee. + if ( enemyDistanceSq <= MELEE_RANGE_SQ ) + return true; + + // if we already started, but no longer in melee range, fail/abort + if ( self.melee.inProgress ) + return false; + + // we can't melee from our position and need to charge, but failed a charge recently on the same target ; fail + if ( isDefined( self.nextMeleeChargeTime ) && isDefined( self.nextMeleeChargeTarget ) && (gettime() < self.nextMeleeChargeTime) && (self.nextMeleeChargeTarget == target) ) + return false; + + return true; +} + +Melee_StartMovement() +{ + self.melee.playingMovementAnim = true; + self.a.movement = "run"; +} + +Melee_StopMovement() +{ + self clearanim( %body, 0.2 ); + self.melee.playingMovementAnim = undefined; + self.a.movement = "stop"; + self orientMode( "face default" ); +} + + +Melee_MainLoop() +{ + self endon( "killanimscript" ); + self endon( "end_melee" ); + + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.func ) ); + + while( true ) + { + prevFunc = self.melee.func; + + [[ self.melee.func ]](); + + // No more melee actions available, or no new ones, finish + if ( !isDefined( self.melee.func ) || (prevFunc == self.melee.func) ) + break; + } +} + +Melee_Standard_DelayStandardCharge( target ) +{ + if ( !isDefined ( target ) ) + return; + + self.nextMeleeStandardChargeTime = getTime() + FAILED_STANDARD_NEXT_MELEE_TIME; + self.nextMeleeStandardChargeTarget = target; +} + +Melee_Standard_CheckTimeConstraints() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.target ) ); + + // out of range and too early to do a standard melee + targetDistSq = distanceSquared( self.melee.target.origin, self.origin ); + if ( (targetDistSq > MELEE_RANGE_SQ) && isDefined( self.nextMeleeStandardChargeTime ) && isDefined( self.nextMeleeStandardChargeTarget ) && (getTime() < self.nextMeleeStandardChargeTime) && (self.nextMeleeStandardChargeTarget == self.melee.target) ) + return false; + + return true; +} + +Melee_Standard_ChooseAction() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.target ) ); + + if ( isDefined( self.melee.target.magic_bullet_shield ) ) + return false; + + if ( !Melee_Standard_CheckTimeConstraints() ) + return false; + + if ( isdefined( self.melee.target.specialMeleeChooseAction ) ) + return false; + + return Melee_Standard_UpdateAndValidateTarget(); +} + +Melee_Standard_ResetGiveUpTime() +{ + if ( isdefined( self.meleeChargeDistSq ) ) + chargeDistSq = self.meleeChargeDistSq; + else if ( isplayer( self.melee.target ) ) + chargeDistSq = CHARGE_RANGE_SQ_VS_PLAYER; + else + chargeDistSq = CHARGE_RANGE_SQ; + + if ( distanceSquared( self.origin, self.melee.target.origin ) > chargeDistSq ) + self.melee.giveUpTime = gettime() + 3000; + else + self.melee.giveUpTime = gettime() + 1000; +} + +Melee_Standard_Main() +{ + self animMode( "zonly_physics" ); + + Melee_Standard_ResetGiveUpTime(); + + while ( true ) + { + assert( isdefined( self.melee.target ) ); + + // first, charge forward if we need to; get into place to play the melee animation + if ( !Melee_Standard_GetInPosition() ) + { + // if we couldn't get in place to melee, don't try to charge for a little while and abort + self.nextMeleeChargeTime = getTime() + FAILED_CHARGE_NEXT_MELEE_TIME; + self.nextMeleeChargeTarget = self.melee.target; + break; + } + + if ( !isdefined( self.melee.target ) ) + break; + + assert( (self.a.pose == "stand") || (self.a.pose == "crouch") ); + + self animscripts\battleChatter_ai::evaluateMeleeEvent(); + + self orientMode( "face point", self.melee.target.origin ); + + melee_anim = self get_melee_anim( "attack" ); + + self setflaggedanimknoballrestart( "meleeanim", melee_anim, %body, 1, .2, 1 ); + self.melee.inProgress = true; + + // If the attack loop returns false, we need to stop this melee + if( !Melee_Standard_PlayAttackLoop() ) + { + // Since getting here means that we've done a melee but our attack is no longer valid, delay before we can do a standard attack again. + Melee_Standard_DelayStandardCharge( self.melee.target ); + break; + } + } + + self animMode( "none" ); +} + + +Melee_Standard_PlayAttackLoop() +{ + while ( true ) + { + self waittill( "meleeanim", note ); + + if ( note == "end" ) + { + return true; + } + + if ( note == "stop" ) + { + // check if it's worth continuing with another melee. + // and see if we could so something better , or continue with our attacks + if ( !Melee_ChooseAction() ) + return false; + + // Return whether the action we choose is the same as this one, in which case we'll simply continue. + assert( isDefined( self.melee.func ) ); + if ( self.melee.func != ::Melee_Standard_Main ) + return true; + } + + if ( note == "fire" ) + { + if ( isdefined( self.melee.target ) ) + { + oldhealth = self.melee.target.health; + self melee(); + if ( isDefined( self.melee.target ) && (self.melee.target.health < oldhealth) ) + Melee_Standard_ResetGiveUpTime(); + } + } + } +} + +// this will update our target position based on our target +Melee_Standard_UpdateAndValidateTarget() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + if ( !isDefined( self.melee.target ) ) + return false; + + if ( !Melee_IsValid() ) + return false; + + dirToTarget = vectorNormalize( self.melee.target.origin - self.origin ); + self.melee.startPos = self.melee.target.origin - 40.0 * dirToTarget; + + return Melee_UpdateAndValidateStartPos(); +} + +distance2dSquared( a, b ) // should be moved to code +{ + diff = (a[0] - b[0], a[1] - b[1], 0 ); + return lengthSquared( diff ); +} + +// this function makes the guy run towards his enemy, and start raising his gun if he's close enough to melee. +// it will return false if he gives up, or true if he's ready to start a melee animation. +Melee_Standard_GetInPosition() +{ + if ( !Melee_Standard_UpdateAndValidateTarget() ) + return false; + + enemyDistanceSq = distance2dSquared( self.origin, self.melee.target.origin ); + + if ( enemyDistanceSq <= MELEE_RANGE_SQ ) + { + // just play a melee-from-standing transition + melee_anim = self get_melee_anim( "stand_to_melee" ); + self SetFlaggedAnimKnobAll( "readyanim", melee_anim, %body, 1, .3, 1 ); + self animscripts\shared::DoNoteTracks( "readyanim" ); + return true; + } + + self Melee_PlayChargeSound(); + + prevEnemyPos = self.melee.target.origin; + + sampleTime = 0.1; + + raiseGunAnimTravelDist = length( getmovedelta( anim.animsets.melee["charge"], 0, 1 ) ); + meleeAnimTravelDist = 32; + shouldRaiseGunDist = MELEE_RANGE * 0.75 + meleeAnimTravelDist + raiseGunAnimTravelDist; + shouldRaiseGunDistSq = shouldRaiseGunDist * shouldRaiseGunDist; + + shouldMeleeDist = MELEE_RANGE + meleeAnimTravelDist; + shouldMeleeDistSq = shouldMeleeDist * shouldMeleeDist; + + charge_anim = self get_melee_anim( "charge" ); + raiseGunFullDuration = getanimlength( charge_anim ) * 1000; + raiseGunFinishDuration = raiseGunFullDuration - 100; + raiseGunPredictDuration = raiseGunFullDuration - 200; + raiseGunStartTime = 0; + + predictedEnemyDistSqAfterRaiseGun = undefined; + + runAnim = get_melee_anim( "run" ); + + if ( isplayer( self.melee.target ) && self.melee.target == self.enemy ) + self orientMode( "face enemy" ); + else + self orientMode( "face point", self.melee.target.origin ); + + self SetFlaggedAnimKnobAll( "chargeanim", runAnim, %body, 1, .3, 1 ); + raisingGun = false; + + while ( 1 ) + { + time = gettime(); + + willBeWithinRangeWhenGunIsRaised = ( isdefined( predictedEnemyDistSqAfterRaiseGun ) && predictedEnemyDistSqAfterRaiseGun <= shouldRaiseGunDistSq ); + + if ( !raisingGun ) + { + if ( willBeWithinRangeWhenGunIsRaised ) + { + Melee_StartMovement(); + self SetFlaggedAnimKnobAllRestart( "chargeanim", charge_anim, %body, 1, .2, 1 ); + raiseGunStartTime = time; + raisingGun = true; + } + } + else + { + // if we *are* raising our gun, don't stop unless we're hopelessly out of range, + // or if we hit the end of the raise gun animation and didn't melee yet + withinRangeNow = enemyDistanceSq <= shouldRaiseGunDistSq; + if ( time - raiseGunStartTime >= raiseGunFinishDuration || ( !willBeWithinRangeWhenGunIsRaised && !withinRangeNow ) ) + { + Melee_StartMovement(); + self SetFlaggedAnimKnobAll( "chargeanim", runAnim, %body, 1, .3, 1 ); + raisingGun = false; + } + } + self animscripts\shared::DoNoteTracksForTime( sampleTime, "chargeanim" ); + + // now that we moved a bit, see if our target moved before we check for valid melee + // it's possible something happened in the meantime that makes meleeing impossible. + if ( !Melee_Standard_UpdateAndValidateTarget() ) + { + Melee_StopMovement(); + return false; + } + + enemyDistanceSq = distance2dSquared( self.origin, self.melee.target.origin ); + enemyVel = vector_multiply( self.melee.target.origin - prevEnemyPos, 1 / ( gettime() - time ) );// units / msec + prevEnemyPos = self.melee.target.origin; + + // figure out where the player will be when we hit them if we (a) start meleeing now, or (b) start raising our gun now + predictedEnemyPosAfterRaiseGun = self.melee.target.origin + vector_multiply( enemyVel, raiseGunPredictDuration ); + predictedEnemyDistSqAfterRaiseGun = distance2dSquared( self.origin, predictedEnemyPosAfterRaiseGun ); + + // if we're done raising our gun, and starting a melee now will hit the guy, our preparation is finished + // when fighting non-players, don't wait for the gun raise to finish, or we'll walk through them + if ( raisingGun && (enemyDistanceSq <= shouldMeleeDistSq) && (gettime() - raiseGunStartTime >= raiseGunFinishDuration || !isPlayer( self.melee.target )) ) + break; + + // don't keep charging if we've been doing this for too long. + if ( !raisingGun && (gettime() >= self.melee.giveUpTime) ) + { + Melee_StopMovement(); + return false; + } + } + + Melee_StopMovement(); + return true; +} + +Melee_PlayChargeSound() +{ + if ( !isdefined( self.a.nextMeleeChargeSound ) ) + self.a.nextMeleeChargeSound = 0; + + if ( ( isdefined( self.enemy ) && isplayer( self.enemy ) ) || randomint( 3 ) == 0 ) + { + if ( gettime() > self.a.nextMeleeChargeSound ) + { + self animscripts\face::SayGenericDialogue( "meleecharge" ); + self.a.nextMeleeChargeSound = gettime() + 8000; + } + } +} + +// =========================================================== +// AI vs AI synced melee +// =========================================================== + + +Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Flip( angleDiff ) +{ + flipAngleThreshold = 90; + + // Have extra tolerance when already in progress, since some animations twist the origin quite a bit ( for example standard melee ) + if ( self.melee.inProgress ) + flipAngleThreshold += 50; + + // facing each other + if ( abs( angleDiff ) < flipAngleThreshold ) + return false; + + target = self.melee.target; + Melee_Decide_Winner(); + if ( self.melee.winner ) + { + self.melee.animName = %melee_F_awin_attack; + target.melee.animName = %melee_F_awin_defend; + target.melee.surviveAnimName = %melee_F_awin_defend_survive; + } + else + { + self.melee.animName = %melee_F_dwin_attack; + target.melee.animName = %melee_F_dwin_defend; + } + + return true; +} + + +Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Wrestle( angleDiff ) +{ + wrestleAngleThreshold = 100; + + // Have extra tolerance when already in progress, since some animations twist the origin quite a bit ( for example standard melee ) + if ( self.melee.inProgress ) + wrestleAngleThreshold += 50; + + // facing each other + if ( abs( angleDiff ) < wrestleAngleThreshold ) + return false; + + target = self.melee.target; + + // Attacker must be able to win + if ( isDefined( target.magic_bullet_shield ) ) + return false; + + /# + // DEBUGGING CASES FOR TEST MAP + if ( isDefined( target.meleeAlwaysWin ) ) + { + assert( !isDefined( self.magic_bullet_shield ) ); + return false; + } + #/ + + self.melee.winner = true; + self.melee.animName = %bog_melee_R_attack; + target.melee.animName = %bog_melee_R_defend; + target.melee.surviveAnimName = %bog_melee_R_backdeath2; + + return true; +} + + +Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Behind( angleDiff ) +{ + // from behind right + if ( (-90 > angleDiff) || (angleDiff > 0) ) + return false; + + target = self.melee.target; + + // Attacker must be able to win + if ( isDefined( target.magic_bullet_shield ) ) + return false; + + /# + // DEBUGGING CASES FOR TEST MAP + if ( isDefined( target.meleeAlwaysWin ) ) + { + assert( !isDefined( self.magic_bullet_shield ) ); + return false; + } + #/ + + self.melee.winner = true; + self.melee.animName = %melee_sync_attack; + target.melee.animName = %melee_sync_defend; + + return true; +} + + +Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_BuildExposedList() +{ + // If this AI is forced to play a specific melee, do so! + if ( isDefined( self.meleeForcedExposedFlip ) ) + { + assert( !isDefined( self.meleeForcedExposedWrestle ) ); //can't force both + exposedMelees[0] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Flip; + } + else if ( isDefined( self.meleeForcedExposedWrestle ) ) + { + exposedMelees[0] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Wrestle; + } + else + { + // Randomize whether flip or wrestle gets tested first. Behind always tested last. + flipIndex = randomInt( 2 ); + wrestleIndex = 1 - flipIndex; + behindIndex = 2; + + exposedMelees[flipIndex] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Flip; + exposedMelees[wrestleIndex] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Wrestle; + exposedMelees[behindIndex] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Behind; + } + + return exposedMelees; +} + + +Melee_AIvsAI_Exposed_ChooseAnimationAndPosition() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee.target ) ); + + // Choose which sequence to play based on angles + target = self.melee.target; + angleToEnemy = vectortoangles( target.origin - self.origin ); + angleDiff = AngleClamp180( target.angles[ 1 ] - angleToEnemy[ 1 ] ); + + exposedMelees = Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_BuildExposedList(); + for( i = 0; i < exposedMelees.size; i++ ) + { + // Test each melee move in order + if ( [[ exposedMelees[i] ]]( angleDiff ) ) + { + assert( isDefined ( self.melee.animName ) ); + assert( isDefined ( target.melee.animName ) ); + + // Calculate the position based on the chosen animation. The angles are set so that the attacker faces the enemy before linking + self.melee.startAngles = ( 0, angleToEnemy[1], 0 ); + self.melee.startPos = getStartOrigin( target.origin, target.angles, self.melee.animName ); + + // Succeed if it's on a proper floor/height, we can move in position and we we have a LOS to the target + if ( Melee_UpdateAndValidateStartPos() ) + return true; + } + } + + // No moves possible + return false; +} + +Melee_Decide_Winner() +{ + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.target ) ); + + target = self.melee.target; + + /# + // DEBUGGING CASES FOR TEST MAP + if( isDefined( self.meleeAlwaysWin ) ) + { + assert( !isDefined( target.magic_bullet_shield ) ); + self.melee.winner = true; + return; + } + else if ( isDefined( target.meleeAlwaysWin ) ) + { + assert( !isDefined( self.magic_bullet_shield ) ); + self.melee.winner = false; + return; + } + #/ + + // Figure out who wins + if ( isDefined( self.magic_bullet_shield ) ) + { + assert( !isDefined( target.magic_bullet_shield ) ); + self.melee.winner = true; + } + else if ( isDefined( target.magic_bullet_shield ) ) + { + self.melee.winner = false; + } + else + { + self.melee.winner = cointoss(); + } +} + +Melee_AIvsAI_SpecialCover_ChooseAnimationAndPosition() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee.target ) ); + assert( isDefined( self.melee.target.covernode ) ); + + target = self.melee.target; + + Melee_Decide_Winner(); + + if ( target.covernode.type == "Cover Left" ) + { + if ( self.melee.winner ) + { + self.melee.animName = %cornerSdL_melee_winA_attacker; + target.melee.animName = %cornerSdL_melee_winA_defender; + target.melee.surviveAnimName = %cornerSdL_melee_winA_defender_survive; + } + else + { + self.melee.animName = %cornerSdL_melee_winD_attacker; + self.melee.surviveAnimName = %cornerSdL_melee_winD_attacker_survive; + target.melee.animName = %cornerSdL_melee_winD_defender; + } + } + else // Right + { + assert( target.covernode.type == "Cover Right" ); + if ( self.melee.winner ) + { + self.melee.animName = %cornerSdR_melee_winA_attacker; + target.melee.animName = %cornerSdR_melee_winA_defender; + } + else + { + self.melee.animName = %cornerSdR_melee_winD_attacker; + target.melee.animName = %cornerSdR_melee_winD_defender; + } + } + + // The start position is based on the cover node of the target + self.melee.startPos = getStartOrigin( target.covernode.origin, target.covernode.angles, self.melee.animName ); + self.melee.startAngles = ( target.covernode.angles[0], AngleClamp180( target.covernode.angles[1] + 180 ), target.covernode.angles[2] ); + + target.melee.faceYaw = getNodeForwardYaw( target.covernode ); + + // Make sure we can move to the selected point ( no re-try for now ) + self.melee.startToTargetCornerAngles = target.covernode.angles; + if ( !Melee_UpdateAndValidateStartPos() ) + { + self.melee.startToTargetCornerAngles = undefined; + return false; + } + + return true; +} + + +Melee_AIvsAI_SpecialCover_CanExecute() +{ + assert( isDefined ( self ) ); + assert( isDefined ( self.melee.target ) ); + + cover = self.melee.target.covernode; + if ( !isDefined( cover ) ) + return false; + + // Make sure the enemy is hiding or leaning out and not currently exposing + if ( (distanceSquared( cover.origin, self.melee.target.origin ) > 16) && isdefined( self.melee.target.a.coverMode ) && ( (self.melee.target.a.coverMode != "hide") && (self.melee.target.a.coverMode != "lean") ) ) + return false; + + // Must be within a some arc in front of the cover + coverToSelfAngles = vectortoangles( self.origin - cover.origin ); + angleDiff = AngleClamp180( cover.angles[ 1 ] - coverToSelfAngles[ 1 ] ); + + // Only do it for left/right covers for now + if ( cover.type == "Cover Left" ) + { + if ( (angleDiff >= -50) && (angleDiff <= 0) ) + return true; + } + else if ( cover.type == "Cover Right" ) + { + if ( (angleDiff >= 0) && (angleDiff <= 50) ) + return true; + } + + return false; +} + + +Melee_AIvsAI_ChooseAction() +{ + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.target ) ); + + target = self.melee.target; + + // We can only do AI vs AI between human AIs + if ( !isAI( target ) || (target.type != "human") ) + return false; + + // Can't do AIvsAI in stairs + assert( isDefined( self.stairsState ) ); + assert( isDefined( target.stairsState ) ); + if ( (self.stairsState != "none") || (target.stairsState != "none") ) + return false; + + // At least one of the two needs not to have bullet shield to be in melee to begin with + assert( !isDefined( self.magic_bullet_shield ) || !isdefined( self.melee.target.magic_bullet_shield ) ); + + if ( isdefined( self.specialMeleeChooseAction ) ) + { + if ( ![[ self.specialMeleeChooseAction ]]() ) + return false; + self.melee.precisePositioning = true; + } + else if ( isdefined( target.specialMeleeChooseAction ) ) + { + return false; + } + // If we can execute a special cover sequence, do so, otherwise revert to standard + else if ( Melee_AIvsAI_SpecialCover_CanExecute() && Melee_AIvsAI_SpecialCover_ChooseAnimationAndPosition() ) + { + self.melee.precisePositioning = true; + } + else + { + if ( !Melee_AIvsAI_Exposed_ChooseAnimationAndPosition() ) + return false; + self.melee.precisePositioning = false; + } + + // Save the current facing yaw if none of the behaviors requested something specific. + if ( !isDefined ( target.melee.faceYaw ) ) + target.melee.faceYaw = target.angles[1]; + + // And the offset from the target to the start pos so that we can do validity checks + self.melee.startPosOffset = ( self.melee.startPos - target.origin ); + + // If we get here, we can get to our position and an action has been chosen + return true; +} + + +Melee_AIvsAI_ScheduleNoteTrackLink( target ) +{ + // Set us up to get sync'd when we get the note track ( not immediately as regular melees ) + self.melee.syncNoteTrackEnt = target; + target.melee.syncNoteTrackEnt = undefined; +} + + +Melee_AIvsAI_TargetLink( target ) +{ + assert( isDefined( self ) ); + assert( isDefined( target ) ); + + // If the target is no longer meleeing, don't attach (should only be valid if surviving) + if ( !isDefined( target.melee ) ) + { + assert( isDefined( self.melee.survive ) ); + return; + } + + self Melee_PlayChargeSound(); + + // Only attach to our target if he's still alive + if ( !isAlive( target ) ) + return; + + // Sync up - this var needs to stay outside the melee struct because code uses it! + self.syncedMeleeTarget = target; + target.syncedMeleeTarget = self; + + self.melee.linked = true; + target.melee.linked = true; + self linkToBlendToTag( target, "tag_sync", true, true ); +} + + +Melee_AIvsAI_Main() +{ + // charge to correct position + if ( !Melee_AIvsAI_GetInPosition() ) + { + // if we couldn't get in place to melee, don't try to charge for a little while and abort + self.nextMeleeChargeTime = gettime() + FAILED_CHARGE_NEXT_MELEE_TIME; + self.nextMeleeChargeTarget = self.melee.target; + return; + } + + target = self.melee.target; + + // make sure both are still alive - get in position should have aborted otherwise + assert( isAlive( self ) && isAlive( target ) ); + + // setup linking/syncing + + // catch any leftover sync issues + assert( !isDefined( self.syncedMeleeTarget ) ); + assert( !isDefined( target.syncedMeleeTarget ) ); + + assert( isDefined( self.melee.animName ) ); + assert( animHasNotetrack( self.melee.animName, NOTETRACK_SYNC ) ); + self Melee_AIvsAI_ScheduleNoteTrackLink( target ); + + // Setup who gets to live + if ( self.melee.winner ) + { + self.melee.death = undefined; + target.melee.death = true; + } + else + { + target.melee.death = undefined; + self.melee.death = true; + } + + // link up the two in case someone ends the script early + self.melee.partner = target; + target.melee.partner = self; + + if ( self usingSideArm() ) + { + self forceUseWeapon( self.primaryWeapon, "primary" ); + self.lastWeapon = self.primaryWeapon; + } + if ( target usingSideArm() ) + { + target forceUseWeapon( target.primaryWeapon, "primary" ); + target.lastWeapon = target.primaryWeapon; + } + + //save weapons + self.melee.weapon = self.weapon; + self.melee.weaponSlot = self getCurrentWeaponSlotName(); + target.melee.weapon = target.weapon; + target.melee.weaponSlot = target getCurrentWeaponSlotName(); + + // mark melee as in progress for the initiater + self.melee.inProgress = true; + + // Run animation on our target + target animcustom( ::Melee_AIvsAI_Execute, ::Melee_EndScript ); + target thread Melee_AIvsAI_AnimCustomInterruptionMonitor( self ); + + // release the target now that it started, we're no longer allowed to mess with it + self.melee.target = undefined; + + // We're already in a custom, call directly + self Melee_AIvsAI_Execute(); +} + +Melee_AIvsAI_AnimCustomInterruptionMonitor( attacker ) +{ + assert( isDefined( attacker ) ); + + self endon( "end_melee" ); + self endon( "melee_aivsai_execute" ); + + // Wait for a couple of frames. If the execution hasn't started then, fail. + wait 0.1; + + if ( isDefined( attacker ) ) + attacker notify( "end_melee" ); + + self notify( "end_melee" ); +} + + +Melee_AIvsAI_GetInPosition_UpdateAndValidateTarget( initialTargetOrigin, giveUpTime ) +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( initialTargetOrigin ) ); + + // Took too long + if ( isDefined( giveUpTime ) && (giveUpTime <= getTime()) ) + return false; + + // Check if we can still melee while charging + if ( !Melee_IsValid() ) + return false; + + target = self.melee.target; + + // If target moves too much , fail + positionDelta = distanceSquared( target.origin, initialTargetOrigin ); + + // Less tolerant to movement when the target should be in cover + assert( isDefined( self.melee.precisePositioning ) ); + if ( self.melee.precisePositioning ) + positionThreshold = sqr16; + else + positionThreshold = sqr36; + + if ( positionDelta > positionThreshold ) + return false; + + // Make sure the target hasn't moved in a way that would make us unable to do the melee + // Update our starting position + // Make sure target is not out of reach + self.melee.startPos = target.origin + self.melee.startPosOffset; + if ( !Melee_UpdateAndValidateStartPos() ) + return false; + + return true; +} + + +Melee_AIvsAI_GetInPosition_IsSuccessful( initialTargetOrigin ) +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.startPos ) ); + assert( isDefined( self.melee.target ) ); + assert( isDefined( initialTargetOrigin ) ); + + // at the start pos + dist2dToStartPos = distanceSquared( (self.origin[0], self.origin[1], 0), (self.melee.startPos[0], self.melee.startPos[1], 0) ); + if ( (dist2dToStartPos < sqr8) && (abs( self.melee.startPos[2] - self.origin[2] ) < MELEE_RANGE) ) + return true; + + // in between enemy and start pos + dist2dFromStartPosToTargetSq = distanceSquared( (initialTargetOrigin[0], initialTargetOrigin[1], 0), (self.melee.startPos[0], self.melee.startPos[1], 0) ); + dist2dToTargetSq = distanceSquared( (self.origin[0], self.origin[1], 0), (self.melee.target.origin[0], self.melee.target.origin[1], 0) ); + if ( (dist2dFromStartPosToTargetSq > dist2dToTargetSq) && (abs( self.melee.target.origin[2] - self.origin[2] ) < MELEE_RANGE) ) + return true; + + return false; +} + + +Melee_AIvsAI_GetInPosition_Finalize( initialTargetOrigin ) +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.precisePositioning ) ); + assert( isDefined( initialTargetOrigin ) ); + + // stop the animation and such + Melee_StopMovement(); + + if ( self.melee.precisePositioning ) + { + assert( isDefined( self.melee.startPos ) ); + assert( isDefined( self.melee.startAngles ) ); + + self forceTeleport( self.melee.startPos, self.melee.startAngles ); + wait 0.05; + } + else + { + self orientMode( "face angle", self.melee.startAngles[1] ); + wait 0.05; + } + + // Teleport might have made the sequence invalid, make sure it's still right as we exit + return Melee_AIvsAI_GetInPosition_UpdateAndValidateTarget( initialTargetOrigin ); +} + + +Melee_AIvsAI_GetInPosition() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + // Check if we can still melee while charging + if ( !Melee_IsValid() ) + return false; + + Melee_StartMovement(); + self clearanim( %body, 0.2 ); + self setAnimKnobAll( animscripts\run::GetRunAnim(), %body, 1, 0.2 ); + self animMode( "zonly_physics" ); + self.keepClaimedNode = true; + + giveUpTime = getTime() + 1500; + + assert( isDefined( self.melee.target ) ); + assert( isDefined( self.melee.target.origin ) ); + initialTargetOrigin = self.melee.target.origin; + + /# + self notify ( "MDBG_att_getInPosition", self.melee.target ); + self.melee.target notify ( "MDBG_def_getInPosition", self ); + #/ + + while ( Melee_AIvsAI_GetInPosition_UpdateAndValidateTarget( initialTargetOrigin, giveUpTime ) ) + { + if ( Melee_AIvsAI_GetInPosition_IsSuccessful( initialTargetOrigin ) ) + return Melee_AIvsAI_GetInPosition_Finalize( initialTargetOrigin ); + + // play run forward anim + self orientMode( "face point", self.melee.startPos ); + wait .05; + } + + Melee_StopMovement(); + return false; +} + + +Melee_AIvsAI_Execute() +{ + self endon( "killanimscript" ); + self endon( "end_melee" ); + + self notify( "melee_aivsai_execute" ); + + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + self animMode( "zonly_physics" ); + self.a.special = "none"; + self.specialDeathFunc = undefined; + + // Check whether something makes us drop our weapon. If we get revived we'll need to restore it + self thread Melee_DroppedWeaponMonitorThread(); + + // Check for our partner ending melee early, for getting saved etc + self thread Melee_PartnerEndedMeleeMonitorThread(); + + // If we have faceYaw specified, use them, otherwise stay oriented as we were + if ( isDefined( self.melee.faceYaw ) ) + self orientMode( "face angle", self.melee.faceYaw ); + else + self orientMode( "face current" ); + + // only have standing melees for now, set these with notetracks + self.a.pose = "stand"; + self clearanim( %body, 0.2 ); + + // Disable some interruptions if we're going to die, we don't want to break out of melee + if ( isDefined( self.melee.death ) ) + self Melee_DisableInterruptions(); + + // Start the base animation, and loop over the note tracks until one of them tell us to stop + self setFlaggedAnimKnobAllRestart( "meleeAnim", self.melee.animName, %body, 1, 0.2 ); + endNote = self animscripts\shared::DoNoteTracks( "meleeAnim", ::Melee_HandleNoteTracks ); + + // If the survival animation stopped us, play it now + if ( (endNote == NOTETRACK_DEATH) && isDefined( self.melee.survive ) ) + { + // If we dropped our weapon but we got saved, restore it immediately + Melee_DroppedWeaponRestore(); + + self setflaggedanimknoballrestart( "meleeAnim", self.melee.surviveAnimName, %body, 1, 0.2 ); + endNote = self animscripts\shared::DoNoteTracks( "meleeAnim", ::Melee_HandleNoteTracks ); + } + + // If we're marked for death, make sure we die before exiting + if ( isDefined( self.melee ) && isDefined( self.melee.death ) ) + self kill(); + + // note sure what this does: + self.keepClaimedNode = false; +} + + +Melee_DisableInterruptions() +{ + //save the states so we can restore them + self.melee.wasAllowingPain = self.allowPain; + self.melee.wasFlashbangImmune = self.flashBangImmunity; + + //disable what makes sense + self disable_pain(); + self setFlashbangImmunity( true ); +} + + +Melee_NeedsWeaponSwap() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + return ( isDefined( self.melee.weapon ) && (self.melee.weapon != "none") && (self.weapon != self.melee.weapon) ); +} + + +Melee_DroppedWeaponRestore() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + // Give back the weapon we had initially, if we had one, and we dropped it + if ( self.weapon != "none" && self.lastWeapon != "none" ) + return; + + // If we did not have one to begin with, not much we can do + if ( !isDefined( self.melee.weapon ) || (self.melee.weapon == "none") ) + return; + + // Immediately swap the weapon. Can't animate when ending the script, and we don't want to when playing the revive + self forceUseWeapon( self.melee.weapon, self.melee.weaponSlot ); + + // if we dropped the item, destroy it + if ( isDefined( self.melee.droppedWeaponEnt ) ) + { + self.melee.droppedWeaponEnt delete(); + self.melee.droppedWeaponEnt = undefined; + } +} + + +Melee_DroppedWeaponMonitorThread() +{ + self endon( "killanimscript" ); + self endon( "end_melee" ); + assert( isDefined( self.melee ) ); + + self waittill( "weapon_dropped", droppedWeapon ); + + // the weapon drop might fail if in solid and such. droppedWeapon would be 'removed entity' in that case. + if ( isDefined( droppedWeapon ) ) + { + assert( isDefined( self.melee ) ); + self.melee.droppedWeaponEnt = droppedWeapon; + } +} + + +Melee_PartnerEndedMeleeMonitorThread_ShouldAnimSurvive() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + // Doesn't have a survival animation set + if ( !isDefined( self.melee.surviveAnimName ) ) + return false; + + // Too early if before they interact + if ( !isDefined( self.melee.surviveAnimAllowed ) ) + return false; + + return true; +} + + +Melee_PartnerEndedMeleeMonitorThread() +{ + self endon( "killanimscript" ); + self endon( "end_melee" ); + assert( isDefined( self.melee ) ); + + self waittill( "partner_end_melee" ); + + if ( isDefined( self.melee.death ) ) + { + // partner ended the melee, and we're supposed to die. end the script + if ( isDefined( self.melee.animatedDeath ) || isDefined( self.melee.interruptDeath ) ) + { + self kill(); + } + else + { + // don't die! + self.melee.death = undefined; + + // partner ended before we decided we'd die, we should revive now + if ( Melee_PartnerEndedMeleeMonitorThread_ShouldAnimSurvive() ) + { + assert ( animHasNotetrack( self.melee.animName, NOTETRACK_DEATH ) ); + self.melee.survive = true; + } + else + { + self notify( "end_melee" ); + } + } + } + else + { + // if we're not doing the last part of the animation, end immediately + if ( !isDefined( self.melee.unsyncHappened ) ) + self notify( "end_melee" ); + } +} + + + +Melee_Unlink() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + if ( !isDefined( self.melee.linked ) ) + return; + + // Unlink our sync'd target first, because our own unlink will clear this information + if ( isDefined( self.syncedMeleeTarget ) ) + self.syncedMeleeTarget Melee_UnlinkInternal(); + + self Melee_UnlinkInternal(); +} + + +Melee_UnlinkInternal() +{ + assert( isDefined( self ) ); + + self unlink(); + self.syncedMeleeTarget = undefined; + + if ( !isAlive( self ) ) + return; + + assert( isDefined( self.melee ) ); + assert( isDefined( self.melee.linked ) ); + self.melee.linked = undefined; + + self animMode( "zonly_physics" ); + self orientMode( "face angle", self.angles[1] ); +} + + +Melee_HandleNoteTracks_Unsync() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + self Melee_Unlink(); + + // let the AIs know that the unsync happened, which changes the interruption behavior + self.melee.unsyncHappened = true; + if ( isDefined( self.melee.partner ) && isDefined( self.melee.partner.melee ) ) + self.melee.partner.melee.unsyncHappened = true; +} + + +Melee_HandleNoteTracks_ShouldDieAfterUnsync() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + if ( animHasNotetrack( self.melee.animName, NOTETRACK_DEATH ) ) + { + assert( isDefined( self.melee.surviveAnimName ) ); + return false; + } + + return isdefined( self.melee.death ); +} + + +Melee_HandleNoteTracks_Death( interruptAnimation ) +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + assert( isdefined( self.melee.death ) ); + + // set whether we should die immediately if melee were to end, or finish playing the animation + if ( isDefined( interruptAnimation ) && interruptAnimation ) + self.melee.interruptDeath = true; + else + self.melee.animatedDeath = true; +} + + +Melee_HandleNoteTracks( note ) +{ + if ( isSubStr( note, "ps_" ) ) + { + alias = GetSubStr( note, 3 ); + self playSound( alias ); + return; + } + + if ( note == NOTETRACK_SYNC ) + { + if ( isDefined( self.melee.syncNoteTrackEnt ) ) + { + self Melee_AIvsAI_TargetLink( self.melee.syncNoteTrackEnt ); + self.melee.syncNoteTrackEnt = undefined; + } + } + else if ( note == NOTETRACK_UNSYNC ) + { + self Melee_HandleNoteTracks_Unsync(); + + // After the targets unsync, the final 'death' sequence is usually played, and we want to handle the pre-corpse sequence ourself. + // We could add a seperate note track too, if this turns out not to be precise enough. + if ( Melee_HandleNoteTracks_ShouldDieAfterUnsync() ) + Melee_HandleNoteTracks_Death(); + } + else if ( note == NOTETRACK_INTERACT ) + { + // From this point on, it's okay to get revived by the animation + self.melee.surviveAnimAllowed = true; + } + else if ( note == NOTETRACK_DEATH ) + { + // Check if we got saved. If we did, play the alternate ending + if ( isDefined( self.melee.survive ) ) + { + assert( !isdefined( self.melee.death ) ); + assert( isDefined( self.melee.surviveAnimName ) ); + + // Interrupt the waiting loop so that we may start a new one with the survival animation + return note; + } + + assert( isdefined( self.melee.death ) ); + Melee_HandleNoteTracks_Death(); + + if ( isDefined( self.melee.animatedDeath ) ) + return note; // abort DoNoteTracks so we do our death immediately. + } + else if ( note == NOTETRACK_ATTACHKNIFE ) + { + self attach( KNIFE_ATTACK_MODEL, KNIFE_ATTACK_TAG, true ); + self.melee.hasKnife = true; + } + else if ( note == NOTETRACK_DETACTKNIFE ) + { + self detach( KNIFE_ATTACK_MODEL, KNIFE_ATTACK_TAG, true ); + self.melee.hasKnife = undefined; + } + else if ( note == NOTETRACK_STAB ) + { + assert( isDefined( self.melee.hasKnife ) ); + + // Play the knife effect + self playsound( KNIFE_ATTACK_SOUND ); + playfxontag( level._effect[ KNIFE_ATTACK_FX_NAME ], self, KNIFE_ATTACK_FX_TAG ); + + // make sure the target dies after being stabbed if he's still doing the melee + if ( isDefined( self.melee.partner ) && isDefined( self.melee.partner.melee ) ) + self.melee.partner Melee_HandleNoteTracks_Death( true ); + } +} + + +Melee_DeathHandler_Regular() +{ + self endon( "end_melee" ); + self animscripts\shared::DropAllAIWeapons(); + return false; //play regular death +} + + +Melee_DeathHandler_Delayed() +{ + self endon( "end_melee" ); + self animscripts\shared::DoNoteTracksWithTimeout( "meleeAnim", 10.0 ); + self animscripts\shared::DropAllAIWeapons(); + self startRagdoll(); + + return true; //skip regular death +} + + +Melee_EndScript_CheckDeath() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + if ( !isAlive( self ) && isDefined( self.melee.death ) ) + { + if ( isDefined( self.melee.animatedDeath ) ) + self.deathFunction = ::Melee_DeathHandler_Delayed; + else + self.deathFunction = ::Melee_DeathHandler_Regular; + } +} + + +Melee_EndScript_CheckPositionAndMovement() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + if ( !isAlive( self ) ) + return; + + // make sure we're not marked as moving anymore + if ( isDefined( self.melee.playingMovementAnim ) ) + Melee_StopMovement(); + + // Adjust Ground Position + newOrigin = self getDropToFloorPosition(); + if ( isDefined ( newOrigin ) ) + self forceTeleport( newOrigin, self.angles ); + else + println( "Warning: Melee animation might have ended up in solid for entity #" + self getentnum() ); +} + + +Melee_EndScript_CheckWeapon() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + // melee ended with the knife equipped, remove it + if ( isDefined( self.melee.hasKnife ) ) + self detach( KNIFE_ATTACK_MODEL, KNIFE_ATTACK_TAG, true ); + + // If we dropped our weapon but we didn't die, restore it + if ( isAlive( self ) ) + Melee_DroppedWeaponRestore(); +} + + +Melee_EndScript_CheckStateChanges() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + // Restore interruption-based state changes + + if ( isDefined( self.melee.wasAllowingPain ) ) + { + if ( self.melee.wasAllowingPain ) + self enable_pain(); + else + self disable_pain(); + } + + if ( isDefined( self.melee.wasFlashbangImmune ) ) + self setFlashbangImmunity( self.melee.wasFlashbangImmune ); +} + + +Melee_EndScript() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee ) ); + + self Melee_Unlink(); + self Melee_EndScript_CheckDeath(); + self Melee_EndScript_CheckPositionAndMovement(); + self Melee_EndScript_CheckWeapon(); + self Melee_EndScript_CheckStateChanges(); + + // End the melee prematurely for the other sync'd ent when someone dies/script ends + if ( isDefined( self.melee.partner ) ) + self.melee.partner notify( "partner_end_melee" ); + + self Melee_ReleaseMutex( self.melee.target ); +} + + +Melee_AcquireMutex( target ) +{ + assert( isDefined( self ) ); + assert( isDefined( target ) ); + + // Can't acquire when soemone is targeting us for a melee + if ( isDefined( self.melee ) ) + return false; + + // Can't acquire enemy mutex if he's already in a melee process + if ( isDefined( target.melee ) ) + return false; + + self.melee = spawnStruct(); + target.melee = spawnStruct(); + + return true; +} + + +Melee_ReleaseMutex( target ) +{ + assert( isDefined( self ) ); + self.melee = undefined; + + if ( isDefined( target ) ) + target.melee = undefined; +} + +get_melee_anim( anim_type ) +{ + if ( isdefined( self.customMeleeAnims ) ) + { + return self.customMeleeAnims[ anim_type ]; + } + + return anim.animsets.melee[ anim_type ]; +} diff --git a/animscripts/move.gsc b/animscripts/move.gsc new file mode 100644 index 0000000..234a969 --- /dev/null +++ b/animscripts/move.gsc @@ -0,0 +1,1231 @@ +#include animscripts\SetPoseMovement; +#include animscripts\combat_utility; +#include animscripts\utility; +#include animscripts\shared; +#include animscripts\melee; +#include common_scripts\utility; +#include maps\_utility; + +#using_animtree( "generic_human" ); + +main() +{ + self endon( "killanimscript" ); + + [[ self.exception[ "move" ] ]](); + + moveInit(); + getUpIfProne(); + animscripts\utility::initialize( "move" ); + + wasInCover = self wasPreviouslyInCover(); + + if ( wasInCover && isdefined( self.shuffleMove ) ) + { + moveCoverToCover(); + moveCoverToCoverFinish(); + } + else if ( IsDefined( self.battleChatter ) && self.battleChatter ) + { + self moveStartBattleChatter( wasInCover ); + self animscripts\battlechatter::playBattleChatter(); + } + + self thread stairsCheck(); + self thread pathChangeCheck(); + self thread animDodgeObstacle(); + + self animscripts\cover_arrival::startMoveTransition(); + self.doingReacquireStep = undefined; + self.ignorePathChange = undefined; + + self thread startThreadsToRunWhileMoving(); + + self thread animscripts\cover_arrival::setupApproachNode( true ); + + self.shoot_while_moving_thread = undefined; + self.aim_while_moving_thread = undefined; + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ + + self.runNGun = undefined; + + MoveMainLoop( true ); +} + + +// called by code on ending this script +end_script() +{ + if ( isdefined( self.oldGrenadeWeapon ) ) + { + self.grenadeWeapon = self.oldGrenadeWeapon; + self.oldGrenadeWeapon = undefined; + } + + self.teamFlashbangImmunity = undefined; + self.minInDoorTime = undefined; + self.ignorePathChange = undefined; + self.shuffleMove = undefined; + self.shuffleNode = undefined; + self.runNGun = undefined; + self.reactingToBullet = undefined; + self.requestReactToBullet = undefined; + + self.currentDodgeAnim = undefined; + self.moveLoopOverrideFunc = undefined; +} + + +moveInit() +{ + self.reactingToBullet = undefined; + self.requestReactToBullet = undefined; + self.update_move_anim_type = undefined; + self.update_move_front_bias = undefined; + self.runNGunWeight = 0; + self.arrivalStartDist = undefined; +} + +getUpIfProne() +{ + if ( self.a.pose == "prone" ) + { + newPose = self animscripts\utility::choosePose( "stand" ); + + if ( newPose != "prone" ) + { + self orientMode( "face current" ); + self animMode( "zonly_physics", false ); + rate = 1; + if ( isdefined( self.grenade ) ) + rate = 2; + self animscripts\cover_prone::proneTo( newPose, rate ); + self animMode( "none", false ); + self orientMode( "face default" ); + } + } +} + +wasPreviouslyInCover() +{ + switch( self.prevScript ) + { + case "cover_crouch": + case "cover_left": + case "cover_prone": + case "cover_right": + case "cover_stand": + case "concealment_crouch": + case "concealment_prone": + case "concealment_stand": + case "cover_wide_left": + case "cover_wide_right": + case "hide": + case "turret": + return true; + } + + return false; +} + + +moveStartBattleChatter( wasInCover ) +{ + if ( self.moveMode == "run" ) + { + // SRS 10/30/08: removed a bunch of unnecessary logic here + self animscripts\battleChatter_ai::evaluateMoveEvent( wasInCover ); + } +} + +MoveMainLoop( doWalkCheck ) +{ + MoveMainLoopInternal( doWalkCheck ); + self notify( "abort_reload" ); // in case a reload was going and MoveMainLoopInternal hit an endon +} + +ChangeMoveMode( moveMode ) +{ + if ( moveMode != self.prevMoveMode ) + { + if ( isdefined( self.customMoveAnimSet ) && isdefined( self.customMoveAnimSet[ moveMode ] ) ) + { + self.a.moveAnimSet = self.customMoveAnimSet[ moveMode ]; + } + else + { + self.a.moveAnimSet = anim.animsets.move[ moveMode ]; + + if ( ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" ) && + ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) > squared( 100 ) ) ) + { + self.sideStepRate = 1; + animscripts\init_common::set_ambush_sidestep_anims(); + } + else + { + self.sideStepRate = 1.35; + } + } + + self.prevMoveMode = moveMode; + } +} + +MoveMainLoopInternal( doWalkCheck ) +{ + self endon( "killanimscript" ); + self endon( "move_interrupt" ); + + prevLoopTime = self getAnimTime( %walk_and_run_loops ); + self.a.runLoopCount = randomint( 10000 );// integer that is incremented each time we complete a run loop + + self.prevMoveMode = "none"; + + self.moveLoopCleanupFunc = undefined; + + // if initial destination is closer than 64 walk to it. + for ( ;; ) + { + loopTime = self getAnimTime( %walk_and_run_loops ); + if ( loopTime < prevLoopTime ) + self.a.runLoopCount++ ; + prevLoopTime = loopTime; + + ChangeMoveMode( self.moveMode ); + MoveMainLoopProcess( self.moveMode ); + + if ( isDefined( self.moveLoopCleanupFunc ) ) + { + self [[self.moveLoopCleanupFunc]](); + self.moveLoopCleanupFunc = undefined; + } + + self notify( "abort_reload" ); // in case a reload was going and MoveMainLoopProcess hit an endon + } +} + +MoveMainLoopProcess( moveMode ) +{ + self endon( "move_loop_restart" ); + + //prof_begin("MoveMainLoop"); + + self animscripts\face::SetIdleFaceDelayed( anim.alertface ); + + if ( isdefined( self.moveLoopOverrideFunc ) ) + { + self [[ self.moveLoopOverrideFunc ]](); + } + else if ( self shouldCQB() ) + { + self animscripts\cqb::MoveCQB(); + } + else + { + if ( moveMode == "run" ) + { + self animscripts\run::MoveRun(); + } + else + { + assert( moveMode == "walk" ); + self animscripts\walk::MoveWalk(); + } + } + + self.requestReactToBullet = undefined; + //prof_end("MoveMainLoop"); +} + + +MayShootWhileMoving() +{ + if ( self.weapon == "none" ) + return false; + + weapclass = weaponClass( self.weapon ); + if ( !usingRifleLikeWeapon() && !usingMinigun() ) + return false; + + if ( self isSniper() ) + { + if ( !( self isCQBWalking() ) && self.faceMotion ) + return false; + } + + if ( isdefined( self.dontShootWhileMoving ) ) + { + assert( self.dontShootWhileMoving );// true or undefined + return false; + } + + return true; +} + +shootWhileMoving() +{ + self endon( "killanimscript" ); + + // it's possible for this to be called by CQB while it's already running from run.gsc, + // even though run.gsc will kill it on the next frame. We can't let it run twice at once. + self notify( "doing_shootWhileMoving" ); + self endon( "doing_shootWhileMoving" ); + + self.a.array[ "fire" ] = %exposed_shoot_auto_v3; + + if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() ) + self.a.array[ "single" ] = array( %shotgun_stand_fire_1A, %shotgun_stand_fire_1B ); + else + self.a.array[ "single" ] = array( %exposed_shoot_semi1 ); + + self.a.array[ "burst2" ] = %exposed_shoot_burst3; + self.a.array[ "burst3" ] = %exposed_shoot_burst3; + self.a.array[ "burst4" ] = %exposed_shoot_burst4; + self.a.array[ "burst5" ] = %exposed_shoot_burst5; + self.a.array[ "burst6" ] = %exposed_shoot_burst6; + + self.a.array[ "semi2" ] = %exposed_shoot_semi2; + self.a.array[ "semi3" ] = %exposed_shoot_semi3; + self.a.array[ "semi4" ] = %exposed_shoot_semi4; + self.a.array[ "semi5" ] = %exposed_shoot_semi5; + + self.a.array[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 ); + + while ( 1 ) + { + if ( !self.bulletsInClip ) + { + if ( self isCQBWalkingOrFacingEnemy() ) + { + self.ammoCheatTime = 0; + cheatAmmoIfNecessary(); + } + + if ( !self.bulletsInClip ) + { + wait 0.5; + continue; + } + } + + self shootUntilShootBehaviorChange(); + // can't clear %exposed_modern because there are transition animations within it that we might play when going to prone + self clearAnim( %exposed_aiming, 0.2 ); + } +} + + +startThreadsToRunWhileMoving() +{ + self endon( "killanimscript" ); + + // wait a frame so MoveMainLoop can start. Otherwise one of the following threads could unsuccesfully try to interrupt movement before it starts + wait 0.05; + + self thread bulletWhizbyCheck_whileMoving(); + self thread meleeAttackCheck_whileMoving(); + self thread animscripts\door::inDoorCqbToggleCheck(); + self thread animscripts\door::doorEnterExitCheck(); +} + +stairsCheck() +{ + self endon( "killanimscript" ); + + self.prevStairsState = self.stairsState; + + while ( 1 ) + { + wait .05; + if ( self.prevStairsState != self.stairsState ) + { + // don't interrupt path change animation if getting off stairs to flat ground + if ( !isdefined( self.ignorePathChange ) || self.stairsState != "none" ) + self notify( "move_loop_restart" ); + } + + self.prevStairsState = self.stairsState; + } +} + + +restartMoveLoop( skipMoveTransition ) +{ + self endon( "killanimscript" ); + + if ( !skipMoveTransition ) + animscripts\cover_arrival::startMoveTransition(); + + self.ignorePathChange = undefined; + + self clearanim( %root, 0.1 ); + self OrientMode( "face default" ); + self animMode( "none", false ); + + self.requestArrivalNotify = true; + MoveMainLoop( !skipMoveTransition ); +} + + +pathChangeCheck() +{ + self endon( "killanimscript" ); + self endon( "move_interrupt" ); + + self.ignorePathChange = true; // this will be turned on / off in other threads at appropriate times + + while ( 1 ) + { + // no other thread should end on "path_changed" + self waittill( "path_changed", doingReacquire, newDir ); + + // no need to check for doingReacquire since faceMotion should be a good check + + assert( !isdefined( self.ignorePathChange ) || self.ignorePathChange ); // should be true or undefined + + if ( isdefined( self.ignorePathChange ) || isdefined( self.noTurnAnims ) ) + continue; + + if ( !self.faceMotion ) + continue; + + if ( self.a.movement != "run" && self.a.movement != "walk" ) + continue; + + if ( self.a.pose != "stand" ) + continue; + + self notify( "stop_move_anim_update" ); + self.update_move_anim_type = undefined; + + angleDiff = AngleClamp180( self.angles[ 1 ] - vectortoyaw( newDir ) ); + + turnAnim = pathChange_getTurnAnim( angleDiff ); + + if ( isdefined( turnAnim ) ) + { + self.turnAnim = turnAnim; + self.turnTime = getTime(); + self.moveLoopOverrideFunc = ::pathChange_doTurnAnim; + + self notify( "move_loop_restart" ); + self animscripts\run::endFaceEnemyAimTracking(); + } + } +} + +pathChange_getTurnAnim( angleDiff ) +{ + if ( isdefined( self.pathTurnAnimOverrideFunc ) ) + return [[ self.pathTurnAnimOverrideFunc ]]( angleDiff ); + + turnAnim = undefined; + secondTurnAnim = undefined; + + if ( self shouldCQB() || self.movemode == "walk" ) + { + if ( isdefined( self.customTurnAnimSet ) && isdefined( self.customTurnAnimSet[ "cqb" ] ) ) + { + animArray = self.customTurnAnimSet[ "cqb" ]; + } + else + { + animArray = anim.cqbTurnAnims; + } + } + else + { + if ( isdefined( self.customTurnAnimSet ) && isdefined( self.customTurnAnimSet[ "run" ] ) ) + { + animArray = self.customTurnAnimSet[ "run" ]; + } + else + { + animArray = anim.runTurnAnims; + } + } + + if ( angleDiff < -30 ) + { + if ( angleDiff > -60 ) // bias for 45 turns + { + turnAnim = animArray[ "L45" ]; + + // awkward pivot turn after doing animation + //if ( angleDiff < -45 ) + // secondTurnAnim = animArray[ "L90" ]; + } + else if ( angleDiff > -112.5 ) + { + turnAnim = animArray[ "L90" ]; + if ( angleDiff > -90 ) + secondTurnAnim = animArray[ "L45" ]; + else + secondTurnAnim = animArray[ "L135" ]; + } + else if ( angleDiff > -157.5 ) + { + turnAnim = animArray[ "L135" ]; + if ( angleDiff > -135 ) + secondTurnAnim = animArray[ "L90" ]; + else + secondTurnAnim = animArray[ "180" ]; + } + else + { + turnAnim = animArray[ "180" ]; + secondTurnAnim = animArray[ "L135" ]; + } + } + else if ( angleDiff > 30 ) + { + if ( angleDiff < 60 ) + { + turnAnim = animArray[ "R45" ]; + + // awkward pivot turn after doing animation + //if ( angleDiff > 45 ) + // secondTurnAnim = animArray[ "R90" ]; + } + else if ( angleDiff < 112.5 ) + { + turnAnim = animArray[ "R90" ]; + if ( angleDiff < 90 ) + secondTurnAnim = animArray[ "R45" ]; + else + secondTurnAnim = animArray[ "R135" ]; + } + else if ( angleDiff < 157.5 ) + { + turnAnim = animArray[ "R135" ]; + if ( angleDiff < 135 ) + secondTurnAnim = animArray[ "R90" ]; + else + secondTurnAnim = animArray[ "180" ]; + } + else + { + turnAnim = animArray[ "180" ]; + secondTurnAnim = animArray[ "R135" ]; + } + } + + if ( isdefined( turnAnim ) ) + { + if ( pathChange_canDoTurnAnim( turnAnim ) ) + return turnAnim; + } + + if ( isdefined( secondTurnAnim ) ) + { + if ( pathChange_canDoTurnAnim( secondTurnAnim ) ) + return secondTurnAnim; + } + + return undefined; +} + +pathChange_canDoTurnAnim( turnAnim ) +{ + if ( !isdefined( self.pathgoalpos ) ) + return false; + + codeMoveTimes = getNotetrackTimes( turnAnim, "code_move" ); + assert( codeMoveTimes.size == 1 ); + + codeMoveTime = codeMoveTimes[ 0 ]; + assert( codeMoveTime <= 1 ); + + moveDelta = getMoveDelta( turnAnim, 0, codeMoveTime ); + codeMovePoint = self localToWorldCoords( moveDelta ); + + /# + animscripts\utility::drawDebugLine( self.origin, codeMovePoint, ( 1, 1, 0 ), 20 ); + animscripts\utility::drawDebugLine( self.origin, self.pathgoalpos, ( 0, 1, 0 ), 20 ); + #/ + + //if ( distanceSquared( self.origin, codeMovePoint ) > distanceSquared( self.origin, self.pathgoalpos ) ) + if ( isdefined( self.arrivalStartDist ) && ( squared( self.arrivalStartDist ) > distanceSquared( self.pathgoalpos, codeMovePoint ) ) ) + return false; + + moveDelta = getMoveDelta( turnAnim, 0, 1 ); + endPoint = self localToWorldCoords( moveDelta ); + + endPoint = codeMovePoint + vectornormalize( endPoint - codeMovePoint ) * 20; + + /# animscripts\utility::drawDebugLine( codeMovePoint, endPoint, ( 1, 1, 0 ), 20 ); #/ + + return self mayMoveFromPointToPoint( codeMovePoint, endPoint, true, true ); +} + +pathChange_doTurnAnim() +{ + self endon( "killanimscript" ); + + self.moveLoopOverrideFunc = undefined; + + turnAnim = self.turnAnim; + + if ( gettime() > self.turnTime + 50 ) + return; // too late + + self animMode( "zonly_physics", false ); + + self.moveLoopCleanupFunc = ::pathChange_cleanupTurnAnim; + + self.ignorePathChange = true; + + // Figure out blend in time + blendTime = 0.05; + if ( isdefined( self.pathTurnAnimBlendTime ) ) + { + blendTime = isdefined( self.pathTurnAnimBlendTime ); + } + if( self IsCQBWalking() && animHasNotetrack( turnAnim, "cqb_blend_finish" ) ) + { + blend_finish_time = GetNotetrackTimes( turnAnim, "cqb_blend_finish" ); + blendTime = blend_finish_time[0] * GetAnimLength( turnAnim ); + } + else if ( !self IsCQBWalking() && animHasNotetrack( turnAnim, "blend_finish" ) ) + { + blend_finish_time = GetNotetrackTimes( turnAnim, "blend_finish" ); + blendTime = blend_finish_time[0] * GetAnimLength( turnAnim ); + } + + // Figure out and store blend out time (handled in various move scripts) + if ( animHasNotetrack( turnanim, "blend_out_finish" ) ) + { + finish_time = GetNoteTrackTimes( turnanim, "finish" ); + blend_finish_time = GetNotetrackTimes( turnanim, "blend_out_finish" ); + + /# + if ( blend_finish_time[0] < finish_time[0] ) + { + AssertMsg( "blend_finish tag must occur later than finish tag." ); + } + #/ + + assert( blend_finish_time[0] > finish_time[0] ); + if ( self IsCQBWalking() ) + { + self.run_blend_finish_time = undefined; + self.cqb_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( turnanim ); + } + else + { + self.cqb_blend_finish_time = undefined; + self.run_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( turnanim ); + } + } + + handle_move_transition_notes( turnAnim ); + + self clearanim( %body, blendTime ); + self setflaggedanimrestart( "turnAnim", turnAnim, 1, blendTime, self.movePlaybackRate ); + self OrientMode( "face current" ); + + assert( animHasNotetrack( turnAnim, "code_move" ) ); + self animscripts\shared::DoNoteTracks( "turnAnim" ); // until "code_move" + + self.ignorePathChange = undefined; + self OrientMode( "face motion" ); // want to face motion, don't do l / r / b anims + self animmode( "none", false ); + + //assert( animHasNotetrack( turnAnim, "finish" ) ); + self animscripts\shared::DoNoteTracks( "turnAnim" ); +} + +pathChange_doMoveTransition() +{ + self.moveLoopOverrideFunc = undefined; + if ( gettime() > self.turnTime + 50 ) + return; // too late + + self.moveLoopCleanupFunc = ::pathChange_cleanupTurnAnim; + + animscripts\cover_arrival::startMoveTransition(); +} + +pathChange_cleanupTurnAnim() +{ + blend_out_time = 0.1; + + if ( shouldCQB() ) + { + blend_out_time = self animscripts\cqb::get_cqb_blend_in_time(); + } + + self.ignorePathChange = undefined; + + self OrientMode( "face default" ); + self clearanim( %root, blend_out_time ); + self animMode( "none", false ); +} + +dodgeMoveLoopOverride() +{ + self pushplayer( true ); + self animMode( "zonly_physics", false ); + self clearanim( %body, 0.2 ); + + self setflaggedanimrestart( "dodgeAnim", self.currentDodgeAnim, 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "dodgeAnim" ); + + self animmode( "none", false ); + self orientMode( "face default" ); + + if ( animHasNotetrack( self.currentDodgeAnim, "code_move" ) ) + self animscripts\shared::DoNoteTracks( "dodgeAnim" ); // return on code_move + + self clearanim( %civilian_dodge, 0.2 ); + + self pushplayer( false ); + self.currentDodgeAnim = undefined; + self.moveLoopOverrideFunc = undefined; + return true; +} + + +tryDodgeWithAnim( dodgeAnim, dodgeAnimDelta ) +{ + rightDir = ( self.lookAheadDir[1], -1 * self.lookAheadDir[0], 0 ); + + forward = self.lookAheadDir * dodgeAnimDelta[0]; + right = rightDir * dodgeAnimDelta[1]; + + dodgePos = self.origin + forward - right; + + self pushPlayer( true ); + if ( self mayMoveToPoint( dodgePos ) ) + { + self.currentDodgeAnim = dodgeAnim; + self.moveLoopOverrideFunc = ::dodgeMoveLoopOverride; + self notify( "move_loop_restart" ); + + /# + if ( getdvar( "scr_debugdodge" ) == "1" ) + thread debugline( self.origin, dodgePos, ( 0, 1, 0 ), 3 ); + #/ + + return true; + } + + /# + if ( getdvar( "scr_debugdodge" ) == "1" ) + thread debugline( self.origin, dodgePos, ( 0.5, 0.5, 0 ), 3 ); + #/ + + self pushPlayer( false ); + return false; +} + +animDodgeObstacle() +{ + if ( !isdefined( self.dodgeLeftAnim ) || !isdefined( self.dodgeRightAnim ) ) + return; + + self endon( "killanimscript" ); + self endon( "move_interrupt" ); + + while ( 1 ) + { + // no other thread should end on "path_changed" + self waittill( "path_need_dodge", dodgeEnt, dodgeEntPos ); + + if ( self animscripts\utility::IsInCombat() ) + { + self.noDodgeMove = false; + return; + } + + if ( !isSentient( dodgeEnt ) ) + continue; + + /# + if ( getdvar( "scr_debugdodge" ) == "1" ) + { + thread debugline( dodgeEnt.origin + (0, 0, 10), dodgeEntPos, ( 1, 1, 0 ), 3 ); + thread debugline( self.origin, dodgeEntPos, ( 1, 0, 0 ), 3 ); + } + #/ + + dirToDodgeEnt = vectorNormalize( dodgeEntPos - self.origin ); + + if ( ( self.lookAheadDir[0] * dirToDodgeEnt[1] ) - ( dirToDodgeEnt[0] * self.lookAheadDir[1] ) > 0 ) + { + // right first + if ( !tryDodgeWithAnim( self.dodgeRightAnim, self.dodgeRightAnimOffset ) ) + tryDodgeWithAnim( self.dodgeLeftAnim, self.dodgeLeftAnimOffset ); + } + else + { + // left first + if ( !tryDodgeWithAnim( self.dodgeLeftAnim, self.dodgeLeftAnimOffset ) ) + tryDodgeWithAnim( self.dodgeRightAnim, self.dodgeRightAnimOffset ); + } + + if ( isdefined( self.currentDodgeAnim ) ) + wait getanimlength( self.currentDodgeAnim ); + else + wait 0.1; + } +} + +setDodgeAnims( leftAnim, rightAnim ) +{ + self.noDodgeMove = true; // don't let code path around obstacle to dodge + //self pushplayer( true ); + + self.dodgeLeftAnim = leftAnim; + self.dodgeRightAnim = rightAnim; + + time = 1; + if ( animHasNoteTrack( leftAnim, "code_move" ) ) + time = getNotetrackTimes( leftAnim, "code_move" )[0]; + + self.dodgeLeftAnimOffset = getMoveDelta( leftAnim, 0, time ); + + time = 1; + if ( animHasNoteTrack( rightAnim, "code_move" ) ) + time = getNotetrackTimes( rightAnim, "code_move" )[0]; + + self.dodgeRightAnimOffset = getMoveDelta( rightAnim, 0, time ); + + self.interval = 80; // good value for civilian dodge animations +} + +clearDodgeAnims() +{ + self.noDodgeMove = false; + self.dodgeLeftAnim = undefined; + self.dodgeRightAnim = undefined; + self.dodgeLeftAnimOffset = undefined; + self.dodgeRightAnimOffset = undefined; +} + +meleeAttackCheck_whileMoving() +{ + self endon( "killanimscript" ); + + while ( 1 ) + { + // Try to melee our enemy if it's another AI + if ( isDefined( self.enemy ) && ( isAI( self.enemy ) || isdefined( self.meleePlayerWhileMoving ) ) ) + { + if ( abs( self GetMotionAngle() ) <= 135 ) // only when moving forward or sideways + animscripts\melee::Melee_TryExecuting(); + } + + wait 0.1; + } +} + +bulletWhizbyCheck_whileMoving() +{ + self endon( "killanimscript" ); + + if ( isdefined( self.disableBulletWhizbyReaction ) ) + return; + + while ( 1 ) + { + self waittill( "bulletwhizby", shooter ); + + if ( self.moveMode != "run" || !self.faceMotion || self.a.pose != "stand" || isdefined( self.reactingToBullet ) ) + continue; + + if ( self.stairsState != "none" ) + continue; + + if ( !isdefined( self.enemy ) && !self.ignoreAll && isDefined( shooter.team ) && isEnemyTeam( self.team, shooter.team ) ) + { + self.whizbyEnemy = shooter; + self animcustom( animscripts\reactions::bulletWhizbyReaction ); // this will end move script + continue; + } + + if ( self.lookaheadHitsStairs || self.lookaheadDist < 100 ) + continue; + + if ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) < 10000 ) + { + wait 0.2; + continue; + } + + if( !IsDefined( self.noReactToBullets ) || self.noReactToBullets == false ) + { + self.requestReactToBullet = gettime(); + } + + self notify( "move_loop_restart" ); + self animscripts\run::endFaceEnemyAimTracking(); + } +} + + +get_shuffle_to_corner_start_anim( shuffleLeft, startNode ) +{ + if ( startNode.type == "Cover Left" ) + { + assert( !shuffleLeft ); + return %CornerCrL_alert_2_shuffle; + } + else if ( startNode.type == "Cover Right" ) + { + assert( shuffleLeft ); + return %CornerCrR_alert_2_shuffle; + } + else + { + if ( shuffleLeft ) + return %covercrouch_hide_2_shuffleL; + else + return %covercrouch_hide_2_shuffleR; + } +} + + +setup_shuffle_anim_array( shuffleLeft, startNode, endNode ) +{ + anim_array = []; + + assert( isdefined( startNode ) ); + assert( isdefined( endNode ) ); + + if ( endNode.type == "Cover Left" ) + { + assert( shuffleLeft ); + anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); + anim_array[ "shuffle" ] = %covercrouch_shuffleL; + anim_array[ "shuffle_end" ] = %CornerCrL_shuffle_2_alert; + } + else if ( endNode.type == "Cover Right" ) + { + assert( !shuffleLeft ); + anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); + anim_array[ "shuffle" ] = %covercrouch_shuffleR; + anim_array[ "shuffle_end" ] = %CornerCrR_shuffle_2_alert; + } + else if ( endNode.type == "Cover Stand" && startNode.type == endNode.type ) + { + if ( shuffleLeft ) + { + anim_array[ "shuffle_start" ] = %coverstand_hide_2_shuffleL; + anim_array[ "shuffle" ] = %coverstand_shuffleL; + anim_array[ "shuffle_end" ] = %coverstand_shuffleL_2_hide; + } + else + { + anim_array[ "shuffle_start" ] = %coverstand_hide_2_shuffleR; + anim_array[ "shuffle" ] = %coverstand_shuffleR; + anim_array[ "shuffle_end" ] = %coverstand_shuffleR_2_hide; + } + } + else + { + //assert( endNode.type == "Cover Crouch" || endNode.type == "Cover Crouch Window" ); + if ( shuffleLeft ) + { + anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); + anim_array[ "shuffle" ] = %covercrouch_shuffleL; + + if ( endNode.type == "Cover Stand" ) + anim_array[ "shuffle_end" ] = %coverstand_shuffleL_2_hide; + else + anim_array[ "shuffle_end" ] = %covercrouch_shuffleL_2_hide; + } + else + { + anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); + anim_array[ "shuffle" ] = %covercrouch_shuffleR; + + if ( endNode.type == "Cover Stand" ) + anim_array[ "shuffle_end" ] = %coverstand_shuffleR_2_hide; + else + anim_array[ "shuffle_end" ] = %covercrouch_shuffleR_2_hide; + } + } + + self.a.array = anim_array; +} + +moveCoverToCover_checkStartPose( startNode, endNode ) +{ + if ( self.a.pose == "stand" && ( endNode.type != "Cover Stand" || startNode.type != "Cover Stand" ) ) + { + self.a.pose = "crouch"; + return false; + } + + return true; +} + +moveCoverToCover_checkEndPose( endNode ) +{ + if ( self.a.pose == "crouch" && endNode.type == "Cover Stand" ) + { + self.a.pose = "stand"; + return false; + } + + return true; +} + + +serverFPS = 20; +serverSPF = 0.05; + +moveCoverToCover() +{ + self endon( "killanimscript" ); + self endon( "goal_changed" ); + + shuffleNode = self.shuffleNode; + + self.shuffleMove = undefined; + self.shuffleNode = undefined; + self.shuffleMoveInterrupted = true; + + if ( !isdefined( self.prevNode ) ) + return; + + if ( !isdefined( self.node ) || !isdefined( shuffleNode ) || self.node != shuffleNode ) + return; + + shuffleNodeType = self.prevNode; + + node = self.node; + + moveDir = node.origin - self.origin; + if ( lengthSquared( moveDir ) < 1 ) + return; + + moveDir = vectornormalize( moveDir ); + forward = anglestoforward( node.angles ); + + shuffleLeft = ( ( forward[ 0 ] * moveDir[ 1 ] ) - ( forward[ 1 ] * moveDir[ 0 ] ) ) > 0; + + if ( moveDoorSideToSide( shuffleLeft, shuffleNodeType, node ) ) + return; + + if ( moveCoverToCover_checkStartPose( shuffleNodeType, node ) ) + blendTime = 0.1; + else + blendTime = 0.4; + + setup_shuffle_anim_array( shuffleLeft, shuffleNodeType, node ); + + self animMode( "zonly_physics", false ); + + self clearanim( %body, blendTime ); + + startAnim = animarray( "shuffle_start" ); + shuffleAnim = animarray( "shuffle" ); + endAnim = animarray( "shuffle_end" ); + + //assertEx( animhasnotetrack( startAnim, "finish" ), "animation doesn't have finish notetrack " + startAnim ); + if ( animhasnotetrack( startAnim, "finish" ) ) + startEndTime = getNotetrackTimes( startAnim, "finish" )[ 0 ]; + else + startEndTime = 1; + + startDist = length( getMoveDelta( startAnim, 0, startEndTime ) ); + shuffleDist = length( getMoveDelta( shuffleAnim, 0, 1 ) ); + endDist = length( getMoveDelta( endAnim, 0, 1 ) ); + + remainingDist = distance( self.origin, node.origin ); + + if ( remainingDist > startDist ) + { + self OrientMode( "face angle", getNodeForwardYaw( shuffleNodeType ) ); + + self setflaggedanimrestart( "shuffle_start", startAnim, 1, blendTime ); + self animscripts\shared::DoNoteTracks( "shuffle_start" ); + self clearAnim( startAnim, 0.2 ); + remainingDist -= startDist; + + blendTime = 0.2; // reset blend for looping move + } + else + { + self OrientMode( "face angle", node.angles[1] ); + } + + playEnd = false; + if ( remainingDist > endDist ) + { + playEnd = true; + remainingDist -= endDist; + } + + loopTime = getAnimLength( shuffleAnim ); + playTime = loopTime * ( remainingDist / shuffleDist ) * 0.9; + playTime = floor( playTime * serverFPS ) * serverSPF; + + self setflaggedanim( "shuffle", shuffleAnim, 1, blendTime ); + self animscripts\shared::DoNoteTracksForTime( playTime, "shuffle" ); + + // account for loopTime not being exact since loop animation delta isn't uniform over time + for ( i = 0; i < 2; i++ ) + { + remainingDist = distance( self.origin, node.origin ); + if ( playEnd ) + remainingDist -= endDist; + + if ( remainingDist < 4 ) + break; + + playTime = loopTime * ( remainingDist / shuffleDist ) * 0.9; // don't overshoot + playTime = floor( playTime * serverFPS ) * serverSPF; + + if ( playTime < 0.05 ) + break; + + self animscripts\shared::DoNoteTracksForTime( playTime, "shuffle" ); + } + + if ( playEnd ) + { + if ( moveCoverToCover_checkEndPose( node ) ) + blendTime = 0.2; + else + blendTime = 0.4; + + self clearAnim( shuffleAnim, blendTime ); + self setflaggedanim( "shuffle_end", endAnim, 1, blendTime ); + self animscripts\shared::DoNoteTracks( "shuffle_end" ); + + // clear animation in moveCoverToCoverFinish if needed + } + + self safeTeleport( node.origin ); + self animMode( "normal" ); + + self.shuffleMoveInterrupted = undefined; +} + + +moveCoverToCoverFinish() +{ + if ( isdefined( self.shuffleMoveInterrupted ) ) + { + self clearanim( %cover_shuffle, 0.2 ); + + self.shuffleMoveInterrupted = undefined; + self animmode( "none", false ); + self orientmode( "face default" ); + } + else + { + wait 0.2; // don't clear animation, wait for cover script to take over + + self clearanim( %cover_shuffle, 0.2 ); + } +} + +moveDoorSideToSide( shuffleLeft, startNode, endNode ) +{ + sideToSideAnim = undefined; + + if ( startNode.type == "Cover Right" && endNode.type == "Cover Left" && !shuffleLeft ) + sideToSideAnim = %corner_standR_Door_R2L; + else if ( startNode.type == "Cover Left" && endNode.type == "Cover Right" && shuffleLeft ) + sideToSideAnim = %corner_standL_Door_L2R; + + if ( !isdefined( sideToSideAnim ) ) + return false; + + self animMode( "zonly_physics", false ); + self orientmode( "face current" ); + + self setflaggedanimrestart( "sideToSide", sideToSideAnim, 1, 0.2 ); + + assert( animHasNoteTrack( sideToSideAnim, "slide_start" ) ); + assert( animHasNoteTrack( sideToSideAnim, "slide_end" ) ); + + self animscripts\shared::DoNoteTracks( "sideToSide", ::handleSideToSideNotetracks ); + + slideStartTime = self getAnimTime( sideToSideAnim ); + slideDir = endNode.origin - startNode.origin; + slideDir = vectornormalize( ( slideDir[0], slideDir[1], 0 ) ); + + animDelta = getMoveDelta( sideToSideAnim, slideStartTime, 1 ); + remainingVec = endNode.origin - self.origin; + remainingVec = ( remainingVec[0], remainingVec[1], 0 ); + slideDist = vectordot( remainingVec, slideDir ) - abs( animDelta[1] ); + + if ( slideDist > 2 ) + { + slideEndTime = getNoteTrackTimes( sideToSideAnim, "slide_end" )[0]; + slideTime = ( slideEndTime - slideStartTime ) * getAnimLength( sideToSideAnim ); + assert( slideTime > 0 ); + + slideFrames = int( ceil( slideTime / 0.05 ) ); + slideIncrement = slideDir * slideDist / slideFrames; + self thread slideForTime( slideIncrement, slideFrames ); + } + + self animscripts\shared::DoNoteTracks( "sideToSide" ); + + self safeTeleport( endNode.origin ); + self animMode( "none" ); + self orientmode( "face default" ); + + self.shuffleMoveInterrupted = undefined; + wait 0.2; + + return true; +} + +handleSideToSideNotetracks( note ) +{ + if ( note == "slide_start" ) + return true; +} + +slideForTime( slideIncrement, slideFrames ) +{ + self endon( "killanimscript" ); + self endon( "goal_changed" ); + + while ( slideFrames > 0 ) + { + self safeTeleport( self.origin + slideIncrement ); + slideFrames--; + wait 0.05; + } +} + +MoveStandMoveOverride( override_anim, weights ) +{ + self endon( "movemode" ); + self clearanim( %combatrun, 0.6 ); + self setanimknoball( %combatrun, %body, 1, 0.5, self.moveplaybackrate ); + + if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && isdefined( self.run_overrideBulletReact ) && randomFloat( 1 ) < self.a.reactToBulletChance ) + { + animscripts\run::CustomRunningReactToBullets(); + return; + } + + if ( isarray( override_anim ) ) + { + if ( isdefined( self.run_override_weights ) ) + moveAnim = choose_from_weighted_array( override_anim, weights ); + else + moveAnim = override_anim[ randomint( override_anim.size ) ]; + } + else + { + moveAnim = override_anim; + } + + self setflaggedanimknob( "moveanim", moveAnim, 1, 0.2 ); + animscripts\shared::DoNoteTracks( "moveanim" ); +} \ No newline at end of file diff --git a/animscripts/pain.gsc b/animscripts/pain.gsc new file mode 100644 index 0000000..2765909 --- /dev/null +++ b/animscripts/pain.gsc @@ -0,0 +1,1614 @@ +#include animscripts\Utility; +#include animscripts\weaponList; +#include common_scripts\utility; +#include animscripts\Combat_Utility; +#using_animtree( "generic_human" ); + +main() +{ + if ( isdefined( self.longDeathStarting ) ) + { + // important that we don't run any other animscripts. + self waittill( "killanimscript" ); + return; + } + + if ( [[ anim.pain_test ]]() ) + return; + if ( self.a.disablePain ) + return; + if ( isdefined( self.pain_test_func ) && ![[ self.pain_test_func ]]() ) + return; + + self notify( "kill_long_death" ); + + if ( isdefined( self.a.painTime ) ) + self.a.lastPainTime = self.a.painTime; + else + self.a.lastPainTime = 0; + + self.a.painTime = gettime(); + if ( self.stairsState != "none" ) + self.a.painOnStairs = true; + else + self.a.painOnStairs = undefined; + + if ( self.a.nextStandingHitDying ) + self.health = 1; + + dead = false; + stumble = false; + + ratio = self.health / self.maxHealth; + +// println ("hit at " + self.damagelocation); + + self notify( "anim entered pain" ); + self endon( "killanimscript" ); + + // Two pain animations are played. One is a longer, detailed animation with little to do with the actual + // location and direction of the shot, but depends on what pose the character starts in. The other is a + // "hit" animation that is very location-specific, but is just a single pose for the affected bones so it + // can be played easily whichever position the character is in. + animscripts\utility::initialize( "pain" ); + + self animmode( "gravity" ); + + //thread [[anim.println]] ("Shot in "+self.damageLocation+" from "+self.damageYaw+" for "+self.damageTaken+" hit points");#/ + + if ( !isdefined( self.no_pain_sound ) ) + self animscripts\face::SayGenericDialogue( "pain" ); + + if ( self.damageLocation == "helmet" ) + self animscripts\death::helmetPop( false ); + else if ( self wasDamagedByExplosive() && randomint( 2 ) == 0 ) + self animscripts\death::helmetPop( false ); + + if ( isdefined( self.painFunction ) ) + { + self [[ self.painFunction ]](); + return; + } + + // corner grenade death takes priority over crawling pain + /# + if ( getDvarInt( "scr_forceCornerGrenadeDeath" ) == 1 ) + { + if ( self TryCornerRightGrenadeDeath() ) + return; + } + #/ + if ( self is_taser_pain() ) + { + self.a.special = "taser"; + } + + if ( crawlingPain() ) + return; + + if ( specialPain( self.a.special ) ) + return; + + // if we didn't handle self.a.special, we can't rely on it being accurate after the pain animation we're about to play. + //self.a.special = "none"; + //self.specialDeathFunc = undefined; + + painAnim = getPainAnim(); + + /# + if ( getdvarint( "scr_paindebug" ) == 1 ) + println( "^2Playing pain: ", painAnim, " ; pose is ", self.a.pose ); + #/ + + playPainAnim( painAnim ); +} + +initPainFx() +{ + level._effect[ "crawling_death_blood_smear" ] = LoadFX( "impacts/blood_smear_decal" ); +} + +end_script() +{ + if ( isdefined( self.damageShieldPain ) ) + { + self.damageShieldCounter = undefined; + self.damageShieldPain = undefined; + self.allowpain = true; + + // still somewhat risky + if ( !isdefined( self.preDamageShieldIgnoreMe ) ) + self.ignoreme = false; + + self.preDamageShieldIgnoreMe = undefined; + } + + if ( isdefined( self.blockingPain ) ) + { + self.blockingPain = undefined; + self.allowPain = true; + } +} + +is_taser_pain() +{ + return ( self.damageWeapon == "nx_taser" ); +} + +wasDamagedByExplosive() +{ + if ( isExplosiveDamageMOD( self.damageMod ) ) + return true; + + if ( gettime() - anim.lastCarExplosionTime <= 50 ) + { + rangesq = anim.lastCarExplosionRange * anim.lastCarExplosionRange * 1.2 * 1.2; + if ( distanceSquared( self.origin, anim.lastCarExplosionDamageLocation ) < rangesq ) + { + // assume this exploding car damaged us. + upwardsDeathRangeSq = rangesq * 0.5 * 0.5; + self.mayDoUpwardsDeath = ( distanceSquared( self.origin, anim.lastCarExplosionLocation ) < upwardsDeathRangeSq ); + return true; + } + } + + return false; +} + + +maxDamageShieldPainInterval = 1500; + +getDamageShieldPainAnim() +{ + if ( self.a.pose == "prone" ) + return; + + if ( isdefined( self.lastAttacker ) && isdefined( self.lastAttacker.team ) && self.lastAttacker.team == self.team ) + return; + + if ( !isdefined( self.damageShieldCounter ) || ( gettime() - self.a.lastPainTime ) > maxDamageShieldPainInterval ) + self.damageShieldCounter = randomintrange( 2, 3 ); + + if ( isdefined( self.lastAttacker ) && distanceSquared( self.origin, self.lastAttacker.origin ) < squared( 512 ) ) + self.damageShieldCounter = 0; + + if ( self.damageShieldCounter > 0 ) + { + self.damageShieldCounter--; + return; + } + + self.damageShieldPain = true; + self.allowpain = false; + + if ( self.ignoreme ) + self.preDamageShieldIgnoreMe = true; + else + self.ignoreme = true; + + if ( usingSidearm() ) + animscripts\shared::placeWeaponOn( self.primaryweapon, "right" ); + + if ( self.a.pose == "crouch" ) + return %exposed_crouch_extendedpainA; + + painArray = getPainAnimByName( "shield" ); + return painArray[ randomint( painArray.size ) ]; +} + +getPainAnimByName( name ) +{ + assert( IsDefined( name ) ); + assert( IsDefined( anim.animsets.painAnimSet ) ); + + if( IsDefined( self.customPainAnimSet ) && IsDefined( self.customPainAnimSet[name] ) ) + { + return self.customPainAnimSet[name]; + } + else + { + return anim.animsets.painAnimSet[name]; + } +} + +MAX_RUNNING_PAIN_DIST_SQ = ( 64 * 64 ); + +getPainAnim() +{ + if ( self.damageShield && !isdefined( self.disableDamageShieldPain ) ) + { + painAnim = getDamageShieldPainAnim(); + if ( isdefined( painAnim ) ) + return painAnim; + } + + if ( isdefined( self.a.onback ) ) + { + if ( self.a.pose == "crouch" ) + return %back_pain; + else + animscripts\shared::stopOnBack(); + } + + if ( self.a.pose == "stand" ) + { + closeToNode = isdefined( self.node ) && ( distanceSquared( self.origin, self.node.origin ) < MAX_RUNNING_PAIN_DIST_SQ ); + + if ( !closeToNode && self.a.movement == "run" && ( abs( self getMotionAngle() ) < 60 ) ) + return getRunningForwardPainAnim(); + + self.a.movement = "stop"; + return getStandPainAnim(); + } + else if ( self.a.pose == "crouch" ) + { + self.a.movement = "stop"; + return getCrouchPainAnim(); + } + else if ( self.a.pose == "prone" ) + { + self.a.movement = "stop"; + return getPronePainAnim(); + } +} + +RUN_PAIN_SHORT = 120; // actual animations are 150 but let it run against the wall a bit. +RUN_PAIN_MED = 200; +RUN_PAIN_LONG = 300; + +//******************************************************************* +// * +// * +//******************************************************************* + + +getRunningForwardPainAnim() +{ + // 200 units + runPains = []; + + allowMedPain = false; + allowLongPain = false; + allowShortPain = false; + + if ( self mayMoveToPoint( self localToWorldCoords( ( RUN_PAIN_LONG, 0, 0 ) ) ) ) + { + allowLongPain = true; + allowMedPain = true; + } + else if ( self mayMoveToPoint( self localToWorldCoords( ( RUN_PAIN_MED, 0, 0 ) ) ) ) + { + allowMedPain = true; + } + + if ( allowLongPain ) + { + runPains = array_combine( runPains, getPainAnimByName( "long_run" ) ); + } + + // randomize medium with long pains + if ( allowMedPain ) + { + runPains = array_combine( runPains, getPainAnimByName( "med_run" ) ); + } + // short pains are a back up only + else if ( self mayMoveToPoint( self localToWorldCoords( ( RUN_PAIN_SHORT, 0, 0 ) ) ) ) + { + // drop check + runPains = array_combine( runPains, getPainAnimByName( "short_run" ) ); + } + + if ( !runPains.size ) + { + self.a.movement = "stop"; + return getStandPainAnim(); + } + + return runPains[ randomint( runPains.size ) ]; +} + +getStandPistolPainAnim() +{ + painArray = []; + + if ( self damageLocationIsAny( "torso_upper", "torso_lower", "left_arm_upper", "right_arm_upper", "neck" ) ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_torso" ); + if ( self damageLocationIsAny( "torso_lower", "left_leg_upper", "right_leg_upper" ) ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_legs" ); + if ( self damageLocationIsAny( "head", "neck" ) ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_head" ); + if ( self damageLocationIsAny( "left_arm_lower", "left_arm_upper", "torso_upper" ) ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_left_arm" ); + if ( self damageLocationIsAny( "right_arm_lower", "right_arm_upper", "torso_upper" ) ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_right_arm" ); + + if ( painArray.size < 2 ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_torso" ); + if ( painArray.size < 2 ) + painArray[ painArray.size ] = getPainAnimByName( "pistol_legs" ); + + assertex( painArray.size > 0, painArray.size ); + return painArray[ randomint( painArray.size ) ]; +} + + +getStandPainAnim() +{ + if ( usingSideArm() ) + return getStandPistolPainAnim(); + + painArray = []; + extendedPainArray = []; + + if ( self damageLocationIsAny( "torso_upper", "torso_lower" ) ) + { + extendedPainArray = array_combine( extendedPainArray, getPainAnimByName( "stand_torso_extended" ) ); + } + + if ( self damageLocationIsAny( "torso_upper", "head", "helmet", "neck" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "stand_head" ) ); + extendedPainArray = array_combine( extendedPainArray, getPainAnimByName( "stand_head_extended" ) ); + } + + if ( self damageLocationIsAny( "right_arm_upper", "right_arm_lower" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "stand_right_arm" ) ); + } + if ( self damageLocationIsAny( "left_arm_lower", "left_arm_upper" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "stand_left_arm" ) ); + extendedPainArray = array_combine( extendedPainArray, getPainAnimByName( "stand_left_arm_extended" ) ); + } + + if ( self damageLocationIsAny( "torso_lower", "left_leg_upper", "right_leg_upper" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "stand_legs" ) ); + extendedPainArray = array_combine( extendedPainArray, getPainAnimByName( "stand_legs_extended" ) ); + } + + if ( self damageLocationIsAny( "left_foot", "right_foot", "left_leg_lower", "right_leg_lower" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "stand_feet" ) ); + extendedPainArray = array_combine( extendedPainArray, getPainAnimByName( "stand_feet_extended" ) ); + } + + // default, only exposed pain that takes the AI to ground. Other ones look a bit awkward for getting hit by a bullet + if ( painArray.size < 2 ) + { + if ( !self.a.disableLongDeath ) + { + painArray = array_combine( painArray, getPainAnimByName( "stand_generic_long_death" ) ); + } + else + { + painArray = array_combine( painArray, getPainAnimByName( "stand_generic" ) ); + } + } + + if ( extendedPainArray.size < 2 ) + { + extendedPainArray = array_combine( extendedPainArray, getPainAnimByName( "stand_generic_extended" ) ); + } + + if ( !self.damageShield && !self.a.disableLongDeath ) + { + index = randomint( painArray.size + extendedPainArray.size ); + if ( index < painArray.size ) + return painArray[index]; + else + return extendedPainArray[ index - painArray.size ]; + } + + assertex( painArray.size > 0, painArray.size ); + return painArray[ randomint( painArray.size ) ]; +} + + +removeBlockedAnims( array ) +{ + newArray = []; + for ( index = 0; index < array.size; index++ ) + { + painAnim = array[ index ]; + time = 1; + if ( animHasNoteTrack( painAnim, "code_move" ) ) + time = getNotetrackTimes( painAnim, "code_move" )[0]; + + localDeltaVector = getMoveDelta( painAnim, 0, time ); + endPoint = self localToWorldCoords( localDeltaVector ); + + if ( self mayMoveToPoint( endPoint, true, true ) ) + newArray[ newArray.size ] = painAnim; + } + return newArray; +} + +getCrouchPainAnim() +{ + painArray = []; + + if ( !self.damageShield && !self.a.disableLongDeath ) + { + painArray = array_combine( painArray, getPainAnimByName( "crouch_generic" ) ); + } + + painArray = array_combine( painArray, getPainAnimByName( "crouch_exposed" ) ); + + if ( damageLocationIsAny( "left_hand", "left_arm_lower", "left_arm_upper" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "crouch_left_arm" ) ); + } + if ( damageLocationIsAny( "right_hand", "right_arm_lower", "right_arm_upper" ) ) + { + painArray = array_combine( painArray, getPainAnimByName( "crouch_right_arm" ) ); + } + + assertex( painArray.size > 0, painArray.size ); + return painArray[ randomint( painArray.size ) ]; +} + +getPronePainAnim() +{ + painArray = getPainAnimByName( "prone" ); + return painArray[ randomint( painArray.size ) ]; +} + + +playPainAnim( painAnim ) +{ + // TEMP make all pain faster + // rate = 1.5; + rate = self.painPlaybackRate; + + self setFlaggedAnimKnobAllRestart( "painanim", painAnim, %body, 1, .1, rate ); + + if ( self.a.pose == "prone" ) + self UpdateProne( %prone_legs_up, %prone_legs_down, 1, 0.1, 1 ); + + if ( animHasNotetrack( painAnim, "start_aim" ) ) + { + self thread notifyStartAim( "painanim" ); + self endon( "start_aim" ); + } + + if ( animHasNotetrack( painAnim, "code_move" ) ) + self animscripts\shared::DoNoteTracks( "painanim" ); + + self animscripts\shared::DoNoteTracks( "painanim" ); +} + +notifyStartAim( animFlag ) +{ + self endon( "killanimscript" ); + self waittillmatch( animFlag, "start_aim" ); + self notify( "start_aim" ); +} + + +specialPainBlocker() +{ + self endon( "killanimscript" ); + + assert( self.allowPain ); + + self.blockingPain = true; + self.allowPain = false; + + wait 0.5; + + self.blockingPain = undefined; + self.allowPain = true; +} + +// Special pain is for corners, rambo behavior, mg42's, anything out of the ordinary stand, crouch and prone. +// It returns true if it handles the pain for the special animation state, or false if it wants the regular +// pain function to handle it. +specialPain( anim_special ) +{ + if ( anim_special == "none" ) + return false; + + self.a.special = "none"; + + self thread specialPainBlocker(); + + switch( anim_special ) + { + case "cover_left": + if ( self.a.pose == "stand" ) + { + DoPainFromArray( getPainAnimByName( "cover_left_stand" ) ); + handled = true; + } + else if ( self.a.pose == "crouch" ) + { + DoPainFromArray( getPainAnimByName( "cover_left_crouch" ) ); + handled = true; + } + else + { + handled = false; + } + break; + case "cover_right": + if ( self.a.pose == "stand" ) + { + DoPainFromArray( getPainAnimByName( "cover_right_stand" ) ); + handled = true; + } + else if ( self.a.pose == "crouch" ) + { + DoPainFromArray( getPainAnimByName( "cover_right_crouch" ) ); + handled = true; + } + else + { + handled = false; + } + break; + + case "cover_right_stand_A": + handled = false; + break; + + case "cover_right_stand_B": + DoPainFromArray( getPainAnimByName( "cover_right_stand_B" ) ); + handled = true; + break; + + case "cover_left_stand_A": + DoPainFromArray( getPainAnimByName( "cover_left_stand_A" ) ); + handled = true; + break; + + case "cover_left_stand_B": + DoPainFromArray( getPainAnimByName( "cover_left_stand_B" ) ); + handled = true; + break; + + case "cover_crouch": + DoPainFromArray( getPainAnimByName( "cover_crouch" ) ); + handled = true; + break; + + case "cover_stand": + DoPainFromArray( getPainAnimByName( "cover_stand" ) ); + handled = true; + break; + + case "cover_stand_aim": + DoPainFromArray( getPainAnimByName( "cover_stand_aim" ) ); + handled = true; + break; + + case "cover_crouch_aim": + DoPainFromArray( getPainAnimByName( "cover_stand_aim" ) ); + handled = true; + break; + + case "saw": + if ( self.a.pose == "stand" ) + { + painAnim = getPainAnimByName( "saw_stand" ); + } + else if ( self.a.pose == "crouch" ) + { + painAnim = getPainAnimByName( "saw_crouch" ); + } + else + { + painAnim = getPainAnimByName( "saw_prone" ); + } + + self setflaggedanimknob( "painanim", painAnim, 1, .3, 1 ); + self animscripts\shared::DoNoteTracks( "painanim" ); + handled = true; + break; + case "mg42": + mg42pain( self.a.pose ); + handled = true; + break; + case "minigun": + handled = false; + break; + case "corner_right_martyrdom": + handled = ( self TryCornerRightGrenadeDeath() ); + break; + case "rambo_left": + case "rambo_right": + case "rambo": + case "dying_crawl": + handled = false; + break; + /* + case "taser": + if( self.team == "allies" ) + { + if ( self.a.pose == "stand" ) + { + painAnim = getPainAnimByName( "taser_stand_ally" ); + } + else + { + painAnim = getPainAnimByName( "taser_crouch_ally" ); + } + } + else + { + if ( self.a.pose == "stand" ) + { + painAnim = getPainAnimByName( "taser_stand" ); + } + else + { + painAnim = getPainAnimByName( "taser_crouch" ); + } + } + + self DoPainFromArray( painAnim ); + handled = true; + break; + */ + default: + println( "Unexpected anim_special value : " + anim_special + " in specialPain." ); + handled = false; + } + return handled; +} + +painDeathNotify() +{ + self endon( "death" ); + + // it isn't safe to notify "pain_death" from the start of an animscript. + // this can cause level script to run, which might cause things with this AI to change while the animscript is starting + // and this can screw things up in unexpected ways. + // take my word for it. + wait .05; + self notify( "pain_death" ); +} + +DoPainFromArray( painArray ) +{ + painAnim = painArray[ randomint( painArray.size ) ]; + + self setflaggedanimknob( "painanim", painAnim, 1, .3, 1 ); + self animscripts\shared::DoNoteTracks( "painanim" ); +} + +DoPain( painAnim ) +{ + self setflaggedanimknob( "painanim", painAnim, 1, .3, 1 ); + self animscripts\shared::DoNoteTracks( "painanim" ); +} + +mg42pain( pose ) +{ +// assertmsg("mg42 pain anims not implemented yet");//scripted_mg42gunner_pain + + /# + assertEx( isdefined( level._mg_animmg ), "You're missing maps\\_mganim::main(); Add it to your level." ); + { + println( " maps\\_mganim::main();" ); + return; + } + #/ + + self setflaggedanimknob( "painanim", level._mg_animmg[ "pain_" + pose ], 1, .1, 1 ); + self animscripts\shared::DoNoteTracks( "painanim" ); +} + + +// This is to stop guys from taking off running if they're interrupted during pain. This used to happen when +// guys were running when they entered pain, but didn't play a special running pain (eg because they were +// running sideways). It resulted in a running pain or death being played when they were shot again. +waitSetStop( timetowait, killmestring ) +{ + self endon( "killanimscript" ); + self endon( "death" ); + if ( isDefined( killmestring ) ) + self endon( killmestring ); + wait timetowait; + + self.a.movement = "stop"; +} + +maxCrawlPainHealth = 100; + +crawlingPain() +{ + if ( self.a.disableLongDeath || self.dieQuietly || self.damageShield ) + return false; + + if ( self.stairsState != "none" ) + return false; + + if ( isdefined( self.a.onback ) ) + return false; + + /# + if ( getDvarInt( "scr_forceCrawl" ) == 1 ) + self.forceLongDeath = 1; + #/ + + if ( isdefined( self.forceLongDeath ) ) + { + self.health = 10; + self thread crawlingPistol(); + + self waittill( "killanimscript" ); + return true; + } + + assert( self.a.pose == "prone" || self.a.pose == "stand" || self.a.pose == "crouch" ); + + transAnims = []; + transAnims[ "prone" ] = getPainAnimByName( "crawl_trans_prone" ); + transAnims[ "stand" ] = getPainAnimByName( "crawl_trans_stand" ); + transAnims[ "crouch" ] = getPainAnimByName( "crawl_trans_crouch" ); + + AssertEx(IsDefined(transAnims), "transAnims is not defined."); + AssertEx(IsDefined(transAnims[self.a.pose]), "transAnims is not defined for pose %s.", self.a.pose); + + self.a.crawlingPainTransAnim = transAnims[self.a.pose][ randomint( transAnims[self.a.pose].size ) ]; + + if ( !isCrawlDeltaAllowed( self.a.crawlingPainTransAnim ) ) + return false; + + if ( self.health > maxCrawlPainHealth ) + return false; + + legHit = self damageLocationIsAny( "left_leg_upper", "left_leg_lower", "right_leg_upper", "right_leg_lower", "left_foot", "right_foot" ); + + if ( legHit && self.health < self.maxhealth * .4 ) + { + if ( gettime() < anim.nextCrawlingPainTimeFromLegDamage ) + return false; + } + else + { + if ( anim.numDeathsUntilCrawlingPain > 0 ) + return false; + if ( gettime() < anim.nextCrawlingPainTime ) + return false; + } + + /*if ( self.a.movement != "stop" ) + return false;*/ + + if ( isDefined( self.deathFunction ) ) + return false; + + foreach ( player in level._players ) + { + if ( distance( self.origin, player.origin ) < 175 ) + return false; + } + + if ( self damageLocationIsAny( "head", "helmet", "gun", "right_hand", "left_hand" ) ) + return false; + + if ( usingSidearm() ) + return false; + + // we'll wait a bit to see if this crawling pain will really succeed. + // in the meantime, don't start any other ones. + anim.nextCrawlingPainTime = gettime() + 3000; + anim.nextCrawlingPainTimeFromLegDamage = gettime() + 3000; + + // needs to be threaded + self thread crawlingPistol(); + + self waittill( "killanimscript" ); + return true; +} + +isCrawlDeltaAllowed( theanim ) +{ + if ( isdefined( self.a.force_num_crawls ) ) + return true; + + delta = getMoveDelta( theanim, 0, 1 ); + endPoint = self localToWorldCoords( delta ); + + return self mayMoveToPoint( endPoint ); +} + +initCrawlingPistolAnims() +{ + self.a.array = []; + self.a.array[ "stand_2_crawl" ] = getPainAnimByName( "stand_2_crawl" ); + self.a.array[ "crouch_2_crawl" ] = getPainAnimByName( "crouch_2_crawl" ); + + self.a.array[ "crawl" ] = getPainAnimByName( "crawl" ); + + self.a.array[ "death" ] = getPainAnimByName( "death" ); + + self.a.array[ "back_idle" ] = getPainAnimByName( "back_idle" ); + self.a.array[ "back_idle_twitch" ] = getPainAnimByName( "back_idle_twitch" ); + self.a.array[ "back_crawl" ] = getPainAnimByName( "back_crawl" ); + self.a.array[ "back_fire" ] = getPainAnimByName( "back_fire" ); + + self.a.array[ "back_death" ] = getPainAnimByName( "back_death" ); + + if ( isdefined( self.crawlingPainAnimOverrideFunc ) ) + [[ self.crawlingPainAnimOverrideFunc ]](); +} + +crawlingPistol() +{ + // don't end on killanimscript. pain.gsc will abort if self.crawlingPistolStarting is true. + self endon( "kill_long_death" ); + self endon( "death" ); + + initCrawlingPistolAnims(); + + self thread preventPainForAShortTime( "crawling" ); + + self.a.special = "none"; + self.specialDeathFunc = undefined; + + self thread painDeathNotify(); + //notify ac130 missions that a guy is crawling so context sensative dialog can be played + level notify( "ai_crawling", self ); + + self thread crawling_stab_achievement(); + + self setAnimKnobAll( %dying, %body, 1, 0.1, 1 ); + + // dyingCrawl() returns false if we die without turning around + if ( !self dyingCrawl() ) + return; + + self setFlaggedAnimKnob( "transition", self.a.crawlingPainTransAnim, 1, 0.5, 1 ); + self animscripts\shared::DoNoteTracksIntercept( "transition", ::handleBackCrawlNotetracks ); + assert( isdefined( self.a.onback ) ); + + self.a.special = "dying_crawl"; + + self thread dyingCrawlBackAim(); + + if ( isdefined( self.enemy ) ) + self setLookAtEntity( self.enemy ); + + decideNumCrawls(); + while ( shouldKeepCrawling() ) + { + crawlAnim = animArray( "back_crawl" ); + if ( !self isCrawlDeltaAllowed( crawlAnim ) ) + break; + + self setFlaggedAnimKnobRestart( "back_crawl", crawlAnim, 1, 0.1, 1.0 ); + self animscripts\shared::DoNoteTracksIntercept( "back_crawl", ::handleBackCrawlNotetracks ); + } + + self.desiredTimeOfDeath = gettime() + randomintrange( 4000, 20000 ); + while ( shouldStayAlive() ) + { + if ( self canSeeEnemy() && self aimedSomewhatAtEnemy() ) + { + backAnim = animArray( "back_fire" ); + + self setFlaggedAnimKnobRestart( "back_idle_or_fire", backAnim, 1, 0.2, 1.0 ); + self animscripts\shared::DoNoteTracks( "back_idle_or_fire" ); + } + else + { + backAnim = animArray( "back_idle" ); + if ( randomfloat( 1 ) < .4 ) + backAnim = animArrayPickRandom( "back_idle_twitch" ); + + self setFlaggedAnimKnobRestart( "back_idle_or_fire", backAnim, 1, 0.1, 1.0 ); + + timeRemaining = getAnimLength( backAnim ); + while ( timeRemaining > 0 ) + { + if ( self canSeeEnemy() && self aimedSomewhatAtEnemy() ) + break; + + interval = 0.5; + if ( interval > timeRemaining ) + { + interval = timeRemaining; + timeRemaining = 0; + } + else + { + timeRemaining -= interval; + } + self animscripts\shared::DoNoteTracksForTime( interval, "back_idle_or_fire" ); + } + } + } + + self notify( "end_dying_crawl_back_aim" ); + self clearAnim( %dying_back_aim_4_wrapper, .3 ); + self clearAnim( %dying_back_aim_6_wrapper, .3 ); + + self.deathanim = animArrayPickRandom( "back_death" ); + self killWrapper(); + + self.a.special = "none"; + self.specialDeathFunc = undefined; +} + +crawling_stab_achievement() +{ + if ( self.team == "allies" ) + return; + self endon( "end_dying_crawl_back_aim" ); + self waittill( "death", attacker, type ); + if ( !isdefined( self ) || !isdefined( attacker ) || !isplayer( attacker ) ) + return; +// if ( type == "MOD_MELEE" ) +// maps\_utility::giveachievement_wrapper( "NO_REST_FOR_THE_WEARY" ); +} + +shouldStayAlive() +{ + if ( !enemyIsInGeneralDirection( anglesToForward( self.angles ) ) ) + return false; + + return gettime() < self.desiredTimeOfDeath; +} + +dyingCrawl() +{ + if( !isdefined( self.forceLongDeath ) ) + { + if ( self.a.pose == "prone" ) + return true; + + if ( self.a.movement == "stop" ) + { + if ( randomfloat( 1 ) < .4 ) // chance of randomness + { + if ( randomfloat( 1 ) < .5 ) + return true; + } + else + { + // if hit from front, return true + if ( abs( self.damageYaw ) > 90 ) + return true; + } + } + else + { + // if we're not stopped, we want to fall in the direction of movement + // so return true if moving backwards + if ( abs( self getMotionAngle() ) > 90 ) + return true; + } + } + + if ( self.a.pose != "prone" ) + { + fallAnim = animArrayPickRandom( self.a.pose + "_2_crawl" ); + + if ( !self isCrawlDeltaAllowed( fallAnim ) ) + return true; + + self thread dyingCrawlBloodSmear(); + + self setFlaggedAnimKnob( "falling", fallAnim, 1, 0.5, 1 ); + self animscripts\shared::DoNoteTracks( "falling" ); + assert( self.a.pose == "prone" ); + } + else + { + self thread dyingCrawlBloodSmear(); + } + + self.a.crawlingPainTransAnim = %dying_crawl_2_back; + + self.a.special = "dying_crawl"; + + decideNumCrawls(); + while ( shouldKeepCrawling() ) + { + crawlAnim = animArray( "crawl" ); + + if ( !self isCrawlDeltaAllowed( crawlAnim ) ) + return true; + + if ( isdefined( self.custom_crawl_sound ) ) + { + self playsound( self.custom_crawl_sound ); + } + + self setFlaggedAnimKnobRestart( "crawling", crawlAnim, 1, 0.1, 1.0 ); + self animscripts\shared::DoNoteTracks( "crawling" ); + } + + self notify( "done_crawling" ); + + // check if target is in cone to shoot + if ( !isdefined( self.forceLongDeath ) && enemyIsInGeneralDirection( anglesToForward( self.angles ) * - 1 ) ) + return true; + + deathanim = animArrayPickRandom( "death" ); + + // this particular death animation is long enough that we want it to be interruptible + if( deathanim != %dying_crawl_death_v2 ) + { + // all the others are short so we don't want them to be interruptible + self.a.nodeath = true; + } + + animscripts\death::playDeathAnim( deathanim ); + self killWrapper(); + + self.a.special = "none"; + self.specialDeathFunc = undefined; + + return false; +} + +dyingCrawlBloodSmear() +{ + self endon( "death" ); + + if ( self.a.pose != "prone" ) + { + while( 1 ) + { + self waittill( "falling", note ); + + if ( IsSubStr( note, "bodyfall" ) ) + break; + } + } + + origintag = "J_SpineLower"; + angletag = "tag_origin"; + + fx_rate = .25; + fx = level._effect[ "crawling_death_blood_smear" ]; + + if ( isdefined( self.a.crawl_fx_rate ) ) + fx_rate = self.a.crawl_fx_rate; + if( isdefined( self.a.crawl_fx ) ) + fx = level._effect[ self.a.crawl_fx ]; + + while( fx_rate ) + { + org = self gettagorigin( origintag ); + angles = self GetTagAngles( angletag ); + forward = anglestoright( angles ); + up = anglestoforward( ( 270, 0, 0 ) ); + + playfx( fx, org, up, forward ); + + wait( fx_rate ); + } +} + +dyingCrawlBackAim() +{ + self endon( "kill_long_death" ); + self endon( "death" ); + self endon( "end_dying_crawl_back_aim" ); + + if ( isdefined( self.dyingCrawlAiming ) ) + return; + self.dyingCrawlAiming = true; + + self setAnimLimited( getPainAnimByName( "dying_back_aim_left" ), 1, 0 ); + self setAnimLimited( getPainAnimByName( "dying_back_aim_right" ), 1, 0 ); + + prevyaw = 0; + + while ( 1 ) + { + aimyaw = self getYawToEnemy(); + + diff = AngleClamp180( aimyaw - prevyaw ); + if ( abs( diff ) > 3 ) + diff = sign( diff ) * 3; + + aimyaw = AngleClamp180( prevyaw + diff ); + + if ( aimyaw < 0 ) + { + if ( aimyaw < - 45.0 ) + aimyaw = -45.0; + weight = aimyaw / - 45.0; + self setAnim( %dying_back_aim_4_wrapper, weight, .05 ); + self setAnim( %dying_back_aim_6_wrapper, 0, .05 ); + } + else + { + if ( aimyaw > 45.0 ) + aimyaw = 45.0; + weight = aimyaw / 45.0; + self setAnim( %dying_back_aim_6_wrapper, weight, .05 ); + self setAnim( %dying_back_aim_4_wrapper, 0, .05 ); + } + + prevyaw = aimyaw; + + wait .05; + } +} + +startDyingCrawlBackAimSoon() +{ + self endon( "kill_long_death" ); + self endon( "death" ); + + wait 0.5; + self thread dyingCrawlBackAim(); +} + +handleBackCrawlNotetracks( note ) +{ + if ( note == "fire_spray" ) + { + if ( !self canSeeEnemy() ) + return true; + + if ( !self aimedSomewhatAtEnemy() ) + return true; + + self shootEnemyWrapper(); + + return true; + } + else if ( note == "pistol_pickup" ) + { + self thread startDyingCrawlBackAimSoon(); + return false; + } + return false; +} + +aimedSomewhatAtEnemy() +{ + assert( isdefined( self.enemy ) ); + + enemyShootAtPos = self.enemy getShootAtPos(); + + weaponAngles = self getMuzzleAngle(); + anglesToEnemy = vectorToAngles( enemyShootAtPos - self getMuzzlePos() ); + + absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToEnemy[ 1 ] ); + if ( absyawdiff > anim.painYawDiffFarTolerance ) + { + if ( distanceSquared( self getEye(), enemyShootAtPos ) > anim.painYawDiffCloseDistSQ || absyawdiff > anim.painYawDiffCloseTolerance ) + return false; + } + + return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToEnemy[ 0 ] ) <= anim.painPitchDiffTolerance; +} + +enemyIsInGeneralDirection( dir ) +{ + if ( !isdefined( self.enemy ) ) + return false; + + toenemy = vectorNormalize( self.enemy getShootAtPos() - self getEye() ); + + return( vectorDot( toenemy, dir ) > 0.5 );// cos( 60 ) = 0.5 +} + + +preventPainForAShortTime( type ) +{ + self endon( "kill_long_death" ); + self endon( "death" ); + + self.flashBangImmunity = true; + + self.longDeathStarting = true; + self.a.doingLongDeath = true; + self notify( "long_death" ); + self.health = 10000;// also prevent death + self.threatbias = self.threatbias - 2000; + + // during this time, we won't be interrupted by more pain. + // this increases the chances of the crawling pain succeeding. + wait .75; + + // important that we die the next time we get hit, + // instead of maybe going into pain and coming out and going into combat or something + if ( self.health > 1 ) + self.health = 1; + + // important that we wait a bit in case we're about to start pain later in this frame + wait .05; + + self.longDeathStarting = undefined; + self.a.mayOnlyDie = true;// we've probably dropped our weapon and stuff; we must not do any other animscripts but death! + + if ( type == "crawling" ) + { + wait 1.0; + + // we've essentially succeeded in doing a crawling pain. + if ( isdefined( level._player ) && distanceSquared( self.origin, level._player.origin ) < 1024 * 1024 ) + { + anim.numDeathsUntilCrawlingPain = randomintrange( 10, 30 ); + anim.nextCrawlingPainTime = gettime() + randomintrange( 15000, 60000 ); + } + else + { + anim.numDeathsUntilCrawlingPain = randomintrange( 5, 12 ); + anim.nextCrawlingPainTime = gettime() + randomintrange( 5000, 25000 ); + } + anim.nextCrawlingPainTimeFromLegDamage = gettime() + randomintrange( 7000, 13000 ); + /# + if ( getDebugDvarInt( "scr_crawldebug" ) == 1 ) + { + thread printLongDeathDebugText( self.origin + ( 0, 0, 64 ), "crawl death" ); + return; + } + #/ + } + else if ( type == "corner_grenade" ) + { + wait 1.0; + + // we've essentially succeeded in doing a corner grenade death. + if ( isdefined( level._player ) && distanceSquared( self.origin, level._player.origin ) < 700 * 700 ) + { + anim.numDeathsUntilCornerGrenadeDeath = randomintrange( 10, 30 ); + anim.nextCornerGrenadeDeathTime = gettime() + randomintrange( 15000, 60000 ); + } + else + { + anim.numDeathsUntilCornerGrenadeDeath = randomintrange( 5, 12 ); + anim.nextCornerGrenadeDeathTime = gettime() + randomintrange( 5000, 25000 ); + } + /# + if ( getDebugDvarInt( "scr_cornergrenadedebug" ) == 1 ) + { + thread printLongDeathDebugText( self.origin + ( 0, 0, 64 ), "grenade death" ); + return; + } + #/ + } +} + + /# +printLongDeathDebugText( loc, text ) +{ + for ( i = 0; i < 100; i++ ) + { + print3d( loc, text ); + wait .05; + } +} +#/ + +decideNumCrawls() +{ + if( isdefined( self.a.force_num_crawls ) ) + self.a.numCrawls = self.a.force_num_crawls; + else + self.a.numCrawls = randomIntRange( 1, 5 ); +} + +shouldKeepCrawling() +{ + // TODO: player distance checks, etc... + + assert( isDefined( self.a.numCrawls ) ); + + if ( !self.a.numCrawls ) + { + self.a.numCrawls = undefined; + return false; + } + + self.a.numCrawls--; + + return true; +} + + +TryCornerRightGrenadeDeath() +{ + /# + if ( getDvarInt( "scr_forceCornerGrenadeDeath" ) == 1 ) + { + self thread CornerRightGrenadeDeath(); + self waittill( "killanimscript" ); + return true; + } + #/ + + if ( anim.numDeathsUntilCornerGrenadeDeath > 0 ) + return false; + if ( gettime() < anim.nextCornerGrenadeDeathTime ) + return false; + + if ( self.a.disableLongDeath || self.dieQuietly || self.damageShield ) + return false; + + if ( isDefined( self.deathFunction ) ) + return false; + + if ( distance( self.origin, level._player.origin ) < 175 ) + return false; + + // we'll wait a bit to see if this crawling pain will really succeed. + // in the meantime, don't start any other ones. + anim.nextCornerGrenadeDeathTime = gettime() + 3000; + + self thread CornerRightGrenadeDeath(); + + self waittill( "killanimscript" ); + return true; +} + +CornerRightGrenadeDeath() +{ + self endon( "kill_long_death" ); + self endon( "death" ); + + self thread painDeathNotify(); + + self thread preventPainForAShortTime( "corner_grenade" ); + + self thread maps\_utility::set_battlechatter( false ); + + self.threatbias = -1000;// no need for AI to target me + + self setFlaggedAnimKnobAllRestart( "corner_grenade_pain", %corner_standR_death_grenade_hit, %body, 1, .1 ); + + //wait getAnimLength( %corner_standR_death_grenade_hit ) * 0.2; + self waittillmatch( "corner_grenade_pain", "dropgun" ); + self animscripts\shared::DropAllAIWeapons(); + + self waittillmatch( "corner_grenade_pain", "anim_pose = \"back\"" ); + animscripts\shared::noteTrackPoseBack(); + + self waittillmatch( "corner_grenade_pain", "grenade_left" ); + model = getWeaponModel( "fraggrenade" ); + self attach( model, "tag_inhand" ); + self.deathFunction = ::prematureCornerGrenadeDeath; + + self waittillmatch( "corner_grenade_pain", "end" ); + + + desiredDeathTime = gettime() + randomintrange( 25000, 60000 ); + + self setFlaggedAnimKnobAllRestart( "corner_grenade_idle", %corner_standR_death_grenade_idle, %body, 1, .2 ); + + self thread watchEnemyVelocity(); + while ( !enemyIsApproaching() ) + { + if ( gettime() >= desiredDeathTime ) + break; + + self animscripts\shared::DoNoteTracksForTime( 0.1, "corner_grenade_idle" ); + } + + dropAnim = %corner_standR_death_grenade_slump; + self setFlaggedAnimKnobAllRestart( "corner_grenade_release", dropAnim, %body, 1, .2 ); + + dropTimeArray = getNotetrackTimes( dropAnim, "grenade_drop" ); + assert( dropTimeArray.size == 1 ); + dropTime = dropTimeArray[ 0 ] * getAnimLength( dropAnim ); + + wait dropTime - 1.0; + + self animscripts\death::PlayDeathSound(); + + wait 0.7; + + self.deathFunction = ::waitTillGrenadeDrops; + + velocity = ( 0, 0, 30 ) - anglesToRight( self.angles ) * 70; + self CornerDeathReleaseGrenade( velocity, randomfloatrange( 2.0, 3.0 ) ); + + wait .05; + self detach( model, "tag_inhand" ); + + self thread killSelf(); +} + +CornerDeathReleaseGrenade( velocity, fusetime ) +{ + releasePoint = self getTagOrigin( "tag_inhand" ); + + // avoid dropping under the floor. + releasePointLifted = releasePoint + ( 0, 0, 20 ); + releasePointDropped = releasePoint - ( 0, 0, 20 ); + trace = bullettrace( releasePointLifted, releasePointDropped, false, undefined ); + + if ( trace[ "fraction" ] < .5 ) + releasePoint = trace[ "position" ]; + + surfaceType = "default"; + if ( trace[ "surfacetype" ] != "none" ) + surfaceType = trace[ "surfacetype" ]; + + // play the grenade drop sound because we're probably not dropping it with enough velocity for it to play it normally + thread playSoundAtPoint( "grenade_bounce_" + surfaceType, releasePoint ); + + self.grenadeWeapon = "fraggrenade"; + self magicGrenadeManual( releasePoint, velocity, fusetime ); +} + +playSoundAtPoint( alias, origin ) +{ + org = spawn( "sound_emitter", origin ); + org playsound( alias, "sounddone" ); + org waittill( "sounddone" ); + org delete(); +} + +killSelf() +{ + self.a.nodeath = true; + self killWrapper(); + self startragdoll(); + wait .1; + self notify( "grenade_drop_done" ); +} + +killWrapper() +{ + // Set in maps\_spawner.gsc, mainly for SpecOps + // This helps ensure the kill is done by the player if a player is the one who put the Ai into the long-death + if ( IsDefined( self.last_dmg_player ) ) + { + self Kill( self.origin, self.last_dmg_player ); + } + else + { + self Kill(); + } +} + +enemyIsApproaching() +{ + if ( !isdefined( self.enemy ) ) + return false; + if ( distanceSquared( self.origin, self.enemy.origin ) > 384 * 384 ) + return false; + if ( distanceSquared( self.origin, self.enemy.origin ) < 128 * 128 ) + return true; + + predictedEnemyPos = self.enemy.origin + self.enemyVelocity * 3.0; + + nearestPos = self.enemy.origin; + if ( self.enemy.origin != predictedEnemyPos ) + nearestPos = pointOnSegmentNearestToPoint( self.enemy.origin, predictedEnemyPos, self.origin ); + + if ( distanceSquared( self.origin, nearestPos ) < 128 * 128 ) + return true; + + return false; +} + +prematureCornerGrenadeDeath() +{ + deathArray = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3, %dying_back_death_v4 ); + deathAnim = deathArray[ randomint( deathArray.size ) ]; + + self animscripts\death::PlayDeathSound(); + + self setFlaggedAnimKnobAllRestart( "corner_grenade_die", deathAnim, %body, 1, .2 ); + + velocity = getGrenadeDropVelocity(); + self CornerDeathReleaseGrenade( velocity, 3.0 ); + + model = getWeaponModel( "fraggrenade" ); + self detach( model, "tag_inhand" ); + + wait .05; + + self startragdoll(); + + self waittillmatch( "corner_grenade_die", "end" ); +} + +waitTillGrenadeDrops() +{ + self waittill( "grenade_drop_done" ); +} + +watchEnemyVelocity() +{ + self endon( "kill_long_death" ); + self endon( "death" ); + + self.enemyVelocity = ( 0, 0, 0 ); + + prevenemy = undefined; + prevpos = self.origin; + + interval = .15; + + while ( 1 ) + { + if ( isdefined( self.enemy ) && isdefined( prevenemy ) && self.enemy == prevenemy ) + { + curpos = self.enemy.origin; + self.enemyVelocity = vector_multiply( curpos - prevpos, 1 / interval ); + prevpos = curpos; + } + else + { + if ( isdefined( self.enemy ) ) + prevpos = self.enemy.origin; + else + prevpos = self.origin; + prevenemy = self.enemy; + + self.shootEntVelocity = ( 0, 0, 0 ); + } + + wait interval; + } +} + + +additive_pain( damage, attacker, direction_vec, point, type, modelName, tagName ) +{ + self endon( "death" ); + + if ( !isdefined( self ) ) + return; + + if ( isdefined( self.doingAdditivePain ) ) + return; + + if ( damage > self.minPainDamage ) + return; + + self.doingAdditivePain = true; + + painAnimArray = []; + + if ( self damageLocationIsAny( "left_arm_lower", "left_arm_upper", "left_hand" ) ) + { + painAnimArray = getPainAnimByName( "add_left_arm" ); + } + else if ( self damageLocationIsAny( "right_arm_lower", "right_arm_upper", "right_hand" ) ) + { + painAnimArray = getPainAnimByName( "add_right_arm" ); + } + else if ( self damageLocationIsAny( "left_leg_upper", "left_leg_lower", "left_foot" ) ) + { + painAnimArray = getPainAnimByName( "add_left_leg" ); + } + else if ( self damageLocationIsAny( "right_leg_upper", "right_leg_lower", "right_foot" ) ) + { + painAnimArray = getPainAnimByName( "add_right_leg" ); + } + else + { + painAnimArray = getPainAnimByName( "add_generic" ); + } + + painAnim = painAnimArray[ randomint( painAnimArray.size ) ]; + + blend_weight = 1; + if ( isdefined( self.additivePainBlendWeight ) ) + { + blend_weight = self.additivePainBlendWeight; + } + + self setanimlimited( %add_pain, blend_weight, 0.1, 1 ); + self setanimlimited( painAnim, blend_weight, 0, 1 ); + + wait 0.4; + + self clearanim( painAnim, 0.2 ); + self clearanim( %add_pain, 0.2 ); + self.doingAdditivePain = undefined; +} + +play_shock( note, flagName ) +{ + + if ( !isdefined( self ) ) + return; + + self playSound( "taser_impact_flesh" ); +} \ No newline at end of file diff --git a/animscripts/reactions.gsc b/animscripts/reactions.gsc new file mode 100644 index 0000000..ff77bdc --- /dev/null +++ b/animscripts/reactions.gsc @@ -0,0 +1,396 @@ +#include animscripts\SetPoseMovement; +#include animscripts\Utility; +#include common_scripts\Utility; +#using_animtree( "generic_human" ); + +main() +{ + self endon( "killanimscript" ); + animscripts\utility::initialize( "reactions" ); + + self newEnemySurprisedReaction(); +} + +getReactionAnim( name ) +{ + if ( IsDefined( self.customCoverReactions ) && IsDefined( self.customCoverReactions[ name ] ) ) + { + return self.customCoverReactions[ name ]; + } + else + { + return anim.reactionAnimArray[ name ]; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////// +reactionsCheckLoop() +{ + self thread bulletWhizbyCheckLoop(); +} + + +/////////////////////////////////////////////////////////////////////////// +// death reactions +/////////////////////////////////////////////////////////////////////////// +/* disabled for now since the animations aren't in common csv + +MoveDeathReaction() +{ + // Decide what pose to use + desiredPose = self animscripts\utility::choosePose(); + + if ( desiredPose == "stand" ) + { + deathAnim = getDeathReactionAnim(); + DoDeathReactionAnim( deathAnim ); + } +} + +ExposedCombatDeathReaction() +{ + // Decide what pose to use + desiredPose = self animscripts\utility::choosePose(); + + if ( desiredPose == "stand" ) + { + deathAnim = getDeathReactionAnim(); + DoDeathReactionAnim( deathAnim ); + } +} + +DoDeathReactionAnim( deathAnim ) +{ + self endon( "movemode" ); + + rate = self.moveplaybackrate; + + self setFlaggedAnimKnobAll( "deathanim", deathAnim, %body, 1, 1, rate, true ); + + self animscripts\shared::DoNoteTracks( "deathanim" ); + self.deathTeamate = false; +} + +getDeathReactionAnim() +{ + if ( self.deathTeamateReaction == "back" ) + return %run_reaction_180; + else if ( self.deathTeamateReaction == "left" ) + return %run_reaction_L_quick; + else if ( self.deathTeamateReaction == "right" ) + return %run_reaction_R_quick; +} + +deathCheck() +{ + self endon( "killanimscript" ); + + self.deathTeamateReaction = "none"; + self.deathTeamate = false; + + minDeathDistance = 100; + maxDeathDistance = 500; + minGoalDistance = 200; + maxTurnAngle = 135; + minTurnAngle = 10; + + self AddAIEventListener( "death" ); + + for ( ;; ) + { + self waittill( "ai_event", event, originator, position ); + if ( event != "death" ) + continue; + + deathDirection = position - self.origin; + deathDistance = Length( deathDirection ); + if ( deathDistance >= minDeathDistance && deathDistance <= maxDeathDistance ) + { + goalDirection = self.goalpos - self.origin; + goalDistance = Length( goalDirection ); + if ( goalDistance >= minGoalDistance ) + { + goalAngles = VectorToAngles( goalDirection ); + deltaAngles = Abs( self.angles[1] - goalAngles[1] ); + if ( deltaAngles > minTurnAngle ) + { + if ( deltaAngles > maxTurnAngle ) + self.deathTeamateReaction = "back"; + else if ( self.angles[1] > goalAngles[1] ) + self.deathTeamateReaction = "left"; + else + self.deathTeamateReaction = "right"; + + self.deathTeamate = true; + } + } + } + } +} + +*/ + +canReactAgain() +{ + return ( !isdefined( self.lastReactTime ) || gettime() - self.lastReactTime > 2000 ); +} + +/////////////////////////////////////////////////////////////////////////// +// bullet whizby reaction +/////////////////////////////////////////////////////////////////////////// + +bulletWhizbyReaction() +{ + self endon( "killanimscript" ); + + self.lastReactTime = gettime(); + self.a.movement = "stop"; + + enemyNear = ( isDefined( self.whizbyEnemy ) && distanceSquared( self.origin, self.whizbyEnemy.origin ) < 400 * 400 ); + + self animmode( "gravity" ); + self orientmode( "face current" ); + + // react and go to prone + if ( enemyNear || cointoss() ) + { + self clearanim( %root, 0.1 ); + + reactionAnims = getReactionAnim( "wizby_idle" ); + + reaction = reactionAnims[ randomint( reactionAnims.size ) ]; + + if ( enemyNear ) + waitTime = 1 + randomfloat( 0.5 ); + else + waitTime = 0.2 + randomfloat( 0.5 ); + + self setFlaggedAnimKnobRestart( "reactanim", reaction, 1, 0.1, 1 ); + self animscripts\shared::DoNoteTracksForTime( waitTime, "reactanim" ); + + self clearanim( %root, 0.1 ); + + if ( !enemyNear && self.stairsState == "none" ) + { + rate = 1 + randomfloat( 0.2 ); + + reactionAnims = getReactionAnim( "wizby_dive" ); + + diveAnim = reactionAnims[ randomint( reactionAnims.size ) ]; + + self setFlaggedAnimKnobRestart( "dive", diveAnim, 1, 0.1, rate ); + self animscripts\shared::DoNoteTracks( "dive" ); + } + } + else // crouch then handsignal or turn + { + wait randomfloat( 0.2 ); + + rate = 1.2 + randomfloat( 0.3 ); + + if ( self.a.pose == "stand" ) + { + self clearanim( %root, 0.1 ); + reactionAnim = getReactionAnim( "wizby_crouch" ); + self setFlaggedAnimKnobRestart( "crouch", reactionAnim, 1, 0.1, rate ); + self animscripts\shared::DoNoteTracks( "crouch" ); + } + + forward = anglesToForward( self.angles ); + + if ( isDefined( self.whizbyEnemy ) ) + dirToEnemy = vectorNormalize( self.whizbyEnemy.origin - self.origin ); + else + dirToEnemy = forward; + + if ( vectordot( dirToEnemy, forward ) > 0 ) + { + reactionAnims = getReactionAnim( "wizby_twitch" ); + twitchAnim = reactionAnims[ randomint( reactionAnims.size ) ]; + + self clearanim( %root, 0.1 ); + self setFlaggedAnimKnobRestart( "twitch", twitchAnim, 1, 0.1, 1 ); + self animscripts\shared::DoNoteTracks( "twitch" ); + + //if ( cointoss() ) + // self handsignal( "go" ); + } + else + { + reactionAnims = getReactionAnim( "wizby_turn" ); + turnAnim = reactionAnims[ randomint( reactionAnims.size ) ]; + + self clearanim( %root, 0.1 ); + self setFlaggedAnimKnobRestart( "turn", turnAnim, 1, 0.1, 1 ); + self animscripts\shared::DoNoteTracks( "turn" ); + } + } + + self clearanim( %root, 0.1 ); + self.whizbyEnemy = undefined; + self animmode( "normal" ); + self orientmode( "face default" ); +} + + +bulletWhizbyCheckLoop() +{ + self endon( "killanimscript" ); + + if ( isdefined( self.disableBulletWhizbyReaction ) ) + return; + + while ( 1 ) + { + self waittill( "bulletwhizby", shooter ); + + if ( !isdefined( shooter.team ) || self.team == shooter.team ) + continue; + + if ( isdefined( self.coverNode ) || isdefined( self.ambushNode ) ) + continue; + + if ( self.a.pose != "stand" ) + continue; + + if ( !canReactAgain() ) + continue; + + self.whizbyEnemy = shooter; + self animcustom( ::bulletWhizbyReaction ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// surprised by new enemy reaction +/////////////////////////////////////////////////////////////////////////// + +clearLookAtThread() +{ + self endon( "killanimscript" ); + + wait 0.3; + self setLookAtEntity(); +} + + +getNewEnemyReactionAnim() +{ + reactAnim = undefined; + + if ( self nearClaimNodeAndAngle() && isdefined( anim.reactionAnimArray[ self.prevScript ] ) ) + { + nodeForward = anglesToForward( self.node.angles ); + dirToReactionTarget = vectorNormalize( self.reactionTargetPos - self.origin ); + + if ( vectorDot( nodeForward, dirToReactionTarget ) < -0.5 ) + { + self orientmode( "face current" ); + reactionAnims = getReactionAnim( self.prevScript ); + reactAnim = reactionAnims[ randomint( reactionAnims.size ) ]; + } + } + + if ( !isdefined( reactAnim ) ) + { + if ( isdefined( self.enemy ) && distanceSquared( self.enemy.origin, self.reactionTargetPos ) < 256 * 256 ) + self orientmode( "face enemy" ); + else + self orientmode( "face point", self.reactionTargetPos ); + + if ( self.a.pose == "crouch" ) + { + dirToReactionTarget = vectorNormalize( self.reactionTargetPos - self.origin ); + forward = anglesToForward( self.angles ); + if ( vectorDot( forward, dirToReactionTarget ) < -0.5 ) + { + self orientmode( "face current" ); + reactionAnims = getReactionAnim( "crouch" ); + return reactionAnims[ randomint( reactionAnims.size ) ]; + } + } + + reactionAnims = getReactionAnim( "stand" ); + reactAnim = reactionAnims[ randomint( reactionAnims.size ) ]; + } + + return reactAnim; +} + + +stealthNewEnemyReactAnim() +{ + self clearanim( %root, 0.2 ); + + if ( randomint( 4 ) < 3 ) + { + self orientmode( "face enemy" ); + self setFlaggedAnimKnobRestart( "reactanim", getReactionAnim( "stealth" ), 1, 0.2, 1 ); + time = getAnimLength( getReactionAnim( "stealth" ) ); + self animscripts\shared::DoNoteTracksForTime( time * 0.8, "reactanim" ); + + self orientmode( "face current" ); + } + else + { + self orientmode( "face enemy" ); + self setFlaggedAnimKnobRestart( "reactanim", getReactionAnim( "stealth_backpedal" ), 1, 0.2, 1 ); + time = getAnimLength( getReactionAnim( "stealth_backpedal" ) ); + self animscripts\shared::DoNoteTracksForTime( time * 0.8, "reactanim" ); + + self orientmode( "face current" ); + + self clearanim( %root, 0.2 ); + self setFlaggedAnimKnobRestart( "reactanim", getReactionAnim( "stealth_backpedal2" ), 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "reactanim" ); + } +} + + +newEnemyReactionAnim() +{ + self endon( "death" ); + self endon( "endNewEnemyReactionAnim" ); + + self.lastReactTime = gettime(); + self.a.movement = "stop"; + + if ( isdefined( self._stealth ) && self.alertLevel != "combat" ) + { + stealthNewEnemyReactAnim(); + } + else + { + reactAnim = self getNewEnemyReactionAnim(); + + self clearanim( %root, 0.2 ); + self setFlaggedAnimKnobRestart( "reactanim", reactAnim, 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "reactanim" ); + } + + self notify( "newEnemyReactionDone" ); +} + + +newEnemySurprisedReaction() +{ + self endon( "death" ); + + if ( isdefined( self.disableReactionAnims ) ) + return; + + if ( !canReactAgain() ) + return; + + if ( self.a.pose == "prone" || isdefined( self.a.onback ) ) + return; + + self animmode( "gravity" ); + + if ( isdefined( self.enemy ) ) + newEnemyReactionAnim(); +} diff --git a/animscripts/riotshield/riotshield.gsc b/animscripts/riotshield/riotshield.gsc new file mode 100644 index 0000000..08db44c --- /dev/null +++ b/animscripts/riotshield/riotshield.gsc @@ -0,0 +1,801 @@ +#include maps\_utility; +#include animscripts\utility; +#include animscripts\Combat_utility; +#include animscripts\melee; +#include common_scripts\utility; + +RIOTSHIELD_FACE_ENEMY_DIST = 1500; +RIOTSHIELD_FORCE_WALK_DIST = 500; + +#using_animtree( "generic_human" ); + +init_riotshield_AI_anims() +{ + anim.notetracks[ "detach shield" ] = ::noteTrackDetachShield; + + animscripts\init_move_transitions::init_move_transition_arrays(); + + anim.coverTrans[ "riotshield" ] = []; + anim.coverTrans[ "riotshield" ][ 1 ] = %riotshield_run_approach_1; + anim.coverTrans[ "riotshield" ][ 2 ] = %riotshield_run_approach_2; + anim.coverTrans[ "riotshield" ][ 3 ] = %riotshield_run_approach_3; + anim.coverTrans[ "riotshield" ][ 4 ] = %riotshield_run_approach_4; + anim.coverTrans[ "riotshield" ][ 6 ] = %riotshield_run_approach_6; + anim.coverTrans[ "riotshield" ][ 7 ] = undefined; + anim.coverTrans[ "riotshield" ][ 8 ] = %riotshield_walk2crouch_8; + anim.coverTrans[ "riotshield" ][ 9 ] = undefined; + + anim.coverTrans[ "riotshield_crouch" ] = []; + anim.coverTrans[ "riotshield_crouch" ][ 1 ] = %riotshield_walk_approach_1; + anim.coverTrans[ "riotshield_crouch" ][ 2 ] = %riotshield_walk_approach_2; + anim.coverTrans[ "riotshield_crouch" ][ 3 ] = %riotshield_walk_approach_3; + anim.coverTrans[ "riotshield_crouch" ][ 4 ] = %riotshield_walk_approach_4; + anim.coverTrans[ "riotshield_crouch" ][ 6 ] = %riotshield_walk_approach_6; + anim.coverTrans[ "riotshield_crouch" ][ 7 ] = undefined; + anim.coverTrans[ "riotshield_crouch" ][ 8 ] = %riotshield_walk2crouch_8; + anim.coverTrans[ "riotshield_crouch" ][ 9 ] = undefined; + + riotshieldTransTypes = []; + riotshieldTransTypes[0] = "riotshield"; + riotshieldTransTypes[1] = "riotshield_crouch"; + + for ( j = 0; j < riotshieldTransTypes.size; j++ ) + { + trans = riotshieldTransTypes[ j ]; + + for ( i = 1; i <= 9; i++ ) + { + if ( i == 5 ) + continue; + + if ( isdefined( anim.coverTrans[ trans ][ i ] ) ) + { + anim.coverTransDist [ trans ][ i ] = getMoveDelta( anim.coverTrans[ trans ][ i ], 0, 1 ); + } + } + } + + anim.coverTransAngles[ "riotshield_crouch" ][ 1 ] = 45; + anim.coverTransAngles[ "riotshield_crouch" ][ 2 ] = 0; + anim.coverTransAngles[ "riotshield_crouch" ][ 3 ] = -45; + anim.coverTransAngles[ "riotshield_crouch" ][ 4 ] = 90; + anim.coverTransAngles[ "riotshield_crouch" ][ 6 ] = -90; + anim.coverTransAngles[ "riotshield_crouch" ][ 8 ] = 180; + + anim.coverTransAngles[ "riotshield" ][ 1 ] = 45; + anim.coverTransAngles[ "riotshield" ][ 2 ] = 0; + anim.coverTransAngles[ "riotshield" ][ 3 ] = -45; + anim.coverTransAngles[ "riotshield" ][ 4 ] = 90; + anim.coverTransAngles[ "riotshield" ][ 6 ] = -90; + anim.coverTransAngles[ "riotshield" ][ 8 ] = 180; + + anim.arrivalEndStance[ "riotshield" ] = "crouch"; + anim.arrivalEndStance[ "riotshield_crouch" ] = "crouch"; + + + animscripts\init_common::addGrenadeThrowAnimOffset( %riotshield_crouch_grenade_toss, (-3.20014, 1.7098, 55.6886) ); +} + +noteTrackDetachShield( note, flagName ) +{ + self animscripts\shared::DropAIWeapon( self.secondaryWeapon ); + self.secondaryWeapon = "none"; + + if ( isAlive( self ) ) + riotshield_turn_into_regular_ai(); +} + +riotshield_approach_type() +{ + if ( self.a.pose == "crouch" ) + return "riotshield_crouch"; + + return "riotshield"; +} + +riotshield_approach_conditions( node ) +{ + // to allow approach while facing enemy and crouch walking + return true; +} + + +init_riotshield_AI() +{ + //shieldModel = getWeaponModel( self.secondaryWeapon ); + //self attach( shieldModel, "tag_weapon_left" ); + + animscripts\shared::placeWeaponOn( self.secondaryWeapon, "left", false ); + + self.subclass = "riotshield"; // incase guy didn't spawn as a "riotshield" ai + + self.approachTypeFunc = ::riotshield_approach_type; + self.approachConditionCheckFunc = ::riotshield_approach_conditions; + + self.faceEnemyArrival = true; + self.disableCoverArrivalsOnly = true; + self.pathRandomPercent = 0; + self.interval = 0; + self.disableDoorBehavior = true; + self.no_pistol_switch = true; + self.dontShootWhileMoving = true; + self.disableBulletWhizbyReaction = true; + self.disableFriendlyFireReaction = true; + self.neverSprintForVariation = true; + self.combatMode = "no_cover"; + self.fixednode = false; + self.maxFaceEnemyDist = RIOTSHIELD_FACE_ENEMY_DIST; + self.noMeleeChargeDelay = true; + self.meleeChargeDistSq = squared( 256 ); + self.meleePlayerWhileMoving = true; + self.useMuzzleSideOffset = true; + + // fall over after getting hit this many times on the shield all within 0.3 seconds of each other + if ( level._gameSkill < 1 ) + self.shieldBulletBlockLimit = randomintrange( 4, 8 ); + else + self.shieldBulletBlockLimit = randomintrange( 8, 12 ); + + self.shieldBulletBlockCount = 0; + self.shieldBulletBlockTime = 0; + + self.walkDist = RIOTSHIELD_FORCE_WALK_DIST; + self.walkDistFacingMotion = RIOTSHIELD_FORCE_WALK_DIST; + + self.grenadeAwareness = 1; + self.frontShieldAngleCos = 0.5; + self.noGrenadeReturnThrow = true; + self.a.grenadeThrowPose = "crouch"; + self.minExposedGrenadeDist = 400; + + self.ignoresuppression = true; + + self.specialMelee_Standard = ::riotshield_melee_standard; + self.specialMeleeChooseAction = ::riotshield_melee_AIvsAI; + + self disable_turnAnims(); + self disable_surprise(); + self disable_cqbwalk(); + + init_riotshield_animsets(); + + if ( level._gameSkill < 1 ) + self.bullet_resistance = 30; + else + self.bullet_resistance = 40; + + self add_damage_function( maps\_spawner::bullet_resistance ); + self add_damage_function( animscripts\pain::additive_pain ); +} + + +riotshield_charge() +{ + if ( !Melee_Standard_UpdateAndValidateTarget() ) + return false; + + // get from animation + delta = getMoveDelta( %riotshield_bashA_attack, 0, 1 ); + rangeSq = lengthSquared( delta ); + + if ( distanceSquared( self.origin, self.melee.target.origin ) < rangeSq ) + return true; + + self animscripts\melee::Melee_PlayChargeSound(); + + sampleTime = 0.1; + firstTry = true; + + while ( 1 ) + { + assert( isdefined( self.melee.target ) ); + + // now that we moved a bit, see if our target moved before we check for valid melee + // it's possible something happened in the meantime that makes meleeing impossible. + if ( !Melee_Standard_UpdateAndValidateTarget() ) + return false; + + if ( firstTry ) + { + self.a.pose = "stand"; + self SetFlaggedAnimKnobAll( "chargeanim", %riotshield_sprint, %body, 1, .2, 1 ); + firstTry = false; + } + + self orientMode( "face point", self.melee.target.origin ); + self animscripts\shared::DoNoteTracksForTime( sampleTime, "chargeanim" ); + + enemyDistanceSq = distanceSquared( self.origin, self.melee.target.origin ); + + // if we're done raising our gun, and starting a melee now will hit the guy, our preparation is finished + if ( enemyDistanceSq < rangeSq ) + break; + + // don't keep charging if we've been doing this for too long. + if ( gettime() >= self.melee.giveUpTime ) + return false; + } + + return true; +} + + +riotshield_melee_standard() +{ + self animMode( "zonly_physics" ); + + animscripts\melee::Melee_Standard_ResetGiveUpTime(); + + while ( true ) + { + if ( !riotshield_charge() ) + { + // if we couldn't get in place to melee, don't try to charge for a little while and abort + self.nextMeleeChargeTime = getTime() + 1500; + self.nextMeleeChargeTarget = self.melee.target; + break; + } + + assert( (self.a.pose == "stand") || (self.a.pose == "crouch") ); + + self animscripts\battleChatter_ai::evaluateMeleeEvent(); + + self orientMode( "face point", self.melee.target.origin ); + self setflaggedanimknoballrestart( "meleeanim", %riotshield_bash_vs_player, %body, 1, .2, 1 ); + + self.melee.inProgress = true; + + // If the attack loop returns false, we need to stop this melee + if( !animscripts\melee::Melee_Standard_PlayAttackLoop() ) + { + // Since getting here means that we've done a melee but our attack is no longer valid, delay before we can do a standard attack again. + animscripts\melee::Melee_Standard_DelayStandardCharge( self.melee.target ); + break; + } + + self animMode( "none" ); + } + + self animMode( "none" ); +} + +riotshield_melee_AIvsAI() +{ + assert( isDefined( self ) ); + assert( isDefined( self.melee.target ) ); + + target = self.melee.target; + + animscripts\melee::Melee_Decide_Winner(); + + // Choose which sequence to play based on angles + angleToEnemy = vectortoangles( target.origin - self.origin ); + angleDiff = AngleClamp180( target.angles[ 1 ] - angleToEnemy[ 1 ] ); + + if ( abs( angleDiff ) > 100 ) // facing each other + { + if ( self.melee.winner ) + { + if ( self.subclass == "riotshield" ) + { + self.melee.animName = %riotshield_bashA_attack; + target.melee.animName = %riotshield_bashA_defend; + target.melee.surviveAnimName = %riotshield_bashA_defend_survive; + } + else + { + assert( target.subclass == "riotshield" ); + self.melee.animName = %riotshield_bashB_defend; + target.melee.animName = %riotshield_bashB_attack; + } + } + else + { + if ( self.subclass == "riotshield" ) + { + self.melee.animName = %riotshield_bashB_attack; + target.melee.animName = %riotshield_bashB_defend; + } + else + { + assert( target.subclass == "riotshield" ); + self.melee.animName = %riotshield_bashA_defend; + target.melee.animName = %riotshield_bashA_attack; + } + } + } + else + { + return false; + } + + self.melee.startPos = getStartOrigin( target.origin, target.angles, self.melee.animName ); + self.melee.startAngles = ( target.angles[0], AngleClamp180( target.angles[1] + 180 ), target.angles[2] ); + + self.lockOrientation = false; + target.lockOrientation = false; + + // Make sure we can move to the selected point ( no re-try for now ) + return Melee_UpdateAndValidateStartPos(); +} + +riotshield_startMoveTransition() +{ + if ( isdefined( self.disableExits ) ) + return; + + self orientmode( "face angle", self.angles[1] ); + self animmode( "zonly_physics", false ); + + if ( self.a.pose == "crouch" ) + { + if ( isdefined( self.sprint ) || isdefined( self.fastwalk ) ) + transAnim = %riotshield_crouch2stand; + else + transAnim = %riotshield_crouch2walk; + + rate = randomfloatrange( 0.9, 1.1 ); + self setFlaggedAnimKnobAllRestart( "startmove", transAnim, %body, 1, .1, rate ); + self animscripts\shared::DoNoteTracks( "startmove" ); + self clearanim( %riotshield_crouch2walk, 0.5 ); + } + + if ( isdefined( self.sprint ) || isdefined( self.fastwalk ) ) + { + self allowedStances( "stand", "crouch" ); + self.a.pose = "stand"; + } + + self orientmode( "face default" ); + self animMode( "normal", false ); + + self thread riotshield_bullet_hit_shield(); +} + +riotshield_endMoveTransition() +{ + if ( self.prevScript == "move" && self.a.pose == "crouch" ) + { + self clearAnim( %root, .2 ); + + rate = randomfloatrange( 0.9, 1.1 ); + self animmode( "zonly_physics" ); + self setFlaggedAnimKnobAllRestart( "endmove", %riotshield_walk2crouch_8, %body, 1, .2, rate ); + self animscripts\shared::DoNoteTracks( "endmove" ); + self animMode( "normal" ); + } + + self allowedStances( "crouch" ); +} + +riotshield_startCombat() +{ + //assertex( self.combatmode == "no_cover", "riotshield AI combat mode should be 'no_cover'" ); + riotshield_endMoveTransition(); + self.pushable = false; + self thread riotshield_bullet_hit_shield(); +} + +riotshield_bullet_hit_shield() +{ + self endon( "killanimscript" ); + + while (1) + { + self waittill( "bullet_hitshield" ); + + time = gettime(); + if ( time - self.shieldBulletBlockTime > 500 ) + self.shieldBulletBlockCount = 0; + else + self.shieldBulletBlockCount++; + + self.shieldBulletBlockTime = time; + if ( self.shieldBulletBlockCount > self.shieldBulletBlockLimit ) + self doDamage( 1, ( 0, 0, 0 ) ); // do minimal damage to fall down + + if ( cointoss() ) + reactAnim = %riotshield_reactA; + else + reactAnim = %riotshield_reactB; + + self notify( "new_hit_react" ); + self setFlaggedAnimRestart( "hitreact", reactAnim, 1, 0.1, 1 ); + self thread riotshield_bullet_hit_shield_clear(); + } +} + + +riotshield_bullet_hit_shield_clear() +{ + self endon( "killanimscript" ); + self endon( "new_hit_react" ); + + self waittillmatch( "hitreact", "end" ); + self clearanim( %riotshield_react, 0.1 ); +} + + +riotshield_grenadeCower() +{ + if ( self.a.pose == "stand" ) + { + self clearanim( %root, .2 ); + self setFlaggedAnimKnobAllRestart( "trans", %riotshield_walk2crouch_8, %body, 1, .2, 1.2 ); + self animscripts\shared::DoNoteTracks( "trans" ); + } + + if ( isdefined( self.grenade ) ) + { + faceGrenade = true; + dirToGrenade = self.grenade.origin - self.origin; + + if ( isdefined( self.enemy ) ) + { + dirToEnemy = self.enemy.origin - self.origin; + if ( vectorDot( dirToGrenade, dirToEnemy ) < 0 ) + faceGrenade = false; + } + + if ( faceGrenade ) + { + relYaw = AngleClamp180( self.angles[ 1 ] - vectorToYaw( dirToGrenade ) ); + + if ( !isdefined( self.turnThreshold ) ) + self.turnThreshold = 55; + + while ( abs( relYaw ) > self.turnThreshold ) + { + if ( !isdefined( self.a.array ) ) + animscripts\combat::setup_anim_array(); + + if ( !self animscripts\combat::TurnToFaceRelativeYaw( relYaw ) ) + break; + + relYaw = AngleClamp180( self.angles[ 1 ] - vectorToYaw( dirToGrenade ) ); + } + } + } + + self setAnimKnobAll( %riotshield_crouch_aim_5, %body, 1, 0.2, 1 ); + self setFlaggedAnimKnobAllRestart( "grenadecower", %riotshield_crouch_idle_add, %add_idle, 1, 0.2, self.animplaybackrate ); + self animscripts\shared::DoNoteTracks( "grenadecower" ); +} + + +riotshield_flashbang() +{ + self notify( "flashed" ); + + if ( !isdefined( self.a.onback ) ) + { + rate = randomfloatrange( 0.9, 1.1 ); + self.frontShieldAngleCos = 1; + + flashArray = []; + flashArray[0] = %riotshield_crouch_grenade_flash1; + flashArray[1] = %riotshield_crouch_grenade_flash2; + flashArray[2] = %riotshield_crouch_grenade_flash3; + flashArray[3] = %riotshield_crouch_grenade_flash4; + flashAnim = flashArray[ randomint( flashArray.size ) ]; + + self setFlaggedAnimKnobAllRestart( "flashanim", flashAnim, %body, 1, .1, rate ); + self.minPainDamage = 1000; + } + + self animscripts\shared::DoNoteTracks( "flashanim" ); + self.minPainDamage = 0; + self.frontShieldAngleCos = 0.5; +} + + +riotshield_pain() +{ + // all the pain animations are in crouch + self.a.pose = "crouch"; + + if ( usingSideArm() ) + forceUseWeapon( self.primaryweapon, "primary" ); + + if ( !isdefined( self.a.onback ) ) + { + rate = randomfloatrange( 0.8, 1.15 ); + self.frontShieldAngleCos = 1; + if ( ( self.damageYaw < -120 || self.damageYaw > 120 ) && isExplosiveDamageMOD( self.damageMOD ) ) + { + painArray = []; + painArray[0] = %riotshield_crouch_grenade_blowback; + painArray[1] = %riotshield_crouch_grenade_blowbackL; + painArray[2] = %riotshield_crouch_grenade_blowbackR; + painAnim = painArray[ randomint( painArray.size ) ]; + + self setFlaggedAnimKnobAllRestart( "painanim", painAnim, %body, 1, .2, rate ); + self.minPainDamage = 1000; + } + else + { + self setFlaggedAnimKnobAllRestart( "painanim", %riotshield_crouch_pain, %body, 1, .2, rate ); + } + } + + self animscripts\shared::DoNoteTracks( "painanim" ); + self.minPainDamage = 0; + self.frontShieldAngleCos = 0.5; +} + +riotshield_death() +{ + if ( isdefined( self.a.onback ) && self.a.pose == "crouch" ) + { + deathArray = []; + deathArray[0] = %dying_back_death_v2; + deathArray[1] = %dying_back_death_v3; + deathArray[2] = %dying_back_death_v4; + deathAnim = deathArray[ randomint( deathArray.size ) ]; + + self animscripts\death::playDeathAnim( deathAnim ); + return true; + } + + if ( self.prevScript == "pain" || self.prevScript == "flashed" ) + doShieldDeath = randomInt( 2 ) == 0; + else + doShieldDeath = true; + + if ( doShieldDeath ) + { + if ( cointoss() ) + deathAnim = %riotshield_crouch_death; + else + deathAnim = %riotshield_crouch_death_fallback; + + self animscripts\death::playDeathAnim( deathAnim ); + return true; + } + + self.a.pose = "crouch"; + return false; +} + +init_riotshield_animsets() +{ + // move animations + animset = []; + animset[ "sprint" ] = %riotshield_sprint; + animset[ "prone" ] = %prone_crawl; + + animset[ "straight" ] = %riotshield_run_F; + animset[ "straight_variation" ] = %riotshield_run_F; + + animset[ "move_f" ] = %riotshield_run_F; + animset[ "move_l" ] = %riotshield_run_L; + animset[ "move_r" ] = %riotshield_run_R; + animset[ "move_b" ] = %riotshield_run_B; + + animset[ "crouch" ] = %riotshield_crouchwalk_F; + animset[ "crouch_l" ] = %riotshield_crouchwalk_L; + animset[ "crouch_r" ] = %riotshield_crouchwalk_R; + animset[ "crouch_b" ] = %riotshield_crouchwalk_B; + + animset[ "stairs_up" ] = %traverse_stair_run_01; + animset[ "stairs_down" ] = %traverse_stair_run_down; + + self.customMoveAnimSet[ "run" ] = animset; + self.customMoveAnimSet[ "walk" ] = animset; + self.customMoveAnimSet[ "cqb" ] = animset; + + self.customIdleAnimSet = []; + self.customIdleAnimSet[ "crouch" ] = %riotshield_crouch_aim_5; + self.customIdleAnimSet[ "crouch_add" ] = %riotshield_crouch_idle_add; + self.customIdleAnimSet[ "stand" ] = %riotshield_crouch_aim_5; + self.customIdleAnimSet[ "stand_add" ] = %riotshield_crouch_idle_add; + + self.a.pose = "crouch"; + self allowedStances( "crouch" ); + + // combat animations + animset = anim.animsets.defaultStand; + + animset[ "add_aim_up" ] = %riotshield_crouch_aim_8; + animset[ "add_aim_down" ] = %riotshield_crouch_aim_2; + animset[ "add_aim_left" ] = %riotshield_crouch_aim_4; + animset[ "add_aim_right" ] = %riotshield_crouch_aim_6; + + animset[ "straight_level" ] = %riotshield_crouch_aim_5; + + animset[ "fire" ] = %riotshield_crouch_fire_auto; + animset[ "single" ] = array( %riotshield_crouch_fire_single ); + + // remove this burst, semi nonsense soon + animset[ "burst2" ] = %riotshield_crouch_fire_burst; + animset[ "burst3" ] = %riotshield_crouch_fire_burst; + animset[ "burst4" ] = %riotshield_crouch_fire_burst; + animset[ "burst5" ] = %riotshield_crouch_fire_burst; + animset[ "burst6" ] = %riotshield_crouch_fire_burst; + animset[ "semi2" ] = %riotshield_crouch_fire_burst; + animset[ "semi3" ] = %riotshield_crouch_fire_burst; + animset[ "semi4" ] = %riotshield_crouch_fire_burst; + animset[ "semi5" ] = %riotshield_crouch_fire_burst; + + animset[ "exposed_idle" ] = array( %riotshield_crouch_idle_add, %riotshield_crouch_twitch ); + animset[ "exposed_grenade" ] = array( %riotshield_crouch_grenade_toss ); + + animset[ "reload" ] = array( %riotshield_crouch_reload ); + animset[ "reload_crouchhide" ] = array( %riotshield_crouch_reload ); + + animset[ "turn_left_45" ] = %riotshield_crouch_Lturn; + animset[ "turn_left_90" ] = %riotshield_crouch_Lturn; + animset[ "turn_left_135" ] = %riotshield_crouch_Lturn; + animset[ "turn_left_180" ] = %riotshield_crouch_Lturn; + animset[ "turn_right_45" ] = %riotshield_crouch_Rturn; + animset[ "turn_right_90" ] = %riotshield_crouch_Rturn; + animset[ "turn_right_135" ] = %riotshield_crouch_Rturn; + animset[ "turn_right_180" ] = %riotshield_crouch_Rturn; + + animset[ "stand_2_crouch" ] = %riotshield_walk2crouch_8; + + self animscripts\init_common::set_animset_complete_custom_stand( animset ); + self animscripts\init_common::set_animset_complete_custom_crouch( animset ); + + self.choosePoseFunc = ::riotshield_choose_pose; + self.painFunction = ::riotshield_pain; + self.specialDeathFunc = ::riotshield_death; + self.specialFlashedFunc = ::riotshield_flashbang; + self.grenadeCowerFunction = ::riotshield_grenadeCower; + self.customMoveTransition = ::riotshield_startMoveTransition; + self.permanentCustomMoveTransition = true; + + set_exception( "exposed", ::riotshield_startCombat ); + //set_exception( "stop_immediate", ::riotshield_endMoveTransition ); +} + +riotshield_choose_pose( preferredPose ) +{ + if ( isdefined( self.grenade ) ) + return "stand"; + + return self animscripts\utility::choosePose( preferredPose ); +} + + +riotshield_sprint_on() +{ + self.maxFaceEnemyDist = 128; + self.sprint = true; + self orientmode( "face default" ); + self.lockorientation = false; + + self.walkDist = 32; + self.walkDistFacingMotion = 32; +} + +riotshield_fastwalk_on() +{ + self.maxFaceEnemyDist = 128; + self.fastwalk = true; + + self.walkDist = 32; + self.walkDistFacingMotion = 32; +} + + +riotshield_sprint_off() +{ + self.maxFaceEnemyDist = RIOTSHIELD_FACE_ENEMY_DIST; + + self.walkDist = RIOTSHIELD_FORCE_WALK_DIST; + self.walkDistFacingMotion = RIOTSHIELD_FORCE_WALK_DIST; + self.sprint = undefined; + self allowedStances( "crouch" ); +} + +riotshield_fastwalk_off() +{ + self.maxFaceEnemyDist = RIOTSHIELD_FACE_ENEMY_DIST; + + self.walkDist = RIOTSHIELD_FORCE_WALK_DIST; + self.walkDistFacingMotion = RIOTSHIELD_FORCE_WALK_DIST; + self.fastwalk = undefined; + self allowedStances( "crouch" ); +} + +null_func() +{ +} + +riotshield_init_flee() +{ + // hack to restart move script + if ( self.script == "move" ) + self animcustom( ::null_func ); + + self.customMoveTransition = ::riotshield_flee_and_drop_shield; +} + +riotshield_flee_and_drop_shield() +{ + // restore this incase flee gets interrupted + self.customMoveTransition = ::riotshield_startMoveTransition; + + self animmode( "zonly_physics", false ); + self orientmode( "face current" ); + + if ( !isdefined( self.dropShieldInPlace ) && isdefined( self.enemy ) && vectordot( self.lookaheadDir, anglesToForward( self.angles ) ) < 0 ) + fleeAnim = %riotshield_crouch2walk_2flee; + else + fleeAnim = %riotshield_crouch2stand_shield_drop; + + rate = randomFloatRange( 0.85, 1.1 ); + self SetFlaggedAnimKnobAll( "fleeanim", fleeAnim, %root, 1, .1, rate ); + self animscripts\shared::DoNoteTracks( "fleeanim" ); // return on code_move + + self.maxFaceEnemyDist = 32; + self.lockOrientation = false; + self orientmode( "face default" ); + self animmode( "normal", false ); + self animscripts\shared::DoNoteTracks( "fleeanim" ); + self clearanim( fleeAnim, 0.2 ); + self.maxFaceEnemyDist = 128; +} + +riotshield_turn_into_regular_ai() +{ + self.subclass = "regular"; + + self.combatMode = "cover"; + + self.approachTypeFunc = undefined; + self.approachConditionCheckFunc = undefined; + self.faceEnemyArrival = undefined; + self.disableCoverArrivalsOnly = undefined; + self.pathRandomPercent = 0; + self.interval = 80; + self.disableDoorBehavior = undefined; + self.no_pistol_switch = undefined; + self.dontShootWhileMoving = undefined; + self.disableBulletWhizbyReaction = undefined; + self.disableFriendlyFireReaction = undefined; + self.neverSprintForVariation = undefined; + self.maxFaceEnemyDist = 128; + self.noMeleeChargeDelay = undefined; + self.meleeChargeDistSq = undefined; + self.meleePlayerWhileMoving = undefined; + self.useMuzzleSideOffset = undefined; + + self.pathEnemyFightDist = 128; + self.pathenemylookahead = 128; + + self.walkDist = 256; + self.walkDistFacingMotion = 64; + self.lockorientation = false; + + self.frontShieldAngleCos = 1; + self.noGrenadeReturnThrow = false; + + self.ignoresuppression = false; + self.sprint = undefined; + + self allowedStances( "stand", "crouch", "prone" ); + + self.specialMelee_Standard = undefined; + self.specialMeleeChooseAction = undefined; + + self enable_turnAnims(); + + self.bullet_resistance = undefined; + self remove_damage_function( maps\_spawner::bullet_resistance ); + self remove_damage_function( animscripts\pain::additive_pain ); + + self animscripts\init_common::clear_custom_animset(); + + self.choosePoseFunc = animscripts\utility::choosePose; + self.painFunction = undefined; + self.specialDeathFunc = undefined; + self.specialFlashedFunc = undefined; + self.grenadeCowerFunction = undefined; + self.customMoveTransition = undefined; + self.permanentCustomMoveTransition = undefined; + + clear_exception( "exposed" ); + clear_exception( "stop_immediate" ); +} \ No newline at end of file diff --git a/animscripts/run.gsc b/animscripts/run.gsc new file mode 100644 index 0000000..a88b6fa --- /dev/null +++ b/animscripts/run.gsc @@ -0,0 +1,1177 @@ +#include animscripts\Utility; +#include animscripts\Combat_Utility; +#include animscripts\SetPoseMovement; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + +MoveRun() +{ + desiredPose = [[ self.choosePoseFunc ]]( "stand" ); + + switch( desiredPose ) + { + case "stand": + if ( BeginStandRun() )// returns false( and does nothing ) if we're already stand - running + return; + + if ( isDefined( self.run_overrideanim ) ) + { + animscripts\move::MoveStandMoveOverride( self.run_overrideanim, self.run_override_weights ); + return; + } + + if ( changeWeaponStandRun() ) + return; + + if ( ReloadStandRun() ) + return; + + if ( self animscripts\utility::IsInCombat() ) + { + if ( self.subclass == "moon" ) + { + MoveStandCombatMoon(); + } + else + { + MoveStandCombatNormal(); + } + } + else + { + if ( self.subclass == "moon" ) + { + MoveStandNoncombatMoon(); + } + else + { + MoveStandNoncombatNormal(); + } + } + break; + + case "crouch": + if ( BeginCrouchRun() )// returns false( and does nothing ) if we're already crouch - running + return; + + if ( isDefined( self.crouchrun_combatanim ) ) + { + MoveCrouchRunOverride(); + } + else + if ( self.subclass == "moon" ) + { + MoveCrouchRunMoon(); + } + else + { + MoveCrouchRunNormal(); + } + break; + + default: + assert( desiredPose == "prone" ); + if ( BeginProneRun() )// returns false( and does nothing ) if we're already prone - running + return; + + ProneCrawl(); + break; + } +} + +GetRunAnim() +{ + if ( !isdefined( self.a.moveAnimSet ) ) + return anim.animsets.move[ "run" ][ "straight" ]; + + if ( !self.faceMotion ) + { + if ( self.stairsState == "none" || abs( self getMotionAngle() ) > 45 ) + return moveAnim( "move_f" ); + } + + if ( self.stairsState == "up" ) + return moveAnim( "stairs_up" ); + else if ( self.stairsState == "down" ) + return moveAnim( "stairs_down" ); + + return moveAnim( "straight" ); +} + +GetCrouchRunAnim() +{ + if ( !isdefined( self.a.moveAnimSet ) ) + return anim.animsets.move[ "run" ][ "crouch" ]; + + return moveAnim( "crouch" ); +} + + +ProneCrawl() +{ + self.a.movement = "run"; + self setflaggedanimknob( "runanim", moveAnim( "prone" ), 1, .3, self.moveplaybackrate ); + animscripts\shared::DoNoteTracksForTime( 0.25, "runanim" ); +} + + +InitRunNGun() +{ + if ( !isdefined( self.runNGun ) ) + { + self notify( "stop_move_anim_update" ); + self.update_move_anim_type = undefined; + + self clearanim( %combatrun_backward, 0.2 ); + self clearanim( %combatrun_right, 0.2 ); + self clearanim( %combatrun_left, 0.2 ); + + self clearanim( %w_aim_2, 0.2 ); + self clearanim( %w_aim_4, 0.2 ); + self clearanim( %w_aim_6, 0.2 ); + self clearanim( %w_aim_8, 0.2 ); + + self.runNGun = true; + } +} + +StopRunNGun() +{ + if ( isdefined( self.runNGun ) ) + { + self clearanim( %run_n_gun, 0.2 ); + self.runNGun = undefined; + } + + return false; +} + + +RunNGun( validTarget ) +{ + if ( validTarget ) + { + enemyyaw = self GetPredictedYawToEnemy( 0.2 ); + leftWeight = enemyyaw < 0; + } + else + { + enemyyaw = 0; + leftWeight = self.runNGunWeight < 0; + } + + rightWeight = 1 - leftWeight; + + maxRunNGunAngle = self.maxRunNGunAngle; + runNGunTransitionPoint = self.runNGunTransitionPoint; + runNGunIncrement = self.runNGunIncrement; + + if ( !validTarget || ( squared( enemyyaw ) > maxRunNGunAngle * maxRunNGunAngle ) ) + { + // phase out run n gun + self clearAnim( %add_fire, 0 ); + if ( squared( self.runNGunWeight ) < runNGunIncrement * runNGunIncrement ) + { + self.runNGunWeight = 0; + self.runNGun = undefined; + return false; + } + else if ( self.runNGunWeight > 0 ) + { + self.runNGunWeight = self.runNGunWeight - runNGunIncrement; + } + else + { + self.runNGunWeight = self.runNGunWeight + runNGunIncrement; + } + } + else + { + newWeight = enemyyaw / maxRunNGunAngle; + diff = newWeight - self.runNGunWeight; + + if ( abs( diff ) < runNGunTransitionPoint * 0.7 ) + self.runNGunWeight = newWeight; + else if ( diff > 0 ) + self.runNGunWeight = self.runNGunWeight + runNGunIncrement; + else + self.runNGunWeight = self.runNGunWeight - runNGunIncrement; + } + + InitRunNGun(); + + absRunNGunWeight = abs( self.runNGunWeight ); + + if ( absRunNGunWeight > runNGunTransitionPoint ) + { + weight = ( absRunNGunWeight - runNGunTransitionPoint ) / runNGunTransitionPoint; + weight = clamp( weight, 0, 1 ); + + self clearanim( self.runNGunAnims[ "F" ], 0.2 ); + self setAnimLimited( self.runNGunAnims[ "L" ], ( 1.0 - weight ) * leftWeight, 0.2 ); + self setAnimLimited( self.runNGunAnims[ "R" ], ( 1.0 - weight ) * rightWeight, 0.2 ); + self setAnimLimited( self.runNGunAnims[ "LB" ], weight * leftWeight, 0.2 ); + self setAnimLimited( self.runNGunAnims[ "RB" ], weight * rightWeight, 0.2 ); + } + else + { + weight = clamp( absRunNGunWeight / runNGunTransitionPoint, 0, 1 ); + + self setAnimLimited( self.runNGunAnims[ "F" ], 1.0 - weight, 0.2 ); + self setAnimLimited( self.runNGunAnims[ "L" ], weight * leftWeight, 0.2 ); + self setAnimLimited( self.runNGunAnims[ "R" ], weight * rightWeight, 0.2 ); + + if ( runNGunTransitionPoint < 1 ) + { + self clearanim( self.runNGunAnims[ "LB" ], 0.2 ); + self clearanim( self.runNGunAnims[ "RB" ], 0.2 ); + } + } + + self setFlaggedAnimKnob( "runanim", %run_n_gun, 1, 0.3, 0.8 ); + + self.a.allowedPartialReloadOnTheRunTime = gettime() + 500; + + if ( validTarget && isplayer( self.enemy ) ) + self updatePlayerSightAccuracy(); + + return true; +} + +RunNGun_Backward() +{ + // we don't blend the running-backward animation because it + // doesn't blend well with the run-left and run-right animations. + // it's also easier to just play one animation than rework everything + // to consider the possibility of multiple "backwards" animations + + InitRunNGun(); + + self setFlaggedAnimKnob( "runanim", %combatwalk_B, 1, 0.3, 0.8 ); + + if ( isplayer( self.enemy ) ) + self updatePlayerSightAccuracy(); + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); + + self thread stopShootWhileMovingThreads(); + + self clearAnim( %combatwalk_B, 0.2 ); +} + + +ReactToBulletsInterruptCheck() +{ + self endon( "killanimscript" ); + + while ( 1 ) + { + wait 0.2; + + if ( !isdefined( self.reactingToBullet ) ) + break; + + if ( !isdefined( self.pathGoalPos ) || distanceSquared( self.pathGoalPos, self.origin ) < squared( 80 ) ) + { + EndRunningReactToBullets(); + self notify( "interrupt_react_to_bullet" ); + break; + } + } +} + +EndRunningReactToBullets() +{ + self orientmode( "face default" ); + self.reactingToBullet = undefined; + self.requestReactToBullet = undefined; +} + +RunningReactToBullets() +{ + self.aim_while_moving_thread = undefined; + self notify( "end_face_enemy_tracking" ); + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ + + self endon( "interrupt_react_to_bullet" ); + + self.reactingToBullet = true; + self orientmode( "face motion" ); + + reactAnimIndex = randomint( anim.runningReactToBullets.size ); + if ( reactAnimIndex == anim.lastRunningReactAnim ) + reactAnimIndex = ( reactAnimIndex + 1 ) % anim.runningReactToBullets.size; + + anim.lastRunningReactAnim = reactAnimIndex; + + reactAnim = anim.runningReactToBullets[ reactAnimIndex ]; + self setFlaggedAnimKnobRestart( "reactanim", reactAnim, 1, 0.5 ); + + self thread ReactToBulletsInterruptCheck(); + self animscripts\shared::DoNoteTracks( "reactanim" ); + + EndRunningReactToBullets(); +} + + +CustomRunningReactToBullets() +{ + self.aim_while_moving_thread = undefined; + self notify( "end_face_enemy_tracking" ); + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ + + self.reactingToBullet = true; + self orientmode( "face motion" ); + + assert( isdefined( self.run_overrideBulletReact ) ); + + reactAnimIndex = randomint( self.run_overrideBulletReact.size ); + reactAnim = self.run_overrideBulletReact[ reactAnimIndex ]; + + self setFlaggedAnimKnobRestart( "reactanim", reactAnim, 1, 0.5 ); + self thread ReactToBulletsInterruptCheck(); + self animscripts\shared::DoNoteTracks( "reactanim" ); + + EndRunningReactToBullets(); +} + + +GetSprintAnim() +{ + sprintAnim = undefined; + + if ( isdefined( self.grenade ) ) + sprintAnim = moveAnim( "sprint_short" ); + + if ( !isdefined( sprintAnim ) ) + sprintAnim = moveAnim( "sprint" ); + + return sprintAnim; +} + +ShouldSprint() +{ + if ( isdefined( self.sprint ) ) + return true; + + if ( isdefined( self.grenade ) && isdefined( self.enemy ) && self.frontShieldAngleCos == 1 ) + return ( distanceSquared( self.origin, self.enemy.origin ) > 300 * 300 ); + + return false; +} + + +ShouldSprintForVariation() +{ + if ( isdefined( self.neverSprintForVariation ) ) + return false; + + if ( !self.faceMotion || self.stairsState != "none" ) + return false; + + time = gettime(); + + if ( isdefined( self.dangerSprintTime ) ) + { + if ( time < self.dangerSprintTime ) + return true; + + // if already sprinted, don't do it again for at least 5 seconds + if ( time - self.dangerSprintTime < 6000 ) + return false; + } + + if ( !isdefined( self.enemy ) || !isSentient( self.enemy ) ) + return false; + + if ( randomInt( 100 ) < 25 && ( self lastKnownTime( self.enemy ) + 2000 ) > time ) + { + self.dangerSprintTime = time + 2000 + randomint( 1000 ); + return true; + } + + return false; +} + +GetMovePlaybackRate() +{ + rate = self.moveplaybackrate; + + if ( self.lookaheadHitsStairs && self.stairsState == "none" && self.lookaheadDist < 300 ) + rate *= 0.75; + + return rate; +} + +MoveStandCombatNormal() +{ + //self clearanim( %walk_and_run_loops, 0.2 ); + + rate = GetMovePlaybackRate(); + + self setanimknob( %combatrun, 1.0, 0.5, rate ); + + decidedAnimation = false; + + if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && randomFloat( 1 ) < self.a.reactToBulletChance ) + { + StopRunNGun(); + RunningReactToBullets(); + return; + } + + if ( self ShouldSprint() ) + { + self setFlaggedAnimKnob( "runanim", GetSprintAnim(), 1, 0.5 ); + decidedAnimation = true; + } + else if ( isdefined( self.enemy ) && animscripts\move::MayShootWhileMoving() ) + { + runShootWhileMovingThreads(); + + if ( !self.faceMotion ) + { + self thread faceEnemyAimTracking(); + } + else if ( ( self.shootStyle != "none" && !isdefined( self.noRunNGun ) ) ) + { + self notify( "end_face_enemy_tracking" ); + self.aim_while_moving_thread = undefined; + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ + + if ( CanShootWhileRunningForward() ) + { + decidedAnimation = self RunNGun( true ); + } + else if ( CanShootWhileRunningBackward() ) + { + self RunNGun_Backward(); + return; + } + } + else if ( isdefined( self.runNGunWeight ) && self.runNGunWeight != 0 ) + { + // can't shoot enemy anymore but still need to clear out runNGun + decidedAnimation = self RunNGun( false ); + } + } + else if ( isdefined( self.runNGunWeight ) && self.runNGunWeight != 0 ) + { + decidedAnimation = self RunNGun( false ); + } + + if ( !decidedAnimation ) + { + StopRunNGun(); + + if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && self.a.reactToBulletChance != 0 ) + { + RunningReactToBullets(); + return; + } + + if ( ShouldSprintForVariation() ) + runAnim = moveAnim( "sprint_short" ); + else + runAnim = GetRunAnim(); + + self setFlaggedAnimKnobLimited( "runanim", runAnim, 1, 0.1, 1, true ); + self set_move_anim_start_point(); + self SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ), self.sideStepRate ); + + // Play the appropriately weighted run animations for the direction he's moving + self thread SetCombatStandMoveAnimWeights( "run" ); + } + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); + + self thread stopShootWhileMovingThreads(); +} + +//blend differently than MoveStandCombatNormal based on a dvar. This is done so cqb blend can be changed on the moon. +MoveStandCombatMoon() +{ + + rate = GetMovePlaybackRate(); + + run_anim_blend = 0.8; + if ( IsDefined( self.run_blend_finish_time ) ) + { + run_anim_blend = self.run_blend_finish_time; + self.run_blend_finish_time = undefined; + } + + run_blend_str = GetDvar("scr_run_blend"); + if(run_blend_str != "") + { + run_anim_blend = Float(run_blend_str); + } + + self setanimknob( %combatrun, 1.0, run_anim_blend, rate ); + + decidedAnimation = false; + + if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && randomFloat( 1 ) < self.a.reactToBulletChance ) + { + StopRunNGun(); + RunningReactToBullets(); + return; + } + + if ( self ShouldSprint() ) + { + self setFlaggedAnimKnob( "runanim", GetSprintAnim(), 1, run_anim_blend ); + decidedAnimation = true; + } + else if ( isdefined( self.enemy ) && animscripts\move::MayShootWhileMoving() ) + { + runShootWhileMovingThreads(); + + if ( !self.faceMotion ) + { + self thread faceEnemyAimTracking(); + } + else if ( ( self.shootStyle != "none" && !isdefined( self.noRunNGun ) ) ) + { + self notify( "end_face_enemy_tracking" ); + self.aim_while_moving_thread = undefined; + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ + + if ( CanShootWhileRunningForward() ) + { + decidedAnimation = self RunNGun( true ); + } + else if ( CanShootWhileRunningBackward() ) + { + self RunNGun_Backward(); + return; + } + } + else if ( isdefined( self.runNGunWeight ) && self.runNGunWeight != 0 ) + { + // can't shoot enemy anymore but still need to clear out runNGun + decidedAnimation = self RunNGun( false ); + } + } + else if ( isdefined( self.runNGunWeight ) && self.runNGunWeight != 0 ) + { + decidedAnimation = self RunNGun( false ); + } + + if ( !decidedAnimation ) + { + StopRunNGun(); + + if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && self.a.reactToBulletChance != 0 ) + { + RunningReactToBullets(); + return; + } + + if ( ShouldSprintForVariation() ) + runAnim = moveAnim( "sprint_short" ); + else + runAnim = GetRunAnim(); + + self setFlaggedAnimKnobLimited( "runanim", runAnim, 1, run_anim_blend, 1, true ); + self set_move_anim_start_point(); + self SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ), self.sideStepRate ); + + // Play the appropriately weighted run animations for the direction he's moving + self thread SetCombatStandMoveAnimWeights( "run" ); + } + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); + + self thread stopShootWhileMovingThreads(); +} + +faceEnemyAimTracking() +{ + self notify( "want_aim_while_moving" ); + + assert( isdefined( self.aim_while_moving_thread ) == isdefined( self.trackLoopThread ) ); + assertex( !isdefined( self.trackLoopThread ) || (self.trackLoopThreadType == "faceEnemyAimTracking"), self.trackLoopThreadType ); + + if ( isdefined( self.aim_while_moving_thread ) ) + return; + + self.aim_while_moving_thread = true; + +/# + self.trackLoopThread = thisthread; + self.trackLoopThreadType = "faceEnemyAimTracking"; +#/ + + self endon( "killanimscript" ); + self endon( "end_face_enemy_tracking" ); + + self setDefaultAimLimits(); + + self setAnimLimited( get_run_aim_tracking_anim_by_name( "add_aim_down" ) ); + self setAnimLimited( get_run_aim_tracking_anim_by_name( "add_aim_left" ) ); + self setAnimLimited( get_run_aim_tracking_anim_by_name( "add_aim_right" ) ); + self setAnimLimited( get_run_aim_tracking_anim_by_name( "add_aim_up" ) ); + + self animscripts\shared::trackLoop( %w_aim_2, %w_aim_4, %w_aim_6, %w_aim_8 ); +} + +get_run_aim_tracking_anim_by_name( name ) +{ + assert( IsDefined( name ) ); + assert( IsDefined( anim.animsets.runAimTracking[name] ) ); + + if( IsDefined( self.customRunAimTrackingAnimset ) && IsDefined( self.customRunAimTrackingAnimset[name] ) ) + { + return self.customRunAimTrackingAnimset[name]; + } + else + { + return anim.animsets.runAimTracking[name]; + } +} + +endFaceEnemyAimTracking() +{ + self.aim_while_moving_thread = undefined; + self notify( "end_face_enemy_tracking" ); + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ +} + +runShootWhileMovingThreads() +{ + self notify( "want_shoot_while_moving" ); + + if ( isdefined( self.shoot_while_moving_thread ) ) + return; + self.shoot_while_moving_thread = true; + + self thread RunDecideWhatAndHowToShoot(); + self thread RunShootWhileMoving(); +} + +stopShootWhileMovingThreads()// we don't stop them if we shoot while moving again +{ + self endon( "killanimscript" ); + self endon( "want_shoot_while_moving" ); + self endon( "want_aim_while_moving" ); + + wait .05; + + self notify( "end_shoot_while_moving" ); + self notify( "end_face_enemy_tracking" ); + self.shoot_while_moving_thread = undefined; + self.aim_while_moving_thread = undefined; + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = undefined; +#/ + + self.runNGun = undefined; +} + + +RunDecideWhatAndHowToShoot() +{ + self endon( "killanimscript" ); + self endon( "end_shoot_while_moving" ); + self animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" ); +} +RunShootWhileMoving() +{ + self endon( "killanimscript" ); + self endon( "end_shoot_while_moving" ); + self animscripts\move::shootWhileMoving(); +} + +aimedSomewhatAtEnemy() +{ + weaponAngles = self getMuzzleAngle(); + anglesToShootPos = vectorToAngles( self.enemy getShootAtPos() - self getMuzzlePos() ); + + if ( AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] ) > 15 ) + return false; + + return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= 20; +} + +CanShootWhileRunningForward() +{ + // continue runNGun if runNGunWeight != 0 + if ( ( !isdefined( self.runNGunWeight ) || self.runNGunWeight == 0 ) && abs( self getMotionAngle() ) > self.maxRunNGunAngle ) + return false; + + return true; +} + +CanShootWhileRunningBackward() +{ + if ( 180 - abs( self getMotionAngle() ) >= 45 ) + return false; + + enemyyaw = self GetPredictedYawToEnemy( 0.2 ); + if ( abs( enemyyaw ) > 30 ) + return false; + + return true; +} + +CanShootWhileRunning() +{ + return animscripts\move::MayShootWhileMoving() && isdefined( self.enemy ) && ( CanShootWhileRunningForward() || CanShootWhileRunningBackward() ); +} + +GetPredictedYawToEnemy( lookAheadTime ) +{ + assert( isdefined( self.enemy ) ); + + selfPredictedPos = self.origin; + moveAngle = self.angles[ 1 ] + self getMotionAngle(); + selfPredictedPos += ( cos( moveAngle ), sin( moveAngle ), 0 ) * length( self.velocity ) * lookAheadTime; + + yaw = self.angles[ 1 ] - VectorToYaw( self.enemy.origin - selfPredictedPos ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +MoveStandNoncombatNormal() +{ + self endon( "movemode" ); + + self clearanim( %combatrun, 0.6 ); + + rate = GetMovePlaybackRate(); + + combat_run_blend = 0.2; + self setanimknoball( %combatrun, %body, 1, combat_run_blend, rate ); + + if ( self ShouldSprint() ) + runAnim = GetSprintAnim(); + else + runAnim = GetRunAnim(); + + run_anim_blend = 0.3; + + if ( self.stairsState == "none" ) + { + transTime = 0.3; // 0.3 because it pops when the AI goes from combat to noncombat + if ( IsDefined( self.run_blend_finish_time ) ) + { + transTime = self.run_blend_finish_time; + self.run_blend_finish_time = undefined; + } + } + else + { + transTime = 0.1; // need to transition to stairs quickly + } + + self setflaggedanimknob( "runanim", runAnim, 1, transTime, 1, true ); + self set_move_anim_start_point(); + self SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + self thread SetCombatStandMoveAnimWeights( "run" ); + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); +} + +//same as MoveStandNoncombatNormal, but by splitting this out we can have different blends for the moon actors. +MoveStandNoncombatMoon() +{ + self endon( "movemode" ); + + self clearanim( %combatrun, 0.6 ); + + rate = GetMovePlaybackRate(); + + run_anim_blend = 0.8; + + if ( IsDefined( self.run_blend_finish_time ) ) + { + run_anim_blend = self.run_blend_finish_time; + self.run_blend_finish_time = undefined; + } + + //TagCC: want to eventually get past using this, and delete. + run_blend_str = GetDvar("scr_run_blend"); + if(run_blend_str != "") + { + run_anim_blend = Float(run_blend_str); + } + + self setanimknoball( %combatrun, %body, 1, run_anim_blend, rate ); + + if ( self ShouldSprint() ) + runAnim = GetSprintAnim(); + else + runAnim = GetRunAnim(); + + if ( self.stairsState != "none" ) + { + run_anim_blend = 0.1; // need to transition to stairs quickly + } + + self setflaggedanimknob( "runanim", runAnim, 1, run_anim_blend, 1, true ); + self set_move_anim_start_point(); + self SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + self thread SetCombatStandMoveAnimWeights( "run" ); + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); +} + +MoveCrouchRunOverride() +{ + self endon( "movemode" ); + + self setflaggedanimknoball( "runanim", self.crouchrun_combatanim, %body, 1, 0.4, self.moveplaybackrate ); + animscripts\shared::DoNoteTracks( "runanim" ); +} + +MoveCrouchRunNormal() +{ + self endon( "movemode" ); + + // Play the appropriately weighted crouchrun animations for the direction he's moving + forward_anim = GetCrouchRunAnim(); + + run_anim_blend = 0.4; + if ( IsDefined( self.run_blend_finish_time ) ) + { + run_anim_blend = self.run_blend_finish_time; + self.run_blend_finish_time = undefined; + } + + self setanimknob( forward_anim, 1, run_anim_blend ); + + self thread UpdateMoveAnimWeights( "crouchrun", forward_anim, anim.animsets.move[ "run" ][ "crouch_b" ], anim.animsets.move[ "run" ][ "crouch_l" ], anim.animsets.move[ "run" ][ "crouch_r" ] ); + + self setflaggedanimknoball( "runanim", %crouchrun, %body, 1, 0.2, self.moveplaybackrate ); + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); +} + +MoveCrouchRunMoon() +{ + self endon( "movemode" ); + + // Play the appropriately weighted crouchrun animations for the direction he's moving + forward_anim = GetCrouchRunAnim(); + + run_anim_blend = 0.8; + + if ( IsDefined( self.run_blend_finish_time ) ) + { + run_anim_blend = self.run_blend_finish_time; + self.run_blend_finish_time = undefined; + } + + run_blend_str = GetDvar("scr_run_blend"); + if(run_blend_str != "") + { + run_anim_blend = Float(run_blend_str); + } + + self setanimknob( forward_anim, 1, run_anim_blend ); + + self thread UpdateMoveAnimWeights( "crouchrun", forward_anim, anim.animsets.move[ "run" ][ "crouch_b" ], anim.animsets.move[ "run" ][ "crouch_l" ], anim.animsets.move[ "run" ][ "crouch_r"] ); + + self setflaggedanimknoball( "runanim", %crouchrun, %body, 1, run_anim_blend, self.moveplaybackrate ); + + animscripts\shared::DoNoteTracksForTime( 0.2, "runanim" ); +} + +ReloadStandRun() +{ + reloadIfEmpty = isdefined( self.a.allowedPartialReloadOnTheRunTime ) && self.a.allowedPartialReloadOnTheRunTime > gettime(); + reloadIfEmpty = reloadIfEmpty || ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < 256 * 256 ); + if ( reloadIfEmpty ) + { + if ( !self NeedToReload( 0 ) ) + return false; + } + else + { + if ( !self NeedToReload( .5 ) ) + return false; + } + + if ( isdefined( self.grenade ) ) + return false; + + if ( !self.faceMotion || self.stairsState != "none" ) + return false; + + // if not allowed to shoot, not allowed to reload + if ( isdefined( self.dontShootWhileMoving ) || isdefined( self.noRunReload ) ) + return false; + + if ( self CanShootWhileRunning() && !self NeedToReload( 0 ) ) + return false; + + if ( !isdefined( self.pathGoalPos ) || distanceSquared( self.origin, self.pathGoalPos ) < 256 * 256 ) + return false; + + motionAngle = AngleClamp180( self getMotionAngle() ); + + // want to be running forward; otherwise we won't see the animation play! + if ( abs( motionAngle ) > 25 ) + return false; + + if ( !usingRifleLikeWeapon() ) + return false; + + // need to restart the run cycle because the reload animation has to be played from start to finish! + // the goal is to play it only when we're near the end of the run cycle. + if ( !runLoopIsNearBeginning() ) + return false; + + // call in a separate function so we can cleanup if we get an endon + ReloadStandRunInternal(); + + // notify "abort_reload" in case the reload didn't finish, maybe due to "movemode" notify. works with handleDropClip() in shared.gsc + self notify( "abort_reload" ); + + self orientmode( "face default" ); + + return true; +} + +ReloadStandRunInternal() +{ + self endon( "movemode" ); + + self orientmode( "face motion" ); + + flagName = "reload_" + getUniqueFlagNameIndex(); + + self setFlaggedAnimKnobAllRestart( flagName, %run_lowready_reload, %body, 1, 0.25 ); + + self.update_move_front_bias = true; + + self SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + self thread SetCombatStandMoveAnimWeights( "run" ); + animscripts\shared::DoNoteTracks( flagName ); + + self.update_move_front_bias = undefined; +} + +runLoopIsNearBeginning() +{ + // there are actually 3 loops (left foot, right foot) in one animation loop. + + animfraction = self getAnimTime( %walk_and_run_loops ); + loopLength = getAnimLength( GetRunAnim() ) / 3.0; + animfraction *= 3.0; + if ( animfraction > 3 ) + animfraction -= 2.0; + else if ( animfraction > 2 ) + animfraction -= 1.0; + + if ( animfraction < .15 / loopLength ) + return true; + if ( animfraction > 1 - .3 / loopLength ) + return true; + + return false; +} + +SetMoveNonForwardAnims( backAnim, leftAnim, rightAnim, rate ) +{ + if ( !isdefined( rate ) ) + rate = 1; + + self setAnimKnobLimited( backAnim, 1, 0.1, rate, true ); + self setAnimKnobLimited( leftAnim, 1, 0.1, rate, true ); + self setAnimKnobLimited( rightAnim, 1, 0.1, rate, true ); +} + +SetCombatStandMoveAnimWeights( moveAnimType ) +{ + UpdateMoveAnimWeights( moveAnimType, %combatrun_forward, %combatrun_backward, %combatrun_left, %combatrun_right ); +} + +UpdateMoveAnimWeights( moveAnimType, frontAnim, backAnim, leftAnim, rightAnim ) +{ + if ( isdefined( self.update_move_anim_type ) && self.update_move_anim_type == moveAnimType ) + return; + + self notify( "stop_move_anim_update" ); + + self.update_move_anim_type = moveAnimType; + self.wasFacingMotion = undefined; + + self endon( "killanimscript" ); + self endon( "move_interrupt" ); + self endon( "stop_move_anim_update" ); + + for ( ;; ) + { + UpdateRunWeightsOnce( frontAnim, backAnim, leftAnim, rightAnim ); + wait .05; + waittillframeend; + } +} + +UpdateRunWeightsOnce( frontAnim, backAnim, leftAnim, rightAnim ) +{ + //assert( !isdefined( self.runNGun ) || isdefined( self.update_move_front_bias ) ); + + if ( self.faceMotion && !self shouldCQB() && !isdefined( self.update_move_front_bias ) ) + { + // once you start to face motion, don't need to change weights + if ( !isdefined( self.wasFacingMotion ) ) + { + self.wasFacingMotion = 1; + self setanim( frontAnim, 1, 0.2, 1, true ); + self setanim( backAnim, 0, 0.2, 1, true ); + self setanim( leftAnim, 0, 0.2, 1, true ); + self setanim( rightAnim, 0, 0.2, 1, true ); + } + } + else + { + self.wasFacingMotion = undefined; + + // Play the appropriately weighted animations for the direction he's moving. + animWeights = animscripts\utility::QuadrantAnimWeights( self getMotionAngle() ); + + if ( isdefined( self.update_move_front_bias ) ) + { + animWeights[ "back" ] = 0.0; + if ( animWeights[ "front" ] < .2 ) + animWeights[ "front" ] = .2; + } + + self setanim( frontAnim, animWeights[ "front" ], 0.2, 1, true ); + self setanim( backAnim, animWeights[ "back" ], 0.2, 1, true ); + self setanim( leftAnim, animWeights[ "left" ], 0.2, 1, true ); + self setanim( rightAnim, animWeights[ "right" ], 0.2, 1, true ); + } +} + + +// change our weapon while running if we want to and can +changeWeaponStandRun() +{ + // right now this only handles shotguns, but it could do other things too + wantShotgun = ( isdefined( self.wantShotgun ) && self.wantShotgun ); + usingShotgun = isShotgun( self.weapon ); + if ( wantShotgun == usingShotgun ) + return false; + + if ( !isdefined( self.pathGoalPos ) || distanceSquared( self.origin, self.pathGoalPos ) < 256 * 256 ) + return false; + + if ( usingSidearm() ) + return false; + assert( self.weapon == self.primaryweapon || self.weapon == self.secondaryweapon ); + + if ( self.weapon == self.primaryweapon ) + { + if ( !wantShotgun ) + return false; + if ( isShotgun( self.secondaryweapon ) ) + return false; + } + else + { + assert( self.weapon == self.secondaryweapon ); + + if ( wantShotgun ) + return false; + if ( isShotgun( self.primaryweapon ) ) + return false; + } + + // want to be running forward; otherwise we won't see the animation play! + motionAngle = AngleClamp180( self getMotionAngle() ); + if ( abs( motionAngle ) > 25 ) + return false; + + if ( !runLoopIsNearBeginning() ) + return false; + + if ( wantShotgun ) + shotgunSwitchStandRunInternal( "shotgunPullout", %shotgun_CQBrun_pullout, "gun_2_chest", "none", self.secondaryweapon, "shotgun_pickup" ); + else + shotgunSwitchStandRunInternal( "shotgunPutaway", %shotgun_CQBrun_putaway, "gun_2_back", "back", self.primaryweapon, "shotgun_pickup" ); + + self notify( "switchEnded" ); + + return true; +} + +shotgunSwitchStandRunInternal( flagName, switchAnim, dropGunNotetrack, putGunOnTag, newGun, pickupNewGunNotetrack ) +{ + self endon( "movemode" ); + + self setFlaggedAnimKnobAllRestart( flagName, switchAnim, %body, 1, 0.25 ); + + self.update_move_front_bias = true; + + self SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + self thread SetCombatStandMoveAnimWeights( "run" ); + + self thread watchShotgunSwitchNotetracks( flagName, dropGunNotetrack, putGunOnTag, newGun, pickupNewGunNotetrack ); + + animscripts\shared::DoNoteTracksForTimeIntercept( getAnimLength( switchAnim ) - 0.25, flagName, ::interceptNotetracksForWeaponSwitch ); + + self.update_move_front_bias = undefined; +} + +interceptNotetracksForWeaponSwitch( notetrack ) +{ + if ( notetrack == "gun_2_chest" || notetrack == "gun_2_back" ) + return true;// "don't do the default behavior for this notetrack" +} + +watchShotgunSwitchNotetracks( flagName, dropGunNotetrack, putGunOnTag, newGun, pickupNewGunNotetrack ) +{ + self endon( "killanimscript" ); + self endon( "movemode" ); + self endon( "switchEnded" ); + + self waittillmatch( flagName, dropGunNotetrack ); + + animscripts\shared::placeWeaponOn( self.weapon, putGunOnTag ); + self thread shotgunSwitchFinish( newGun ); + + self waittillmatch( flagName, pickupNewGunNotetrack ); + self notify( "complete_weapon_switch" ); +} + +shotgunSwitchFinish( newGun ) +{ + self endon( "death" ); + + self waittill_any( "killanimscript", "movemode", "switchEnded", "complete_weapon_switch" ); + + self.lastweapon = self.weapon; + + animscripts\shared::placeWeaponOn( newGun, "right" ); + assert( self.weapon == newGun );// placeWeaponOn should have handled this + + // reset ammo (assume fully loaded weapon) + self.bulletsInClip = weaponClipSize( self.weapon ); +} + diff --git a/animscripts/saw/common.gsc b/animscripts/saw/common.gsc new file mode 100644 index 0000000..ce8a508 --- /dev/null +++ b/animscripts/saw/common.gsc @@ -0,0 +1,326 @@ +main( turret ) +{ + self endon( "killanimscript" );// code + + assert( isdefined( turret ) ); + + animscripts\utility::initialize( "saw" ); + + // when we ran our postscriptfunc we may have decided to stop using our turret, + // in which case it's gone now + if ( !isdefined( turret ) ) + return; + + self.a.special = "saw"; + + pauseUntilTime = getTime(); + turretState = "start"; + + self animscripts\shared::placeWeaponOn( self.weapon, "none" ); + turret show(); + + if ( isDefined( turret.aiOwner ) ) + { + assert( turret.aiOwner == self ); + self.a.postScriptFunc = ::postScriptFunc; + self.a.usingTurret = turret; + turret notify( "being_used" ); + self thread stopUsingTurretWhenNodeLost(); + } + else + { + self.a.postScriptFunc = ::preplacedPostScriptFunc; + } + + turret.doFiring = false; + self thread fireController( turret ); + + + self setTurretAnim( self.primaryTurretAnim ); + self setAnimKnobRestart( self.primaryTurretAnim, 1, 0.2, 1 ); + + self setAnimKnobLimitedRestart( self.additiveTurretIdle ); + self setAnimKnobLimitedRestart( self.additiveTurretFire ); + + turret setAnimKnobLimitedRestart( turret.additiveTurretIdle ); + turret setAnimKnobLimitedRestart( turret.additiveTurretFire ); + + turret endon( "death" ); + for ( ;; ) + { + // tagBK< NOTE > System allows for dynamic updates of fire rate. Ported from above. + if ( isDefined( turret.script_delay_min )) + { + turret_delay = turret.script_delay_min; + } + else + { + turret_delay = maps\_mgturret::burst_fire_settings( "delay" ); + } + + if ( isDefined( turret.script_delay_max )) + { + turret_delay_range = turret.script_delay_max - turret_delay; + } + else + { + turret_delay_range = maps\_mgturret::burst_fire_settings( "delay_range" ); + } + + if ( isDefined( turret.script_burst_min )) + { + turret_burst = turret.script_burst_min; + } + else + { + turret_burst = maps\_mgturret::burst_fire_settings( "burst" ); + } + + if ( isDefined( turret.script_burst_max )) + { + turret_burst_range = turret.script_burst_max - turret_burst; + } + else + { + turret_burst_range = maps\_mgturret::burst_fire_settings( "burst_range" ); + } + + // Recalculate wait time. + if ( turret_burst_range <= 0 ) + { + waittime = turret_delay; + burst = false; + } + else + { + waittime = randomFloatRange( turret_burst, turret_burst + turret_burst_range ); + burst = true; + } + + // Primary update. + if ( turret.doFiring ) + { + thread DoShoot( turret ); + + if ( burst == false ) + { + turret notify( "turretstatechange" ); + } + + self waitTimeOrUntilTurretStateChange( waittime, turret ); + turret notify( "turretstatechange" ); + + if ( turret.doFiring ) + { + thread DoAim( turret ); + wait( waittime ); + } + } + else + { + thread DoAim( turret ); + turret waittill( "turretstatechange" ); + } + } +} + +waitTimeOrUntilTurretStateChange( time, turret ) +{ + turret endon( "turretstatechange" ); + wait time; +} + +fireController( turret ) +{ + self endon( "killanimscript" ); + + fovdot = cos( 15 ); + + for ( ;; ) + { + while ( isDefined( self.enemy ) ) + { + enemypos = self.enemy.origin; + + //if ( isSentient( enemypos ) ) + // enemypos += (0,0,32); + turretAimPos = turret getTagAngles( "tag_aim" ); + + if ( within_fov( turret.origin, turretAimPos, enemypos, fovdot ) || distanceSquared( turret.origin, enemyPos ) < 200 * 200 ) + { + if ( !turret.doFiring ) + { + turret.doFiring = true; + turret notify( "turretstatechange" ); + } + } + else if ( turret.doFiring ) + { + turret.doFiring = false; + turret notify( "turretstatechange" ); + } + + wait( 0.05 ); + } + + if ( turret.doFiring ) + { + turret.doFiring = false; + turret notify( "turretstatechange" ); + } + + wait( 0.05 ); + } +} + + +turretTimer( duration, turret ) +{ + if ( duration <= 0 ) + return; + + self endon( "killanimscript" );// code + turret endon( "turretstatechange" );// code + + wait( duration ); + turret notify( "turretstatechange" ); +} + +stopUsingTurretWhenNodeLost() +{ + self endon( "killanimscript" ); + + // sometimes someone else will come and steal our node. when that happens, + // we should leave so we don't try to use the same MG at once. + while ( 1 ) + { + if ( !isdefined( self.node ) || distancesquared( self.origin, self.node.origin ) > 64 * 64 ) + self stopUseTurret(); + wait .25; + } +} + + +postScriptFunc( animscript ) +{ + if ( animscript == "pain" ) + { + if ( isdefined( self.node ) && distancesquared( self.origin, self.node.origin ) < 64 * 64 ) + { + self.a.usingTurret hide(); + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); + self.a.postScriptFunc = ::postPainFunc; + return; + } + else + { + self stopUseTurret(); + } + } + + assert( self.a.usingTurret.aiOwner == self ); + + if ( animscript == "saw" ) + { + turret = self getTurret(); + assert( isDefined( turret ) && turret == self.a.usingTurret ); + return; + } + + self.a.usingTurret delete(); + self.a.usingTurret = undefined; + + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); +} + +postPainFunc( animscript ) +{ + assert( isDefined( self.a.usingTurret ) ); + assert( self.a.usingTurret.aiOwner == self ); + + if ( !isdefined( self.node ) || distancesquared( self.origin, self.node.origin ) > 64 * 64 ) + { + self stopUseTurret(); + + self.a.usingTurret delete(); + self.a.usingTurret = undefined; + + // we may have gone into long death, in which case our weapon is gone + if ( isdefined( self.weapon ) && self.weapon != "none" ) + { + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); + } + } + else if ( animscript != "saw" ) + { + self.a.usingTurret delete(); + } +} + + +preplacedPostScriptFunc( animscript ) +{ + self animscripts\shared::placeWeaponOn( self.weapon, "right" ); +} + + +within_fov( start_origin, start_angles, end_origin, fov ) +{ + normal = vectorNormalize( end_origin - start_origin ); + forward = anglestoforward( start_angles ); + dot = vectorDot( forward, normal ); + + return dot >= fov; +} + + +// ================================== + +#using_animtree( "generic_human" ); + +DoShoot( turret ) +{ + self setAnim( %additive_saw_idle, 0, .1 ); + self setAnim( %additive_saw_fire, 1, .1 ); + + turret turretDoShootAnims(); + + TurretDoShoot( turret ); +} + +DoAim( turret ) +{ + self setAnim( %additive_saw_idle, 1, .1 ); + self setAnim( %additive_saw_fire, 0, .1 ); + + turret turretDoAimAnims(); +} + + +//===================================== +#using_animtree( "mg42" ); + +TurretDoShoot( turret ) +{ + self endon( "killanimscript" ); + turret endon( "turretstatechange" );// code or script + + for ( ;; ) + { + turret ShootTurret(); + wait 0.1; + } +} + +turretDoShootAnims() +{ + self setAnim( %additive_saw_idle, 0, .1 ); + self setAnim( %additive_saw_fire, 1, .1 ); +} + +turretDoAimAnims() +{ + self setAnim( %additive_saw_idle, 1, .1 ); + self setAnim( %additive_saw_fire, 0, .1 ); +} + diff --git a/animscripts/saw/crouch.gsc b/animscripts/saw/crouch.gsc new file mode 100644 index 0000000..2a6f1ce --- /dev/null +++ b/animscripts/saw/crouch.gsc @@ -0,0 +1,36 @@ +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + // It'd be nice if I had an animation to get to stand without moving... + self.a.movement = "stop"; + + turret = self getTurret(); + turret thread turretInit( self ); + + self.primaryTurretAnim = %crouchSAWgunner_aim; + self.additiveTurretIdle = %saw_gunner_lowwall_idle; + self.additiveTurretFire = %saw_gunner_lowwall_firing; + + thread animscripts\saw\common::main( turret ); +} + +//===================================== +#using_animtree( "mg42" ); + +turretInit( owner ) +{ + self UseAnimTree( #animtree ); + + self.additiveTurretIdle = %saw_gunner_lowwall_idle_mg; + self.additiveTurretFire = %saw_gunner_lowwall_firing_mg; + + self endon( "death" ); + owner waittill( "killanimscript" );// code + + self stopUseAnimTree(); +} diff --git a/animscripts/saw/prone.gsc b/animscripts/saw/prone.gsc new file mode 100644 index 0000000..866f653 --- /dev/null +++ b/animscripts/saw/prone.gsc @@ -0,0 +1,37 @@ +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "prone"; + animscripts\utility::UpdateAnimPose(); + + // It'd be nice if I had an animation to get to stand without moving... + self.a.movement = "stop"; + + turret = self getTurret(); + turret thread turretInit( self ); + + self.primaryTurretAnim = %proneSAWgunner_aim; + self.additiveTurretIdle = %saw_gunner_prone_idle; + self.additiveTurretFire = %saw_gunner_prone_firing; + + thread animscripts\saw\common::main( turret ); +} + +//===================================== +#using_animtree( "mg42" ); + +turretInit( owner ) +{ + self UseAnimTree( #animtree ); + + self.additiveTurretIdle = %saw_gunner_prone_idle_mg; + self.additiveTurretFire = %saw_gunner_prone_firing_mg; + + self endon( "death" ); + owner waittill( "killanimscript" );// code + + self stopUseAnimTree(); +} + diff --git a/animscripts/saw/stand.gsc b/animscripts/saw/stand.gsc new file mode 100644 index 0000000..3a792f2 --- /dev/null +++ b/animscripts/saw/stand.gsc @@ -0,0 +1,37 @@ +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + // It'd be nice if I had an animation to get to stand without moving... + self.a.movement = "stop"; + + turret = self getTurret(); + turret thread turretInit( self ); + + self.primaryTurretAnim = %standSAWgunner_aim; + self.additiveTurretIdle = %saw_gunner_idle; + self.additiveTurretFire = %saw_gunner_firing_add; + + thread animscripts\saw\common::main( turret ); +} + +//===================================== +#using_animtree( "mg42" ); + +turretInit( owner ) +{ + self UseAnimTree( #animtree ); + + self.additiveTurretIdle = %saw_gunner_idle_mg; + self.additiveTurretFire = %saw_gunner_firing_mg_add; + + self endon( "death" ); + owner waittill( "killanimscript" );// code + + self stopUseAnimTree(); +} + diff --git a/animscripts/scripted.gsc b/animscripts/scripted.gsc new file mode 100644 index 0000000..3770d9b --- /dev/null +++ b/animscripts/scripted.gsc @@ -0,0 +1,70 @@ +// Note that this script is called from the level script command animscripted, only for AI. If animscripted +// is done on a script model, this script is not called - startscriptedanim is called directly. + +#using_animtree( "generic_human" ); +main() +{ + //thread [[anim.println]]("Entering animscripts\\scripted. anim: ",self.codeScripted["anim"],", notify: ",self.codeScripted["notifyName"],", dialogue: ",self.scripted_dialogue,", facial: ",self.facial_animation, "root: ", self.codeScripted["root"]);#/ + self endon( "death" ); + +// wait (0); + self notify( "killanimscript" ); + self notify( "clearSuppressionAttack" ); + self.a.suppressingEnemy = false; + + + self.codeScripted[ "root" ] = %body; // TEMP! + + self endon( "end_sequence" ); +// Causes potential variable overflow in Stalingrad +// self thread DebugPrintEndSequence(); + self startscriptedanim( self.codeScripted[ "notifyName" ], self.codeScripted[ "origin" ], self.codeScripted[ "angles" ], self.codeScripted[ "anim" ], self.codeScripted[ "blend_time" ], self.codeScripted[ "animMode" ], self.codeScripted[ "root" ] ); + + self.codeScripted = undefined; +// if (isdefined (self.facial_animation)) +// { +// self SetFlaggedAnimRestart("scripted_anim_facedone", self.facial_animation, 1, .1, 1); +// self.facial_animation = undefined; +// } + if ( isdefined( self.scripted_dialogue ) || isdefined( self.facial_animation ) ) + { + self animscripts\face::SaySpecificDialogue( self.facial_animation, self.scripted_dialogue, 0.9, "scripted_anim_facedone" ); + self.facial_animation = undefined; + self.scripted_dialogue = undefined; + } + + if ( isdefined( self.deathstring_passed ) ) + self.deathstring = self.deathstring_passed; + + self waittill( "killanimscript" ); +} + +#using_animtree( "generic_human" ); +init( notifyName, origin, angles, theAnim, blend_time, animMode, root ) +{ + self.codeScripted[ "notifyName" ] = notifyName; + self.codeScripted[ "origin" ] = origin; + self.codeScripted[ "angles" ] = angles; + self.codeScripted[ "anim" ] = theAnim; + self.codeScripted[ "blend_time" ] = blend_time; + + if ( isDefined( animMode ) ) + self.codeScripted[ "animMode" ] = animMode; + else + self.codeScripted[ "animMode" ] = "normal"; + if ( isDefined( root ) ) + self.codeScripted[ "root" ] = root; + else + self.codeScripted[ "root" ] = %body; +} + +// Causes potential variable overflow in Stalingrad + +/* +DebugPrintEndSequence() +{ + self endon ("death"); + self waittill ("end_sequence"); + /#thread [[anim.println]]("scripted.gsc: \"end_sequence\" was notified. Time: ",GetTime(),".");#/ +} +*/ \ No newline at end of file diff --git a/animscripts/scripted/truckride_backoftruck.gsc b/animscripts/scripted/truckride_backoftruck.gsc new file mode 100644 index 0000000..c860cf9 --- /dev/null +++ b/animscripts/scripted/truckride_backoftruck.gsc @@ -0,0 +1,193 @@ +#include animscripts\SetPoseMovement; +#include animscripts\combat_utility; +#using_animtree( "generic_human" ); + +hackangle() +{ + self endon( "killanimscript" ); + + for ( ;; ) + { + enemyAngle = animscripts\utility::GetYawToEnemy(); + self OrientMode( "face angle", enemyAngle ); + wait .05; + } +} + +main() +{ + println( "anim1" ); + self endon( "killanimscript" ); + self endon( "outoftruck" ); + animscripts\utility::initialize( "l33t truckride combat" ); + + thread hackangle(); + self OrientMode( "face enemy" ); + if ( randomint( 100 ) > 50 ) + nextaction = ( "stand" ); + else + nextaction = ( "crouch" ); + + for ( ;; ) + { + // Nothing below will work if our gun is completely empty. + + Reload( 0 ); +// if ( canShootstand && canStand && +// ( !canShootCrouch || !canCrouch || ( dist < anim.standRangeSq )) ) + + if ( nextaction == ( "stand" ) ) + { + timer = gettime() + randomint( 2000 ) + 2000; + while ( timer > gettime() ) + { + + +// self animscripts\aim::aim(); + success = LocalShootVolley( 0 ); + // if (!success) + // self interruptPoint(); // We couldn't shoot for some reason, so now would be a good time to run for cover. + nextaction = ( "crouch" ); + + } + } + else if ( nextaction == ( "crouch" ) ) + { + timer = gettime() + randomint( 2000 ) + 2000; + while ( timer > gettime() ) + { + /#thread [[ anim.println ]]( "ExposedCombat - Crouched combat" );#/ + + +// self animscripts\aim::aim(); + success = ShootVolley(); + if ( !success ) + continue; + nextaction = ( "stand" ); + } + + } + } +} + + +LocalShootVolley( completeLastShot, forceShoot, posOverrideEntity ) +{ + if ( !isDefined( forceShoot ) ) + { + forceShoot = "dontForceShoot"; + } + self animscripts\shared::placeWeaponOn( self.primaryweapon, "none" ); + if ( self.a.pose == "stand" ) + { + anim_autofire = %stand_shoot_auto; + anim_semiautofire = %stand_shoot; + anim_boltfire = %stand_shoot; + } + else// assume crouch + { + anim_autofire = %crouch_shoot_auto; + anim_semiautofire = %crouch_shoot; + anim_boltfire = %crouch_shoot; + } + + if ( animscripts\weaponList::usingAutomaticWeapon() ) + { + self animscripts\face::SetIdleFace( anim.autofireface ); + self setflaggedanimknob( "animdone", anim_autofire, 1, .15, 0 ); + wait 0.20; + animRate = animscripts\weaponList::autoShootAnimRate(); + self setFlaggedAnimKnobRestart( "shootdone", anim_autofire, 1, .05, animRate ); + numShots = randomint( 8 ) + 6; + enemyAngle = animscripts\utility::AbsYawToEnemy(); + /#thread [[ anim.locspam ]]( "c16a" );#/ + for ( i = 0;( i < numShots && self.bulletsInClip > 0 && enemyAngle < 20 ); i++ ) + { + self waittillmatch( "shootdone", "fire" ); + if ( isDefined( posOverrideEntity ) ) + { + if ( isSentient( posOverrideEntity ) ) + { + pos = posOverrideEntity GetEye(); + } + else + { + pos = posOverrideEntity.origin; + } + self shoot( 1, pos ); + } + else + self shoot(); + self decrementBulletsInClip(); + enemyAngle = animscripts\utility::AbsYawToEnemy(); + } + if ( completeLastShot ) + wait animscripts\weaponList::waitAfterShot(); + self notify( "stopautofireFace" ); + } + else if ( animscripts\weaponList::usingSemiAutoWeapon() ) + { + + self animscripts\face::SetIdleFace( anim.aimface ); + + self setanimknob( anim_semiautofire, 1, .15, 0 ); + wait 0.2; + + rand = randomint( 2 ) + 2; + for ( i = 0;( i < rand && self.bulletsInClip > 0 ); i++ ) + { + self setFlaggedAnimKnobRestart( "shootdone", anim_semiautofire, 1, 0, 1 ); + if ( isDefined( posOverrideEntity ) ) + self shoot( 1, posOverrideEntity . origin ); + else + self shoot(); + self decrementBulletsInClip(); + /#thread [[ anim.locspam ]]( "c17.1b" );#/ + shootTime = animscripts\weaponList::shootAnimTime(); + quickTime = animscripts\weaponList::waitAfterShot(); + wait quickTime; + if ( ( ( completeLastShot ) || ( i < rand - 1 ) ) && shootTime > quickTime ) + wait shootTime - quickTime; + } + } + else// Bolt action + { + Rechamber(); // In theory you will almost never need to rechamber here, because you will have done + // it somewhere smarter, like in cover. + self animscripts\face::SetIdleFace( anim.aimface ); + // Slowly blend in the first frame of the shoot instead of playing the transition. + self setanimknob( anim_boltfire, 1, .15, 0 ); + + wait 0.2; + + self setFlaggedAnimKnobRestart( "shootdone", anim_boltfire, 1, 0, 1 ); + if ( isDefined( posOverrideEntity ) ) + self shoot( 1, posOverrideEntity . origin ); + else + self shoot(); + self.a.needsToRechamber = 1; + self decrementBulletsInClip(); + shootTime = animscripts\weaponList::shootAnimTime(); + quickTime = animscripts\weaponList::waitAfterShot(); + wait quickTime; + } + + return 1; +} + + + + + + + + + + + + + + + + + diff --git a/animscripts/setposemovement.gsc b/animscripts/setposemovement.gsc new file mode 100644 index 0000000..d4bec9e --- /dev/null +++ b/animscripts/setposemovement.gsc @@ -0,0 +1,1111 @@ +//================================================================================ +// SetPoseMovement - Sets the pose (stand, crouch, prone) and movement (run, walk, +// crawl, stop) to the specified values. Accounts for all possible starting poses +// and movements. +//================================================================================ + +#include animscripts\Utility; +#include maps\_Utility; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + + +SetPoseMovement( desiredPose, desiredMovement ) +{ + // Scripts can pass empty strings, meaning they don't want to change that aspect of the state. + if ( desiredPose == "" ) + { + if ( ( self.a.pose == "prone" ) && ( ( desiredMovement == "walk" ) || ( desiredMovement == "run" ) ) ) + desiredPose = "crouch"; + else + desiredPose = self.a.pose; + } + if ( !IsDefined( desiredMovement ) || desiredMovement == "" ) + { + desiredMovement = self.a.movement; + } + + // Now call the function. + [[ anim.SetPoseMovementFnArray[ desiredPose ][ desiredMovement ] ]](); +} + + +// ***************************** +// All of the following "Begin" functions ensure that the actor is in the given pose and movement type. +// They return false if nothing needs to be done, true otherwise. +// ***************************** + +InitPoseMovementFunctions() +{ + // Make an array of movement and pose changing functions. + // Indices are: "desired movement", "desired pose" + anim.SetPoseMovementFnArray[ "stand" ][ "stop" ] = ::BeginStandStop; + anim.SetPoseMovementFnArray[ "stand" ][ "walk" ] = ::BeginStandWalk; + anim.SetPoseMovementFnArray[ "stand" ][ "run" ] = ::BeginStandRun; + + anim.SetPoseMovementFnArray[ "crouch" ][ "stop" ] = ::BeginCrouchStop; + anim.SetPoseMovementFnArray[ "crouch" ][ "walk" ] = ::BeginCrouchWalk; + anim.SetPoseMovementFnArray[ "crouch" ][ "run" ] = ::BeginCrouchRun; + + anim.SetPoseMovementFnArray[ "prone" ][ "stop" ] = ::BeginProneStop; + anim.SetPoseMovementFnArray[ "prone" ][ "walk" ] = ::BeginProneWalk; + anim.SetPoseMovementFnArray[ "prone" ][ "run" ] = ::BeginProneRun; +} + + +//-------------------------------------------------------------------------------- +// Standing poses +//-------------------------------------------------------------------------------- + +BeginStandStop() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + return false; + + case "walk": + StandWalkToStand(); + break; + + default: + assert( self.a.movement == "run" ); + StandRunToStand(); + break; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToStand(); + break; + + case "walk": + CrouchWalkToStand(); + break; + + default: + assert( self.a.movement == "run" ); + CrouchRunToStand(); + break; + } + break; + + default: + assert( self.a.pose == "prone" ); + switch( self.a.movement ) + { + case "stop": + ProneToStand(); + break; + + default: + assert( self.a.movement == "walk" || self.a.movement == "run" ); + ProneToStand(); // Do I need to stop crawling first? Hope not. + break; + } + break; + } + + return true; +} + +BeginStandWalk() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + BlendIntoStandWalk(); + break; + + case "walk": + return false; + + default: + assert( self.a.movement == "run" ); + BlendIntoStandWalk(); + break; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToStandWalk(); + break; + + case "walk": + BlendIntoStandWalk(); + break; + + default: + assert( self.a.movement == "run" ); + BlendIntoStandWalk(); + break; + } + break; + + default: + assert( self.a.pose == "prone" ); + ProneToStandWalk(); + break; + } + + return true; +} + +BeginStandRun() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + case "walk": + return BlendIntoStandRun(); + + default: + assert( self.a.movement == "run" ); + return false; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + return CrouchToStandRun(); + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + return BlendIntoStandRun(); + } + break; + + default: + assert( self.a.pose == "prone" ); + ProneToStandRun(); + break; + } + + return true; +} + +//-------------------------------------------------------------------------------- +// Crouching functions +//-------------------------------------------------------------------------------- +BeginCrouchStop() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + StandToCrouch(); + break; + case "walk": + StandWalkToCrouch(); + break; + case "run": + StandRunToCrouch(); + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchStop " + self.a.pose + " " + self.a.movement ); + } + break; + case "crouch": + switch( self.a.movement ) + { + case "stop": + // Do nothing + break; + case "walk": + CrouchWalkToCrouch(); + break; + case "run": + CrouchRunToCrouch(); + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchStop " + self.a.pose + " " + self.a.movement ); + } + break; + case "prone": + ProneToCrouch(); + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchStop " + self.a.pose + " " + self.a.movement ); + } +} + +BeginCrouchWalk() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + BlendIntoCrouchWalk(); + break; + + case "walk": + BlendIntoCrouchWalk(); + break; + + default: + assert( self.a.movement == "run" ); + BlendIntoCrouchWalk(); + break; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToCrouchWalk(); + break; + + case "walk": + return false; + + default: + assert( self.a.movement == "run" ); + BlendIntoCrouchWalk(); + break; + } + break; + + default: + assert( self.a.pose == "prone" ); + // Let's try going straight to the run and then blending back to see what it looks like. + ProneToCrouchWalk(); + break; + } + + return true; +} + +BeginCrouchRun() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + BlendIntoCrouchRun(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + BlendIntoCrouchRun(); + break; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToCrouchRun(); + break; + + case "walk": + BlendIntoCrouchRun(); + break; + + default: + assert( self.a.movement == "run" ); + return false; + } + break; + + default: + assert( self.a.pose == "prone" ); + ProneToCrouchRun(); + break; + } + + return true; +} + + +//-------------------------------------------------------------------------------- +// Prone Functions +//-------------------------------------------------------------------------------- + +BeginProneStop() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + StandToProne(); + break; + case "walk": + StandToProne(); + break; + case "run": + CrouchRunToProne(); + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchRun " + self.a.pose + " " + self.a.movement ); + } + break; + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToProne(); + break; + case "walk": + CrouchToProne(); + break; + case "run": + CrouchRunToProne(); + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchRun " + self.a.pose + " " + self.a.movement ); + } + break; + case "prone": + switch( self.a.movement ) + { + case "stop": + // Do nothing + break; + case "walk": + case "run": + ProneCrawlToProne(); + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchRun " + self.a.pose + " " + self.a.movement ); + } + break; + default: + assertEX( 0, "SetPoseMovement::BeginCrouchRun " + self.a.pose + " " + self.a.movement ); + } +} + +BeginProneWalk() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + StandToProneWalk(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + CrouchRunToProneWalk(); + break; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToProneWalk(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + CrouchRunToProneWalk(); + break; + } + break; + + default: + assert( self.a.pose == "prone" ); + switch( self.a.movement ) + { + case "stop": + ProneToProneRun(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + self.a.movement = "walk"; + return false; + } + break; + } + + return true; +} + +BeginProneRun() +{ + switch( self.a.pose ) + { + case "stand": + switch( self.a.movement ) + { + case "stop": + StandToProneRun(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + CrouchRunToProneRun(); + break; + } + break; + + case "crouch": + switch( self.a.movement ) + { + case "stop": + CrouchToProneRun(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + CrouchRunToProneRun(); + break; + } + break; + + default: + assert( self.a.pose == "prone" ); + switch( self.a.movement ) + { + case "stop": + assert( self.a.movement == "stop" ); + ProneToProneRun(); + break; + + default: + assert( self.a.movement == "run" || self.a.movement == "walk" ); + self.a.movement = "run"; + return false; + } + break; + } + + return true; +} + + +//-------------------------------------------------------------------------------- +// Standing support functions +//-------------------------------------------------------------------------------- + +PlayBlendTransition( transAnim, crossblendTime, endPose, endMovement ) +{ + endTime = gettime() + crossblendTime * 1000; + + if( isarray( transAnim ) ) + transAnim = transAnim[ randomint( transAnim.size ) ]; + + self setflaggedanimknoball( "blendTransition", transAnim, %body, 1, crossblendTime, 1 ); + + self animscripts\shared::DoNoteTracksForTime( crossblendTime / 2, "blendTransition" ); + + self.a.pose = endPose; + self.a.movement = endMovement; + + waittime = ( endTime - gettime() ) / 1000; + if ( waittime < 0.05 ) + waittime = 0.05; + + self animscripts\shared::DoNoteTracksForTime( waittime, "blendTransition" ); +} + +PlayTransitionStandWalk( transAnim, finalAnim ) +{ + PlayTransitionAnimation( transAnim, "stand", "walk", finalAnim ); +} + +StandWalkToStand() +{ + assertEX( self.a.pose == "stand", "SetPoseMovement::StandWalkToStand " + self.a.pose ); + assertEX( self.a.movement == "walk", "SetPoseMovement::StandWalkToStand " + self.a.movement ); + + // no transition animations. + + self.a.movement = "stop"; +} + + +StandWalkToCrouch() +{ + StandWalkToStand(); + StandToCrouch(); +} + + +StandRunToStand() +{ + assertEX( self.a.pose == "stand", "SetPoseMovement::StandRunToStand " + self.a.pose ); + assertEX( self.a.movement == "run", "SetPoseMovement::StandRunToStand " + self.a.movement ); + + // Do nothing, just blend straight in + self.a.movement = "stop"; +} + +StandRunToCrouch() +{ + self.a.movement = "stop"; + self.a.pose = "crouch"; +} + +PlayBlendTransitionStandRun( animname ) +{ + // if we're blending into stand run from stop, + // we probably just did utility::initialize's clearAnim(body, .3) + // so we don't have to spend more than .3 seconds here. + // (then we can do fun things like shooting or reloading.) + transtime = 0.3; + if ( self.a.movement != "stop" ) + { + self endon( "movemode" ); + transtime = 1.0; + } + + PlayBlendTransition( animname, transtime, "stand", "run" ); +} + +BlendIntoStandRun() +{ + if ( !self.faceMotion ) + { + self.a.movement = "run"; + self.a.pose = "stand"; + return false; + } + + if ( isDefined( self.run_overrideanim ) ) + { + PlayBlendTransitionStandRun( self.run_overrideanim ); + return true; + } + + // Set the specific forward animation we are using to weight 1 immediately + // we will make sure it is blended smoothly by blending in its parent, combatrun_forward + runAnimTransTime = 0.1; + if ( self.a.movement != "stop" && self.stairsState == "none" ) + runAnimTransTime = 0.5; + + if ( isdefined( self.sprint ) ) + self setAnimKnobLimited( moveAnim( "sprint" ), 1, runAnimTransTime, 1 ); + else + self setAnimKnobLimited( animscripts\run::GetRunAnim(), 1, runAnimTransTime, 1 ); + + self animscripts\run::SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ), self.sideStepRate ); + self thread animscripts\run::SetCombatStandMoveAnimWeights( "run" ); + + wait 0.05; + PlayBlendTransitionStandRun( %combatrun ); + return true; +} + + +BlendIntoStandWalk() +{ + if ( self.a.movement != "stop" ) + self endon( "movemode" ); + + if ( !isdefined( self.alwaysRunForward ) && self.a.pose != "prone" ) + self animscripts\run::SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + + self.a.pose = "stand"; + self.a.movement = "walk"; +} + + +CrouchToStand() +{ + assertEX( self.a.pose == "crouch", "SetPoseMovement::CrouchToStand " + self.a.pose ); + assertEX( self.a.movement == "stop", "SetPoseMovement::CrouchToStand " + self.a.movement ); + + standSpeed = 1; + if ( isdefined( self.fastStand ) ) + { + standSpeed = 1.8; + self.fastStand = undefined; + } + + if ( self usingSidearm() ) + { + PlayTransitionAnimation( %pistol_crouchaimstraight2stand, "stand", "stop", undefined, standSpeed ); + } + else + { + // Decide which idle animation to do + self randomizeIdleSet(); + + PlayTransitionAnimation( %crouch2stand, "stand", "stop", undefined, standSpeed ); + } + +} + + +//-------------------------------------------------------------------------------- +// Crouched Support Functions +//-------------------------------------------------------------------------------- + + +CrouchToCrouchWalk() +{ + assertEX( self.a.pose == "crouch", "SetPoseMovement::CrouchToCrouchWalk " + self.a.pose ); + assertEX( self.a.movement == "stop", "SetPoseMovement::CrouchToCrouchWalk " + self.a.movement ); + + BlendIntoCrouchWalk(); +} + +CrouchToStandWalk() +{ + CrouchToCrouchWalk(); + BlendIntoStandWalk(); +} + +CrouchWalkToCrouch() +{ + assertEX( self.a.pose == "crouch", "SetPoseMovement::CrouchWalkToCrouch " + self.a.pose ); + assertEX( self.a.movement == "walk", "SetPoseMovement::CrouchWalkToCrouch " + self.a.movement ); + + // Do nothing, just blend straight in + self.a.movement = "stop"; +} + +CrouchWalkToStand() +{ + CrouchWalkToCrouch(); + CrouchToStand(); +} + +CrouchRunToCrouch() +{ + assertEX( self.a.pose == "crouch", "SetPoseMovement::CrouchRunToCrouch " + self.a.pose ); + assertEX( self.a.movement == "run", "SetPoseMovement::CrouchRunToCrouch " + self.a.movement ); + + // Do nothing, just blend straight in + self.a.movement = "stop"; +} + +CrouchRunToStand() +{ + CrouchRunToCrouch(); + CrouchToStand(); +} + +CrouchToCrouchRun() +{ + assertEX( self.a.pose == "crouch", "SetPoseMovement::CrouchToCrouchRun " + self.a.pose ); + assertEX( self.a.movement == "stop", "SetPoseMovement::CrouchToCrouchRun " + self.a.movement ); + + BlendIntoCrouchRun(); +} + +CrouchToStandRun() +{ + return BlendIntoStandRun(); +} + +BlendIntoCrouchRun() +{ + if ( isDefined( self.crouchrun_combatanim ) ) + { + PlayBlendTransition( self.crouchrun_combatanim, 0.6, "crouch", "run" ); + } + else + { + self setanimknob( %crouchrun, 1, 0.4, self.moveplaybackrate ); + + self thread animscripts\run::UpdateMoveAnimWeights( "crouchrun", moveAnim( "crouch" ), moveAnim( "crouch_b" ), moveAnim( "crouch_l" ), moveAnim( "crouch_r" ) ); + + wait 0.05; + PlayBlendTransition( %crouchrun, 0.4, "crouch", "run" ); + } +} + +ProneToCrouchRun() +{ + assertEX( self.a.pose == "prone", "SetPoseMovement::ProneToCrouchRun " + self.a.pose ); + + self OrientMode( "face current" ); // We don't want to rotate arbitrarily until we've actually stood up. + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + ProneLegsStraightTree( 0.2 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + PlayTransitionAnimation( %prone_2_crouch, "crouch", "run", animscripts\run::GetCrouchRunAnim() ); +} + +ProneToStandRun() +{ + ProneToCrouchRun(); + BlendIntoStandRun(); +} + +ProneToCrouchWalk() +{ + ProneToCrouchRun(); + BlendIntoCrouchWalk(); +} + +BlendIntoCrouchWalk() +{ + if ( isDefined( self.crouchrun_combatanim ) ) + { + self setanimknoball( self.crouchrun_combatanim, %body, 1, 0.4 ); + PlayBlendTransition( self.crouchrun_combatanim, 0.6, "crouch", "walk" ); + self notify( "BlendIntoCrouchWalk" ); + } + else + { + self setanimknob( %crouchrun, 1, 0.4, self.moveplaybackrate ); + + self thread animscripts\run::UpdateMoveAnimWeights( "crouchrun", moveAnim( "crouch" ), moveAnim( "crouch_b" ), moveAnim( "crouch_l" ), moveAnim( "crouch_r" ) ); + + wait 0.05; + PlayBlendTransition( %crouchrun, 0.4, "crouch", "run" ); + } +} + +StandToCrouch() +{ + assertEX( self.a.pose == "stand", "SetPoseMovement::StandToCrouch " + self.a.pose ); + assertEX( self.a.movement == "stop", "SetPoseMovement::StandToCrouch " + self.a.movement ); + + // Decide which idle animation to do + self randomizeIdleSet(); + + crouchSpeed = 1; + if ( isdefined( self.fastCrouch ) ) + { + crouchSpeed = 1.8; + self.fastCrouch = undefined; + } + + PlayTransitionAnimation( %exposed_stand_2_crouch, "crouch", "stop", undefined, crouchspeed ); +} + +ProneToCrouch() +{ + assertEX( self.a.pose == "prone", "SetPoseMovement::StandToCrouch " + self.a.pose ); + + // Decide which idle animation to do + self randomizeIdleSet(); + + self OrientMode( "face current" ); // We don't want to rotate arbitrarily until we've actually stood up. + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + ProneLegsStraightTree( 0.1 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + PlayTransitionAnimation( %prone_2_crouch, "crouch", "stop" ); + +// TODO: Find out if the above lines give the same functionality as below (notably the UpdateProne bit) +// self exitprone(1.0); // make code stop lerping in the prone orientation to ground +// +// ProneLegsStraightTree(0.1); +// self setflaggedanimknob("animdone", %prone2crouch_straight, 1, .1, 1); +// self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); +// self waittill ("animdone"); +// self.a.pose = "crouch"; +} + +ProneToStand() +{ + assertEx( self.a.pose == "prone", self.a.pose ); + + self OrientMode( "face current" ); // We don't want to rotate arbitrarily until we've actually stood up. + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + ProneLegsStraightTree( 0.1 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + PlayTransitionAnimation( %prone_2_stand, "stand", "stop" ); +} + +ProneToStandWalk() +{ + ProneToCrouch(); + CrouchToCrouchWalk(); + BlendIntoStandWalk(); +} + +//-------------------------------------------------------------------------------- +// Prone Support Functions +//-------------------------------------------------------------------------------- + +ProneToProneMove( movement ) +{ + // (The parameter "movement" is just used for setting the state variable, since prone guys move the same whether + // "walking" or "running". + assertEX( self.a.pose == "prone", "SetPoseMovement::ProneToProneMove " + self.a.pose ); + assertEX( self.a.movement == "stop", "SetPoseMovement::ProneToProneMove " + self.a.movement ); + assertEX( ( movement == "walk" || movement == "run" ), "SetPoseMovement::ProneToProneMove got bad parameter " + movement ); + + ProneLegsStraightTree( 0.1 ); + PlayTransitionAnimation( %prone_2_prone_crawl, "prone", movement, %prone_crawl ); + + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); +} + + +ProneToProneRun() +{ + ProneToProneMove( "run" ); +} + +ProneCrawlToProne() +{ + assertEX( self.a.pose == "prone", "SetPoseMovement::ProneCrawlToProne " + self.a.pose ); + assertEX( ( self.a.movement == "walk" || self.a.movement == "run" ), "SetPoseMovement::ProneCrawlToProne " + self.a.movement ); + + ProneLegsStraightTree( 0.1 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + PlayTransitionAnimation( %prone_crawl_2_prone, "prone", "stop" ); + +// TODO: Find out if the above lines give the same functionality as below (notably the UpdateProne bit) +// ProneLegsStraightTree(0.1); +// self setflaggedanimknob("animdone", %prone_crawl2aim, 1, 0.1, 1); +// self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); +// self waittill("animdone"); +// self.a.movement = "stop"; +} + +CrouchToProne() +{ + assertEX( self.a.pose == "crouch", "SetPoseMovement::CrouchToProne " + self.a.pose ); + // I'd like to be able to assert that I'm stopped at this point, but until I get a better solution for + // guys who are walking and running, this is used for them too. +// assertEX(self.a.movement == "stop", "SetPoseMovement::CrouchToProne "+self.a.movement); + + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up ); + self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground + + ProneLegsStraightTree( 0.3 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + PlayTransitionAnimation( %crouch_2_prone, "prone", "stop" ); + +// TODO: Find out if the above lines give the same functionality as below (notably the UpdateProne bit) +// self SetProneAnimNodes(-45, 45, %prone_legsdown, %prone_legsstraight, %prone_legsup); +// self EnterProne(1.0); // make code start lerping in the prone orientation to ground +// +// ProneLegsStraightTree(0.3); +// self setflaggedanimknob("animdone", %crouch_2_prone, 1, .3, 1); +// self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); +// self waittill ("animdone"); +// self.a.pose = "prone"; +} + +CrouchToProneWalk() +{ + CrouchToProne(); + ProneToProneRun(); +} + +CrouchToProneRun() +{ + CrouchToProne(); + ProneToProneRun(); +} + +StandToProne() +{ + assertEX( self.a.pose == "stand", "SetPoseMovement::StandToProne " + self.a.pose ); + // I'd like to be able to assert that I'm stopped at this point, but until I get a better solution for + // guys who are walking and running, this is used for them too. +// assertEX(self.a.movement == "stop", "SetPoseMovement::StandToProne "+self.a.movement); + self endon( "entered_pose" + "prone" ); + + proneTime = 0.5;// was 1 + thread PlayTransitionAnimationThread_WithoutWaitSetStates( %stand_2_prone, "prone", "stop", undefined, proneTime ); + + self waittillmatch( "transAnimDone2", "anim_pose = \"crouch\"" ); + waittillframeend;// so that the one in donotetracks gets hit first + // cause the next pose is prone + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up ); + self EnterProneWrapper( proneTime );// make code start lerping in the prone orientation to ground + self.a.movement = "stop"; + + ProneLegsStraightTree( 0.2 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + self waittillmatch( "transAnimDone2", "end" ); + +// TODO: Find out if the above lines give the same functionality as below (notably the UpdateProne bit) +// self SetProneAnimNodes(-45, 45, %prone_legsdown, %prone_legsstraight, %prone_legsup); +// self EnterProne(1.0); // make code start lerping in the prone orientation to ground +// +// ProneLegsStraightTree(0.2); +// self setflaggedanimknob("animdone", %stand2prone_onehand, 1, .2, 1); +// self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); +// self waittill ("animdone"); +// self.a.pose = "prone"; +} + +StandToProneWalk() +{ + StandToProne(); + ProneToProneRun(); +} + +StandToProneRun() +{ + StandToProne(); + ProneToProneRun(); +} + +CrouchRunToProne() +{ + assertEX( ( self.a.pose == "crouch" ) || ( self.a.pose == "stand" ), "SetPoseMovement::CrouchRunToProne " + self.a.pose ); + assertEX( ( self.a.movement == "run" || self.a.movement == "walk" ), "SetPoseMovement::CrouchRunToProne " + self.a.movement ); + + pronetime = 0.5;// was 1 + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up ); + self EnterProneWrapper( proneTime );// make code start lerping in the prone orientation to ground + + ProneLegsStraightTree( 0.2 ); + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + + runDirection = animscripts\utility::getQuadrant( self getMotionAngle() ); + + diveanim = %crouch_2_prone; + + localDeltaVector = GetMoveDelta( diveanim, 0, 1 ); + endPoint = self LocalToWorldCoords( localDeltaVector ); + if ( self maymovetopoint( endPoint ) ) + { + PlayTransitionAnimation( diveanim, "prone", "stop", undefined, pronetime ); + } + else + { + //thread [[anim.println]]("Can't dive to prone.");#/ + PlayTransitionAnimation( %crouch_2_prone_firing, "prone", "stop", undefined, pronetime ); + } +} + +CrouchRunToProneWalk() +{ + CrouchRunToProne(); + ProneToProneRun(); +} + +CrouchRunToProneRun() +{ + CrouchRunToProne(); + ProneToProneRun(); +} + +//-------------------------------------------------------------------------------- +// General support functions +//-------------------------------------------------------------------------------- + + +PlayTransitionAnimationThread_WithoutWaitSetStates( transAnim, endPose, endMovement, finalAnim, rate ) +{ + self endon( "killanimscript" );// the threaded one needs this or it wont die + self endon( "entered_pose" + endPose ); + PlayTransitionAnimationFunc( transAnim, endPose, endMovement, finalAnim, rate, false ); +} + +PlayTransitionAnimation( transAnim, endPose, endMovement, finalAnim, rate ) +{ + PlayTransitionAnimationFunc( transAnim, endPose, endMovement, finalAnim, rate, true ); +} + + +PlayTransitionAnimationFunc( transAnim, endPose, endMovement, finalAnim, rate, waitSetStatesEnabled ) +{ + if ( !isdefined( rate ) ) + rate = 1; + + /# + if ( getdebugdvar( "debug_grenadehand" ) == "on" ) + { + if ( endPose != self.a.pose ) + { + if ( !animhasnotetrack( transAnim, "anim_pose = \"" + endPose + "\"" ) ) + { + println( "Animation ", transAnim, " lacks an endpose notetrack of ", endPose ); + assertEx( 0, "A transition animation is missing a pose notetrack (see the line above)" ); + } + } + if ( endMovement != self.a.movement ) + { + if ( !animhasnotetrack( transAnim, "anim_movement = \"" + endMovement + "\"" ) ) + { + println( "Animation ", transAnim, " lacks an endmovement notetrack of ", endMovement ); + assertEx( 0, "A transition animation is missing a movement notetrack (see the line above)" ); + } + } + } + #/ + + // Use a second thread to set the anim state halfway through the animation + if ( waitSetStatesEnabled ) + self thread waitSetStates( getanimlength( transAnim ) / 2.0, "killtimerscript", endPose ); + + // Play the anim + // setflaggedanimknoball(notifyName, anim, rootAnim, goalWeight, goalTime, rate) + self setflaggedanimknoballrestart( "transAnimDone2", transAnim, %body, 1, .2, rate ); + if ( !isDefined( self.a.pose ) ) + self.pose = "undefined"; + if ( !isDefined( self.a.movement ) ) + self.movement = "undefined"; + debugIdentifier = ""; + /#debugIdentifier = self.script + ", " + self.a.pose + " to " + endPose + ", " + self.a.movement + " to " + endMovement;#/ + self animscripts\shared::DoNoteTracks( "transAnimDone2", undefined, debugIdentifier ); + + // In case we finished earlier than we expected (eg the animation was already playing before we started), + // set the variables and kill the other thread. + self notify( "killtimerscript" ); + self.a.pose = endPose; + self notify( "entered_pose" + endPose ); + + self.a.movement = endMovement; + + if ( isDefined( finalAnim ) ) + { + // setflaggedanimknoball(notifyName, anim, rootAnim, goalWeight, goalTime, rate) + self setanimknoball( finalAnim, %body, 1, 0.3, rate ); // Set the animation instantly + } +} + + +waitSetStates( timetowait, killmestring, endPose ) +{ + self endon( "killanimscript" ); + self endon( "death" ); + self endon( killmestring ); + oldpose = self.a.pose; + wait timetowait; + + // We called Enter/ExitProne before this function was called. These lines should not be necessary, but + // for some reason the code is picking up that I'm setting pose from prone to crouch without calling + // exitprone(). I just hope it's not a thread leak I've missed. + if ( oldpose!= "prone" && endPose == "prone" ) + { + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground + } + else + if ( oldpose == "prone" && endPose != "prone" ) + { + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + self OrientMode( "face default" ); // We were most likely in "face current" while we were prone. + } +} + + +ProneLegsStraightTree( blendtime ) +{ + self setanimknoball( %prone_legsstraight, %body, 1, blendtime, 1 ); +} + diff --git a/animscripts/shared.gsc b/animscripts/shared.gsc new file mode 100644 index 0000000..c310e8b --- /dev/null +++ b/animscripts/shared.gsc @@ -0,0 +1,2152 @@ +// Shared.gsc - Functions that are shared between animscripts and level scripts. +// Functions in this file can't rely on the animscripts\init function having run, and can't call any +// functions not allowed in level scripts. + +#include maps\_utility; +#include animscripts\utility; +#include animscripts\combat_utility; +#include common_scripts\utility; + +#using_animtree( "generic_human" ); + +placeWeaponOn( weapon, position, activeWeapon ) +{ + //prof_begin( "placeWeaponOn" ); + // make sure this it one of our weapons + assert( AIHasWeapon( weapon ) ); + + self notify( "weapon_position_change" ); + + curPosition = self.weaponInfo[ weapon ].position; + + // make sure we're not out of sync + assert( curPosition == "none" || self.a.weaponPos[ curPosition ] == weapon ); + + // weapon already in place + if ( position != "none" && self.a.weaponPos[ position ] == weapon ) + { + //prof_end( "placeWeaponOn" ); + return; + } + + self detachAllWeaponModels(); + + // detach if we're already in a position + if ( curPosition != "none" ) + self detachWeapon( weapon ); + + // nothing more to do + if ( position == "none" ) + { + self updateAttachedWeaponModels(); + //prof_end( "placeWeaponOn" ); + return; + } + + if ( self.a.weaponPos[ position ] != "none" ) + self detachWeapon( self.a.weaponPos[ position ] ); + + // to ensure that the correct tags for the active weapon are used, we need to make sure it gets attached first + if ( !isdefined( activeWeapon ) ) + activeWeapon = true; + + if ( activeWeapon && ( position == "left" || position == "right" ) ) + { + self attachWeapon( weapon, position ); + self.weapon = weapon; + } + else + { + self attachWeapon( weapon, position ); + } + + self updateAttachedWeaponModels(); + + // make sure we don't have a weapon in each hand + //assert( self.a.weaponPos[ "left" ] == "none" || self.a.weaponPos[ "right" ] == "none" ); + //prof_end( "placeWeaponOn" ); +} + +detachWeapon( weapon ) +{ + self.a.weaponPos[ self.weaponInfo[ weapon ].position ] = "none"; + self.weaponInfo[ weapon ].position = "none"; +} + + +attachWeapon( weapon, position ) +{ + self.weaponInfo[ weapon ].position = position; + self.a.weaponPos[ position ] = weapon; + + if ( self.a.weaponPosDropping[ position ] != "none" ) + { + // a new weapon has taken the place of the weapon we were dropping, so just stop showing the model for the dropping weapon. + self notify( "end_weapon_drop_" + position ); + self.a.weaponPosDropping[ position ] = "none"; + } +} + +getWeaponForPos( position ) // returns the weapon that should currently be visible in a given location. +{ + weapon = self.a.weaponPos[ position ]; + + if ( weapon == "none" ) + return self.a.weaponPosDropping[ position ]; + + assert( self.a.weaponPosDropping[ position ] == "none" ); + + return weapon; +} + +detachAllWeaponModels() +{ + positions = []; + positions[ positions.size ] = "right"; + positions[ positions.size ] = "left"; + positions[ positions.size ] = "chest"; + positions[ positions.size ] = "back"; + + self laserOff(); + + foreach ( position in positions ) + { + weapon = self getWeaponForPos( position ); + + if ( weapon == "none" ) + continue; + + self detach( getWeaponModel( weapon ), getTagForPos( position ) ); + } +} + +NO_COLLISION = true; + +updateAttachedWeaponModels() +{ + positions = []; + positions[ positions.size ] = "right"; + positions[ positions.size ] = "left"; + positions[ positions.size ] = "chest"; + positions[ positions.size ] = "back"; + + foreach ( position in positions ) + { + weapon = self getWeaponForPos( position ); + + if ( weapon == "none" ) + continue; + + weapon_model = getWeaponModel( weapon ); + assertEx( weapon_model != "", "No weapon model for '" + weapon + "', make sure it is precached" ); + + if ( weapon == "riotshield" ) + self attach( weapon_model, getTagForPos( position ) ); + else + self attach( weapon_model, getTagForPos( position ), NO_COLLISION ); + + hideTagList = GetWeaponHideTags( weapon ); + for ( i = 0; i < hideTagList.size; i++ ) + { + self HidePart( hideTagList[ i ], weapon_model ); + } + + if ( self.weaponInfo[ weapon ].useClip && !self.weaponInfo[ weapon ].hasClip ) + self hidepart( "tag_clip" ); + } + + self updateLaserStatus(); +} + +updateLaserStatus() +{ + if ( isdefined( self.custom_laser_function ) ) + { + [[ self.custom_laser_function ]](); + return; + } + + // we have no weapon so there's no laser to turn off or on + if ( self.a.weaponPos[ "right" ] == "none" ) + return; + + if ( canUseLaser() ) + self laserOn(); + else + self laserOff(); +} + +canUseLaser() +{ + if ( !self.a.laserOn ) + return false; + + // shotguns don't have lasers + if ( isShotgun( self.weapon ) ) + return false; + + return isAlive( self ); +} + +getTagForPos( position ) +{ + switch( position ) + { + case "chest": + return "tag_weapon_chest"; + case "back": + return "tag_stowed_back"; + case "left": + return "tag_weapon_left"; + case "right": + return "tag_weapon_right"; + case "hand": + return "tag_inhand"; + default: + assertMsg( "unknown weapon placement position: " + position ); + break; + } +} + +DropAIWeapon( weapon ) +{ + if ( !isDefined( weapon ) ) + weapon = self.weapon; + + if ( weapon == "none" ) + return; + + if ( isdefined( self.noDrop ) ) + return; + + self detachAllWeaponModels(); + + position = self.weaponInfo[ weapon ].position; + + if ( self.dropWeapon && position != "none" ) + self thread DropWeaponWrapper( weapon, position ); + + self detachWeapon( weapon ); + if ( weapon == self.weapon ) + self.weapon = "none"; + + self updateAttachedWeaponModels(); +} + +DropAllAIWeapons() +{ + if ( isdefined( self.noDrop ) ) + return "none"; + + positions = []; + positions[ positions.size ] = "left"; + positions[ positions.size ] = "right"; + positions[ positions.size ] = "chest"; + positions[ positions.size ] = "back"; + + self detachAllWeaponModels(); + + foreach ( position in positions ) + { + weapon = self.a.weaponPos[ position ]; + + if ( weapon == "none" ) + continue; + + self.weaponInfo[ weapon ].position = "none"; + self.a.weaponPos[ position ] = "none"; + + if ( self.dropWeapon ) + self thread DropWeaponWrapper( weapon, position ); + } + + self.weapon = "none"; + + self updateAttachedWeaponModels(); +} + + +DropWeaponWrapper( weapon, position ) +{ + // this must be between calls to detachAllWeaponModels and updateAttachedWeaponModels! + + if ( self IsRagdoll() ) + return "none"; // too late. our weapon is no longer where it looks like it is. + + assert( self.a.weaponPosDropping[ position ] == "none" ); + self.a.weaponPosDropping[ position ] = weapon; + + actualDroppedWeapon = weapon; + if ( issubstr( tolower( actualDroppedWeapon ), "rpg" ) ) + actualDroppedWeapon = "rpg_player"; + + // unless we're already in the process of dropping more than one weapon, + // this will not actually create the weapon until the next frame, so it can get the tag's velocity. + self DropWeapon( actualDroppedWeapon, position, 0 ); + + // So we want to wait a bit before detaching the model. + + // No waiting before this point! + self endon( "end_weapon_drop_" + position ); + wait .1; + + if ( !isDefined( self ) ) + return; + + self detachAllWeaponModels(); + self.a.weaponPosDropping[ position ] = "none"; + self updateAttachedWeaponModels(); +} + + + /# +showNoteTrack( note ) +{ + if ( getdebugdvar( "scr_shownotetracks" ) != "on" && getdebugdvarint( "scr_shownotetracks" ) != self getentnum() ) + return; + + self endon( "death" ); + + anim.showNotetrackSpeed = 30;// units / sec + anim.showNotetrackDuration = 30;// frames + + if ( !isdefined( self.a.shownotetrackoffset ) ) + { + thisoffset = 0; + self.a.shownotetrackoffset = 10; + self thread reduceShowNotetrackOffset(); + } + else + { + thisoffset = self.a.shownotetrackoffset; + self.a.shownotetrackoffset += 10; + } + + duration = anim.showNotetrackDuration + int( 20.0 * thisoffset / anim.showNotetrackSpeed ); + + color = ( .5, .75, 1 ); + if ( note == "end" || note == "finish" ) + color = ( .25, .4, .5 ); + else if ( note == "undefined" ) + color = ( 1, .5, .5 ); + + for ( i = 0; i < duration; i++ ) + { + if ( duration - i <= anim.showNotetrackDuration ) + amnt = 1.0 * ( i - ( duration - anim.showNotetrackDuration ) ) / anim.showNotetrackDuration; + else + amnt = 0.0; + time = 1.0 * i / 20; + + alpha = 1.0 - amnt * amnt; + pos = self geteye() + ( 0, 0, 20 + anim.showNotetrackSpeed * time - thisoffset ); + + print3d( pos, note, color, alpha ); + + wait .05; + } +} +reduceShowNotetrackOffset() +{ + self endon( "death" ); + while ( self.a.shownotetrackoffset > 0 ) + { + wait .05; + self.a.shownotetrackoffset -= anim.showNotetrackSpeed * .05; + } + self.a.shownotetrackoffset = undefined; +} +#/ + +HandleDogSoundNoteTracks( note ) +{ + if ( note == "sound_dogstep_run_default" ) + { + self playsound( "dogstep_run_default" ); + return true; + } + + prefix = getsubstr( note, 0, 5 ); + + if ( prefix != "sound" ) + return false; + + alias = "anml" + getsubstr( note, 5 ); + +// if ( growling() && !issubstr( alias, "growl" ) ) +// return false; + + if ( isalive( self ) ) + self thread play_sound_on_tag_endon_death( alias, "tag_eye" ); + else + self thread play_sound_in_space( alias, self GetEye() ); + return true; +} + +growling() +{ + return isdefined( self.script_growl ); +} + +sfx_test_printscreen() +{ + + println( "i'm playing this ambience: " ); + +} + +registerNoteTracks() +{ + + anim.notetracks["sfx_test_printscreen" ] = ::sfx_test_printscreen; + anim.notetracks[ "anim_pose = \"stand\"" ] = ::noteTrackPoseStand; + anim.notetracks[ "anim_pose = \"crouch\"" ] = ::noteTrackPoseCrouch; + anim.notetracks[ "anim_pose = \"prone\"" ] = ::noteTrackPoseProne; + anim.notetracks[ "anim_pose = \"crawl\"" ] = ::noteTrackPoseCrawl; + anim.notetracks[ "anim_pose = \"back\"" ] = ::noteTrackPoseBack; + + anim.notetracks[ "anim_movement = \"stop\"" ] = ::noteTrackMovementStop; + anim.notetracks[ "anim_movement = \"walk\"" ] = ::noteTrackMovementWalk; + anim.notetracks[ "anim_movement = \"run\"" ] = ::noteTrackMovementRun; + + anim.notetracks[ "anim_aiming = 1" ] = ::noteTrackAlertnessAiming; + anim.notetracks[ "anim_aiming = 0" ] = ::noteTrackAlertnessAlert; + anim.notetracks[ "anim_alertness = causal" ] = ::noteTrackAlertnessCasual; + anim.notetracks[ "anim_alertness = alert" ] = ::noteTrackAlertnessAlert; + anim.notetracks[ "anim_alertness = aiming" ] = ::noteTrackAlertnessAiming; + + anim.notetracks[ "gunhand = (gunhand)_left" ] = ::noteTrackGunhand; + anim.notetracks[ "anim_gunhand = \"left\"" ] = ::noteTrackGunhand; + anim.notetracks[ "gunhand = (gunhand)_right" ] = ::noteTrackGunhand; + anim.notetracks[ "anim_gunhand = \"right\"" ] = ::noteTrackGunhand; + anim.notetracks[ "anim_gunhand = \"none\"" ] = ::noteTrackGunhand; + anim.notetracks[ "gun drop" ] = ::noteTrackGunDrop; + anim.notetracks[ "dropgun" ] = ::noteTrackGunDrop; + + anim.notetracks[ "gun_2_chest" ] = ::noteTrackGunToChest; + anim.notetracks[ "gun_2_back" ] = ::noteTrackGunToBack; + anim.notetracks[ "pistol_pickup" ] = ::noteTrackPistolPickup; + anim.notetracks[ "pistol_putaway" ] = ::noteTrackPistolPutaway; + anim.notetracks[ "drop clip" ] = ::noteTrackDropClip; + anim.notetracks[ "refill clip" ] = ::noteTrackRefillClip; + anim.notetracks[ "reload done" ] = ::noteTrackRefillClip; + anim.notetracks[ "load_shell" ] = ::noteTrackLoadShell; + anim.notetracks[ "pistol_rechamber" ] = ::noteTrackPistolRechamber; + + anim.notetracks[ "gravity on" ] = ::noteTrackGravity; + anim.notetracks[ "gravity off" ] = ::noteTrackGravity; + + anim.notetracks[ "footstep_right_large" ] = ::noteTrackFootStep; + anim.notetracks[ "footstep_right_small" ] = ::noteTrackFootStepSmall; + anim.notetracks[ "footstep_left_large" ] = ::noteTrackFootStep; + anim.notetracks[ "footstep_left_small" ] = ::noteTrackFootStepSmall; + anim.notetracks[ "footscrape" ] = ::noteTrackFootScrape; + anim.notetracks[ "land" ] = ::noteTrackLand; + + anim.notetracks[ "test_script" ] = ::noteTrackTestScript; + + anim.notetracks[ "code_move" ] = ::noteTrackCodeMove; + anim.notetracks[ "face_enemy" ] = ::noteTrackFaceEnemy; + + anim.notetracks[ "laser_on" ] = ::noteTrackLaser; + anim.notetracks[ "laser_off" ] = ::noteTrackLaser; + + anim.notetracks[ "start_ragdoll" ] = ::noteTrackStartRagdoll; + + anim.notetracks[ "fire" ] = ::noteTrackFire; + anim.notetracks[ "fire_spray" ] = ::noteTrackFireSpray; + + anim.notetracks[ "bloodpool" ] = animscripts\death::play_blood_pool; + + anim.notetracks[ "shock" ] = animscripts\pain::play_shock; + + /# + anim.notetracks[ "attach clip left" ] = animscripts\shared::insure_dropping_clip; + anim.notetracks[ "attach clip right" ] = animscripts\shared::insure_dropping_clip; + anim.notetracks[ "detach clip left" ] = animscripts\shared::insure_dropping_clip; + anim.notetracks[ "detach clip right" ] = animscripts\shared::insure_dropping_clip; + #/ + + refresh_custom_note_track_fx(); +} + +refresh_custom_note_track_fx() +{ + if ( isdefined( level._notetrackFX ) ) + { + keys = getArrayKeys( level._notetrackFX ); + foreach( key in keys ) + anim.notetracks[ key ] = ::customNotetrackFX; + } +} + +noteTrackFire( note, flagName ) +{ + if ( isdefined( anim.fire_notetrack_functions[ self.script ] ) ) + thread [[ anim.fire_notetrack_functions[ self.script ] ]](); + else + thread [[ animscripts\shared::shootNotetrack ]](); +} + +noteTrackLaser( note, flagName ) +{ + if ( isSubStr( note, "on" ) ) + self.a.laserOn = true; + else + self.a.laserOn = false; + self animscripts\shared::updateLaserStatus(); +} + + +noteTrackStopAnim( note, flagName ) +{ +} + +unlinkNextFrame() +{ + // by waiting a couple frames, we let ragdoll inherit our velocity. + wait .1; + if ( isdefined( self ) ) + self unlink(); +} + +noteTrackStartRagdoll( note, flagName ) +{ + if ( isdefined( self.noragdoll ) && self.noRagdoll ) + return; // Nate - hack for armless zakhaev who doesn't do ragdoll + if ( !isdefined( self.dont_unlink_ragdoll ) ) + self thread unlinkNextFrame(); + self startRagdoll(); + /# + if ( isalive( self ) ) + println( "^4Warning!! Living guy did ragdoll!" ); + #/ +} + +noteTrackMovementStop( note, flagName ) +{ + self.a.movement = "stop"; +} + +noteTrackMovementWalk( note, flagName ) +{ + self.a.movement = "walk"; +} + +noteTrackMovementRun( note, flagName ) +{ + self.a.movement = "run"; +} + + +noteTrackAlertnessAiming( note, flagName ) +{ + //self.a.alertness = "aiming"; +} + +noteTrackAlertnessCasual( note, flagName ) +{ + //self.a.alertness = "casual"; +} + +noteTrackAlertnessAlert( note, flagName ) +{ + //self.a.alertness = "alert"; +} + +stopOnBack() +{ + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + self.a.onback = undefined; +} + +setPose( pose ) +{ + self.a.pose = pose; + + if ( isdefined( self.a.onback ) ) + stopOnBack(); + + self notify( "entered_pose" + pose ); +} + +noteTrackPoseStand( note, flagName ) +{ + if ( self.a.pose == "prone" ) + { + self OrientMode( "face default" ); // We were most likely in "face current" while we were prone. + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + } + setPose( "stand" ); +} + +noteTrackPoseCrouch( note, flagName ) +{ + if ( self.a.pose == "prone" ) + { + self OrientMode( "face default" ); // We were most likely in "face current" while we were prone. + self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground + } + setPose( "crouch" ); +} + +noteTrackPoseProne( note, flagName ) +{ + if ( !issentient( self ) ) + return; + + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up ); + self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground + setPose( "prone" ); + + if ( isdefined( self.a.goingToProneAim ) ) + self.a.proneAiming = true; + else + self.a.proneAiming = undefined; +} + + +noteTrackPoseCrawl( note, flagName ) +{ + if ( !issentient( self ) ) + return; + + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up ); + self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground + setPose( "prone" ); + self.a.proneAiming = undefined; +} + + +noteTrackPoseBack( note, flagName ) +{ + if ( !issentient( self ) ) + return; + + setPose( "crouch" ); + self.a.onback = true; + self.a.movement = "stop"; + + self setProneAnimNodes( -90, 90, %prone_legs_down, %exposed_aiming, %prone_legs_up ); + self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground +} + + +noteTrackGunHand( note, flagName ) +{ + if ( isSubStr( note, "left" ) ) + { + animscripts\shared::placeWeaponOn( self.weapon, "left" ); + self notify( "weapon_switch_done" ); + } + else if ( isSubStr( note, "right" ) ) + { + animscripts\shared::placeWeaponOn( self.weapon, "right" ); + self notify( "weapon_switch_done" ); + } + else if ( isSubStr( note, "none" ) ) + { + animscripts\shared::placeWeaponOn( self.weapon, "none" ); + } +} + + +noteTrackGunDrop( note, flagName ) +{ + self DropAIWeapon(); + + self.lastWeapon = self.weapon; +} + + +noteTrackGunToChest( note, flagName ) +{ + //assert( !usingSidearm() ); + animscripts\shared::placeWeaponOn( self.weapon, "chest" ); +} + + +noteTrackGunToBack( note, flagName ) +{ + animscripts\shared::placeWeaponOn( self.weapon, "back" ); + // TODO: more asserts and elegant handling of weapon switching here + self.weapon = self getPreferredWeapon(); + self.bulletsInClip = weaponClipSize( self.weapon ); +} + + +noteTrackPistolPickup( note, flagName ) +{ + animscripts\shared::placeWeaponOn( self.sidearm, "right" ); + + AssertEx( isdefined( self.weapon ) && self.weapon != "none", "Actor classname " + self.classname + " at " + self.origin + " has no weapon!" ); + self.bulletsInClip = weaponClipSize( self.weapon ); + self notify( "weapon_switch_done" ); +} + + +noteTrackPistolPutaway( note, flagName ) +{ + animscripts\shared::placeWeaponOn( self.weapon, "none" ); + // TODO: more asserts and elegant handling of weapon switching here + self.weapon = self getPreferredWeapon(); + self.bulletsInClip = weaponClipSize( self.weapon ); +} + + +noteTrackDropClip( note, flagName ) +{ + self thread handleDropClip( flagName ); +} + + +noteTrackRefillClip( note, flagName ) +{ + if ( weaponClass( self.weapon ) == "rocketlauncher" ) + self showRocket(); + self animscripts\weaponList::RefillClip(); + self.a.needsToRechamber = 0; +} + +noteTrackLoadShell( note, flagName ) +{ + self playSound( "weap_reload_shotgun_loop_npc" ); +} + +noteTrackPistolRechamber( note, flagName ) +{ + self playSound( "weap_reload_pistol_chamber_npc" ); +} + +noteTrackGravity( note, flagName ) +{ + if ( isSubStr( note, "on" ) ) + self animMode( "gravity" ); + else if ( isSubStr( note, "off" ) ) + self animMode( "nogravity" ); +} + +noteTrackTestScript( note, flagName ) +{ + println("noteTrackTestScript"); +// self playSound( "sp_level_up" ); +} + +noteTrackFootStep( note, flagName ) +{ + if ( isSubStr( note, "left" ) ) + playFootStep( "J_Ball_LE" ); + else + playFootStep( "J_BALL_RI" ); + + self playSound( "gear_rattle_run" ); +} + +noteTrackFootStepSmall( note, flagName ) +{ + if ( isSubStr( note, "left" ) ) + playFootStepSmall( "J_Ball_LE" ); + else + playFootStepSmall( "J_BALL_RI" ); + + self playSound( "gear_rattle_run" ); +} + +customNotetrackFX( note, flagName ) +{ + assert( IsDefined( level._notetrackFX[ note ] ) ); + + if ( IsDefined( self.groundType ) ) + { + groundType = self.groundType; + } + else + { + groundType = "dirt"; + } + + fxStruct = undefined; + if ( IsDefined( level._notetrackFX[ note ][ groundType ] ) ) + { + fxStruct = level._notetrackFX[ note ][ groundType ]; + } + else if ( IsDefined( level._notetrackFX[ note ][ "all" ] ) ) + { + fxStruct = level._notetrackFX[ note ][ "all" ]; + } + + if ( !isdefined( fxStruct ) ) + { + return; + } + + foreach( fxItem in fxStruct ) + { + if ( !IsDefined( fxItem ) ) + { + continue; + } + + if ( isAI( self ) ) + { + playFXOnTag( fxItem.fx, self, fxItem.tag ); + } + + if ( !IsDefined( fxItem.sound_prefix ) && !isdefined( fxItem.sound_suffix ) ) + { + continue; + } + + soundAlias = "" + fxItem.sound_prefix + groundType + fxItem.sound_suffix; + self playsound( soundAlias ); + } +} + +noteTrackFootScrape( note, flagName ) +{ + if ( isDefined( self.groundType ) ) + groundType = self.groundType; + else + groundType = "dirt"; + + self playsound( "step_scrape_" + groundType ); +} + + +noteTrackLand( note, flagName ) +{ + if ( isDefined( self.groundType ) ) + groundType = self.groundType; + else + groundType = "dirt"; + + self playsound( "land_" + groundType ); +} + + +noteTrackCodeMove( note, flagName ) +{ + return "code_move"; +} + + +noteTrackFaceEnemy( note, flagName ) +{ + if ( self.script != "reactions" ) + { + self orientmode( "face enemy" ); + } + else + { + if ( isdefined( self.enemy ) && distanceSquared( self.enemy.origin, self.reactionTargetPos ) < 64 * 64 ) + self orientmode( "face enemy" ); + else + self orientmode( "face point", self.reactionTargetPos ); + } +} + +HandleNoteTrack( note, flagName, customFunction ) +{ + /# + self thread showNoteTrack( note ); + #/ + + if ( isAI( self ) && self.type == "dog" ) + if ( HandleDogSoundNoteTracks( note ) ) + return; + + notetrackFunc = anim.notetracks[ note ]; + if ( isDefined( notetrackFunc ) ) + { + return [[ notetrackFunc ]]( note, flagName ); + } + + prefix = GetSubStr( note, 0, 3 ); + if ( prefix == "ps_" ) + { + alias = GetSubStr( note, 3 ); + self thread play_sound_on_tag( alias, undefined, true ); + return; + } + if ( prefix == "ss_" ) + { + alias = GetSubStr( note, 3 ); + self thread play_stoppable_sound_on_entity( alias, true ); + return; + } + + switch( note ) + { + case "end": + case "finish": + case "undefined": + return note; + + case "finish early": + if ( isdefined( self.enemy ) ) + return note; + break; + + case "swish small": + self thread play_sound_in_space( "melee_swing_small", self gettagorigin( "TAG_WEAPON_RIGHT" ) ); + break; + case "swish large": + self thread play_sound_in_space( "melee_swing_large", self gettagorigin( "TAG_WEAPON_RIGHT" ) ); + break; + + case "rechamber": + if ( weapon_pump_action_shotgun() ) + self playSound( "weap_reload_shotgun_pump_npc" ); + self.a.needsToRechamber = 0; + break; + case "no death": + // does not play a death anim when he dies + self.a.nodeath = true; + break; + case "no pain": + self.allowpain = false; + break; + case "allow pain": + self.allowpain = true; + break; + case "anim_melee = right": + case "anim_melee = \"right\"": + self.a.meleeState = "right"; + break; + case "anim_melee = left": + case "anim_melee = \"left\"": + self.a.meleeState = "left"; + break; + case "swap taghelmet to tagleft": + if ( isDefined( self.hatModel ) ) + { + if ( isdefined( self.helmetSideModel ) ) + { + self detach( self.helmetSideModel, "TAG_HELMETSIDE" ); + self.helmetSideModel = undefined; + } + self detach( self.hatModel, "" ); + self attach( self.hatModel, "TAG_WEAPON_LEFT" ); + self.hatModel = undefined; + } + break; + case "stop anim": + anim_stopanimscripted(); + return note; + case "break glass": + level notify( "glass_break", self ); + break; + case "break_glass": + level notify( "glass_break", self ); + break; + case "ignoreall true": + self.ignoreall = true; + break; + case "ignoreall false": + self.ignoreall = false; + break; + case "ignoreme true": + self.ignoreme = true; + break; + case "ignoreme false": + self.ignoreme = false; + break; + case "allowdeath true": + self.allowdeath = true; + break; + case "allowdeath false": + self.allowdeath = false; + break; + default: + if ( isDefined( customFunction ) ) + return [[ customFunction ]]( note ); + break; + } +} + +// DoNoteTracks waits for and responds to standard noteTracks on the animation, returning when it gets an "end" or a "finish" +// For level scripts, a pointer to a custom function should be passed as the second argument, which handles notetracks not +// already handled by the generic function. This call should take the form DoNoteTracks(flagName, ::customFunction); +// The custom function will be called for each notetrack not recognized, and will pass the notetrack name. Note that this +// function could be called multiple times for a single animation. +DoNoteTracks( flagName, customFunction, debugIdentifier )// debugIdentifier isn't even used. we should get rid of it. +{ + for ( ;; ) + { + self waittill( flagName, note ); + + if ( !isDefined( note ) ) + note = "undefined"; + + //prof_begin("HandleNoteTrack"); + val = self HandleNoteTrack( note, flagName, customFunction ); + //prof_end("HandleNoteTrack"); + + if ( isDefined( val ) ) + return val; + } +} + + +DoNoteTracksIntercept( flagName, interceptFunction, debugIdentifier )// debugIdentifier isn't even used. we should get rid of it. +{ + assert( isDefined( interceptFunction ) ); + + for ( ;; ) + { + self waittill( flagName, note ); + + if ( !isDefined( note ) ) + note = "undefined"; + + intercepted = [[ interceptFunction ]]( note ); + if ( isDefined( intercepted ) && intercepted ) + continue; + + //prof_begin("HandleNoteTrack"); + val = self HandleNoteTrack( note, flagName ); + //prof_end("HandleNoteTrack"); + + if ( isDefined( val ) ) + return val; + } +} + + +DoNoteTracksPostCallback( flagName, postFunction ) +{ + assert( isDefined( postFunction ) ); + + for ( ;; ) + { + self waittill( flagName, note ); + + if ( !isDefined( note ) ) + note = "undefined"; + + //prof_begin("HandleNoteTrack"); + val = self HandleNoteTrack( note, flagName ); + //prof_end("HandleNoteTrack"); + + [[ postFunction ]]( note ); + + if ( isDefined( val ) ) + return val; + } +} + +DoNoteTracksForTimeout( flagName, killString, customFunction, debugIdentifier ) +{ + DoNoteTracks( flagName, customFunction, debugIdentifier ); +} + +// Don't call this function except as a thread you're going to kill - it lasts forever. +DoNoteTracksForever( flagName, killString, customFunction, debugIdentifier ) +{ + DoNoteTracksForeverProc( ::DoNoteTracks, flagName, killString, customFunction, debugIdentifier ); +} + +DoNoteTracksForeverIntercept( flagName, killString, interceptFunction, debugIdentifier ) +{ + DoNoteTracksForeverProc( ::DoNoteTracksIntercept, flagName, killString, interceptFunction, debugIdentifier ); +} + +DoNoteTracksForeverProc( notetracksFunc, flagName, killString, customFunction, debugIdentifier ) +{ + if ( isdefined( killString ) ) + self endon( killString ); + self endon( "killanimscript" ); + if ( !isDefined( debugIdentifier ) ) + debugIdentifier = "undefined"; + + for ( ;; ) + { + //prof_begin( "DoNoteTracksForeverProc" ); + time = GetTime(); + //prof_begin( "notetracksFunc" ); + returnedNote = [[ notetracksFunc ]]( flagName, customFunction, debugIdentifier ); + //prof_end( "notetracksFunc" ); + timetaken = GetTime() - time; + if ( timetaken < 0.05 ) + { + time = GetTime(); + //prof_begin( "notetracksFunc" ); + returnedNote = [[ notetracksFunc ]]( flagName, customFunction, debugIdentifier ); + //prof_end( "notetracksFunc" ); + timetaken = GetTime() - time; + if ( timetaken < 0.05 ) + { + println( GetTime() + " " + debugIdentifier + " animscripts\shared::DoNoteTracksForever is trying to cause an infinite loop on anim " + flagName + ", returned " + returnedNote + "." ); + wait( 0.05 - timetaken ); + } + } + //(GetTime()+" "+debugIdentifier+" DoNoteTracksForever returned in "+timetaken+" ms.");#/ + //prof_end( "DoNoteTracksForeverProc" ); + } +} + + +// Designed for using DoNoteTracks until "end" is reached, or a specified amount of time, whichever happens first +DoNoteTracksWithTimeout( flagName, time, customFunction, debugIdentifier ) +{ + ent = spawnstruct(); + ent thread doNoteTracksForTimeEndNotify( time ); + DoNoteTracksForTimeProc( ::DoNoteTracksForTimeout, flagName, customFunction, debugIdentifier, ent ); +} + +// Designed for using DoNoteTracks on looping animations, so you can wait for a time instead of the "end" parameter +DoNoteTracksForTime( time, flagName, customFunction, debugIdentifier ) +{ + ent = spawnstruct(); + ent thread doNoteTracksForTimeEndNotify( time ); + DoNoteTracksForTimeProc( ::DoNoteTracksForever, flagName, customFunction, debugIdentifier, ent ); +} + +DoNoteTracksForTimeIntercept( time, flagName, interceptFunction, debugIdentifier ) +{ + ent = spawnstruct(); + ent thread doNoteTracksForTimeEndNotify( time ); + DoNoteTracksForTimeProc( ::DoNoteTracksForeverIntercept, flagName, interceptFunction, debugIdentifier, ent ); +} + +DoNoteTracksForTimeProc( doNoteTracksForeverFunc, flagName, customFunction, debugIdentifier, ent ) +{ + ent endon( "stop_notetracks" ); + [[ doNoteTracksForeverFunc ]]( flagName, undefined, customFunction, debugIdentifier ); +} + +doNoteTracksForTimeEndNotify( time ) +{ + wait( time ); + self notify( "stop_notetracks" ); +} + +playFootStep( foot ) +{ + if ( ! isAI( self ) ) + { + self playsound( "step_run_dirt" ); + return; + } + + groundType = undefined; + // gotta record the groundtype in case it goes undefined on us + if ( !isdefined( self.groundtype ) ) + { + if ( !isdefined( self.lastGroundtype ) ) + { + self playsound( "step_run_dirt" ); + return; + } + + groundtype = self.lastGroundtype; + } + else + { + groundtype = self.groundtype; + self.lastGroundtype = self.groundType; + } + + self playsound( "step_run_" + groundType ); + if ( ![[ anim.optionalStepEffectFunction ]]( foot, groundType ) ) + playFootStepEffectSmall( foot, groundType ); +} + + +playFootStepSmall( foot ) +{ + if ( ! isAI( self ) ) + { + self playsound( "step_run_dirt" ); + return; + } + + groundType = undefined; + // gotta record the groundtype in case it goes undefined on us + if ( !isdefined( self.groundtype ) ) + { + if ( !isdefined( self.lastGroundtype ) ) + { + self playsound( "step_run_dirt" ); + return; + } + + groundtype = self.lastGroundtype; + } + else + { + groundtype = self.groundtype; + self.lastGroundtype = self.groundType; + } + + self playsound( "step_run_" + groundType ); + if ( ![[ anim.optionalStepEffectSmallFunction ]]( foot, groundType ) ) + playFootStepEffect( foot, groundType ); +} + + +playFootStepEffect( foot, groundType ) +{ + for ( i = 0;i < anim.optionalStepEffects.size;i++ ) + { + if ( groundType != anim.optionalStepEffects[ i ] ) + continue; + org = self gettagorigin( foot ); + angles = self.angles; + forward = anglestoforward( angles ); + back = forward * - 1; + up = anglestoup( angles ); + + playfx( level._effect[ "step_" + anim.optionalStepEffects[ i ] ], org, up, back ); + return true; + } + + return false; +} + +playFootStepEffectSmall( foot, groundType ) +{ + for ( i = 0;i < anim.optionalStepEffectsSmall.size;i++ ) + { + if ( groundType != anim.optionalStepEffectsSmall[ i ] ) + continue; + org = self gettagorigin( foot ); + angles = self.angles; + forward = anglestoforward( angles ); + back = forward * - 1; + up = anglestoup( angles ); + + playfx( level._effect[ "step_small_" + anim.optionalStepEffectsSmall[ i ] ], org, up, back ); + return true; + } + return false; +} + +shootNotetrack() +{ + waittillframeend;// this gives a chance for anything else waiting on "fire" to shoot + if ( isdefined( self ) && gettime() > self.a.lastShootTime ) + { + self shootEnemyWrapper(); + self decrementBulletsInClip(); + if ( weaponClass( self.weapon ) == "rocketlauncher" ) + self.a.rockets -- ; + } +} + +fire_straight() +{ + if ( self.a.weaponPos[ "right" ] == "none" ) + return; + + if ( isdefined( self.dontShootStraight ) ) + { + shootNotetrack(); + return; + } + + weaporig = self gettagorigin( "tag_weapon" ); + if ( !IsDefined( weaporig ) ) + { + error( "Error: fire_straigth() called on an entity which does not have the a tag_weapon." ); + return; + } + dir = anglestoforward( self getMuzzleAngle() ); + pos = weaporig + vector_multiply( dir, 1000 ); + // note, shootwrapper is not called because shootwrapper applies a random spread, and shots + // fired in a scripted sequence need to go perfectly straight so they get the same result each time. + self shoot( 1, pos ); + self decrementBulletsInClip(); +} + +noteTrackFireSpray( note, flagName ) +{ + if ( !isalive( self ) && self isBadGuy() ) + { + if ( isdefined( self.changed_team ) ) + return; + + + self.changed_team = true; + teams[ "axis" ] = "team3"; + teams[ "team3" ] = "axis"; + assertex( isdefined( teams[ self.team ] ), "no team for " + self.team ); + self.team = teams[ self.team ]; + } + + // TODO: make AI not use anims with this notetrack if they don't have a weapon + if ( !issentient( self ) ) + { + // for drones + self notify( "fire" ); +// self shoot(); + return; + } + + if ( self.a.weaponPos[ "right" ] == "none" ) + return; + + //prof_begin( "noteTrackFireSpray" ); + + weaporig = self getMuzzlePos(); + dir = anglestoforward( self getMuzzleAngle() ); + + // rambo set sprays at a wider range than other fire_spray anims + ang = 10; + if ( isdefined( self.isRambo ) ) + ang = 20; + + hitenemy = false; + // check if we're aiming closish to our enemy + if ( isalive( self.enemy ) && issentient( self.enemy ) && self canShootEnemy() ) + { + enemydir = vectornormalize( self.enemy geteye() - weaporig ); + if ( vectordot( dir, enemydir ) > cos( ang ) ) + { + hitenemy = true; + } + } + + if ( hitenemy ) + { + self shootEnemyWrapper(); + } + else + { + dir += ( ( randomfloat( 2 ) - 1 ) * .1, ( randomfloat( 2 ) - 1 ) * .1, ( randomfloat( 2 ) - 1 ) * .1 ); + pos = weaporig + vector_multiply( dir, 1000 ); + + self shootPosWrapper( pos ); + } + + self decrementBulletsInClip(); + + //prof_end( "noteTrackFireSpray" ); +} + + +getPredictedAimYawToShootEntOrPos( time ) +{ + if ( !isdefined( self.shootEnt ) ) + { + if ( !isdefined( self.shootPos ) ) + return 0; + + return getAimYawToPoint( self.shootPos ); + } + + predictedPos = self.shootEnt.origin + vector_multiply( self.shootEntVelocity, time ); + return getAimYawToPoint( predictedPos ); +} + +getAimYawToShootEntOrPos() +{ + // make use of the fact that shootPos = shootEnt getShootAtPos() if shootEnt is defined + if ( !isdefined( self.shootEnt ) ) + { + if ( !isdefined( self.shootPos ) ) + return 0; + + return getAimYawToPoint( self.shootPos ); + } + + return getAimYawToPoint( self.shootEnt getShootAtPos() ); +} + +getAimPitchToShootEntOrPos() +{ + pitch = getPitchToShootEntOrPos(); + if ( self.script == "cover_crouch" && isdefined( self.a.coverMode ) && self.a.coverMode == "lean" ) + pitch -= anim.coverCrouchLeanPitch; + return pitch; +} + +getPitchToShootEntOrPos() +{ + if ( !isdefined( self.shootEnt ) ) + { + // make use of the fact that shootPos = shootEnt getShootAtPos() if shootEnt is defined + if ( !isdefined( self.shootPos ) ) + return 0; + + return animscripts\combat_utility::getPitchToSpot( self.shootPos ); + } + + return animscripts\combat_utility::getPitchToSpot( self.shootEnt getShootAtPos() ); +} + +getShootFromPos() +{ + if ( isdefined( self.useMuzzleSideOffset ) ) + { + muzzlePos = self getMuzzleSideOffsetPos(); + return ( muzzlePos[ 0 ], muzzlePos[ 1 ], self getEye()[ 2 ] ); + } + + return ( self.origin[ 0 ], self.origin[ 1 ], self getEye()[ 2 ] ); +} + +getAimYawToPoint( point ) +{ + yaw = GetYawToSpot( point ); + + // need to have fudge factor because the gun's origin is different than our origin, + // the closer our distance, the more we need to fudge. + dist = distance( self.origin, point ); + if ( dist > 3 ) + { + angleFudge = asin( -3 / dist ); + yaw += angleFudge; + } + yaw = AngleClamp180( yaw ); + return yaw; +} + +trackShootEntOrPos() +{ + self endon( "killanimscript" ); + self endon( "stop tracking" ); + self endon( "melee" ); + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = thisthread; + self.trackLoopThreadType = "trackShootEntOrPos"; +#/ + + if ( isDefined( self.laser_while_shooting ) ) + { + self LaserForceOn(); + self thread turn_off_laser_on_stop_tracking(); + + if ( IsDefined( level._effect[ "laser_aim" ] ) ) + { + StopFXOnTag( level._effect[ "laser_aim" ], self, "tag_flash" ); + PlayFxOnTag( level._effect[ "laser_aim" ], self, "tag_flash" ); + } + } + + trackLoop( %aim_2, %aim_4, %aim_6, %aim_8 ); +} + +turn_off_laser_on_stop_tracking() +{ + self waittill_any( "death", "killanimscript", "stop tracking", "melee", "pain_death" ); + if ( !isDefined( self ) ) + { + return; + } + + self LaserForceOff(); + + if ( IsDefined( level._effect[ "laser_aim" ] ) ) + { + StopFXOnTag( level._effect[ "laser_aim" ], self, "tag_flash", true ); + } +} + +// max change in angle in 1 frame +normalDeltaChangePerFrame = 10; +largeDeltaChangePerFrame = 30; + + +trackLoop( aim2, aim4, aim6, aim8 ) +{ + assert( isdefined( self.trackLoopThread ) ); + + prevYawDelta = 0; + prevPitchDelta = 0; + pitchAdd = 0; + yawAdd = 0; + wasOnStairs = false; + angleDeltas = ( 0, 0, 0 ); + firstFrame = true; + prevMotionRelativeDir = 0; + quickTurnFrames = 0; + deltaChangePerFrame = normalDeltaChangePerFrame; + + if ( self.type == "dog" ) + { + doMaxAngleCheck = false; + self.shootEnt = self.enemy; + } + else + { + doMaxAngleCheck = true; + + if ( isdefined( self.coverCrouchLean_aimmode ) ) + pitchAdd = anim.coverCrouchLeanPitch; + + if ( ( self.script == "cover_left" || self.script == "cover_right" ) && isdefined( self.a.cornerMode ) && self.a.cornerMode == "lean" ) + yawAdd = self.coverNode.angles[ 1 ] - self.angles[ 1 ]; + } + + for ( ;; ) + { + //prof_begin("trackLoop"); + + incrAnimAimWeight(); + + shootFromPos = getShootFromPos(); + + shootPos = self.shootPos; + if ( isdefined( self.shootEnt ) ) + shootPos = self.shootEnt getShootAtPos(); + + if ( !isdefined( shootPos ) && self shouldCQB() ) + shootPos = trackLoop_CQBShootPos( shootFromPos ); + + if ( self.stairsState == "up" ) + { + pitchAdd = -40; + wasOnStairs = true; + } + else if ( self.stairsState == "down" ) + { + pitchAdd = 40; + yawAdd = 12; + wasOnStairs = true; + } + else if ( wasOnStairs ) + { + pitchAdd = 0; + yawAdd = 0; + wasOnStairs = false; + } + + if ( !isdefined( shootPos ) ) + angleDeltas = trackLoop_anglesForNoShootPos( shootFromPos, pitchAdd, yawAdd ); + else + angleDeltas = trackLoop_getDesiredAngles( ( shootPos - shootFromPos ), pitchAdd, yawAdd ); + + angleDeltas = trackLoop_clampAngles( angleDeltas[ 0 ], angleDeltas[ 1 ], doMaxAngleCheck ); + + pitchDelta = angleDeltas[ 0 ]; + yawDelta = angleDeltas[ 1 ]; + + if ( quickTurnFrames > 0 ) + { + quickTurnFrames = quickTurnFrames - 1; + deltaChangePerFrame = max( normalDeltaChangePerFrame, deltaChangePerFrame - 5 ); + } + else if ( self.relativeDir && self.relativeDir != prevMotionRelativeDir ) + { + quickTurnFrames = 2; + deltaChangePerFrame = largeDeltaChangePerFrame; + } + else + { + deltaChangePerFrame = normalDeltaChangePerFrame; + } + + deltaChangePerFrameSq = squared( deltaChangePerFrame ); + + prevMotionRelativeDir = self.relativeDir; + + checkDeltaChange = ( self.moveMode != "stop" ) || !firstFrame; + + + if ( checkDeltaChange ) + { + yawDeltaChange = yawDelta - prevYawDelta; + if ( squared( yawDeltaChange ) > deltaChangePerFrameSq ) + { + yawDelta = prevYawDelta + clamp( yawDeltaChange, -1 * deltaChangePerFrame, deltaChangePerFrame ); + yawDelta = clamp( yawDelta, self.leftAimLimit, self.rightAimLimit ); + } + + pitchDeltaChange = pitchDelta - prevPitchDelta; + if ( squared( pitchDeltaChange ) > deltaChangePerFrameSq ) + { + pitchDelta = prevPitchDelta + clamp( pitchDeltaChange, -1 * deltaChangePerFrame, deltaChangePerFrame ); + pitchDelta = clamp( pitchDelta, self.downAimLimit, self.upAimLimit ); + } + } + + firstFrame = false; + prevYawDelta = yawDelta; + prevPitchDelta = pitchDelta; + + trackLoop_setAnimWeights( aim2, aim4, aim6, aim8, pitchDelta, yawDelta ); + + //prof_end("trackLoop"); + wait( 0.05 ); + } +} + + +trackLoop_CQBShootPos( shootFromPos ) +{ + shootPos = undefined; + selfForward = anglesToForward( self.angles ); + + if ( isdefined( self.cqb_target ) ) + { + shootPos = self.cqb_target getShootAtPos(); + if ( vectorDot( vectorNormalize( shootPos - shootFromPos ), selfForward ) < 0.643 )// 0.643 = cos50 + shootPos = undefined; + } + if ( !isdefined( shootPos ) && isdefined( self.cqb_point_of_interest ) ) + { + shootPos = self.cqb_point_of_interest; + if ( vectorDot( vectorNormalize( shootPos - shootFromPos ), selfForward ) < 0.643 )// 0.643 = cos50 + shootPos = undefined; + } + + return shootPos; +} + + +trackLoop_anglesForNoShootPos( shootFromPos, pitchAdd, yawAdd ) +{ + assert( !isdefined( self.shootEnt ) ); + + if ( recentlySawEnemy() ) + { + shootAtOffset = ( self.enemy getShootAtPos() - self.enemy.origin ); + shootAtPos = ( self lastKnownPos( self.enemy ) + shootAtOffset ); + return trackLoop_getDesiredAngles( (shootAtPos - shootFromPos), pitchAdd, yawAdd ); + } + + pitchDelta = 0; + yawDelta = 0; + + if ( isdefined( self.node ) && isdefined( anim.isCombatScriptNode[ self.node.type ] ) && distanceSquared( self.origin, self.node.origin ) < 16 ) + { + yawDelta = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); + } + else + { + likelyEnemyDir = self getAnglesToLikelyEnemyPath(); + if ( isdefined( likelyEnemyDir ) ) + { + yawDelta = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] ); + pitchDelta = AngleClamp180( 360 - likelyEnemyDir[ 0 ] ); + } + } + + return( pitchDelta, yawDelta, 0 ); +} + + +trackLoop_getDesiredAngles( vectorToShootPos, pitchAdd, yawAdd ) +{ + anglesToShootPos = vectorToAngles( vectorToShootPos ); + + pitchDelta = 360 - anglesToShootPos[ 0 ]; + pitchDelta = AngleClamp180( pitchDelta + pitchAdd ); + + if ( isDefined( self.stepOutYaw ) ) + { + yawDelta = self.stepOutYaw - anglesToShootPos[ 1 ]; + } + else + { + yawOffset = AngleClamp180( self.desiredAngle - self.angles[ 1 ] ) * 0.5; + yawDelta = yawOffset + self.angles[ 1 ] - anglesToShootPos[ 1 ]; + } + yawDelta = AngleClamp180( yawDelta + yawAdd ); + + return( pitchDelta, yawDelta, 0 ); +} + + +trackLoop_clampAngles( pitchDelta, yawDelta, doMaxAngleCheck ) +{ + if ( isdefined( self.onSnowMobile ) ) + { + if ( yawDelta > self.rightAimLimit || yawDelta < self.leftAimLimit ) + yawDelta = 0; + if ( pitchDelta > self.upAimLimit || pitchDelta < self.downAimLimit ) + pitchDelta = 0; + } + else if ( doMaxAngleCheck && ( abs( yawDelta ) > anim.maxAngleCheckYawDelta || abs( pitchDelta ) > anim.maxAngleCheckPitchDelta ) ) + { + yawDelta = 0; + pitchDelta = 0; + } + else + { + if ( self.gunBlockedByWall ) + yawDelta = clamp( yawDelta, -10, 10 ); + else + yawDelta = clamp( yawDelta, self.leftAimLimit, self.rightAimLimit ); + + pitchDelta = clamp( pitchDelta, self.downAimLimit, self.upAimLimit ); + } + + return( pitchDelta, yawDelta, 0 ); +} + +aimBlendTime = .1; + +trackLoop_setAnimWeights( aim2, aim4, aim6, aim8, pitchDelta, yawDelta ) +{ + if ( yawDelta > 0 ) + { + assert( yawDelta <= self.rightAimLimit ); + weight = yawDelta / self.rightAimLimit * self.a.aimweight; + self setAnimLimited( aim4, 0, aimBlendTime, 1, true ); + self setAnimLimited( aim6, weight, aimBlendTime, 1, true ); + } + else if ( yawDelta < 0 ) + { + assert( yawDelta >= self.leftAimLimit ); + weight = yawDelta / self.leftAimLimit * self.a.aimweight; + self setAnimLimited( aim6, 0, aimBlendTime, 1, true ); + self setAnimLimited( aim4, weight, aimBlendTime, 1, true ); + } + + if ( pitchDelta > 0 ) + { + assert( pitchDelta <= self.upAimLimit ); + weight = pitchDelta / self.upAimLimit * self.a.aimweight; + self setAnimLimited( aim2, 0, aimBlendTime, 1, true ); + self setAnimLimited( aim8, weight, aimBlendTime, 1, true ); + } + else if ( pitchDelta < 0 ) + { + assert( pitchDelta >= self.downAimLimit ); + weight = pitchDelta / self.downAimLimit * self.a.aimweight; + self setAnimLimited( aim8, 0, aimBlendTime, 1, true ); + self setAnimLimited( aim2, weight, aimBlendTime, 1, true ); + } +} + + +//setAnimAimWeight works just like setanimlimited on an imaginary anim node that affects the four aiming directions. +setAnimAimWeight( goalweight, goaltime ) +{ + if ( !isdefined( goaltime ) || goaltime <= 0 ) + { + self.a.aimweight = goalweight; + self.a.aimweight_start = goalweight; + self.a.aimweight_end = goalweight; + self.a.aimweight_transframes = 0; + } + else + { + if ( !isdefined( self.a.aimweight ) ) + self.a.aimweight = 0; + self.a.aimweight_start = self.a.aimweight; + self.a.aimweight_end = goalweight; + self.a.aimweight_transframes = int( goaltime * 20 ); + } + self.a.aimweight_t = 0; +} +incrAnimAimWeight() +{ + if ( self.a.aimweight_t < self.a.aimweight_transframes ) + { + self.a.aimweight_t++ ; + t = 1.0 * self.a.aimweight_t / self.a.aimweight_transframes; + self.a.aimweight = self.a.aimweight_start * ( 1 - t ) + self.a.aimweight_end * t; + } +} + + +ramboAim( baseYaw ) +{ + self endon( "killanimscript" ); + + ramboAimInternal( baseYaw ); + + self clearAnim( %generic_aim_left, 0.5 ); + self clearAnim( %generic_aim_right, 0.5 ); +} + +ramboAimInternal( baseYaw ) +{ + self endon( "rambo_aim_end" ); + + waittillframeend; // in case a previous ramboAim call is still doing its clearanims + + self clearAnim( %generic_aim_left, 0.2 ); + self clearAnim( %generic_aim_right, 0.2 ); + + self setAnimLimited( %generic_aim_45l, 1, 0.2 ); + self setAnimLimited( %generic_aim_45r, 1, 0.2 ); + + interval = 0.2; + + yaw = 0; + for ( ;; ) + { + if ( isDefined( self.shootPos ) ) + { + newyaw = GetYaw( self.shootPos ) - self.coverNode.angles[1]; + newyaw = AngleClamp180( newyaw - baseYaw ); + + if ( abs( newyaw - yaw ) > 10 ) + { + if ( newyaw > yaw ) + newyaw = yaw + 10; + else + newyaw = yaw - 10; + } + yaw = newyaw; + } + // otherwise reuse old yaw + + if ( yaw < 0 ) + { + weight = yaw / -45; + if ( weight > 1 ) + weight = 1; + + self setAnimLimited( %generic_aim_right, weight, interval ); + self setAnimLimited( %generic_aim_left, 0, interval ); + } + else + { + weight = yaw / 45; + if ( weight > 1 ) + weight = 1; + + self setAnimLimited( %generic_aim_left, weight, interval ); + self setAnimLimited( %generic_aim_right, 0, interval ); + } + + wait interval; + } +} + + +// decides on the number of shots to do in a burst. +decideNumShotsForBurst() +{ + numShots = 0; + fixedBurstCount = weaponBurstCount( self.weapon ); + + if ( fixedBurstCount ) + numShots = fixedBurstCount; + else if ( animscripts\weaponList::usingSemiAutoWeapon() ) + numShots = anim.semiFireNumShots[ randomint( anim.semiFireNumShots.size ) ]; + else if ( self.fastBurst ) + numShots = anim.fastBurstFireNumShots[ randomint( anim.fastBurstFireNumShots.size ) ]; + else + numShots = anim.burstFireNumShots[ randomint( anim.burstFireNumShots.size ) ]; + + if ( numShots <= self.bulletsInClip ) + return numShots; + + assertex( self.bulletsInClip >= 0, self.bulletsInClip ); + + if ( self.bulletsInClip <= 0 ) + return 1; + + return self.bulletsInClip; +} + +decideNumShotsForFull() +{ + numShots = self.bulletsInClip; + if ( weaponClass( self.weapon ) == "mg" ) + { + choice = randomfloat( 10 ); + if ( choice < 3 ) + numShots = randomIntRange( 2, 6 ); + else if ( choice < 8 ) + numShots = randomIntRange( 6, 12 ); + else + numShots = randomIntRange( 12, 20 ); + } + + return numShots; +} + +insure_dropping_clip( note, flagName ) +{ + /# + // will turn this assert on after the current anims get fixed + //assertex( isdefined( self.last_drop_clip_time ) && self.last_drop_clip_time > gettime() - 5000, "Tried to do attach clip notetrack without doing drop clip notetrack first, do /g_dumpanims " + self getentnum() + " and report erroneous anim." ); + #/ +} + +handleDropClip( flagName ) +{ + self endon( "killanimscript" ); + self endon( "abort_reload" ); + + /# + // make sure that we don't do clip anims without drop clip first + self.last_drop_clip_time = gettime(); + #/ + //prof_begin( "handleDropClip" ); + + clipModel = undefined; + if ( self.weaponInfo[ self.weapon ].useClip ) + clipModel = getWeaponClipModel( self.weapon ); + + /# + if ( isdefined( clipModel ) ) + self thread assertDropClipCleanedUp( 4, clipModel ); + #/ + + if ( self.weaponInfo[ self.weapon ].hasClip ) + { + if ( usingSidearm() ) + self playsound( "weap_reload_pistol_clipout_npc" ); + else + { + if( GetDvar("environment_pressurized") == "1" ) + self playsound("weap_reload_smg_clipout_npc"); + else + self playsound("weap_reload_smg_clipout_npc_depress"); + } + + if ( isDefined( clipModel ) ) + { + self hidepart( "tag_clip" ); + self thread dropClipModel( clipModel, "tag_clip" ); + self.weaponInfo[ self.weapon ].hasClip = false; + + self thread resetClipOnAbort( clipModel ); + } + } + + //prof_end( "handleDropClip" ); + + for ( ;; ) + { + self waittill( flagName, noteTrack ); + + switch( noteTrack ) + { + case "attach clip left": + case "attach clip right": + if ( isdefined( clipModel ) ) + { + self attach( clipModel, "tag_inhand" ); + self thread resetClipOnAbort( clipModel, "tag_inhand" ); + } + + // if we abort the reload after this point, we don't want to have to do it again + self animscripts\weaponList::RefillClip(); + + break; + + case "detach clip nohand": + if ( isdefined( clipModel ) ) + self detach( clipModel, "tag_inhand" ); + break; + + case "detach clip right": + case "detach clip left": + if ( isdefined( clipModel ) ) + { + self detach( clipModel, "tag_inhand" ); + self showpart( "tag_clip" ); + self notify( "clip_detached" ); + self.weaponInfo[ self.weapon ].hasClip = true; + } + + if ( usingSidearm() ) + self playsound( "weap_reload_pistol_clipin_npc" ); + else + { + if( GetDvar("environment_pressurized") == "1" ) + self playsound( "weap_reload_smg_clipin_npc" ); + else + self playsound("weap_reload_smg_clipin_npc_depress"); + } + + + + self.a.needsToRechamber = 0; + + return; + } + } +} + + +resetClipOnAbort( clipModel, currentTag ) +{ + self notify( "clip_detached" ); + self endon( "clip_detached" ); + //self endon ( "death" ); // don't end on death or we won't delete the clip when we die! + + self waittill_any( "killanimscript", "abort_reload" ); + + // we can be dead but still defined. if we're undefined we got deleted. + if ( !isDefined( self ) ) + return; + + if ( isDefined( currentTag ) ) + self detach( clipModel, currentTag ); + + if ( isAlive( self ) ) + { + self showpart( "tag_clip" ); + self.weaponInfo[ self.weapon ].hasClip = true; + } + else + { + if ( isDefined( currentTag ) ) + self dropClipModel( clipModel, currentTag ); + } +} + + +dropClipModel( clipModel, tagName ) +{ + clip = spawn( "script_model", self getTagOrigin( tagName ) ); + clip setModel( clipModel ); + clip.angles = self getTagAngles( tagName ); + clip PhysicsLaunchClient( clip.origin, (0,0,0) ); + + wait 10; + + if ( isDefined( clip ) ) + clip delete(); +} + + +/# +assertDropClipCleanedUp( waitTime, clipModel ) +{ + self endon( "death" ); + self endon( "abort_reload" ); + self endon( "clip_detached" ); + + wait waitTime; + + // this assert can be fixed by adding an "abort_reload" notify from whatever interrupted the reload. + assertmsg( "AI " + self getEntityNumber() + " started a reload and didn't reset clip models after " + waitTime + " seconds" ); +} +#/ + +moveToOriginOverTime( origin, time ) +{ + self endon( "killanimscript" ); + + distSq = distanceSquared( self.origin, origin ); + + if ( distSq < 1 ) + { + self safeTeleport( origin ); + return; + } + + + if ( distSq > 16 * 16 && !self mayMoveToPoint( origin ) ) + { + /# println( "^1Warning: AI starting behavior for node at " + origin + " but could not move to that point." ); #/ + return; + } + + self.keepClaimedNodeIfValid = true; + + offset = self.origin - origin; + + frames = int( time * 20 ); + offsetreduction = vector_multiply( offset, 1.0 / frames ); + + for ( i = 0; i < frames; i++ ) + { + offset -= offsetreduction; + self safeTeleport( origin + offset ); + wait .05; + } + + self.keepClaimedNodeIfValid = false; +} + +returnTrue() { return true; } + +playLookAnimation( lookAnim, lookTime, canStopCallback ) +{ + if ( !isdefined( canStopCallback ) ) + canStopCallback = ::returnTrue; + + for ( i = 0; i < lookTime * 10; i++ ) + { + // Break out if you saw somebody lately + if ( isalive( self.enemy ) ) + { + if ( self canSeeEnemy() && [[ canStopCallback ]]() ) + return; + } + if ( self isSuppressedWrapper() && [[ canStopCallback ]]() ) + return; + + self setAnimKnobAll( lookAnim, %body, 1, .1 ); + wait( 0.1 ); + } +} + + +throwDownWeapon( swapAnim ) +{ + self endon( "killanimscript" ); + + // Too many issues right now +// self animMode( "angle deltas" ); +// self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .1, 1 ); +// self DoNoteTracks( "weapon swap" ); + + self animscripts\shared::placeWeaponOn( self.secondaryweapon, "right" ); + + self maps\_gameskill::didSomethingOtherThanShooting(); +} + +rpgPlayerRepulsor() +{ + // Creates a repulsor on the player when shooting at the player + // After a couple freebe misses the repulsor is removed + + MISSES_REMAINING = rpgPlayerRepulsor_getNumMisses(); + if ( MISSES_REMAINING == 0 ) + return; + + self endon( "death" ); + for(;;) + { + level waittill( "an_enemy_shot", guy ); + + if ( guy != self ) + continue; + + if ( !isdefined( guy.enemy ) ) + continue; + + if ( guy.enemy != level._player ) + continue; + + if ( ( isdefined( level._createRpgRepulsors ) ) && ( level._createRpgRepulsors == false ) ) + continue; + + thread rpgPlayerRepulsor_create(); + + MISSES_REMAINING--; + if ( MISSES_REMAINING <= 0 ) + return; + } +} + +rpgPlayerRepulsor_getNumMisses() +{ + skill = getdifficulty(); + switch( skill ) + { + case "gimp": + case "easy": + return 2; + case "medium": + case "hard": + case "difficult": + return 1; + case "fu": + return 0; + } + return 2; +} + +rpgPlayerRepulsor_create() +{ + repulsor = Missile_CreateRepulsorEnt( level._player, 5000, 800 ); + wait 4.0; + Missile_DeleteAttractor( repulsor ); +} + diff --git a/animscripts/shoot_behavior.gsc b/animscripts/shoot_behavior.gsc new file mode 100644 index 0000000..bb99af5 --- /dev/null +++ b/animscripts/shoot_behavior.gsc @@ -0,0 +1,715 @@ +#include common_scripts\utility; +#include animscripts\combat_utility; +#include animscripts\utility; +#include animscripts\shared; + +// Ideally, this will be the only thread *anywhere* that decides what/where to shoot at +// and how to shoot at it. + +// This thread keeps three variables updated, and notifies "shoot_behavior_change" when any of them have changed. +// They are: +// shootEnt - an entity. aim/shoot at this if it's defined. +// shootPos - a vector. aim/shoot towards this if shootEnt isn't defined. if not defined, stop shooting entirely and return to cover if possible. +// Whenever shootEnt is defined, shootPos will be defined as its getShootAtPos(). +// shootStyle - how to shoot. +// "full" (unload on the target), +// "burst" (occasional groups of shots), +// "semi" (occasianal single shots), +// "single" (occasional single shots), +// "none" (don't shoot, just aim). +// This thread will also notify "return_to_cover" and set self.shouldReturnToCover = true if it's a good idea to do so. +// Notify "stop_deciding_how_to_shoot" to end this thread if no longer trying to shoot. + +decideWhatAndHowToShoot( objective ) +{ + self endon( "killanimscript" ); + self notify( "stop_deciding_how_to_shoot" );// just in case... + self endon( "stop_deciding_how_to_shoot" ); + self endon( "death" ); + + assert( isdefined( objective ) );// just use "normal" if you don't know what to use + + maps\_gameskill::resetMissTime(); + self.shootObjective = objective; + // self.shootObjective is always "normal", "suppress", or "ambush" + + self.shootEnt = undefined; + self.shootPos = undefined; + self.shootStyle = "none"; + self.fastBurst = false; + self.shouldReturnToCover = undefined; + + if ( !isdefined( self.changingCoverPos ) ) + self.changingCoverPos = false; + + atCover = isDefined( self.coverNode ) && self.coverNode.type != "Cover Prone" && self.coverNode.type != "Conceal Prone"; + + if ( atCover ) + { + // it's not safe to do some things until the next frame, + // such as canSuppressEnemy(), which may change the state of + // self.goodShootPos, which will screw up cover_behavior::main + // when this is called but then stopped immediately. + wait .05; + } + + prevShootEnt = self.shootEnt; + prevShootPos = self.shootPos; + prevShootStyle = self.shootStyle; + + if ( !isdefined( self.has_no_ir ) ) + { + self.a.laserOn = true; + self animscripts\shared::updateLaserStatus(); + } + + if ( self isSniper() ) + self resetSniperAim(); + + // only watch for incoming fire if it will be beneficial for us to return to cover when shot at. + if ( atCover && ( !self.a.atConcealmentNode || !self canSeeEnemy() ) ) + thread watchForIncomingFire(); + thread runOnShootBehaviorEnd(); + + self.ambushEndTime = undefined; + + prof_begin( "decideWhatAndHowToShoot" ); + + while ( 1 ) + { + if ( isdefined( self.shootPosOverride ) ) + { + if ( !isdefined( self.enemy ) ) + { + self.shootPos = self.shootPosOverride; + self.shootPosOverride = undefined; + WaitABit(); + } + else + { + self.shootPosOverride = undefined; + } + } + + assert( self.shootObjective == "normal" || self.shootObjective == "suppress" || self.shootObjective == "ambush" ); + assert( !isdefined( self.shootEnt ) || isdefined( self.shootPos ) );// shootPos must be shootEnt's shootAtPos if shootEnt is defined, for convenience elsewhere + + result = undefined; + if ( self.weapon == "none" ) + noGunShoot(); + else if ( usingStreamgun() ) + result = streamgunShoot(); + else if ( usingRocketLauncher() ) + result = rpgShoot(); + else if ( usingSidearm() ) + result = pistolShoot(); + else if ( usingMinigun() ) + result = rifleShoot();//minigunShoot(); + else + result = rifleShoot(); + + if ( isDefined( self.a.specialShootBehavior ) ) + [[self.a.specialShootBehavior]](); + + + if ( checkChanged( prevShootEnt, self.shootEnt ) || ( !isdefined( self.shootEnt ) && checkChanged( prevShootPos, self.shootPos ) ) || checkChanged( prevShootStyle, self.shootStyle ) ) + self notify( "shoot_behavior_change" ); + prevShootEnt = self.shootEnt; + prevShootPos = self.shootPos; + prevShootStyle = self.shootStyle; + + + // (trying to prevent many AI from doing lots of work on the same frame) + if ( !isdefined( result ) ) + WaitABit(); + } + + prof_end( "decideWhatAndHowToShoot" ); +} + +WaitABit() +{ + self endon( "enemy" ); + self endon( "done_changing_cover_pos" ); + self endon( "weapon_position_change" ); + self endon( "enemy_visible" ); + + if ( isdefined( self.shootEnt ) ) + { + self.shootEnt endon( "death" ); + + self endon( "do_slow_things" ); + + // (want to keep self.shootPos up to date) + wait .05; + while ( isdefined( self.shootEnt ) ) + { + self.shootPos = self.shootEnt getShootAtPos(); + wait .05; + } + } + else + { + self waittill( "do_slow_things" ); + } +} + +noGunShoot() +{ + /# + println( "^1Warning: AI at " + self.origin + ", entnum " + self getEntNum() + ", export " + self.export + " trying to shoot but has no gun" ); + #/ + self.shootEnt = undefined; + self.shootPos = undefined; + self.shootStyle = "none"; + self.shootObjective = "normal"; +} + +shouldSuppress() +{ + return !self isSniper() && !isShotgun( self.weapon ); +} + +shouldShootEnemyEnt() +{ + assert( isDefined ( self ) ); + + if ( !self canSeeEnemy() ) + return false; + + // When not in cover, check if we can shoot at our current enemy as well + if ( !isDefined( self.coverNode ) && !self canShootEnemy() ) + return false; + + return true; +} + + +rifleShootObjectiveNormal() +{ + if ( !shouldShootEnemyEnt() ) + { + // enemy disappeared! + if ( self isSniper() ) + self resetSniperAim(); + + if ( self.doingAmbush ) + { + self.shootObjective = "ambush"; + return "retry"; + } + + if ( !isdefined( self.enemy ) ) + { + haveNothingToShoot(); + } + else + { + markEnemyPosInvisible(); + + if ( ( self.provideCoveringFire || randomint( 5 ) > 0 ) && shouldSuppress() ) + self.shootObjective = "suppress"; + else + self.shootObjective = "ambush"; + return "retry"; + } + } + else + { + setShootEntToEnemy(); + self setShootStyleForVisibleEnemy(); + } +} + + +rifleShootObjectiveSuppress( enemySuppressable ) +{ + if ( !enemySuppressable ) + { + haveNothingToShoot(); + } + else + { + self.shootEnt = undefined; + self.shootPos = getEnemySightPos(); + + self setShootStyleForSuppression(); + } +} + +rifleShootObjectiveAmbush( enemySuppressable ) +{ + assert( self.shootObjective == "ambush" ); + + self.shootStyle = "none"; + self.shootEnt = undefined; + + if ( !enemySuppressable ) + { + getAmbushShootPos(); + + if ( shouldStopAmbushing() ) + { + self.ambushEndTime = undefined; + self notify( "return_to_cover" ); + self.shouldReturnToCover = true; + } + } + else + { + self.shootPos = getEnemySightPos(); + + if ( self shouldStopAmbushing() ) + { + self.ambushEndTime = undefined; + + if ( shouldSuppress() ) + self.shootObjective = "suppress"; + + if ( randomint( 3 ) == 0 ) + { + self notify( "return_to_cover" ); + self.shouldReturnToCover = true; + } + return "retry"; + } + } +} + + +getAmbushShootPos() +{ + if ( isdefined( self.enemy ) && self cansee( self.enemy ) ) + { + setShootEntToEnemy(); + return; + } + + likelyEnemyDir = self getAnglesToLikelyEnemyPath(); + + if ( !isdefined( likelyEnemyDir ) ) + { + if ( isDefined( self.coverNode ) ) + likelyEnemyDir = self.coverNode.angles; + else if ( isdefined( self.ambushNode ) ) + likelyEnemyDir = self.ambushNode.angles; + else if ( isdefined( self.enemy ) ) + likelyEnemyDir = vectorToAngles( self lastKnownPos( self.enemy ) - self.origin ); + else + likelyEnemyDir = self.angles; + } + + dist = 1024; + if ( isdefined( self.enemy ) ) + dist = distance( self.origin, self.enemy.origin ); + + newShootPos = self getEye() + anglesToForward( likelyEnemyDir ) * dist; + + if ( !isdefined( self.shootPos ) || distanceSquared( newShootPos, self.shootPos ) > 5 * 5 )// avoid frequent "shoot_behavior_change" notifies + self.shootPos = newShootPos; +} + + +rifleShoot() +{ + if ( self.shootObjective == "normal" ) + { + rifleShootObjectiveNormal(); + } + else + { + if ( shouldShootEnemyEnt() )// later, maybe we can be more realistic than just shooting at the enemy the instant he becomes visible + { + self.shootObjective = "normal"; + self.ambushEndTime = undefined; + return "retry"; + } + + markEnemyPosInvisible(); + + if ( self isSniper() ) + self resetSniperAim(); + + enemySuppressable = canSuppressEnemy(); + + if ( self.shootObjective == "suppress" || ( self.team == "allies" && !isdefined( self.enemy ) && !enemySuppressable ) ) + rifleShootObjectiveSuppress( enemySuppressable ); + else + rifleShootObjectiveAmbush( enemySuppressable ); + } +} + +streamgunShoot() +{ + if ( shouldShootEnemyEnt() ) + { + setShootEntToEnemy(); + setShootStyle( "continuous", false ); + } +} + +shouldStopAmbushing() +{ + if ( !isdefined( self.ambushEndTime ) ) + { + if ( self isBadGuy() ) + self.ambushEndTime = gettime() + randomintrange( 10000, 60000 ); + else + self.ambushEndTime = gettime() + randomintrange( 4000, 10000 ); + } + return self.ambushEndTime < gettime(); +} + +rpgShoot() +{ + if ( !shouldShootEnemyEnt() ) + { + markEnemyPosInvisible(); + + haveNothingToShoot(); + return; + } + + setShootEntToEnemy(); + self.shootStyle = "single"; + + distSqToShootPos = lengthsquared( self.origin - self.shootPos ); + // too close for RPG + if ( distSqToShootPos < squared( 512 ) ) + { + self notify( "return_to_cover" ); + self.shouldReturnToCover = true; + return; + } +} + + +pistolShoot() +{ + if ( self.shootObjective == "normal" ) + { + if ( !shouldShootEnemyEnt() ) + { + // enemy disappeared! + if ( !isdefined( self.enemy ) ) + { + haveNothingToShoot(); + return; + } + else + { + markEnemyPosInvisible(); + + self.shootObjective = "ambush"; + return "retry"; + } + } + else + { + setShootEntToEnemy(); + self.shootStyle = "single"; + } + } + else + { + if ( shouldShootEnemyEnt() )// later, maybe we can be more realistic than just shooting at the enemy the instant he becomes visible + { + self.shootObjective = "normal"; + self.ambushEndTime = undefined; + return "retry"; + } + + markEnemyPosInvisible(); + + self.shootEnt = undefined; + self.shootStyle = "none"; + self.shootPos = getEnemySightPos(); + + // stop ambushing after a while + if ( !isdefined( self.ambushEndTime ) ) + self.ambushEndTime = gettime() + randomintrange( 4000, 8000 ); + + if ( self.ambushEndTime < gettime() ) + { + self.shootObjective = "normal"; + self.ambushEndTime = undefined; + return "retry"; + } + } +} + +markEnemyPosInvisible() +{ + if ( isdefined( self.enemy ) && !self.changingCoverPos && self.script != "combat" ) + { + // make sure they're not just hiding + if ( isAI( self.enemy ) && isdefined( self.enemy.script ) && ( self.enemy.script == "cover_stand" || self.enemy.script == "cover_crouch" ) ) + { + if ( isdefined( self.enemy.a.coverMode ) && self.enemy.a.coverMode == "hide" ) + return; + } + + self.couldntSeeEnemyPos = self.enemy.origin; + } +} + +watchForIncomingFire() +{ + self endon( "killanimscript" ); + self endon( "stop_deciding_how_to_shoot" ); + + while ( 1 ) + { + self waittill( "suppression" ); + + if ( self.suppressionMeter > self.suppressionThreshold ) + { + if ( self readyToReturnToCover() ) + { + self notify( "return_to_cover" ); + self.shouldReturnToCover = true; + } + } + } +} + +readyToReturnToCover() +{ + if ( self.changingCoverPos ) + return false; + + assert( isdefined( self.coverPosEstablishedTime ) ); + + if ( !isdefined( self.enemy ) || !self canSee( self.enemy ) ) + return true; + + if ( gettime() < self.coverPosEstablishedTime + 800 ) + { + // don't return to cover until we had time to fire a couple shots; + // better to look daring than indecisive + return false; + } + + if ( isPlayer( self.enemy ) && self.enemy.health < self.enemy.maxHealth * .5 ) + { + // give ourselves some time to take them down + if ( gettime() < self.coverPosEstablishedTime + 3000 ) + return false; + } + + return true; +} + +runOnShootBehaviorEnd() +{ + self endon( "death" ); + + self waittill_any( "killanimscript", "stop_deciding_how_to_shoot"/*, "return_to_cover"*/ ); + + self.a.laserOn = false; + self animscripts\shared::updateLaserStatus(); +} + +checkChanged( prevval, newval ) +{ + if ( isdefined( prevval ) != isdefined( newval ) ) + return true; + if ( !isdefined( newval ) ) + { + assert( !isdefined( prevval ) ); + return false; + } + return prevval != newval; +} + +setShootEntToEnemy() +{ + self.shootEnt = self.enemy; + self.shootPos = self.shootEnt getShootAtPos(); +} + +haveNothingToShoot() +{ + self.shootEnt = undefined; + self.shootPos = undefined; + self.shootStyle = "none"; + + if ( self.doingAmbush ) + self.shootObjective = "ambush"; + + if ( !self.changingCoverPos ) + { + self notify( "return_to_cover" ); + self.shouldReturnToCover = true; + } +} + +shouldBeAJerk() +{ + return level._gameskill == 3 && isPlayer( self.enemy );// && self shouldDoSemiForVariety(); +} + +fullAutoRangeSq = 250 * 250; +burstRangeSq = 900 * 900; +singleShotRangeSq = 1600 * 1600; + +setShootStyleForVisibleEnemy() +{ + assert( isdefined( self.shootPos ) ); + assert( isdefined( self.shootEnt ) ); + + if ( isdefined( self.shootEnt.enemy ) && isdefined( self.shootEnt.enemy.syncedMeleeTarget ) ) + return setShootStyle( "single", false ); + + if ( self isSniper() ) + return setShootStyle( "single", false ); + + if ( isStreamgun( self.weapon ) ) + { + return setShootStyle( "continuous", false ); + } + + if ( isShotgun( self.weapon ) ) + { + if ( weapon_pump_action_shotgun() ) + return setShootStyle( "single", false ); + else + return setShootStyle( "semi", false ); + } + + if ( weaponBurstCount( self.weapon ) > 0 ) + return setShootStyle( "burst", false ); + + distanceSq = distanceSquared( self getShootAtPos(), self.shootPos ); + + isMG = weaponClass( self.weapon ) == "mg"; + + if ( self.provideCoveringFire && isMG ) + return setShootStyle( "full", false ); + + if ( distanceSq < fullAutoRangeSq ) + { + if ( isdefined( self.shootEnt ) && isdefined( self.shootEnt.magic_bullet_shield ) ) + return setShootStyle( "single", false ); + else + return setShootStyle( "full", false ); + } + else if ( distanceSq < burstRangeSq || shouldBeAJerk() ) + { + if ( weaponIsSemiAuto( self.weapon ) || shouldDoSemiForVariety() ) + return setShootStyle( "semi", true ); + else + return setShootStyle( "burst", true ); + } + else if ( self.provideCoveringFire || isMG || distanceSq < singleShotRangeSq ) + { + if ( shouldDoSemiForVariety() ) + return setShootStyle( "semi", false ); + else + return setShootStyle( "burst", false ); + } + + return setShootStyle( "single", false ); +} + +setShootStyleForSuppression() +{ + assert( isdefined( self.shootPos ) ); + + distanceSq = distanceSquared( self getShootAtPos(), self.shootPos ); + + assert( !self isSniper() );// snipers shouldn't be suppressing! + assert( !isShotgun( self.weapon ) );// shotgun users shouldn't be suppressing! + assert( !isStreamgun( self.weapon ) ); // streamguns shouldn't be suppressing! + + if ( weaponIsSemiAuto( self.weapon ) ) + { + if ( distanceSq < singleShotRangeSq ) + return setShootStyle( "semi", false ); + return setShootStyle( "single", false ); + } + + if ( weaponClass( self.weapon ) == "mg" ) + return setShootStyle( "full", false ); + + if ( self.provideCoveringFire || distanceSq < singleShotRangeSq ) + { + if ( shouldDoSemiForVariety() ) + return setShootStyle( "semi", false ); + else + return setShootStyle( "burst", false ); + } + + return setShootStyle( "single", false ); +} + +setShootStyle( style, fastBurst ) +{ + self.shootStyle = style; + self.fastBurst = fastBurst; +} + +shouldDoSemiForVariety() +{ + if ( weaponClass( self.weapon ) != "rifle" ) + return false; + + if ( self.team != "allies" ) + return false; + + // true randomness isn't safe, because that will cause frequent shoot_behavior_change notifies. + // fake the randomness in a way that won't change frequently. + changeFrequency = safemod( int( self.origin[ 1 ] ), 10000 ) + 2000; + fakeTimeValue = int( self.origin[ 0 ] ) + gettime(); + + return fakeTimeValue %( 2 * changeFrequency ) > changeFrequency; +} + +resetSniperAim() +{ + assert( self isSniper() ); + self.sniperShotCount = 0; + self.sniperHitCount = 0; + + thread sniper_glint_behavior(); +} + +sniper_glint_behavior() +{ + self endon( "killanimscript" ); + self endon( "enemy" ); + self endon( "return_to_cover" ); + self notify( "new_glint_thread" ); + self endon( "new_glint_thread" ); + + assertex( self isSniper(), "Not a sniper!" ); + if ( !isdefined( level._effect[ "sniper_glint" ] ) ) + { + println( "^3Warning, sniper glint is not setup for sniper with classname " + self.classname ); + return; + } + + if ( !isAlive( self.enemy ) ) + return; + + //if ( !isPlayer( self.enemy ) ) + // return; + + fx = getfx( "sniper_glint" ); + + wait 0.2; + + for ( ;; ) + { + if ( self.weapon == self.primaryweapon && player_sees_my_scope() ) + { + if ( distanceSquared( self.origin, self.enemy.origin ) > 256 * 256 ) + PlayFXOnTag( fx, self, "tag_flash" ); + + timer = randomfloatrange( 3, 5 ); + wait( timer ); + } + wait( 0.2 ); + } +} + diff --git a/animscripts/snowmobile.gsc b/animscripts/snowmobile.gsc new file mode 100644 index 0000000..95afbc4 --- /dev/null +++ b/animscripts/snowmobile.gsc @@ -0,0 +1,957 @@ +#include common_scripts\utility; +#include maps\_utility; +#include maps\_anim; +#include animscripts\shared; +#include animscripts\utility; + +#using_animtree( "generic_human" ); +CONST_MPHCONVERSION = 17.6; + +main() +{ + assert( isdefined( self.ridingvehicle ) ); + + self.current_event = "none"; + self.shoot_while_driving_thread = undefined; + + self snowmobile_geton(); + + if ( isdefined( self.drivingvehicle ) ) + main_driver(); + else + main_passenger(); +} + +snowmobile_geton() +{ + self.grenadeawareness = 0; + self.a.pose = "crouch"; + self disable_surprise(); + self.allowpain = false; + + self.getOffVehicleFunc = ::snowmobile_getoff; + self.specialDeathFunc = animscripts\snowmobile::snowmobile_normal_death; + self.disableBulletWhizbyReaction = true; +} + +snowmobile_getoff() +{ + self.allowpain = true; + + self.getOffVehicleFunc = undefined; + self.specialDeathFunc = undefined; + self.a.specialShootBehavior = undefined; + self.disableBulletWhizbyReaction = undefined; +} + + + +main_driver() +{ + driver_shooting = self.ridingvehicle.driver_shooting || self.ridingvehicle.riders.size == 1; + snowmobile_setanim_driver( driver_shooting ); + + if ( driver_shooting ) + { + placeweaponon( self.primaryweapon, "left" ); + + self.rightaimlimit = 90; + self.leftaimlimit = -90; + self setanimaimweight( 1, 0.2 ); + + self thread snowmobile_trackshootentorpos_driver(); + self thread snowmobile_loop_driver_shooting(); + } + else + { + placeweaponon( self.primaryweapon, "none" ); + + self thread snowmobile_loop_driver(); + } + + snowmobile_handle_events( "driver" ); +} + +main_passenger() +{ + snowmobile_setanim_passenger( self.ridingvehicle.passenger_shooting ); + + if ( self.ridingvehicle.passenger_shooting ) + { + self.rightaimlimit = 180; + self.leftaimlimit = -180; + self.diraimlimit = 1; + self setanimaimweight( 1, 0.2 ); + + self thread snowmobile_trackshootentorpos_passenger(); + self thread snowmobile_loop_passenger_shooting(); + } + else + { + self thread snowmobile_loop_passenger(); + } + + snowmobile_handle_events( "passenger" ); +} + +snowmobile_loop_driver() +{ + self endon( "death" ); + self endon( "killanimscript" ); + + current_anim = "left2right"; + + anim_length = []; + anim_length[ "left2right" ] = getanimlength( animarray( "left2right" ) ); + anim_length[ "right2left" ] = getanimlength( animarray( "right2left" ) ); + + self setanimknoball( %sm_turn, %body, 1, 0 ); + self setanim( animarray( "drive" ), 1, 0 ); + self setanimknob( animarray( current_anim ), 1, 0 ); + self setanimtime( animarray( current_anim ), 0.5 ); + + for ( ;; ) + { + if ( self.ridingvehicle.steering_enable ) + { + steering = 0.5*(1 + maps\_vehicle::update_steering( self.ridingvehicle )); + + anim_time = self getanimtime( animarray( current_anim ) ); + if ( current_anim == "right2left" ) + anim_time = 1 - anim_time; + + rate = 20*abs( anim_time - steering ); + + if ( anim_time < steering ) + { + current_anim = "left2right"; + rate *= anim_length[ "left2right" ]; + } + else + { + current_anim = "right2left"; + rate *= anim_length[ "right2left" ]; + anim_time = 1 - anim_time; + } + } + else + { + current_anim = "left2right"; + rate = 0; + anim_time = 0.5; + } + + self setanimknoblimited( animarray( current_anim ), 1, 0.1, rate ); + self setanimtime( animarray( current_anim ), anim_time ); + + wait( 0.05 ); + } +} + +snowmobile_loop_passenger() +{ + self endon( "death" ); + self endon( "killanimscript" ); + + self setanimknoball( animarray( "hide" ), %body, 1, 0 ); + self setanimknob( animarray( "drive" ), 1, 0 ); + + for (;;) + { + steering = maps\_vehicle::update_steering( self.ridingvehicle ); + self setanimlimited( %sm_lean, abs( steering ), 0.05 ); + if ( steering >= 0 ) { + self setanimknoblimited( animarray( "lean_right" ), 1, 0.05 ); + } else { + self setanimknoblimited( animarray( "lean_left" ), 1, 0.05 ); + } + + wait 0.05; + } +} + +snowmobile_loop_driver_shooting() +{ + self endon( "death" ); + self endon( "killanimscript" ); + + leanblendtime = .05; + reloadFinishedTime = 0; + + self setanimknoball( %sm_aiming, %body, 1, 0 ); + self setanimknob( animarray( "idle" ), 1, 0 ); + + for ( ;; ) + { + if ( self.current_event != "none" ) + { + self waittill( "snowmobile_event_finished" ); + continue; + } + + steering = maps\_vehicle::update_steering( self.ridingvehicle ); + center_steering = 1 - abs( steering ); + left_steering = max( 0, 0 - steering ); + right_steering = max( 0, steering ); + + self setanimlimited( animarray( "straight_level_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "straight_level_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "straight_level_right" ), right_steering, leanblendtime ); + + if ( self.bulletsinclip <= 0 ) + { + self animscripts\weaponList::RefillClip(); + reloadFinishedTime = gettime() + 3000; + } + + if ( reloadFinishedTime <= gettime() ) + snowmobile_start_shooting(); + + self setanimknoblimited( animarray( "add_aim_left_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_left_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_left_right" ), right_steering, leanblendtime ); + + self setanimknoblimited( animarray( "add_aim_right_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_right_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_right_right" ), right_steering, leanblendtime ); + + self thread snowmobile_stop_shooting(); + + wait( 0.05 ); + } +} + +snowmobile_loop_passenger_shooting() +{ + self endon( "death" ); + self endon( "killanimscript" ); + + leanblendtime = .05; + + self setanimknoball( %sm_aiming, %body, 1, 0 ); + self setanimknob( animarray( "idle" ), 1, 0 ); + + for ( ;; ) + { + if ( self.current_event != "none" ) + { + self waittill( "snowmobile_event_finished" ); + continue; + } + + if ( snowmobile_reload() ) + continue; + + steering = maps\_vehicle::update_steering( self.ridingvehicle ); + center_steering = 1 - abs( steering ); + left_steering = max( 0, 0 - steering ); + right_steering = max( 0, steering ); + + self setanimlimited( animarray( "straight_level_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "straight_level_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "straight_level_right" ), right_steering, leanblendtime ); + + snowmobile_start_shooting(); + + self setanimlimited( animarray( "aim_left_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "aim_left_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "aim_left_right" ), right_steering, leanblendtime ); + + self setanimlimited( animarray( "aim_right_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "aim_right_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "aim_right_right" ), right_steering, leanblendtime ); + + self setanimlimited( animarray( "add_aim_backleft_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_backleft_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_backleft_right" ), right_steering, leanblendtime ); + + self setanimlimited( animarray( "add_aim_backright_center" ), center_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_backright_left" ), left_steering, leanblendtime ); + self setanimlimited( animarray( "add_aim_backright_right" ), right_steering, leanblendtime ); + + if ( isplayer( self.enemy ) ) + self updateplayersightaccuracy(); + + wait( 0.05 ); + self thread snowmobile_stop_shooting(); // stop shooting on the next frame unless snowmobile_start_shooting is called again + } +} + +snowmobile_do_event( animation ) +{ + self endon( "death" ); + + self.ridingvehicle.steering_enable = false; + self setflaggedanimknoblimitedrestart( "snowmobile_event", animation, 1, 0.17 ); + donotetracks( "snowmobile_event", ::snowmobile_waitfor_start_lean ); + self setanimknoblimited( animarray( "event_restore" ), 1, 0.1 ); + self.ridingvehicle.steering_enable = true; + self.current_event = "none"; + self notify( "snowmobile_event_finished" ); +} + +snowmobile_handle_events( rider ) +{ + self endon( "death" ); + self endon( "killanimscript" ); + + snowmobile = self.ridingvehicle; + + for ( ;; ) + { + if ( snowmobile.event[ "jump" ][ rider ] ) + { + snowmobile.event[ "jump" ][ rider ] = false; + self notify( "snowmobile_event_occurred" ); + self.current_event = "jump"; + snowmobile.steering_enable = false; + self setflaggedanimknoblimitedrestart( "jump", animarray( "event_jump" ), 1, 0.17 ); + } + + if ( snowmobile.event[ "bump" ][ rider ] ) + { + snowmobile.event[ "bump" ][ rider ] = false; + self notify( "snowmobile_event_occurred" ); + if ( self.current_event != "bump_big" ) + self thread snowmobile_do_event( animarray( "event_bump" ) ); + } + + if ( snowmobile.event[ "bump_big" ][ rider ] ) + { + snowmobile.event[ "bump_big" ][ rider ] = false; + self notify( "snowmobile_event_occurred" ); + self.current_event = "bump_big"; + self thread snowmobile_do_event( animarray( "event_bump_big" ) ); + } + + if ( snowmobile.event[ "sway_left" ][ rider ] ) + { + snowmobile.event[ "sway_left" ][ rider ] = false; + self notify( "snowmobile_event_occurred" ); + if ( self.current_event != "bump_big" ) + self thread snowmobile_do_event( animarray( "event_sway" )[ "left" ] ); + } + + if ( snowmobile.event[ "sway_right" ][ rider ] ) + { + snowmobile.event[ "sway_right" ][ rider ] = false; + self notify( "snowmobile_event_occurred" ); + if ( self.current_event != "bump_big" ) + self thread snowmobile_do_event( animarray( "event_sway" )[ "right" ] ); + } + + wait( 0.05 ); + } +} + +snowmobile_start_shooting() +{ + self notify( "want_shoot_while_driving" ); + + self setAnim( %sm_add_fire, 1, 0.2 ); + + if ( isdefined( self.shoot_while_driving_thread ) ) + return; + self.shoot_while_driving_thread = true; + + self thread snowmobile_decide_shoot(); + self thread snowmobile_shoot(); +} + +snowmobile_stop_shooting() +{ + self endon( "killanimscript" ); + self endon( "want_shoot_while_driving" ); + + wait .05; + + self notify( "end_shoot_while_driving" ); + self.shoot_while_driving_thread = undefined; + self clearAnim( %sm_add_fire, 0.2 ); +} + +snowmobile_decide_shoot() +{ + self endon( "killanimscript" ); + self endon( "end_shoot_while_driving" ); + + self.a.specialShootBehavior = ::snowmobileShootBehavior; + + snowmobile_decide_shoot_internal(); + + self.shoot_while_driving_thread = undefined; // start shooting again the next time we want it +} + +snowmobile_decide_shoot_internal() +{ + // events stop the shooting animations, so stop shooting when they happen + self endon( "snowmobile_event_occurred" ); + + self animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" ); +} + + +snowmobileShootBehavior() +{ + if ( !isdefined( self.enemy ) ) + { + self.shootent = undefined; + self.shootpos = undefined; + self.shootstyle = "none"; + return; + } + + self.shootent = self.enemy; + self.shootpos = self.enemy getShootAtPos(); + distSq = distanceSquared( self.origin, self.enemy.origin ); + + if ( distSq < 1000*1000 ) + self.shootstyle = "full"; + else if ( distSq < 2000*2000 ) + self.shootstyle = "burst"; + else + self.shootstyle = "single"; + + + if ( isdefined( self.enemy.vehicle ) ) + { + shoot_ahead_speed_multiplier = 0.5; + //shoot_ahead_random_spread = 50; + + vehicle = self.shootent.vehicle; + snowmobile = self.ridingvehicle; + delta = snowmobile.origin - vehicle.origin; + forward = anglestoforward( vehicle.angles ); + right = anglestoright( vehicle.angles ); + dot = vectordot( delta, forward ); + if ( dot < 0 ) + { + speed = vehicle vehicle_getspeed() * shoot_ahead_speed_multiplier; + speed *= CONST_MPHCONVERSION; + + if ( speed > 50 ) + { + sideness = vectordot( delta, right ); + sideness /= 3; + if ( sideness > 128 ) + sideness = 128; + else if ( sideness < -128 ) + sideness = -128; + // flip it so guys farther to the side shoot in front of you + if ( sideness > 0 ) + sideness = 128 - sideness; + else + sideness = -128 - sideness; + + self.shootent = undefined; + self.shootpos = vehicle.origin + speed * forward + sideness * right; + + return; + } + } + } +} + + +snowmobile_shoot() +{ + self endon( "killanimscript" ); + self endon( "end_shoot_while_driving" ); + + self notify( "doing_shootWhileDriving" ); + self endon( "doing_shootWhileDriving" ); + + for ( ;; ) + { + if ( !self.bulletsInClip ) + { + wait 0.5; + continue; + } + + self animscripts\combat_utility::shootUntilShootBehaviorChange(); + // self clearAnim( %exposed_modern, 0.2 ); + } +} + +snowmobile_reload() +{ + if ( !self.ridingvehicle.steering_enable ) + return false; + + if ( !self animscripts\combat_utility::needtoreload( 0 ) ) + return false; + + if ( !usingRifleLikeWeapon() ) + return false; + + snowmobile_reload_internal(); + + // notify "abort_reload" in case the reload didn't finish. works with handledropclip() in shared.gsc + self notify( "abort_reload" ); + + return true; +} + +snowmobile_reload_internal() +{ + self endon( "snowmobile_event_occurred" ); + + self.stop_aiming_for_reload = true; + self waittill( "start_blending_reload" ); + + self setanim( %sm_aiming, 0, 0.25 ); + self setflaggedanimrestart( "gun_down", animarray( "gun_down" ), 1, 0.25 ); + DoNoteTracks( "gun_down" ); + self clearAnim( animarray( "gun_down" ), 0 ); + + self setflaggedanimknoballrestart( "reload_anim", animarray( "reload" ), %body, 1, 0.25 ); + DoNoteTracks( "reload_anim" ); + self clearAnim( %sm_reload, 0.2 ); + + self setflaggedanimrestart( "gun_up", animarray( "gun_up" ), 1, 0.25 ); + self.gun_up_for_reload = true; + DoNoteTracks( "gun_up", ::snowmobile_waitfor_start_aim ); + + + self.stop_aiming_for_reload = undefined; + self clearAnim( %sm_reload, 0.1 ); + self setanim( %sm_aiming, 1, 0.1 ); + + if ( isdefined( self.gun_up_for_reload ) ) + { + self.gun_up_for_reload = undefined; + DoNoteTracks( "gun_up", ::snowmobile_waitfor_end ); + self clearAnim( animarray( "gun_up" ), 0 ); + } +} + +snowmobile_waitfor_start_aim( note ) +{ + if ( note == "start_aim" ) + return true; +} + +snowmobile_waitfor_end( note ) +{ + if ( note == "end" ) + return true; +} + +snowmobile_waitfor_start_lean( note ) +{ + if ( note == "start_lean" ) + return true; +} + +snowmobile_trackshootentorpos_driver() +{ + self endon( "killanimscript" ); + self endon( "stop tracking" ); + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = thisthread; + self.trackLoopThreadType = "snowmobile_trackshootentorpos_driver"; +#/ + + aimblendtime = .05; + + maxyawdeltachange = 8;// max change in yaw in 1 frame + prevyawdelta = 0; + yawdelta = 0; + + firstframe = true; + + for ( ;; ) + { + incranimaimweight(); + + selfshootatpos = ( self.origin[ 0 ], self.origin[ 1 ], self geteye()[ 2 ] ); + + shootpos = self.shootpos; + if ( isdefined( self.shootent ) ) + shootpos = self.shootent getshootatpos(); + + if ( !isdefined( shootpos ) ) + { + assert( !isdefined( self.shootent ) ); + + yawdelta = 0; + likelyenemydir = self getanglestolikelyenemypath(); + if ( isdefined( likelyenemydir ) ) + { + yawdelta = angleclamp180( self.angles[ 1 ] - likelyenemydir[ 1 ] ); + } + } + else + { + vectortoshootpos = shootpos - selfshootatpos; + anglestoshootpos = vectortoangles( vectortoshootpos ); + + yawdelta = self.angles[ 1 ] - anglestoshootpos[ 1 ]; + yawdelta = angleclamp180( yawdelta ); + } + + assert( self.rightaimlimit >= 0 ); + assert( self.leftaimlimit <= 0 ); + if ( yawdelta > self.rightaimlimit || yawdelta < self.leftaimlimit ) + yawdelta = 0; + + if ( firstframe ) + { + firstframe = false; + } + else + { + yawdeltachange = yawdelta - prevyawdelta; + + if ( abs( yawdeltachange ) > maxyawdeltachange ) + yawdelta = prevyawdelta + maxyawdeltachange * sign( yawdeltachange ); + } + + prevyawdelta = yawdelta; + + weight4 = min( max( 0 - yawdelta, 0 ), 90 ) / 90 * self.a.aimweight; + weight6 = min( max( yawdelta, 0 ), 90 ) / 90 * self.a.aimweight; + + self setanimlimited( %sm_aim_4, weight4, aimblendtime ); + self setanimlimited( %sm_aim_6, weight6, aimblendtime ); + + wait( 0.05 ); + } +} + +snowmobile_trackshootentorpos_passenger() +{ + self endon( "killanimscript" ); + self endon( "stop tracking" ); + +/# + assert( !isdefined( self.trackLoopThread ) ); + self.trackLoopThread = thisthread; + self.trackLoopThreadType = "snowmobile_trackshootentorpos_passenger"; +#/ + + aimblendtime = .05; + + maxyawdeltachange_default = 5;// max change in yaw in 1 frame + maxyawdeltachange_fast = 20; + maxyawdeltachange_reload = 15; + yawdelta_overshoot_begin = 40; + yawdelta_overshoot_end = 30; + + prevyawdelta = 0; + yawdelta = 0; + + firstframe = true; + + for ( ;; ) + { + incranimaimweight(); + + selfshootatpos = ( self.origin[ 0 ], self.origin[ 1 ], self geteye()[ 2 ] ); + + shootpos = self.shootpos; + if ( isdefined( self.shootent ) ) + shootpos = self.shootent getshootatpos(); + + if ( !isdefined( shootpos ) ) + { + assert( !isdefined( self.shootent ) ); + + yawdelta = 0; + likelyenemydir = self getanglestolikelyenemypath(); + if ( isdefined( likelyenemydir ) ) + { + yawdelta = angleclamp180( self.angles[ 1 ] - likelyenemydir[ 1 ] ); + } + } + else + { + vectortoshootpos = shootpos - selfshootatpos; + anglestoshootpos = vectortoangles( vectortoshootpos ); + + yawdelta = self.angles[ 1 ] - anglestoshootpos[ 1 ]; + yawdelta = angleclamp180( yawdelta ); + + //line( selfshootatpos, shootpos ); + } + + assert( self.diraimlimit == 1 || self.diraimlimit == -1 ); + + if ( isdefined( self.stop_aiming_for_reload ) || ( yawdelta > 0 && ( yawdelta - self.rightaimlimit ) * self.diraimlimit > 0 ) || ( yawdelta < 0 && ( yawdelta - self.leftaimlimit ) * self.diraimlimit < 0 ) ) + yawdelta = 0; + + if ( firstframe ) + { + firstframe = false; + } + else + { + if ( prevyawdelta < -180 + yawdelta_overshoot_begin && yawdelta > 180 - yawdelta_overshoot_end ) + yawdelta = -179; + if ( prevyawdelta > 180 - yawdelta_overshoot_begin && yawdelta < -180 + yawdelta_overshoot_end ) + yawdelta = 179; + + yawdeltachange = yawdelta - prevyawdelta; + + maxyawdeltachange = (maxyawdeltachange_fast - maxyawdeltachange_default) * abs( yawdeltachange ) / 180 + maxyawdeltachange_default; + if ( isdefined( self.stop_aiming_for_reload ) ) + { + maxyawdeltachange = maxyawdeltachange_reload; + if ( abs( prevyawdelta ) < 45 ) + self notify( "start_blending_reload" ); + } + + if ( abs( yawdeltachange ) > maxyawdeltachange ) + yawdelta = prevyawdelta + maxyawdeltachange * sign( yawdeltachange ); + } + + prevyawdelta = yawdelta; + + weight1 = max( -90 - yawdelta, 0 ) / 90 * self.a.aimweight; + weight4 = min( max( 0 - yawdelta, 0 ), 90 ) / 90 * self.a.aimweight; + weight5 = max( 90 - abs( yawdelta ), 0 ) / 90 * self.a.aimweight; + weight6 = min( max( yawdelta, 0 ), 90 ) / 90 * self.a.aimweight; + weight3 = max( -90 + yawdelta, 0 ) / 90 * self.a.aimweight; + + self setanimlimited( %sm_aim_1, weight1, aimblendtime ); + self setanimlimited( %sm_aim_4_delta, weight4, aimblendtime ); + self setanimlimited( %sm_aim_5_delta, weight5, aimblendtime ); + self setanimlimited( %sm_aim_6_delta, weight6, aimblendtime ); + self setanimlimited( %sm_aim_3, weight3, aimblendtime ); + + wait( 0.05 ); + } +} + + +snowmobile_get_death_anim( deathAnims, deathAnimDirs, goalDir ) +{ + bestDeathAnim = undefined; + secondBestDeathAnim = undefined; + bestDeathAnimDiff = 0; + for ( i = 0; i < deathAnims.size; i++ ) + { + diff = AbsAngleClamp180( goalDir - deathAnimDirs[i] ); + if ( !isdefined( bestDeathAnim ) || diff < bestDeathAnimDiff ) + { + secondBestDeathAnim = bestDeathAnim; + + bestDeathAnim = deathAnims[i]; + bestDeathAnimDiff = diff; + } + else if ( !isdefined( secondBestDeathAnim ) ) + { + secondBestDeathAnim = deathAnims[i]; + } + } + assert( isdefined( bestDeathAnim ) ); + assert( isdefined( secondBestDeathAnim ) ); + + deathAnim = bestDeathAnim; + if ( isDefined( anim.prevSnowmobileDeath ) && deathAnim == anim.prevSnowmobileDeath && gettime() - anim.prevSnowmobileDeathTime < 500 ) + deathAnim = secondBestDeathAnim; + anim.prevSnowmobileDeath = deathAnim; + anim.prevSnowmobileDeathTime = gettime(); + + return deathAnim; +} + +snowmobile_death_launchslide() +{ + snowmobile = self.ridingvehicle; + assert( isdefined( snowmobile ) ); + + velocity = snowmobile.prevFrameVelocity; + velocity = ( velocity[0], velocity[1], randomfloatrange( 200, 400 ) ) * .75; + //println( length( velocity ) ); + if ( lengthSquared( velocity ) > 1000 * 1000 ) + velocity = vectornormalize( velocity ) * 1000; + + model = spawn( "script_origin", self.origin ); + model moveSlide( ( 0, 0, 40 ), 15, velocity ); + self linkto( model ); + + model thread deleteShortly(); +} + +snowmobile_normal_death() +{ + //self snowmobile_death_launchslide(); + + deathAnims = []; + deathAnims[0] = level._scr_anim[ "snowmobile" ][ "small" ][ "death" ][ "back" ]; + deathAnims[1] = level._scr_anim[ "snowmobile" ][ "small" ][ "death" ][ "right" ]; + deathAnims[2] = level._scr_anim[ "snowmobile" ][ "small" ][ "death" ][ "left" ]; + deathAnimDirs = []; + deathAnimDirs[0] = -180; + deathAnimDirs[1] = -90; + deathAnimDirs[2] = 90; + + deathAnim = snowmobile_get_death_anim( deathAnims, deathAnimDirs, self.damageyaw ); + + animscripts\death::playDeathAnim( deathAnim ); + return true; +} + +snowmobile_collide_death() +{ + snowmobile = self.ridingvehicle; + if ( !isdefined( snowmobile ) ) + return snowmobile_normal_death(); + + velocity = snowmobile.prevFrameVelocity; + + self snowmobile_death_launchslide(); + + angles = vectortoangles( velocity ); + delta = AngleClamp180( angles[1] - self.angles[1] ); + + deathAnims = []; + deathAnims[0] = level._scr_anim[ "snowmobile" ][ "big" ][ "death" ][ "back" ]; + deathAnims[1] = level._scr_anim[ "snowmobile" ][ "big" ][ "death" ][ "left" ]; + deathAnims[2] = level._scr_anim[ "snowmobile" ][ "big" ][ "death" ][ "front" ]; + deathAnims[3] = level._scr_anim[ "snowmobile" ][ "big" ][ "death" ][ "right" ]; + deathAnimDirs = []; + deathAnimDirs[0] = -180; + deathAnimDirs[1] = -90; + deathAnimDirs[2] = 0; + deathAnimDirs[3] = 90; + + deathAnim = snowmobile_get_death_anim( deathAnims, deathAnimDirs, delta ); + + animscripts\death::playDeathAnim( deathAnim ); + return true; +} + +deleteShortly() +{ + prevorg = self.origin; + for ( i = 0; i < 60; i++ ) + { + wait .05; + line(self.origin,prevorg); + prevorg = self.origin; + } + wait 3; + if ( isdefined( self ) ) + self delete(); +} + + +snowmobile_setanim_common( seat ) +{ + self.a.array[ "idle" ] = level._scr_anim[ "snowmobile" ][ seat ][ "idle" ]; + self.a.array[ "drive" ] = level._scr_anim[ "snowmobile" ][ seat ][ "drive" ]; + + self.a.array[ "fire" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "single" ] = array( level._scr_anim[ "snowmobile" ][ seat ][ "single" ] ); + + self.a.array[ "burst2" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "burst3" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "burst4" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "burst5" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "burst6" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + + self.a.array[ "semi2" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "semi3" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "semi4" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; + self.a.array[ "semi5" ] = level._scr_anim[ "snowmobile" ][ seat ][ "fire" ]; +} + +snowmobile_setanim_driver( shooting ) +{ + self.a.array = []; + + snowmobile_setanim_common( "driver" ); + + self.a.array[ "left2right" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "left2right" ]; + self.a.array[ "right2left" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "right2left" ]; + + self.a.array[ "straight_level_left" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "straight_level" ][ "left" ]; + self.a.array[ "straight_level_center" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "straight_level" ][ "center" ]; + self.a.array[ "straight_level_right" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "straight_level" ][ "right" ]; + self.a.array[ "add_aim_left_left" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "add_aim_left" ][ "left" ]; + self.a.array[ "add_aim_left_center" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "add_aim_left" ][ "center" ]; + self.a.array[ "add_aim_left_right" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "add_aim_left" ][ "right" ]; + self.a.array[ "add_aim_right_left" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "add_aim_right" ][ "left" ]; + self.a.array[ "add_aim_right_center" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "add_aim_right" ][ "center" ]; + self.a.array[ "add_aim_right_right" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "add_aim_right" ][ "right" ]; + + if ( shooting ) + { + self.a.array["event_jump"] = level._scr_anim[ "snowmobile" ][ "driver" ][ "shoot_jump" ]; + self.a.array["event_bump"] = level._scr_anim[ "snowmobile" ][ "driver" ][ "shoot_bump" ]; + self.a.array["event_bump_big"] = level._scr_anim[ "snowmobile" ][ "driver" ][ "shoot_bump_big" ]; + self.a.array["event_sway"] = []; + self.a.array["event_sway"][ "left" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "shoot_sway_left" ]; + self.a.array["event_sway"][ "right" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "shoot_sway_right" ]; + + self.a.array["event_restore"] = %sm_aiming; + } + else + { + self.a.array["event_jump"] = level._scr_anim[ "snowmobile" ][ "driver" ][ "drive_jump" ]; + self.a.array["event_bump"] = level._scr_anim[ "snowmobile" ][ "driver" ][ "drive_bump" ]; + self.a.array["event_bump_big"] = level._scr_anim[ "snowmobile" ][ "driver" ][ "drive_bump_big" ]; + self.a.array["event_sway"] = []; + self.a.array["event_sway"][ "left" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "drive_sway_left" ]; + self.a.array["event_sway"][ "right" ] = level._scr_anim[ "snowmobile" ][ "driver" ][ "drive_sway_right" ]; + + self.a.array["event_restore"] = %sm_turn; + } +} + +snowmobile_setanim_passenger( shooting ) +{ + self.a.array = []; + + snowmobile_setanim_common( "passenger" ); + + self.a.array[ "hide" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "hide" ]; + self.a.array[ "lean_left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_lean" ][ "left" ]; + self.a.array[ "lean_right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_lean" ][ "right" ]; + + self.a.array[ "reload" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "reload" ]; + self.a.array[ "gun_up" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "gun_up" ]; + self.a.array[ "gun_down" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "gun_down" ]; + + self.a.array[ "aim_left_left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "aim_left" ][ "left" ]; + self.a.array[ "aim_left_center" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "aim_left" ][ "center" ]; + self.a.array[ "aim_left_right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "aim_left" ][ "right" ]; + self.a.array[ "aim_right_left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "aim_right" ][ "left" ]; + self.a.array[ "aim_right_center" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "aim_right" ][ "center" ]; + self.a.array[ "aim_right_right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "aim_right" ][ "right" ]; + self.a.array[ "add_aim_backleft_left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_aim_backleft" ][ "left" ]; + self.a.array[ "add_aim_backleft_center" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_aim_backleft" ][ "center" ]; + self.a.array[ "add_aim_backleft_right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_aim_backleft" ][ "right" ]; + self.a.array[ "add_aim_backright_left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_aim_backright" ][ "left" ]; + self.a.array[ "add_aim_backright_center" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_aim_backright" ][ "center" ]; + self.a.array[ "add_aim_backright_right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "add_aim_backright" ][ "right" ]; + self.a.array[ "straight_level_left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "straight_level" ][ "left" ]; + self.a.array[ "straight_level_center" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "straight_level" ][ "center" ]; + self.a.array[ "straight_level_right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "straight_level" ][ "right" ]; + + if ( shooting ) + { + self.a.array["event_jump"] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "drive_jump" ]; + self.a.array["event_bump"] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "drive_bump" ]; + self.a.array["event_bump_big"] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "drive_bump_big" ]; + self.a.array["event_sway"] = []; + self.a.array["event_sway"][ "left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "drive_sway_left" ]; + self.a.array["event_sway"][ "right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "drive_sway_right" ]; + self.a.array["event_restore"] = %sm_aiming; + } + else + { + self.a.array["event_jump"] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "hide_jump" ]; + self.a.array["event_bump"] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "hide_bump" ]; + self.a.array["event_bump_big"] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "hide_bump_big" ]; + self.a.array["event_sway"] = []; + self.a.array["event_sway"][ "left" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "hide_sway_left" ]; + self.a.array["event_sway"][ "right" ] = level._scr_anim[ "snowmobile" ][ "passenger" ][ "hide_sway_right" ]; + self.a.array["event_restore"] = %sm_turn; + } +} diff --git a/animscripts/squadmanager.gsc b/animscripts/squadmanager.gsc new file mode 100644 index 0000000..4778ef6 --- /dev/null +++ b/animscripts/squadmanager.gsc @@ -0,0 +1,823 @@ +// squadmanager.gsc + +/**************************************************************************** + initialization +*****************************************************************************/ + +// initializes the squad management system +init_squadManager() +{ + if ( isdefined( anim.squadInitialized ) && anim.squadInitialized ) + return; + + anim.squadCreateFuncs = []; + anim.squadCreateStrings = []; + + anim.squads = []; + anim.squadIndex = []; + anim.squadRand = 0; + + anim.squadInitialized = true; +} + +/**************************************************************************** + functionality +*****************************************************************************/ + +createSquad( squadName, squadCreator ) +{ + assertex( !isdefined( anim.squads[ squadName ] ), "createSquad attempted to create a squad with the same name as an existing squad" ); + + //prof_begin( "createSquad" ); + + anim.squads[ squadName ] = spawnstruct(); + squad = anim.squads[ squadName ]; + squad.squadName = squadName; + + // SRS 10/17/08: added squad.team so we can still support custom-named squads, + // not just "axis" and "allies" + squad.team = getSquadTeam( squadCreator ); + + squad.sightTime = 0; + squad.origin = undefined; + squad.forward = undefined; + squad.enemy = undefined; + squad.isInCombat = false; + + squad.memberCount = 0; + squad.members = []; + squad.officers = []; + squad.officerCount = 0; + squad.squadList = []; + + squad.memberAddFuncs = []; + squad.memberAddStrings = []; + squad.memberRemoveFuncs = []; + squad.memberRemoveStrings = []; + squad.squadUpdateFuncs = []; + squad.squadUpdateStrings = []; + squad.squadID = anim.squadIndex.size; + + squad initState( "combat", 0.75 ); + squad initState( "cover", 0.75 ); + squad initState( "move", 0.75 ); + squad initState( "stop", 0.75 ); + squad initState( "death", 0.75 ); + squad initState( "suppressed", 0.75 ); + squad initState( "attacking", 0.5 ); + + // add this squad to the global index + anim.squadIndex[ anim.squadIndex.size ] = squad; + + squad updateSquadList(); + + // notifies for other scripts + level notify( "squad created " + squadName ); + anim notify( "squad created " + squadName ); + + for ( i = 0; i < anim.squadCreateFuncs.size; i++ ) + { + squadCreateFunc = anim.squadCreateFuncs[ i ]; + squad thread [[ squadCreateFunc ]](); + } + + // battlechatter init +// squad animscripts\battlechatter::init_squadBattleChatter(); + + // tell all other squads to add us to their lists + for ( i = 0; i < anim.squadIndex.size; i++ ) + anim.squadIndex[ i ] updateSquadList(); + + squad thread updateWaiter(); + squad thread squadTracker(); + squad thread officerWaiter(); + squad thread updateMemberStates(); + + //prof_end( "createSquad" ); + return( squad ); +} + +deleteSquad( squadName ) +{ + assertex( isdefined( anim.squads[ squadName ] ), "deleteSquad attempted to delete a squad that does not exist" ); + + if ( squadName == "axis" || squadName == "team3" || squadName == "allies" ) + return; + + squadID = anim.squads[ squadName ].squadID; + squad = anim.squads[ squadName ]; + + squad notify( "squad_deleting" ); + +// for (i = 0; i < squad.members.size; i++) + while ( squad.members.size ) + squad.members[ 0 ] addToSquad( squad.members[ 0 ].team ); + + anim.squadIndex[ squadID ] = anim.squadIndex[ anim.squadIndex.size - 1 ]; + anim.squadIndex[ squadID ].squadID = squadID; + anim.squadIndex[ anim.squadIndex.size - 1 ] = undefined; + + anim.squads[ squadName ] = undefined; + + level notify( "squad deleted " + squadName ); + anim notify( "squad deleted " + squadName ); + + for ( i = 0; i < anim.squadIndex.size; i++ ) + anim.squadIndex[ i ] updateSquadList(); +} + +generateSquadName() +{ + squadName = "auto" + anim.squadRand; + anim.squadRand++ ; + + return( squadName ); +} + +addPlayerToSquad( squadName ) +{ + if ( !isdefined( squadName ) ) + { + if ( isdefined( self.script_squadname ) ) + squadName = self.script_squadname; + else + squadName = self.team; + } + + if ( !isdefined( anim.squads[ squadName ] ) ) + { + anim createSquad( squadName, self ); + } + + squad = anim.squads[ squadName ]; + + self.squad = squad; + +// self.squad updateOrigin(); +} + +// adds the ai that calls the function to the specified squad +// if not squad is specified, the ai is added to one of the defaults +squadChange() +{ + self endon( "death" ); + wait( 10.0 );// ??? + + if ( !isdefined( self.script_squadname ) ) + squadName = ( self.team + self.script_flanker ); + else + squadName = ( self.script_squadname + self.script_flanker ); + + self addToSquad( squadName ); +} + +getSquadTeam( squadCreator ) +{ + squadTeam = "allies"; // player, allies + if( squadCreator.team == "axis" || squadCreator.team == "neutral" || squadCreator.team == "team3" ) + { + squadteam = squadCreator.team; + } + + return squadTeam; +} + +addToSquad( squadName ) +{ + assertex( IsSentient( self ), "addToSquad attempted to add a non-sentient member to a squad" ); + + //prof_begin( "addToSquad" ); + + if ( !isdefined( squadName ) ) + { + if ( isdefined( self.script_flanker ) ) + self thread squadChange(); + + if ( isdefined( self.script_squadname ) ) + squadName = self.script_squadname; + else + squadName = self.team; + } + if ( !isdefined( anim.squads[ squadName ] ) ) + anim createSquad( squadName, self ); + + squad = anim.squads[ squadName ]; + + if ( isdefined( self.squad ) ) + { + if ( self.squad == squad ) + return; + else + self removeFromSquad(); + } + + // move to init.gsc + self.lastEnemySightTime = 0; + self.combatTime = 0; + // end move + + self.squad = squad; + self.memberID = squad.members.size; + squad.members[ self.memberID ] = self; + squad.memberCount = squad.members.size; + + // this is handled in wait for loadout function + if ( isdefined( level._loadoutComplete ) ) + { + if ( self.team == "allies" && self animscripts\battlechatter::isOfficer() ) + { + self addOfficerToSquad(); + } + } + +// self.squad updateOrigin(); + + for ( i = 0; i < self.squad.memberAddFuncs.size; i++ ) + { + memberAddFunc = self.squad.memberAddFuncs[ i ]; + self thread [[ memberAddFunc ]]( self.squad.squadName ); + } + + //prof_end( "addToSquad" ); + + self thread memberCombatWaiter(); + self thread memberDeathWaiter(); +} + +removeFromSquad() +{ + //prof_begin( "removeFromSquad" ); + ASSERTEX( IsDefined( self.squad ), "removeFromSquad attempted to remove a member who was not part of a squad (self.squad == undefined)" ); + + squad = self.squad; + memberID = -1; + + if ( IsDefined( self ) ) + { + memberID = self.memberID; + } + else + { + for ( i = 0; i < squad.members.size; i++ ) + { + if ( squad.members[ i ] == self ) + memberID = i; + } + } + + ASSERTEX( memberID > - 1, "removeFromSquad could not find memberID" ); + + if ( memberID != squad.members.size - 1 ) + { + other = squad.members[ squad.members.size - 1 ]; + squad.members[ memberID ] = other; + if ( isdefined( other ) ) + other.memberID = memberID; + } + + squad.members[ squad.members.size - 1 ] = undefined; + squad.memberCount = squad.members.size; + + if ( IsDefined( self.officerID ) ) + { + self removeOfficerFromSquad(); + } + + for ( i = 0; i < self.squad.memberRemoveFuncs.size; i++ ) + { + memberRemoveFunc = self.squad.memberRemoveFuncs[ i ]; + self thread [[ memberRemoveFunc ]]( squad.squadName ); + } + + assert( squad.members.size == squad.memberCount ); + + if ( squad.memberCount == 0 ) + { + deleteSquad( squad.squadName ); + } + + if ( isdefined( self ) ) + { + self.squad = undefined; + self.memberID = undefined; + } + + self notify( "removed from squad" ); + + //prof_end( "removeFromSquad" ); +} + +addOfficerToSquad() +{ + squad = self.squad; + + if ( isdefined( self.officerID ) ) + return; + + assertex( !isdefined( self.officerID ), "addOfficerToSquad attempted to add a member that is already in an officers" ); + + self.officerID = squad.officers.size; + squad.officers[ self.officerID ] = self; + squad.officerCount = squad.officers.size; +} + +removeOfficerFromSquad() +{ + //prof_begin( "removeOfficerFromSquad" ); + + squad = self.squad; + officerID = -1; + + if ( isdefined( self ) ) + { + officerID = self.officerID; + } + else + { + for ( i = 0; i < squad.officers.size; i++ ) + { + if ( squad.officers[ i ] == self ) + officerID = i; + } + } + + assertex( officerID > - 1, "removeOfficerFromSquad could not find officerID" ); + + if ( officerID != squad.officers.size - 1 ) + { + other = squad.officers[ squad.officers.size - 1 ]; + squad.officers[ officerID ] = other; + if ( isdefined( other ) ) + other.officerID = officerID; + } + + squad.officers[ squad.officers.size - 1 ] = undefined; + squad.officerCount = squad.officers.size; + + assert( squad.officers.size == squad.officerCount ); + + if ( isdefined( self ) ) + self.officerID = undefined; + + //prof_end( "removeOfficerFromSquad" ); + +} + +/**************************************************************************** + trackers/waiters +*****************************************************************************/ + +officerWaiter() +{ + if ( !isdefined( level._loadoutComplete ) ) + { + anim waittill( "loadout complete" ); + } + + for ( i = 0; i < self.members.size; i++ ) + { + if ( self.members[ i ] animscripts\battlechatter::isOfficer() ) + { + self.members[ i ] addOfficerToSquad(); + } + } +} + +updateWaiter() +{ + while ( 1 ) + { + anim waittill( "squadupdate", action ); + + //prof_begin( "updateWaiter" ); + + switch( action ) + { + case "squadlist": + self updateSquadList(); + break; + case "combat": + self updateCombat(); + break; + case "origin": + self updateOrigin(); + break; + case "forward": + self updateHeading(); + break; + } + + //prof_end( "updateWaiter" ); + } +} + +squadTracker() +{ + anim endon( "squad deleted " + self.squadName ); + // even with the long wait time, this is a crappy way to track things + // ideally the updateFunctions would only be called when another function needs to know the status of something + // rather than polling like it's doing now. + while ( 1 ) + { + // combat can be removed + self updateAll(); + + wait( 0.1 ); + } +} + +memberDeathWaiter() +{ +// self notify ("squad change"); + self endon( "removed from squad" ); + + self waittill( "death", attacker ); + + if ( isdefined( self ) ) + { + self.attacker = attacker; + } + + self removeFromSquad(); +} + +memberCombatWaiter() +{ +// self notify ("squad change"); + self endon( "removed from squad" ); + + while ( 1 ) + { + self waittill( "enemy" ); + + if ( !isdefined( self.enemy ) ) + self.squad notify( "squadupdate", "combat" ); + else + self.squad.isInCombat = true; + + wait( 0.05 ); + } +} + +/**************************************************************************** + utility +*****************************************************************************/ + +updateHeading() +{ + if ( isdefined( self.enemy ) ) + { + self.forward = vectornormalize( self.enemy.origin - self.origin ); + return; + } + + newHeading = ( 0, 0, 0 ); + numInfluences = 0; + + for ( i = 0; i < self.members.size; i++ ) + { + if ( !isalive( self.members[ i ] ) ) + continue; + // logic here to prune out separated members... otherwise origin could be too vague + newHeading += anglestoforward( self.members[ i ].angles ); + numInfluences++ ; + } + + if ( numInfluences ) + self.forward = ( newHeading[ 0 ] / numInfluences, newHeading[ 1 ] / numInfluences, newHeading[ 2 ] / numInfluences ); + else + self.forward = newHeading; +} + +updateOrigin() +{ + //prof_begin( "updateOrigin" ); + newOrigin = ( 0, 0, 0 ); + numInfluences = 0; + + for ( i = 0; i < self.members.size; i++ ) + { + // logic here to prune out separated members... otherwise origin could be too vague + +// assertex (isdefined (self.members[i]), "updateOrigin run while a squad member was undefined"); + if ( !isalive( self.members[ i ] ) ) + continue; + + newOrigin += self.members[ i ].origin; + numInfluences++ ; + } + + if ( numInfluences ) + self.origin = ( newOrigin[ 0 ] / numInfluences, newOrigin[ 1 ] / numInfluences, newOrigin[ 2 ] / numInfluences ); + else + self.origin = newOrigin; + + //prof_end( "updateOrigin" ); +} + +updateCombat() +{ + //prof_begin( "updateCombat" ); + + self.isInCombat = false; + + // reset squad contact status + for ( i = 0; i < anim.squadIndex.size; i++ ) + self.squadList[ anim.squadIndex[ i ].squadName ].isInContact = false; + + for ( i = 0; i < self.members.size; i++ ) + { + if ( isdefined( self.members[ i ].enemy ) && isdefined( self.members[ i ].enemy.squad ) && self.members[ i ].combatTime > 0 ) + self.squadList[ self.members[ i ].enemy.squad.squadName ].isInContact = true; + } + + //prof_end( "updateCombat" ); +} + +updateEnemy() +{ + //prof_begin( "updateEnemy" ); + curEnemy = undefined; + for ( i = 0; i < self.members.size; i++ ) + { + if ( isdefined( self.members[ i ].enemy ) && isdefined( self.members[ i ].enemy.squad ) ) + { + if ( !isdefined( curEnemy ) ) + curEnemy = self.members[ i ].enemy.squad; + else if ( self.members[ i ].enemy.squad.memberCount > curEnemy.memberCount ) + curEnemy = self.members[ i ].enemy.squad; + } + } + + self.enemy = curEnemy; + //prof_end( "updateEnemy" ); +} + +updateAll() +{ + //prof_begin( "updateAll" ); + + newOrigin = ( 0, 0, 0 ); + numInfluences = 0; + curEnemy = undefined; + isInCombat = false; + + self updateCombat(); + + for ( i = 0; i < self.members.size; i++ ) + { + if ( !isalive( self.members[ i ] ) ) + continue; + + // logic here to prune out separated members... otherwise origin could be too vague + newOrigin += self.members[ i ].origin; + numInfluences++ ; + + if ( isdefined( self.members[ i ].enemy ) && isdefined( self.members[ i ].enemy.squad ) ) + { + if ( !isdefined( curEnemy ) ) + curEnemy = self.members[ i ].enemy.squad; + else if ( self.members[ i ].enemy.squad.memberCount > curEnemy.memberCount ) + curEnemy = self.members[ i ].enemy.squad; + } + } + + if ( numInfluences ) + self.origin = ( newOrigin[ 0 ] / numInfluences, newOrigin[ 1 ] / numInfluences, newOrigin[ 2 ] / numInfluences ); + else + self.origin = newOrigin; + + self.isInCombat = isInCombat; + self.enemy = curEnemy; + + // integreate this at some point + self updateHeading(); + + //prof_end( "updateAll" ); +} + +updateSquadList() +{ + //prof_begin( "updateSquadList" ); + + for ( i = 0; i < anim.squadIndex.size; i++ ) + { + if ( !isdefined( self.squadList[ anim.squadIndex[ i ].squadName ] ) ) + { + self.squadList[ anim.squadIndex[ i ].squadName ] = spawnstruct(); + self.squadList[ anim.squadIndex[ i ].squadName ].isInContact = false; + } + + for ( j = 0; j < self.squadUpdateFuncs.size; j++ ) + { + squadUpdateFunc = self.squadUpdateFuncs[ j ]; + self thread [[ squadUpdateFunc ]]( anim.squadIndex[ i ].squadName ); + } + } + + //prof_end( "updateSquadList" ); +} + +printAboveHead( string, duration, offset, color ) +{ + self endon( "death" ); + + if ( !isdefined( offset ) ) + offset = ( 0, 0, 0 ); + if ( !isdefined( color ) ) + color = ( 1, 0, 0 ); + + for ( i = 0; i < ( duration * 2 ); i++ ) + { + if ( !isalive( self ) ) + return; + + aboveHead = self getshootatpos() + ( 0, 0, 10 ) + offset; + print3d( aboveHead, string, color, 1, 0.5 ); // origin, text, RGB, alpha, scale + wait( 0.05 ); + } +} + +/**************************************************************************** + ai functions +*****************************************************************************/ + +aiUpdateAnimState( animscript ) +{ + //prof_begin( "aiUpdateAnimState" ); + + switch( animscript ) + { + case "combat": + case "move": + case "stop": + case "death": + self.a.state = animscript; + break; + + case "pain": + case "grenadecower": + break; + + case "cover_crouch": + case "cover_left": + case "cover_prone": + case "cover_right": + case "cover_stand": + case "cover_wide_left": + case "cover_wide_right": + case "concealment_crouch": + case "concealment_prone": + case "concealment_stand": + case "stalingrad_cover_crouch": + self.a.state = "cover"; + break; + + case "aim": + case "l33t truckride combat": + self.a.state = "combat"; + break; + } + + //prof_end( "aiUpdateAnimState" ); +} + +/**************************************************************************** + squad functions +*****************************************************************************/ + +updateStates() +{ + self resetState( "combat" ); + self resetState( "cover" ); + self resetState( "move" ); + self resetState( "stop" ); + self resetState( "death" ); + self resetState( "suppressed" ); + self resetState( "attacking" ); + + for ( i = 0; i < self.members.size; i++ ) + { + if ( !isalive( self.members[ i ] ) ) + continue; + + self queryMemberAnimState( self.members[ i ] ); + self queryMemberState( self.members[ i ], "suppressed" ); + self queryMemberState( self.members[ i ], "combat" ); + self queryMemberState( self.members[ i ], "attacking" ); + self queryMemberState( self.members[ i ], "cover" ); + } +} + +updateMemberStates() +{ + anim endon( "squad deleted " + self.squadName ); + + timeSlice = 0.05; + while ( 1 ) + { + //prof_begin( "updateMemberStates" ); + + for ( i = 0; i < self.members.size; i++ ) + { + if ( !isalive( self.members[ i ] ) ) + continue; + + self.members[ i ] aiUpdateCombat( timeSlice ); + self.members[ i ] aiUpdateSuppressed( timeSlice ); + } + + //prof_end( "updateMemberStates" ); + wait( timeSlice ); + } +} + +aiUpdateCombat( timeSlice ) +{ + if ( isdefined( self.lastEnemySightPos ) ) + { + if ( self.combatTime < 0 ) + self.combatTime = timeSlice; + else + self.combatTime += timeSlice; + + self.lastEnemySightTime = gettime(); + return; + } + else if ( self issuppressed() ) + { + self.combatTime += timeSlice; + return; + } + + if ( self.combatTime > 0 ) + self.combatTime = ( 0 - timeSlice ); + else + self.combatTime -= timeSlice; +} + +aiUpdateSuppressed( timeSlice ) +{ + if ( self.suppressed ) + { + if ( self.suppressedTime < 0 ) + self.suppressedTime = timeSlice; + else + self.suppressedTime += timeSlice; + + return; + } + + if ( self.suppressedTime > 0 ) + self.suppressedTime = ( 0 - timeSlice ); + else + self.suppressedTime -= timeSlice; +} + +initState( state, activateRatio ) +{ + self.squadStates[ state ] = spawnstruct(); + self.squadStates[ state ].activateRatio = activateRatio; + self.squadStates[ state ].isActive = false; + self.squadStates[ state ].numActive = 0; +} + +resetState( state ) +{ + self.squadStates[ state ].isActive = false; + self.squadStates[ state ].numActive = 0; +} + +queryMemberAnimState( member ) +{ + self.squadStates[ member.a.state ].numActive++ ; + if ( self.squadStates[ member.a.state ].numActive > ( self.squadStates[ member.a.state ].activateRatio * self.members.size ) ) + self.squadStates[ member.a.state ].isActive = true; +} + +queryMemberState( member, state ) +{ + //prof_begin( "queryMemberState" ); + switch( state ) + { + case "suppressed": + if ( member.suppressedTime > 1.0 ) + self.squadStates[ state ].numActive++ ; + break; + case "combat": + if ( member.combatTime > 0.0 ) + self.squadStates[ state ].numActive++ ; + break; + case "attacking": + if ( gettime() < member.a.lastShootTime + 2000 ) + self.squadStates[ state ].numActive++ ; + break; + case "cover": + if ( !member animscripts\battlechatter::isExposed() ) + self.squadStates[ state ].numActive++ ; + break; + } + if ( self.squadStates[ state ].numActive > ( self.squadStates[ state ].activateRatio * self.members.size ) ) + self.squadStates[ state ].isActive = true; + //prof_end( "queryMemberState" ); +} diff --git a/animscripts/stop.gsc b/animscripts/stop.gsc new file mode 100644 index 0000000..307bd11 --- /dev/null +++ b/animscripts/stop.gsc @@ -0,0 +1,281 @@ +// "Stop" makes the character not walk, run or fight. He can be standing, crouching or lying +// prone; he can be alert or idle. + +#include animscripts\combat_utility; +#include animscripts\Utility; +#include animscripts\SetPoseMovement; +#using_animtree( "generic_human" ); + +main() +{ + if ( isdefined( self.no_ai ) ) + return; + + if ( isdefined( self.locked_combat ) ) + { + animscripts\locked_combat::locked_combat(); + return; + } + + if ( isdefined( self.onSnowMobile ) ) + { + animscripts\snowmobile::main(); + return; + } + + self notify( "stopScript" ); + self endon( "killanimscript" ); + /# + if ( getdebugdvar( "anim_preview" ) != "" ) + return; + #/ + + [[ self.exception[ "stop_immediate" ] ]](); + // We do the exception_stop script a little late so that the AI has some animation they're playing + // otherwise they'd go into basepose. + thread delayedException(); + + animscripts\utility::initialize( "stop" ); + + specialIdleLoop(); + + self randomizeIdleSet(); + + self thread setLastStoppedTime(); + self thread animscripts\reactions::reactionsCheckLoop(); + + transitionedToIdle = isdefined( self.customIdleAnimSet ); + if ( !transitionedToIdle ) + { + if ( self.a.weaponPos[ "right" ] == "none" && self.a.weaponPos[ "left" ] == "none" ) + transitionedToIdle = true; + else if ( AngleClamp180( self getMuzzleAngle()[ 0 ] ) > 20 ) + transitionedToIdle = true; + } + + for ( ;; ) + { + desiredPose = getDesiredIdlePose(); + + if ( desiredPose == "prone" ) + { + transitionedToIdle = true; + self ProneStill(); + } + else + { + assertex( desiredPose == "crouch" || desiredPose == "stand", desiredPose ); + + if ( self.a.pose != desiredPose ) + { + self clearAnim( %root, 0.3 ); + transitionedToIdle = false; + } + self SetPoseMovement( desiredPose, "stop" ); + + if ( !transitionedToIdle ) + { + self transitionToIdle( desiredPose, self.a.idleSet ); + transitionedToIdle = true; + } + else + { + self playIdle( desiredPose, self.a.idleSet ); + } + } + } +} + +setLastStoppedTime() +{ + self endon( "death" ); + self waittill( "killanimscript" ); + self.lastStoppedTime = gettime(); +} + +specialIdleLoop() +{ + self endon( "stop_specialidle" ); + + if ( isdefined( self.specialIdleAnim ) ) + { + idleAnimArray = self.specialIdleAnim; + self.specialIdleAnim = undefined; + self notify( "clearing_specialIdleAnim" ); + + self animmode( "gravity" ); + self orientmode( "face current" ); + + self clearAnim( %root, .2 ); + + while ( 1 ) + { + self setflaggedanimrestart( "special_idle", idleAnimArray[ randomint( idleAnimArray.size ) ], 1, 0.2, 1 ); + self waittillmatch( "special_idle", "end" ); + } + } +} + +getDesiredIdlePose() +{ + myNode = animscripts\utility::GetClaimedNode(); + if ( isDefined( myNode ) ) + { + myNodeAngle = myNode.angles[ 1 ]; + myNodeType = myNode.type; + } + else + { + myNodeAngle = self.desiredAngle; + myNodeType = "node was undefined"; + } + + self animscripts\face::SetIdleFace( anim.alertface ); + + // Find out if we should be standing, crouched or prone + desiredPose = animscripts\utility::choosePose(); + + if ( myNodeType == "Cover Stand" || myNodeType == "Conceal Stand" ) + { + // At cover_stand nodes, we don't want to crouch since it'll most likely make our gun go through the wall. + desiredPose = animscripts\utility::choosePose( "stand" ); + } + else if ( myNodeType == "Cover Crouch" || myNodeType == "Conceal Crouch" ) + { + // We should crouch at concealment crouch nodes. + desiredPose = animscripts\utility::choosePose( "crouch" ); + } + else if ( myNodeType == "Cover Prone" || myNodeType == "Conceal Prone" ) + { + // We should go prone at prone nodes. + desiredPose = animscripts\utility::choosePose( "prone" ); + } + + return desiredPose; +} + +transitionToIdle( pose, idleSet ) +{ + if ( self isCQBWalking() && self.a.pose == "stand" ) + pose = "stand_cqb"; + + if ( isdefined( anim.idleAnimTransition[ pose ] ) ) + { + assert( isdefined( anim.idleAnimTransition[ pose ][ "in" ] ) ); + + // idles and transitions should have no tag origin movement + //self animmode( "zonly_physics", false ); + idleAnim = anim.idleAnimTransition[ pose ][ "in" ]; + self setFlaggedAnimKnobAllRestart( "idle_transition", idleAnim, %body, 1, .2, self.animplaybackrate ); + self animscripts\shared::DoNoteTracks( "idle_transition" ); + //self animmode( "normal", false ); + } +} + +playIdle( pose, idleSet ) +{ + if ( self isCQBWalking() && self.a.pose == "stand" ) + pose = "stand_cqb"; + + idleAddAnim = undefined; + + if ( isdefined( self.customIdleAnimSet ) && isdefined( self.customIdleAnimSet[ pose ] ) ) + { + idleAnim = self.customIdleAnimSet[ pose ]; + + additive = pose + "_add"; + if ( isdefined( self.customIdleAnimSet[ additive ] ) ) + idleAddAnim = self.customIdleAnimSet[ additive ]; + } + else + { + idleSet = idleSet % anim.idleAnimArray[ pose ].size; + + idleAnim = anim_array( anim.idleAnimArray[ pose ][ idleSet ], anim.idleAnimWeights[ pose ][ idleSet ] ); + } + + transTime = 0.2; + if ( gettime() == self.a.scriptStartTime ) + transTime = 0.5; + + if ( isdefined( idleAddAnim ) ) + { + self setAnimKnobAll( idleAnim, %body, 1, transTime, 1 ); + self setAnim( %add_idle ); + self setFlaggedAnimKnobAllRestart( "idle", idleAddAnim, %add_idle, 1, transTime, self.animplaybackrate ); + } + else + { + self setFlaggedAnimKnobAllRestart( "idle", idleAnim, %body, 1, transTime, self.animplaybackrate ); + } + + self animscripts\shared::DoNoteTracks( "idle" ); +} + +ProneStill() +{ + if ( self.a.pose != "prone" ) + { + anim_array[ "stand_2_prone" ] = %stand_2_prone; + anim_array[ "crouch_2_prone" ] = %crouch_2_prone; + + transAnim = anim_array[ self.a.pose + "_2_prone" ]; + assertex( isdefined( transAnim ), self.a.pose ); + assert( animHasNotetrack( transAnim, "anim_pose = \"prone\"" ) ); + + self setFlaggedAnimKnobAllRestart( "trans", transAnim, %body, 1, .2, 1.0 ); + animscripts\shared::DoNoteTracks( "trans" ); + + assert( self.a.pose == "prone" ); + self.a.movement = "stop"; + + self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_modern, %prone_legs_up ); + + return;// in case we need to change our pose again for whatever reason + } + + self thread UpdateProneThread(); + + if ( randomint( 10 ) < 3 ) + { + twitches = []; + twitches[ 0 ] = %prone_twitch_ammocheck; + twitches[ 1 ] = %prone_twitch_look; + twitches[ 2 ] = %prone_twitch_scan; + twitches[ 3 ] = %prone_twitch_lookfast; + twitches[ 4 ] = %prone_twitch_lookup; + + //twitches[ 1 ] = %prone_twitch_ammocheck2; + //twitches[ 6 ] = %prone_twitch_scan2; + + twitchAnim = twitches[ randomint( twitches.size ) ]; + self setFlaggedAnimKnobAll( "prone_idle", twitchAnim, %exposed_modern, 1, 0.2 ); + } + else + { + self setAnimKnobAll( %prone_aim_5, %exposed_modern, 1, 0.2 ); + self setFlaggedAnimKnob( "prone_idle", %prone_idle, 1, 0.2 );// ( additive idle on top ) + } + self waittillmatch( "prone_idle", "end" ); + + self notify( "kill UpdateProneThread" ); +} + +UpdateProneThread() +{ + self endon( "killanimscript" ); + self endon( "kill UpdateProneThread" ); + + for ( ;; ) + { + self animscripts\cover_prone::UpdateProneWrapper( 0.1 ); + wait 0.1; + } +} + +delayedException() +{ + self endon( "killanimscript" ); + wait( 0.05 ); + [[ self.exception[ "stop" ] ]](); +} \ No newline at end of file diff --git a/animscripts/technical/stand.gsc b/animscripts/technical/stand.gsc new file mode 100644 index 0000000..aff09f6 --- /dev/null +++ b/animscripts/technical/stand.gsc @@ -0,0 +1,48 @@ +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + // It'd be nice if I had an animation to get to stand without moving... + self.a.movement = "stop"; + + turret = self getTurret(); + turret thread turretInit( self ); + + self.primaryTurretAnim = %technicalGunner_aim; + self.additiveTurretIdle = %technical_turret_driveidle; + self.additiveTurretFire = %technical_turret_firing; + + self.painFunction = ::technical_pain; + self.deathAnim = %technical_turret_death; + + thread animscripts\saw\common::main( turret ); +} + +technical_pain() +{ + self setFlaggedAnimKnobAllRestart( "painanim", %technical_turret_pain, %body, 1, .1, 1 ); + self animscripts\shared::DoNoteTracks( "painanim" ); +} + +//===================================== +#using_animtree( "mg42" ); + +turretInit( owner ) +{ + self.leftArc = 180; + self.rightArc = 180; + + self UseAnimTree( #animtree ); + + self.additiveTurretIdle = %saw_gunner_idle_mg; + self.additiveTurretFire = %saw_gunner_firing_mg_add; + + self endon( "death" ); + owner waittill( "killanimscript" );// code + + self stopUseAnimTree(); +} diff --git a/animscripts/traverse/crouch_jump_down_40.gsc b/animscripts/traverse/crouch_jump_down_40.gsc new file mode 100644 index 0000000..5c47eb7 --- /dev/null +++ b/animscripts/traverse/crouch_jump_down_40.gsc @@ -0,0 +1,27 @@ +// crouch_jump_down_40.gsc +// Makes the character roll down off a ledge at no higher than 32 units. Designed for 40 units but should work for 44-70 or so. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self.a.movement = "walk"; + self traverseMode( "nogravity" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + //crouch_jump_down_40 + + self setFlaggedAnimKnoballRestart( "stepanim", %jump_across_72, %body, 1, .1, 1 ); + wait .15; +// self waittillmatch("stepanim", "gravity on"); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "stepanim" ); +} \ No newline at end of file diff --git a/animscripts/traverse/duck_under_56.gsc b/animscripts/traverse/duck_under_56.gsc new file mode 100644 index 0000000..a5f1ce8 --- /dev/null +++ b/animscripts/traverse/duck_under_56.gsc @@ -0,0 +1,25 @@ +// Jump_across_72.gsc +// Makes the character do a lateral jump of 72 units. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "jumpanim", %gulag_pipe_traverse, %body, 1, .1, 1 ); + self waittillmatch( "jumpanim", "finish" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "jumpanim" ); +} diff --git a/animscripts/traverse/fence_climb.gsc b/animscripts/traverse/fence_climb.gsc new file mode 100644 index 0000000..423bb96 --- /dev/null +++ b/animscripts/traverse/fence_climb.gsc @@ -0,0 +1,27 @@ +// Fence_climb.gsc +// Makes the character climb a 48 unit fence +// TEMP - copied wall dive until we get an animation +// Makes the character dive over a low wall + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" );// So he doesn't get stuck if the wall is a little too high + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "diveanim", %fenceclimb, %body, 1, .1, 1 ); +// self waittillmatch("diveanim", "gravity on"); + self animscripts\shared::DoNoteTracks( "diveanim" ); + self traverseMode( "gravity" ); +} diff --git a/animscripts/traverse/jump_across_100.gsc b/animscripts/traverse/jump_across_100.gsc new file mode 100644 index 0000000..6773a9f --- /dev/null +++ b/animscripts/traverse/jump_across_100.gsc @@ -0,0 +1,30 @@ +// Jump_across_100.gsc +// Makes the character do a lateral jump of 100 units. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + jumpAnims = []; + jumpAnims[0] = %jump_across_100_spring; + jumpAnims[1] = %jump_across_100_lunge; + jumpAnims[2] = %jump_across_100_stumble; + + jumpanim = jumpAnims[ randomint( jumpAnims.size ) ]; + + self setFlaggedAnimKnoballRestart( "jumpanim", jumpanim, %body, 1, .1, 1 ); + self animscripts\shared::DoNoteTracks( "jumpanim" ); +} diff --git a/animscripts/traverse/jump_across_72.gsc b/animscripts/traverse/jump_across_72.gsc new file mode 100644 index 0000000..69c42ef --- /dev/null +++ b/animscripts/traverse/jump_across_72.gsc @@ -0,0 +1,25 @@ +// Jump_across_72.gsc +// Makes the character do a lateral jump of 72 units. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "jumpanim", %jump_across_72, %body, 1, .1, 1 ); + self waittillmatch( "jumpanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "jumpanim" ); +} diff --git a/animscripts/traverse/jump_over_high_wall.gsc b/animscripts/traverse/jump_over_high_wall.gsc new file mode 100644 index 0000000..a30dca3 --- /dev/null +++ b/animscripts/traverse/jump_over_high_wall.gsc @@ -0,0 +1,32 @@ +// Jump_over_high_wall.gsc +// Makes the character dive over a high wall. Designed for getting bad guys into levels - it looks bad from the back. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self clearanim( %stand_and_crouch, 0.1 ); + self setFlaggedAnimKnoballRestart( "diveanim", %jump_over_high_wall, %body, 1, .1, 1 ); + self playsound( "dive_wall" ); + self waittillmatch( "diveanim", "gravity on" ); + self traverseMode( "nogravity" ); + self waittillmatch( "diveanim", "noclip" ); + self traverseMode( "noclip" ); + self waittillmatch( "diveanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "diveanim" ); +} + diff --git a/animscripts/traverse/jump_up_80.gsc b/animscripts/traverse/jump_up_80.gsc new file mode 100644 index 0000000..544feff --- /dev/null +++ b/animscripts/traverse/jump_up_80.gsc @@ -0,0 +1,25 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "dog" ); +main() +{ + assertex( self.type == "dog", "Only dogs can do this traverse currently." ); + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + self thread teleportThread( realHeight - 80 ); + + self clearanim( %root, 0.2 ); + self setflaggedanimrestart( "jump_up_80", anim.dogTraverseAnims[ "jump_up_80" ], 1, 0.2, 1 ); + + self animscripts\shared::DoNoteTracks( "jump_up_80" ); + + self.traverseComplete = true; +} diff --git a/animscripts/traverse/jumpdown_130.gsc b/animscripts/traverse/jumpdown_130.gsc new file mode 100644 index 0000000..81d39b9 --- /dev/null +++ b/animscripts/traverse/jumpdown_130.gsc @@ -0,0 +1,19 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_jump_down( 7, 0.7 ); + else + jumpdown_130_human(); +} + +jumpdown_130_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse_jumpdown_130; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/jumpdown_40.gsc b/animscripts/traverse/jumpdown_40.gsc new file mode 100644 index 0000000..e62655a --- /dev/null +++ b/animscripts/traverse/jumpdown_40.gsc @@ -0,0 +1,19 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_jump_down( 3, 1.0 ); + else + low_wall_human(); +} + +low_wall_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse_jumpdown_40; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/jumpdown_56.gsc b/animscripts/traverse/jumpdown_56.gsc new file mode 100644 index 0000000..2c9b889 --- /dev/null +++ b/animscripts/traverse/jumpdown_56.gsc @@ -0,0 +1,19 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_jump_down( 5, 1.0 ); + else + low_wall_human(); +} + +low_wall_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse_jumpdown_56; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/jumpdown_96.gsc b/animscripts/traverse/jumpdown_96.gsc new file mode 100644 index 0000000..17cce6b --- /dev/null +++ b/animscripts/traverse/jumpdown_96.gsc @@ -0,0 +1,19 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_jump_down( 7, 0.8 ); + else + low_wall_human(); +} + +low_wall_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse_jumpdown_96; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/ladder_down.gsc b/animscripts/traverse/ladder_down.gsc new file mode 100644 index 0000000..e6432eb --- /dev/null +++ b/animscripts/traverse/ladder_down.gsc @@ -0,0 +1,44 @@ +// ladder_down.gsc +// Climbs down a ladder of any height by using a looping animation. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // First, get on + endnode = self getnegotiationendnode(); + assert( isdefined( endnode ) ); + endPos = endnode.origin; + //("ladder_down: about to start climbing. Height to climb: " + (endPos[2] - self.origin[2]) );#/ + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "climbanim", %ladder_climbon, %body, 1, .1, 1 ); + self animscripts\shared::DoNoteTracks( "climbanim" ); + + // Now do the cycle + climbAnim = %ladder_climbdown; + self setFlaggedAnimKnoballRestart( "climbanim", climbAnim, %body, 1, .1, 1 ); + + cycleDelta = GetMoveDelta( climbAnim, 0, 1 ); + climbRate = cycleDelta[ 2 ] / getanimlength( climbAnim ); + climbingTime = ( endPos[ 2 ] - self.origin[ 2 ] ) / climbRate; + + self animscripts\shared::DoNoteTracksForTime( climbingTime, "climbanim" ); + + self traverseMode( "gravity" ); + self.a.movement = "stop"; + self.a.pose = "stand"; + //("ladder_down: all done");#/ +} diff --git a/animscripts/traverse/ladder_up.gsc b/animscripts/traverse/ladder_up.gsc new file mode 100644 index 0000000..30f07f9 --- /dev/null +++ b/animscripts/traverse/ladder_up.gsc @@ -0,0 +1,51 @@ +// ladder_up.gsc +// Climbs a ladder of any height by using a looping animation, and gets off at the top. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); +// self traverseMode("nogravity"); + self traverseMode( "noclip" ); + + climbAnim = %ladder_climbup; + endAnim = %ladder_climboff; + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "climbanim", climbAnim, %body, 1, .1, 1 ); + + endAnimDelta = GetMoveDelta( endAnim, 0, 1 ); + + endNode = self getnegotiationendnode(); + assert( isdefined( endnode ) ); + endPos = endnode.origin - endAnimDelta + ( 0, 0, 1 ); // 1 unit padding + + cycleDelta = GetMoveDelta( climbAnim, 0, 1 ); + climbRate = cycleDelta[ 2 ] / getanimlength( climbAnim ); + //("ladder_up: about to start climbing. Height to climb: " + (endAnimDelta[2] + endPos[2] - self.origin[2]) );#/ + + climbingTime = ( endPos[ 2 ] - self.origin[ 2 ] ) / climbRate; + if ( climbingTime > 0 ) + { + self.allowpain = true; + self animscripts\shared::DoNoteTracksForTime( climbingTime, "climbanim" ); +// println ("elapsed ", (gettime() - timer) * 0.001); + self setFlaggedAnimKnoballRestart( "climbanim", endAnim, %body, 1, .1, 1 ); + self animscripts\shared::DoNoteTracks( "climbanim" ); + } + + self traverseMode( "gravity" ); + self.a.movement = "run"; + self.a.pose = "crouch"; + //("ladder_up: all done");#/ +} + diff --git a/animscripts/traverse/moon_trav_jump_d112_f244.gsc b/animscripts/traverse/moon_trav_jump_d112_f244.gsc new file mode 100644 index 0000000..0611ff5 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d112_f244.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d112_f244; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d128_f50.gsc b/animscripts/traverse/moon_trav_jump_d128_f50.gsc new file mode 100644 index 0000000..feab97e --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d128_f50.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d128_f50; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d160_f160.gsc b/animscripts/traverse/moon_trav_jump_d160_f160.gsc new file mode 100644 index 0000000..d05b0f3 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d160_f160.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d160_f160; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d160_f244.gsc b/animscripts/traverse/moon_trav_jump_d160_f244.gsc new file mode 100644 index 0000000..bb1a46c --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d160_f244.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d160_f244; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d200_f600.gsc b/animscripts/traverse/moon_trav_jump_d200_f600.gsc new file mode 100644 index 0000000..395e55f --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d200_f600.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d200_f600; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d220_f640.gsc b/animscripts/traverse/moon_trav_jump_d220_f640.gsc new file mode 100644 index 0000000..98d481a --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d220_f640.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d220_f640; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d240_f328.gsc b/animscripts/traverse/moon_trav_jump_d240_f328.gsc new file mode 100644 index 0000000..43b2137 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d240_f328.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d240_f328; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_d64_f160.gsc b/animscripts/traverse/moon_trav_jump_d64_f160.gsc new file mode 100644 index 0000000..b40e80f --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_d64_f160.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_d64_f160; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_into_train_f104.gsc b/animscripts/traverse/moon_trav_jump_into_train_f104.gsc new file mode 100644 index 0000000..4311a21 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_into_train_f104.gsc @@ -0,0 +1,14 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_lava_jump_into_train_f104; + traverseData[ "traverseToCoverAnim" ] = %tp_moon_lava_jump_into_train_f104; + traverseData[ "coverType" ] = "Exposed"; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_into_train_f24.gsc b/animscripts/traverse/moon_trav_jump_into_train_f24.gsc new file mode 100644 index 0000000..c8375b3 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_into_train_f24.gsc @@ -0,0 +1,14 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_lava_jump_into_train_f24; + traverseData[ "traverseToCoverAnim" ] = %tp_moon_lava_jump_into_train_f24; + traverseData[ "coverType" ] = "Exposed"; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_into_train_f352.gsc b/animscripts/traverse/moon_trav_jump_into_train_f352.gsc new file mode 100644 index 0000000..2a4639b --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_into_train_f352.gsc @@ -0,0 +1,14 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_lava_jump_into_train_f352; + traverseData[ "traverseToCoverAnim" ] = %tp_moon_lava_jump_into_train_f352; + traverseData[ "coverType" ] = "Cover Crouch"; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_traintop_f104.gsc b/animscripts/traverse/moon_trav_jump_traintop_f104.gsc new file mode 100644 index 0000000..249c998 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_traintop_f104.gsc @@ -0,0 +1,15 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_lava_jump_traintop_f104; + traverseData[ "traverseToCoverAnim" ] = %tp_moon_lava_jump_traintop_f104; + traverseData[ "coverType" ] = "Cover Right"; + traverseData[ "forceTeleport" ] = true; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_u36_f_d188.gsc b/animscripts/traverse/moon_trav_jump_u36_f_d188.gsc new file mode 100644 index 0000000..74f6e40 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_u36_f_d188.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_u36_f_d188; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_u36_f_d248.gsc b/animscripts/traverse/moon_trav_jump_u36_f_d248.gsc new file mode 100644 index 0000000..e18c5fb --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_u36_f_d248.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_u36_f_d248; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_u36_f_d284.gsc b/animscripts/traverse/moon_trav_jump_u36_f_d284.gsc new file mode 100644 index 0000000..ede0c8a --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_u36_f_d284.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_u36_f_d284; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_jump_u48.gsc b/animscripts/traverse/moon_trav_jump_u48.gsc new file mode 100644 index 0000000..21e4e00 --- /dev/null +++ b/animscripts/traverse/moon_trav_jump_u48.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_u48; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_storage_jump.gsc b/animscripts/traverse/moon_trav_storage_jump.gsc new file mode 100644 index 0000000..bb0f6c7 --- /dev/null +++ b/animscripts/traverse/moon_trav_storage_jump.gsc @@ -0,0 +1,12 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_storage_jump; + + DoTraverse( traverseData ); +} + diff --git a/animscripts/traverse/moon_trav_wall_hop_u_40_d_40.gsc b/animscripts/traverse/moon_trav_wall_hop_u_40_d_40.gsc new file mode 100644 index 0000000..67973d9 --- /dev/null +++ b/animscripts/traverse/moon_trav_wall_hop_u_40_d_40.gsc @@ -0,0 +1,40 @@ +// moon_trav_wall_hop_u_40_d_40.gsc +// Makes the character climb a 40 unit fence, lunar + +#include animscripts\traverse\shared; + +main() +{ + wall_hop_human(); +} + +#using_animtree( "generic_human" ); + +wall_hop_human() +{ + if( !IsDefined( level.moon_trav_wall_hop_toggle ) ) + { + level.moon_trav_wall_hop_toggle = 1; + } + + if ( level.moon_trav_wall_hop_toggle == 1 ) + { + level.moon_trav_wall_hop_toggle = 0; + + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_u40_f_d40_a; + + DoTraverse( traverseData ); + + } + else + { + level.moon_trav_wall_hop_toggle = 1; + + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_moon_trav_jump_u40_f_d40_b; + + DoTraverse( traverseData ); + + } +} \ No newline at end of file diff --git a/animscripts/traverse/nx_proto_retro_jump_down_run.gsc b/animscripts/traverse/nx_proto_retro_jump_down_run.gsc new file mode 100644 index 0000000..48a80ce --- /dev/null +++ b/animscripts/traverse/nx_proto_retro_jump_down_run.gsc @@ -0,0 +1,19 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; + +main() +{ + if ( self.type == "dog" ) + dog_jump_down( 3, 1.0 ); + else + retro_jump_down_human(); +} + +#using_animtree( "generic_human" ); +retro_jump_down_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %nx_proto_retro_jump_down_run; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/shared.gsc b/animscripts/traverse/shared.gsc new file mode 100644 index 0000000..c114979 --- /dev/null +++ b/animscripts/traverse/shared.gsc @@ -0,0 +1,378 @@ +#include animscripts\utility; +#include maps\_utility; +#using_animtree( "generic_human" ); + +// Deprecated. only used for old traverses that will be deleted. +advancedTraverse( traverseAnim, normalHeight ) +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" );// So he doesn't get stuck if the wall is a little too high + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + + self thread teleportThread( realHeight - normalHeight ); + + blendTime = 0.15; + + self clearAnim( %body, blendTime ); + self setFlaggedAnimKnoballRestart( "traverse", traverseAnim, %root, 1, blendTime, 1 ); + + gravityToBlendTime = 0.2; + endBlendTime = 0.2; + + self thread animscripts\shared::DoNoteTracksForever( "traverse", "no clear" ); + if ( !animHasNotetrack( traverseAnim, "gravity on" ) ) + { + magicWhateverTime_WhereTheHeckDidWeGetThisNumberAnyway = 1.23; + wait( magicWhateverTime_WhereTheHeckDidWeGetThisNumberAnyway - gravityToBlendTime ); + self traverseMode( "gravity" ); + wait( gravityToBlendTime ); + } + else + { + self waittillmatch( "traverse", "gravity on" ); + self traverseMode( "gravity" ); + if ( !animHasNotetrack( traverseAnim, "blend" ) ) + wait( gravityToBlendTime ); + else + self waittillmatch( "traverse", "blend" ); + } +} + +teleportThread( verticalOffset ) +{ + self endon( "killanimscript" ); + self notify( "endTeleportThread" ); + self endon( "endTeleportThread" ); + + reps = 5; + offset = ( 0, 0, verticalOffset / reps ); + + for ( i = 0; i < reps; i++ ) + { + self forceTeleport( self.origin + offset ); + wait .05; + } +} + + +teleportThreadEx( verticalOffset, delay, frames, animRate ) +{ + self endon( "killanimscript" ); + self notify( "endTeleportThread" ); + self endon( "endTeleportThread" ); + + if ( (verticalOffset == 0) || (frames <= 0) ) + return; + + if ( delay > 0 ) + wait delay; + + offset = ( 0, 0, verticalOffset / frames ); + + if ( isDefined( animRate ) && (animRate < 1.0) ) + self setFlaggedAnimKnoball( "traverseAnim", self.traverseAnim, self.traverseAnimRoot, 1, .2, animRate ); + + for ( i = 0; i < frames; i++ ) + { + self forceTeleport( self.origin + offset ); + wait .05; + } + + if ( isDefined( animRate ) && (animRate < 1.0) ) + self setFlaggedAnimKnoball( "traverseAnim", self.traverseAnim, self.traverseAnimRoot, 1, .2, 1.0 ); +} + + +DoTraverse( traverseData ) +{ + self endon( "killanimscript" ); + + self notify( "traverse_started" ); + + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + // orient to the Negotiation start node + startnode = self getNegotiationStartNode(); + endNode = self getNegotiationEndNode(); + + assert( isDefined( startnode ) ); + assert( isDefined( endNode ) ); + + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self.traverseHeight = traverseData[ "traverseHeight" ]; + self.traverseStartNode = startnode; + + traverseAnim = traverseData[ "traverseAnim" ]; + traverseToCoverAnim = traverseData[ "traverseToCoverAnim" ]; // traversals that end up with 180-degree spins into cover at the end + + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + self.traverseStartZ = self.origin[ 2 ]; + if ( !animHasNotetrack( traverseAnim, "traverse_align" ) ) + { + /# println( "^1Warning: animation ", traverseAnim, " has no traverse_align notetrack" ); #/ + self handleTraverseAlignment(); + } + + toCover = false; + if ( isDefined( traverseToCoverAnim ) && isDefined( self.node ) && self.node.type == traverseData[ "coverType" ] && distanceSquared( self.node.origin, endNode.origin ) < 25 * 25 ) + { + if ( AbsAngleClamp180( self.node.angles[ 1 ] - endNode.angles[ 1 ] ) > 160 ) + { + toCover = true; + traverseAnim = traverseToCoverAnim; + } + } + + if ( toCover ) + { + if ( isdefined( traverseData[ "traverseToCoverSound" ] ) ) + { + self thread play_sound_on_entity( traverseData[ "traverseToCoverSound" ] ); + } + } + else + { + if ( isdefined( traverseData[ "traverseSound" ] ) ) + { + self thread play_sound_on_entity( traverseData[ "traverseSound" ] ); + } + } + self.traverseAnim = traverseAnim; + self.traverseAnimRoot = %body; + + // tagJW: Use the blend_finish tag to determine blend time on the traverse animation + blend_time = 0.2; + + if( self IsCQBWalking() ) + { + blend_finish_time = GetNotetrackTimes( traverseAnim, "cqb_blend_finish" ); + + } + else + { + blend_finish_time = GetNotetrackTimes( traverseAnim, "blend_finish" ); + } + + if ( blend_finish_time.size > 0 ) + { + blend_time = blend_finish_time[0] * GetAnimLength( traverseAnim ); + } + + self setFlaggedAnimKnoballRestart( "traverseAnim", traverseAnim, %body, 1, blend_time, 1 ); + + self.traverseDeathIndex = 0; + self.traverseDeathAnim = traverseData[ "interruptDeathAnim" ]; + self animscripts\shared::DoNoteTracks( "traverseAnim", ::handleTraverseNotetracks ); + self traverseMode( "gravity" ); + + if ( self.delayedDeath ) + return; + + self.a.nodeath = false; + if ( toCover && isDefined( self.node ) && distanceSquared( self.origin, self.node.origin ) < 16 * 16 ) + { + self.a.movement = "stop"; + + if( IsDefined( traverseData[ "forceTeleport" ] ) && traverseData[ "forceTeleport" ] ) + { + self ForceTeleport( self.node.origin ); + } + else + { + self Teleport( self.node.origin ); + } + } + else if( IsDefined( traverseData[ "traverseStopsAtEnd" ] ) ) + { + self.a.movement = "stop"; + } + else + { + self.a.movement = "run"; + //self setAnimKnobAllRestart( animscripts\run::GetRunAnim(), %body, 1, 0.0, 1 ); + self clearanim( traverseAnim, 0.2 ); + self animscripts\utility::handle_move_transition_notes( traverseAnim ); + } + + self notify( "traverse_finished" ); + + self.traverseAnimRoot = undefined; + self.traverseAnim = undefined; + self.deathAnim = undefined; +} + +handleTraverseNotetracks( note ) +{ + if ( note == "traverse_death" ) + return handleTraverseDeathNotetrack(); + else if ( note == "traverse_align" ) + return handleTraverseAlignment(); + else if ( note == "traverse_drop" ) + return handleTraverseDrop(); +} + +handleTraverseDeathNotetrack() +{ + if ( isdefined( self.traverseDeathAnim ) ) + { + deathAnimArray = self.traverseDeathAnim[ self.traverseDeathIndex ]; + self.deathAnim = deathAnimArray[ randomint( deathAnimArray.size ) ]; + self.traverseDeathIndex++; + } +} + +handleTraverseAlignment() +{ + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + if ( isDefined( self.traverseHeight ) && isDefined( self.traverseStartNode.traverse_height ) ) + { + currentHeight = self.traverseStartNode.traverse_height - self.traverseStartZ; + self thread teleportThread( currentHeight - self.traverseHeight ); + } +} + +handleTraverseDrop() +{ + startpos = self.origin + ( 0, 0, 32 ); + trace = bullettrace( startpos, self.origin + ( 0, 0, -512 ), false, undefined ); + endpos = trace[ "position" ]; + dist = distance( startpos, endpos ); + realDropHeight = dist - 32 - 0.5;// 0.5 makes sure we end up above the ground a bit + + traverseAnimPos = self getAnimTime( self.traverseAnim ); + traverseAnimDelta = getMoveDelta( self.traverseAnim, traverseAnimPos, 1.0 ); + traverseAnimLength = getAnimLength( self.traverseAnim ); + + animDropHeight = 0 - traverseAnimDelta[ 2 ]; + assertEx( animDropHeight >= 0, animDropHeight ); + dropOffset = animDropHeight - realDropHeight; + + /# + if ( getdvarint( "scr_traverse_debug" ) ) + { + thread animscripts\utility::debugLine( startpos, endpos, ( 1, 1, 1 ), 2 * 20 ); + thread animscripts\utility::drawStringTime( "drop offset: " + dropOffset, endpos, ( 1, 1, 1 ), 2 ); + } + #/ + + if ( animDropHeight < realDropHeight ) + animRate = animDropHeight / realDropHeight; + else + animRate = 1; + + teleportLength = ( traverseAnimLength - traverseAnimPos ) / 3.0; // let's make the teleport take 1/3 of the animation time roughly + numFrames = ceil( teleportLength * 20 ); // 0.05 per frame. Maximum number of frames we can use + + self thread teleportThreadEx( dropOffset, 0, numFrames, animRate ); + self thread finishTraverseDrop( endpos[ 2 ] ); +} + +finishTraverseDrop( finalz ) +{ + self endon( "killanimscript" ); + + finalz += 4.0; + while ( 1 ) + { + if ( self.origin[ 2 ] < finalz ) + { + self traverseMode( "gravity" ); + break; + } + wait .05; + } +} + +doNothingFunc() +{ + self animMode( "zonly_physics" ); + self waittill( "killanimscript" ); +} + +#using_animtree( "dog" ); + +dog_wall_and_window_hop( traverseName, height ) +{ + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + self thread teleportThread( realHeight - height ); + + self clearanim( %root, 0.2 ); + self setflaggedanimrestart( "dog_traverse", anim.dogTraverseAnims[ traverseName ], 1, 0.2, 1 ); + + self animscripts\shared::DoNoteTracks( "dog_traverse" ); + + self.traverseComplete = true; +} + + +dog_jump_down( frames, rate ) +{ + self endon( "killanimscript" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + height = self getNegotiationStartNode().origin[2] - self getNegotiationEndNode().origin[2]; + self.traverseAnim = anim.dogTraverseAnims[ "jump_down_40" ]; + self.traverseAnimRoot = %root; + self thread teleportThreadEx( 40.0 - height, 0.1, frames, rate ); + + self clearanim( %root, 0.2 ); + self setflaggedanimrestart( "traverseAnim", self.traverseAnim, 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "traverseAnim" ); + + self clearanim( self.traverseAnim, 0 ); // start run immediately + self traverseMode( "gravity" ); + self.traverseComplete = true; + self.traverseAnimRoot = undefined; + self.traverseAnim = undefined; +} + +dog_jump_up( height, frames ) +{ + self endon( "killanimscript" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self thread teleportThreadEx( height - 40.0, 0.2, frames ); + + self clearanim( %root, 0.25 ); + self setflaggedanimrestart( "traverseAnim", anim.dogTraverseAnims[ "jump_up_40" ], 1, 0.2, 1 ); + self animscripts\shared::DoNoteTracks( "traverseAnim" ); + + self clearanim( anim.dogTraverseAnims[ "jump_up_40" ], 0 ); // start run immediately + self traverseMode( "gravity" ); + self.traverseComplete = true; +} diff --git a/animscripts/traverse/slide_across_car.gsc b/animscripts/traverse/slide_across_car.gsc new file mode 100644 index 0000000..6be0adf --- /dev/null +++ b/animscripts/traverse/slide_across_car.gsc @@ -0,0 +1,54 @@ +#include animscripts\traverse\shared; +#include animscripts\utility; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + + +main() +{ + if ( self.type == "dog" ) + slide_across_car_dog(); + else + slide_across_car_human(); +} + +slide_across_car_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %slide_across_car; + traverseData[ "traverseToCoverAnim" ] = %slide_across_car_2_cover; + traverseData[ "coverType" ] = "Cover Crouch"; + traverseData[ "traverseHeight" ] = 38.0; + traverseData[ "interruptDeathAnim" ][ 0 ] = array( %slide_across_car_death ); + traverseData[ "traverseSound" ] = "npc_car_slide_hood"; + traverseData[ "traverseToCoverSound" ] = "npc_car_slide_cover"; + + DoTraverse( traverseData ); +} + +#using_animtree( "dog" ); + +slide_across_car_dog() +{ + self endon( "killanimscript" ); + self traverseMode( "noclip" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self clearanim( %root, 0.1 ); + self setflaggedanimrestart( "traverse", anim.dogTraverseAnims[ "jump_up_40" ], 1, 0.1, 1 ); + self animscripts\shared::DoNoteTracks( "traverse" ); + + // TEMP, can't hear jump over sounds + self thread play_sound_in_space( "anml_dog_bark", self gettagorigin( "tag_eye" ) ); + + self clearanim( %root, 0 ); + self setflaggedanimrestart( "traverse", anim.dogTraverseAnims[ "jump_down_40" ], 1, 0, 1 ); + self animscripts\shared::DoNoteTracks( "traverse" ); + + self traverseMode( "gravity" ); + self.traverseComplete = true; +} \ No newline at end of file diff --git a/animscripts/traverse/stairs_down.gsc b/animscripts/traverse/stairs_down.gsc new file mode 100644 index 0000000..1ca6ef1 --- /dev/null +++ b/animscripts/traverse/stairs_down.gsc @@ -0,0 +1,49 @@ +// stairs_down.gsc +// Climbs down stairs of any height by using a looping animation. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "stand"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + + + endnode = self getnegotiationendnode() + assert( isdefined( endnode ) ); + endPos = endnode.origin; + + horizontalDelta = ( endPos[ 0 ] - self.origin[ 0 ], endPos[ 1 ] - self.origin[ 1 ], 0 ); + horizontalDistance = length( horizontalDelta ); + + // Do the cycle + //if ( self animscripts\utility::weaponAnims() == "none" || self animscripts\utility::weaponAnims() == "pistol" ) + // climbAnim = %climbstairs_down; + //else + climbAnim = %climbstairs_down_armed; + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startNode ) ); + self OrientMode( "face angle", node.angles[ 1 ] ); + + self setFlaggedAnimKnoball( "climbanim", climbAnim, %body, 1, .3, 1 ); + + cycleDelta = GetMoveDelta( climbAnim, 0, 1 ); + cycleDelta = ( cycleDelta[ 0 ], cycleDelta[ 1 ], 0 ); + cycleHorDist = length( cycleDelta ); + cycleTime = getanimlength( climbAnim ); + climbingTime = ( horizontalDistance / cycleHorDist ) * cycleTime; + + //("stairs_down: about to start climbing. Horizontal dist: " +horizontalDistance+ ", dist/cycle: "+cycleHorDist+", time/cycle: "+cycleTime+", time to play: "+climbingTime);#/ + self animscripts\shared::DoNoteTracksForTime( climbingTime, "climbanim" ); + +// self traverseMode("gravity"); + self.a.movement = "walk"; + self.a.pose = "stand"; + //("stairs_down: all done");#/ +} diff --git a/animscripts/traverse/stairs_up.gsc b/animscripts/traverse/stairs_up.gsc new file mode 100644 index 0000000..0d51fdf --- /dev/null +++ b/animscripts/traverse/stairs_up.gsc @@ -0,0 +1,47 @@ +// stairs_up.gsc +// Climbs stairs of any height by using a looping animation, and gets off at the top. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + + //if ( self animscripts\utility::weaponAnims() == "none" || self animscripts\utility::weaponAnims() == "pistol" ) + // climbAnim = %climbstairs_up; + //else + climbAnim = %climbstairs_up_armed; + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startNode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "climbanim", climbAnim, %body, 1, .1, 1 ); + + endnode = self getnegotiationendnode(); + assert( isdefined( endnode ) ); + endPos = endnode.origin + ( 0, 0, 1 ); // 1 unit padding + + horizontalDelta = ( endPos[ 0 ] - self.origin[ 0 ], endPos[ 1 ] - self.origin[ 1 ], 0 ); + horizontalDistance = length( horizontalDelta ); + cycleDelta = GetMoveDelta( climbAnim, 0, 1 ); + cycleDelta = ( cycleDelta[ 0 ], cycleDelta[ 1 ], 0 ); + cycleHorDist = length( cycleDelta ); + cycleTime = getanimlength( climbAnim ); + climbingTime = ( horizontalDistance / cycleHorDist ) * cycleTime; + //("stairs_down: about to start climbing. Horizontal dist: " +horizontalDistance+ ", dist/cycle: "+cycleHorDist+", time/cycle: "+cycleTime+", time to play: "+climbingTime);#/ + + self animscripts\shared::DoNoteTracksForTime( climbingTime, "climbanim" ); + +// self traverseMode("gravity"); + self.a.movement = "walk"; + self.a.pose = "stand"; + //("stairs_up: all done");#/ +} + diff --git a/animscripts/traverse/step_down.gsc b/animscripts/traverse/step_down.gsc new file mode 100644 index 0000000..8c987b4 --- /dev/null +++ b/animscripts/traverse/step_down.gsc @@ -0,0 +1,39 @@ +#include animscripts\traverse\shared; + +// step_down.gsc +// Makes the character step down off a ledge. Currently the ledge is assumed to be 36 units. + +#using_animtree( "generic_human" ); + + +main() +{ + if ( self.type == "dog" ) + dog_jump_down( 40, 3 ); + else + step_down_human(); +} + + +step_down_human() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self.a.movement = "walk"; + self traverseMode( "nogravity" ); +// self traverseMode("noclip"); // Testing to see if a clip brush will stop regular pathfinding and force the traverse script to be used. + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + + self setFlaggedAnimKnoballRestart( "stepanim", %step_down_low_wall, %body, 1, .1, 1 ); + self waittillmatch( "stepanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "stepanim" ); +} \ No newline at end of file diff --git a/animscripts/traverse/step_up.gsc b/animscripts/traverse/step_up.gsc new file mode 100644 index 0000000..c9141d2 --- /dev/null +++ b/animscripts/traverse/step_up.gsc @@ -0,0 +1,38 @@ +#include animscripts\traverse\shared; + +// step_up.gsc +// Makes the character step up onto a ledge. Currently the ledge is assumed to be 36 units. + +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_jump_up( 40, 3 ); + else + step_up_human(); +} + +step_up_human() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self.a.movement = "walk"; + self traverseMode( "nogravity" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + + self setFlaggedAnimKnoballRestart( "stepanim", %step_up_low_wall, %body, 1, .1, 1 ); + self waittillmatch( "stepanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "stepanim" ); + self setAnimKnobAllRestart( animscripts\run::GetCrouchRunAnim(), %body, 1, 0.1, 1 ); +} + diff --git a/animscripts/traverse/step_up_12.gsc b/animscripts/traverse/step_up_12.gsc new file mode 100644 index 0000000..bc54410 --- /dev/null +++ b/animscripts/traverse/step_up_12.gsc @@ -0,0 +1,29 @@ +// step_up.gsc +// Makes the character step up onto a ledge. Currently the ledge is assumed to be 36 units. + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + + self endon( "killanimscript" ); + + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + destination = ( realHeight ); + + reps = 6; + offset = ( 0, 0, destination / reps ); + self traverseMode( "noclip" );// So he doesn't get stuck if the wall is a little too high + for ( i = 0;i < reps;i++ ) + { + self teleport( self.origin + offset ); + wait( 0.05 ); + } + self traverseMode( "gravity" ); + +} diff --git a/animscripts/traverse/stepup_52.gsc b/animscripts/traverse/stepup_52.gsc new file mode 100644 index 0000000..3ea76a4 --- /dev/null +++ b/animscripts/traverse/stepup_52.gsc @@ -0,0 +1,20 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_jump_up( 52.0, 5 ); + else + low_wall_human(); +} + +low_wall_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse_stepup_52; + traverseData[ "traverseHeight" ] = 52.0; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/trench_jump_out.gsc b/animscripts/traverse/trench_jump_out.gsc new file mode 100644 index 0000000..058ae1e --- /dev/null +++ b/animscripts/traverse/trench_jump_out.gsc @@ -0,0 +1,6 @@ +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); +main() +{ + self advancedTraverse( %trench_jump_out, 47.8 ); +} \ No newline at end of file diff --git a/animscripts/traverse/trench_jumpout.gsc b/animscripts/traverse/trench_jumpout.gsc new file mode 100644 index 0000000..0562d6f --- /dev/null +++ b/animscripts/traverse/trench_jumpout.gsc @@ -0,0 +1,22 @@ +#using_animtree( "generic_human" ); +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self.a.movement = "walk"; + self traverseMode( "nogravity" ); + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "stepanim", %gully_trenchjump, %body, 1, .1, 1 ); + self waittillmatch( "stepanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "stepanim" ); + self setAnimKnobAllRestart( animscripts\run::GetCrouchRunAnim(), %body, 1, 0.1, 1 ); +} diff --git a/animscripts/traverse/vault_down_32_56.gsc b/animscripts/traverse/vault_down_32_56.gsc new file mode 100644 index 0000000..04bb895 --- /dev/null +++ b/animscripts/traverse/vault_down_32_56.gsc @@ -0,0 +1,11 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %tp_border_vault_jump_56d; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/wall_dive.gsc b/animscripts/traverse/wall_dive.gsc new file mode 100644 index 0000000..ffbd191 --- /dev/null +++ b/animscripts/traverse/wall_dive.gsc @@ -0,0 +1,27 @@ +// Wall_dive.gsc +// Makes the character dive over a low wall + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" );// JBW Was getting caught on wall in test / obstacle + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "diveanim", %jump_over_low_wall, %body, 1, .1, 1 ); + self playsound( "dive_wall" ); + self waittillmatch( "diveanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "diveanim" ); + self.a.movement = "run"; +} diff --git a/animscripts/traverse/wall_hop.gsc b/animscripts/traverse/wall_hop.gsc new file mode 100644 index 0000000..e8af43c --- /dev/null +++ b/animscripts/traverse/wall_hop.gsc @@ -0,0 +1,24 @@ +// Fence_climb.gsc +// Makes the character climb a 48 unit fence +// TEMP - copied wall dive until we get an animation +// Makes the character dive over a low wall + +#include animscripts\traverse\shared; + +main() +{ + if ( self.type == "dog" ) + dog_wall_and_window_hop( "wallhop", 40 ); + else + wall_hop_human(); +} + +#using_animtree( "generic_human" ); + +wall_hop_human() +{ + if ( randomint( 100 ) < 30 ) + self advancedTraverse( %traverse_wallhop_3, 39.875 ); + else + self advancedTraverse( %traverse_wallhop, 39.875 ); +} \ No newline at end of file diff --git a/animscripts/traverse/wall_over_40.gsc b/animscripts/traverse/wall_over_40.gsc new file mode 100644 index 0000000..dfd611d --- /dev/null +++ b/animscripts/traverse/wall_over_40.gsc @@ -0,0 +1,24 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_wall_and_window_hop( "window_40", 40 ); + else + low_wall_human(); +} + +low_wall_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse40; + traverseData[ "traverseToCoverAnim" ] = %traverse40_2_cover; + traverseData[ "coverType" ] = "Cover Crouch"; + traverseData[ "traverseHeight" ] = 40.0; + traverseData[ "interruptDeathAnim" ][ 0 ] = array( %traverse40_death_start, %traverse40_death_start_2 ); + traverseData[ "interruptDeathAnim" ][ 1 ] = array( %traverse40_death_end, %traverse40_death_end_2 ); + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/wall_over_96.gsc b/animscripts/traverse/wall_over_96.gsc new file mode 100644 index 0000000..f0d08d0 --- /dev/null +++ b/animscripts/traverse/wall_over_96.gsc @@ -0,0 +1,74 @@ +#include animscripts\Utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + self.traverseDeath = 1; + self advancedTraverse2( %traverse90, 96 ); +} + + +advancedTraverse2( traverseAnim, normalHeight ) +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" );// So he doesn't get stuck if the wall is a little too high + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + +// self thread teleportThread(realHeight - normalHeight); + + self setFlaggedAnimKnoballRestart( "traverse", traverseAnim, %body, 1, 0.15, 1 ); + timer = gettime(); + self thread animscripts\shared::DoNoteTracksForever( "traverse", "no clear", ::handle_death ); + if ( !animhasnotetrack( traverseAnim, "gravity on" ) ) + { + timer = 1.23; + timerOffset = 0.2; +// wait (timer - timerOffset); + wait 5.0; + self traverseMode( "gravity" ); + wait( timerOffset ); + } + else + { + self waittillmatch( "traverse", "gravity on" ); + self traverseMode( "gravity" ); + if ( !animhasnotetrack( traverseAnim, "blend" ) ) + wait( 0.2 ); + else + self waittillmatch( "traverse", "blend" ); + } +} + + +handle_death( note ) +{ + println( note ); + + if ( note != "traverse_death" ) + return; + + self endon( "killanimscript" ); + + if ( self.health == 1 ) + { + self.a.nodeath = true; + if ( self.traverseDeath > 1 ) + self setFlaggedAnimKnobAll( "deathanim", %traverse90_end_death, %body, 1, .2, 1 ); + else + self setFlaggedAnimKnobAll( "deathanim", %traverse90_start_death, %body, 1, .2, 1 ); + + self animscripts\face::SayGenericDialogue( "death" ); + } + self.traverseDeath++ ; +} \ No newline at end of file diff --git a/animscripts/traverse/window.gsc b/animscripts/traverse/window.gsc new file mode 100644 index 0000000..3e5fcfa --- /dev/null +++ b/animscripts/traverse/window.gsc @@ -0,0 +1,27 @@ +// Window.gsc +// Makes the character climb through a window with a 36 unit high lower edge. +// TEMP - copied wall dive until we get an animation +// Makes the character dive over a low wall + +#using_animtree( "generic_human" ); + +main() +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" );// JBW - hitting window + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + + self setFlaggedAnimKnoballRestart( "diveanim", %windowclimb, %body, 1, .1, 1 ); + self waittillmatch( "diveanim", "gravity on" ); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "diveanim" ); +} diff --git a/animscripts/traverse/window_2.gsc b/animscripts/traverse/window_2.gsc new file mode 100644 index 0000000..a7a79ed --- /dev/null +++ b/animscripts/traverse/window_2.gsc @@ -0,0 +1,42 @@ +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + dog_wall_and_window_hop( "wallhop", 40 ); + else + self advancedWindowTraverse( %windowclimb, 35 ); +} + +advancedWindowTraverse( traverseAnim, normalHeight ) +{ + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "nogravity" ); + self traverseMode( "noclip" );// So he doesn't get stuck if the wall is a little too high + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + + self setFlaggedAnimKnoballRestart( "traverse", traverseAnim, %body, 1, 0.15, 1 ); + +// self waittillmatch("traverse", "gravity on"); + + // keeps the actor from sinking in to the ground or from levitating to some extent. + wait 0.7; + self thread teleportThread( realHeight - normalHeight ); + wait 0.9; +// wait 1.6; + + self traverseMode( "gravity" ); + + self animscripts\shared::DoNoteTracks( "traverse" ); + +} diff --git a/animscripts/traverse/window_divethrough_36.gsc b/animscripts/traverse/window_divethrough_36.gsc new file mode 100644 index 0000000..3e53130 --- /dev/null +++ b/animscripts/traverse/window_divethrough_36.gsc @@ -0,0 +1,25 @@ +#include animscripts\utility; +#include animscripts\traverse\shared; +#using_animtree( "generic_human" ); + +main() +{ + if ( self.type == "dog" ) + { + dog_wall_and_window_hop( "window_40", 40 ); + } + else + { + low_wall_human(); + } +} + +low_wall_human() +{ + traverseData = []; + traverseData[ "traverseAnim" ] = %traverse_window_M_2_dive; + traverseData[ "traverseStopsAtEnd" ] = true; + traverseData[ "traverseHeight" ] = 36.0; + + DoTraverse( traverseData ); +} diff --git a/animscripts/traverse/window_down.gsc b/animscripts/traverse/window_down.gsc new file mode 100644 index 0000000..74cf019 --- /dev/null +++ b/animscripts/traverse/window_down.gsc @@ -0,0 +1,134 @@ +#include animscripts\traverse\shared; +#include maps\_utility; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + +main() +{ + + traverseAnim = %windowclimb_fall; + landAnim = %windowclimb_land; + normalHeight = 35; + + // do not do code prone in this script + self.desired_anim_pose = "crouch"; + animscripts\utility::UpdateAnimPose(); + + self endon( "killanimscript" ); + self traverseMode( "noclip" );// So he doesn't get stuck if the wall is a little too high + + // orient to the Negotiation start node + startnode = self getnegotiationstartnode(); + assert( isdefined( startnode ) ); + self OrientMode( "face angle", startnode.angles[ 1 ] ); + realHeight = startnode.traverse_height - startnode.origin[ 2 ]; + + self setFlaggedAnimKnoballRestart( "traverse", traverseAnim, %body, 1, 0.15, 1 ); + thread animscripts\shared::DoNoteTracksForever( "traverse", "stop_traverse_notetracks" ); + + + // keeps the actor from sinking in to the ground or from levitating to some extent. + wait 1.5; + angles = ( 0, startnode.angles[ 1 ], 0 ); + forward = anglestoforward( angles ); + //println(forward); + forward = vector_multiply( forward, 85 ); + trace = bullettrace( startnode.origin + forward, startnode.origin + forward + ( 0, 0, -500 ), false, undefined ); +// thread showLine(startnode.origin + forward, trace["position"]); + endheight = trace[ "position" ][ 2 ]; + + finaldif = startnode.origin[ 2 ] - endheight; + heightChange = 0; + for ( i = 0;i < level._window_down_height.size;i++ ) + { + if ( finaldif < level._window_down_height[ i ] ) + continue; + heightChange = finaldif - level._window_down_height[ i ]; + } + assertEx( heightChange > 0, "window_jump at " + startnode.origin + " is too high off the ground" ); +// heightChange -= 0; + self thread teleportThread( heightChange * - 1 ); + +// thread printerdebugger(heightchange, trace["position"]); + + oldheight = self.origin[ 2 ]; + change = 0; + level.traverseFall = []; + for ( ;; ) + { + /* + /# + thread printer(self.origin); + #/ + */ + change = oldheight - self.origin[ 2 ]; + if ( self.origin[ 2 ] - change < endheight )// predict when he's about to hit the ground + { + break; + } + oldheight = self.origin[ 2 ]; + wait( 0.05 ); + } + if ( isdefined( self.groundtype ) ) + self playsound( "Land_" + self.groundtype ); + + self notify( "stop_traverse_notetracks" ); + self setFlaggedAnimKnoballRestart( "traverse", landAnim, %body, 1, 0.15, 1 ); +// self waittillmatch("traverse", "gravity on"); + self traverseMode( "gravity" ); + self animscripts\shared::DoNoteTracks( "traverse" ); +// wait 0.9; +} + +printer( org ) +{ + level notify( "print_this_" + org ); + level endon( "print_this_" + org ); + for ( ;; ) + { + print3d( org, ".", ( 1, 1, 1 ), 5 ); + wait( 0.05 ); + } +} + +showline( start, end ) +{ + for ( ;; ) + { + line( start, end + ( -1, -1, -1 ), ( 1, 0, 0 ) ); + wait( 0.05 ); + } +} + +printerdebugger( msg, org ) +{ + level notify( "prrint_this_" + org ); + level endon( "prrint_this_" + org ); + for ( ;; ) + { + print3d( org, msg, ( 1, 1, 1 ), 5 ); + wait( 0.05 ); + } +} + + /* + /# + dif = startNode.origin[2] - self.origin[2]; + included = false; + for (i=0;i 1 ) + return true; + + if ( isdefined( self.enemy ) ) + { + self.a.combatEndTime = gettime() + anim.combatMemoryTimeConst + randomint( anim.combatMemoryTimeRand ); + return true; + } + return( self.a.combatEndTime > gettime() ); +} + + +/# +DebugIsInCombat() +{ + return( self.a.combatEndTime > gettime() ); +} +#/ + + +GetEnemyEyePos() +{ + if ( isdefined( self.enemy ) ) + { + self.a.lastEnemyPos = self.enemy getShootAtPos(); + self.a.lastEnemyTime = gettime(); + return self.a.lastEnemyPos; + } + else if ( + ( isDefined( self.a.lastEnemyTime ) ) && + ( isDefined( self.a.lastEnemyPos ) ) && + ( self.a.lastEnemyTime + 3000 < gettime() ) + ) + { + return self.a.lastEnemyPos; + } + else + { + // Return a point in front of you. Note that the distance to this point is significant, because + // this function is used to determine an appropriate attack stance. 16 feet( 196 units ) seems good... + targetPos = self getShootAtPos(); + targetPos = targetPos + ( 196 * self.lookforward[ 0 ], 196 * self.lookforward[ 1 ], 196 * self.lookforward[ 2 ] ); + return targetPos; + } +} + + +GetNodeForwardYaw( node ) +{ + if ( !isdefined( self.heat ) ) + { + if ( node.type == "Cover Left" ) + return node.angles[1] + 90; + else if ( node.type == "Cover Right" ) + return node.angles[1] - 90; + } + + return node.angles[1]; +} + +GetNodeYawToOrigin( pos ) +{ + if ( isdefined( self.node ) ) + yaw = self.node.angles[ 1 ] - GetYaw( pos ); + else + yaw = self.angles[ 1 ] - GetYaw( pos ); + + yaw = AngleClamp180( yaw ); + return yaw; +} + +GetNodeYawToEnemy() +{ + pos = undefined; + if ( isdefined( self.enemy ) ) + { + pos = self.enemy.origin; + } + else + { + if ( isdefined( self.node ) ) + forward = anglestoforward( self.node.angles ); + else + forward = anglestoforward( self.angles ); + forward = vector_multiply( forward, 150 ); + pos = self.origin + forward; + } + + if ( isdefined( self.node ) ) + yaw = self.node.angles[ 1 ] - GetYaw( pos ); + else + yaw = self.angles[ 1 ] - GetYaw( pos ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +GetYawToSpot( spot ) +{ + yaw = self.angles[ 1 ] - GetYaw( spot ); + yaw = AngleClamp180( yaw ); + return yaw; +} +// warning! returns (my yaw - yaw to enemy) instead of (yaw to enemy - my yaw) +GetYawToEnemy() +{ + pos = undefined; + if ( isdefined( self.enemy ) ) + { + pos = self.enemy.origin; + } + else + { + forward = anglestoforward( self.angles ); + forward = vector_multiply( forward, 150 ); + pos = self.origin + forward; + } + + yaw = self.angles[ 1 ] - GetYaw( pos ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +GetYaw( org ) +{ + return VectorToYaw( org - self.origin ); +} + +GetYaw2d( org ) +{ + angles = VectorToAngles( ( org[ 0 ], org[ 1 ], 0 ) - ( self.origin[ 0 ], self.origin[ 1 ], 0 ) ); + return angles[ 1 ]; +} + +// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away. +AbsYawToEnemy() +{ + assert( isdefined( self.enemy ) ); + yaw = self.angles[ 1 ] - GetYaw( self.enemy.origin ); + yaw = AngleClamp180( yaw ); + if ( yaw < 0 ) + yaw = -1 * yaw; + return yaw; +} + +// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away. +AbsYawToEnemy2d() +{ + assert( isdefined( self.enemy ) ); + yaw = self.angles[ 1 ] - GetYaw2d( self.enemy.origin ); + yaw = AngleClamp180( yaw ); + if ( yaw < 0 ) + yaw = -1 * yaw; + return yaw; +} + +// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away. +AbsYawToOrigin( org ) +{ + yaw = self.angles[ 1 ] - GetYaw( org ); + yaw = AngleClamp180( yaw ); + if ( yaw < 0 ) + yaw = -1 * yaw; + return yaw; +} + +AbsYawToAngles( angles ) +{ + yaw = self.angles[ 1 ] - angles; + yaw = AngleClamp180( yaw ); + if ( yaw < 0 ) + yaw = -1 * yaw; + return yaw; +} + +GetYawFromOrigin( org, start ) +{ + angles = VectorToAngles( org - start ); + return angles[ 1 ]; +} + +GetYawToTag( tag, org ) +{ + yaw = self gettagangles( tag )[ 1 ] - GetYawFromOrigin( org, self gettagorigin( tag ) ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +GetYawToOrigin( org ) +{ + yaw = self.angles[ 1 ] - GetYaw( org ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +GetEyeYawToOrigin( org ) +{ + yaw = self gettagangles( "TAG_EYE" )[ 1 ] - GetYaw( org ); + yaw = AngleClamp180( yaw ); + return yaw; +} + +isStanceAllowedWrapper( stance ) +{ + if ( isdefined( self.coverNode ) ) + return self.coverNode doesNodeAllowStance( stance ); + return self isStanceAllowed( stance ); +} + + +choosePose( preferredPose ) +{ + if ( !isDefined( preferredPose ) ) + { + preferredPose = self.a.pose; + } + + // Find out if we should be standing, crouched or prone + switch( preferredPose ) + { + case "stand": + if ( self isStanceAllowedWrapper( "stand" ) ) + { + resultPose = "stand"; + } + else if ( self isStanceAllowedWrapper( "crouch" ) ) + { + resultPose = "crouch"; + } + else if ( self isStanceAllowedWrapper( "prone" ) ) + { + resultPose = "prone"; + } + else + { + println( "No stance allowed! Remaining standing." ); + resultPose = "stand"; + } + break; + + case "crouch": + if ( self isStanceAllowedWrapper( "crouch" ) ) + { + resultPose = "crouch"; + } + else if ( self isStanceAllowedWrapper( "stand" ) ) + { + resultPose = "stand"; + } + else if ( self isStanceAllowedWrapper( "prone" ) ) + { + resultPose = "prone"; + } + else + { + println( "No stance allowed! Remaining crouched." ); + resultPose = "crouch"; + } + break; + + case "prone": + if ( self isStanceAllowedWrapper( "prone" ) ) + { + resultPose = "prone"; + } + else if ( self isStanceAllowedWrapper( "crouch" ) ) + { + resultPose = "crouch"; + } + else if ( self isStanceAllowedWrapper( "stand" ) ) + { + resultPose = "stand"; + } + else + { + println( "No stance allowed! Remaining prone." ); + resultPose = "prone"; + } + break; + + default: + println( "utility::choosePose, called in " + self.script + " script: Unhandled anim_pose " + self.a.pose + " - using stand." ); + resultPose = "stand"; + break; + } + return resultPose; +} + +GetClaimedNode() +{ + myNode = self.node; + if ( isdefined( myNode ) && ( self nearNode( myNode ) || ( isdefined( self.coverNode ) && myNode == self.coverNode ) ) ) + return myNode; + return undefined; +} + +GetNodeType() +{ + myNode = GetClaimedNode(); + if ( isDefined( myNode ) ) + return myNode.type; + return "none"; +} + +GetNodeDirection() +{ + myNode = GetClaimedNode(); + if ( isdefined( myNode ) ) + { + // thread [[ anim.println ]]( "GetNodeDirection found node, returned: " + myNode.angles[ 1 ] );#/ + return myNode.angles[ 1 ]; + } + // thread [[ anim.println ]]( "GetNodeDirection didn't find node, returned: " + self.desiredAngle );#/ + return self.desiredAngle; +} + +GetNodeForward() +{ + myNode = GetClaimedNode(); + if ( isdefined( myNode ) ) + return AnglesToForward( myNode.angles ); + return AnglesToForward( self.angles ); +} + +GetNodeOrigin() +{ + myNode = GetClaimedNode(); + if ( isdefined( myNode ) ) + return myNode.origin; + return self.origin; +} + +/# +isDebugOn() +{ + return( ( getdebugdvarint( "animDebug" ) == 1 ) || ( isDefined( anim.debugEnt ) && anim.debugEnt == self ) ); +} + +drawDebugLineInternal( fromPoint, toPoint, color, durationFrames ) +{ + // println( "Drawing line, color " + color[ 0 ] + ", " + color[ 1 ] + ", " + color[ 2 ] ); + // player = getentarray( "player", "classname" )[0]; + // println( "Point1 : " + fromPoint + ", Point2: " + toPoint + ", player: " + player.origin ); + for ( i = 0;i < durationFrames;i++ ) + { + line( fromPoint, toPoint, color ); + wait( 0.05 ); + } +} + +drawDebugLine( fromPoint, toPoint, color, durationFrames ) +{ + if ( isDebugOn() ) + thread drawDebugLineInternal( fromPoint, toPoint, color, durationFrames ); +} + +debugLine( fromPoint, toPoint, color, durationFrames ) +{ + for ( i = 0;i < durationFrames * 20;i++ ) + { + line( fromPoint, toPoint, color ); + wait( 0.05 ); + } +} + +drawDebugCross( atPoint, radius, color, durationFrames ) +{ + atPoint_high = atPoint + ( 0, 0, radius ); + atPoint_low = atPoint + ( 0, 0, - 1 * radius ); + atPoint_left = atPoint + ( 0, radius, 0 ); + atPoint_right = atPoint + ( 0, - 1 * radius, 0 ); + atPoint_forward = atPoint + ( radius, 0, 0 ); + atPoint_back = atPoint + ( -1 * radius, 0, 0 ); + thread debugLine( atPoint_high, atPoint_low, color, durationFrames ); + thread debugLine( atPoint_left, atPoint_right, color, durationFrames ); + thread debugLine( atPoint_forward, atPoint_back, color, durationFrames ); +} + +drawDebugCrossOld( atPoint, radius, color, durationFrames ) +{ + if ( thread isDebugOn() ) + { + atPoint_high = atPoint + ( 0, 0, radius ); + atPoint_low = atPoint + ( 0, 0, - 1 * radius ); + atPoint_left = atPoint + ( 0, radius, 0 ); + atPoint_right = atPoint + ( 0, - 1 * radius, 0 ); + atPoint_forward = atPoint + ( radius, 0, 0 ); + atPoint_back = atPoint + ( -1 * radius, 0, 0 ); + thread animscripts\utility::drawDebugLine( atPoint_high, atPoint_low, color, durationFrames ); + thread animscripts\utility::drawDebugLine( atPoint_left, atPoint_right, color, durationFrames ); + thread animscripts\utility::drawDebugLine( atPoint_forward, atPoint_back, color, durationFrames ); + } +} + +UpdateDebugInfoInternal() +{ + if ( isDefined( anim.debugEnt ) && ( anim.debugEnt == self ) ) + doInfo = true; + else + doInfo = getdebugdvarInt( "animscriptinfo" ); + + if ( doInfo ) + { + thread drawDebugInfoThread(); + } + else + { + self notify( "EndDebugInfo" ); + } +} + +UpdateDebugInfo() +{ + self endon( "death" ); + for ( ;; ) + { + thread UpdateDebugInfoInternal(); + wait 1; + } +} + + +drawDebugInfoThread() +{ + self endon( "EndDebugInfo" ); + self endon( "death" ); + + for ( ;; ) + { + self thread drawDebugInfo(); + wait 0.05; + } +} + +drawDebugInfo() +{ + // What do we want to print? + line[ 0 ] = self getEntityNumber() + " " + self.script; + line[ 1 ] = self.a.pose + " " + self.a.movement; + if ( self thread DebugIsInCombat() ) + line[ 2 ] = "in combat for " + ( self.a.combatEndTime - gettime() ) + " ms."; + else + line[ 2 ] = "not in combat"; + line[ 3 ] = self.a.lastDebugPrint1; + + belowFeet = self.origin + ( 0, 0, -8 ); + // aboveHead = self getShootAtPos() + ( 0, 0, 8 ); + offset = ( 0, 0, -10 ); + for ( i = 0 ; i < line.size ; i++ ) + { + if ( isDefined( line[ i ] ) ) + { + textPos = ( belowFeet[ 0 ] + ( offset[ 0 ] * i ), belowFeet[ 1 ] + ( offset[ 1 ] * i ), belowFeet[ 2 ] + ( offset[ 2 ] * i ) ); + print3d( textPos, line[ i ], ( .2, .2, 1 ), 1, 0.75 ); // origin, text, RGB, alpha, scale + } + } +} +#/ + +safemod( a, b ) +{ + /* + Here are some modulus results from in - game: + 10 % 3 = 1 + 10 % - 3 = 1 + - 10 % 3 = -1 + - 10 % - 3 = -1 + however, we never want a negative result. + */ + result = int( a ) % b; + result += b; + return result % b; +} + +AbsAngleClamp180( angle ) +{ + return abs( AngleClamp180( angle ) ); +} + +// Returns an array of 4 weights( 2 of which are guaranteed to be 0 ), which should be applied to forward, +// right, back and left animations to get the angle specified. +// front +// / -- -- | -- -- \ +// / 180 \ +// / \ | / \ +// / - 135 | 135 \ +// | \ | / | +// left| - 90 -- -- + -- -- 90 - |right +// | / | \ | +// \ - 45 | 45 / +// \ / | \ / +// \ 0 / +// \ -- -- | -- -- / +// back + +QuadrantAnimWeights( yaw ) +{ + forwardWeight = cos( yaw ); + leftWeight = sin( yaw ); + + result[ "front" ] = 0; + result[ "right" ] = 0; + result[ "back" ] = 0; + result[ "left" ] = 0; + + if ( isdefined( self.alwaysRunForward ) ) + { + assert( self.alwaysRunForward );// always set alwaysRunForward to either true or undefined. + + result[ "front" ] = 1; + return result; + } + + if ( forwardWeight > 0 ) + { + if ( leftWeight > forwardWeight ) + result[ "left" ] = 1; + else if ( leftWeight < -1 * forwardWeight ) + result[ "right" ] = 1; + else + result[ "front" ] = 1; + } + else + { + // if moving backwards, don't blend. + // it looks horrible because the feet cycle in the opposite direction. + // either way, feet slide, but this looks better. + backWeight = -1 * forwardWeight; + if ( leftWeight > backWeight ) + result[ "left" ] = 1; + else if ( leftWeight < forwardWeight ) + result[ "right" ] = 1; + else + result[ "back" ] = 1; + } + + + return result; +} + +getQuadrant( angle ) +{ + angle = AngleClamp( angle ); + + if ( angle < 45 || angle > 315 ) + { + quadrant = "front"; + } + else if ( angle < 135 ) + { + quadrant = "left"; + } + else if ( angle < 225 ) + { + quadrant = "back"; + } + else + { + quadrant = "right"; + } + return quadrant; +} + + +// Checks to see if the input is equal to any of up to ten other inputs. +IsInSet( input, set ) +{ + for ( i = set.size - 1; i >= 0; i -- ) + { + if ( input == set[ i ] ) + return true; + } + return false; +} + +playAnim( animation ) +{ + if ( isDefined( animation ) ) + { + // self thread drawString( animation ); // Doesn't work for animations, only strings. + println( "NOW PLAYING: ", animation ); + self setFlaggedAnimKnobAllRestart( "playAnim", animation, %root, 1, .1, 1 ); + timeToWait = getanimlength( animation ); + timeToWait = ( 3 * timeToWait ) + 1; // So looping animations play through 3 times. + self thread NotifyAfterTime( "time is up", "time is up", timeToWait ); + self waittill( "time is up" ); + self notify( "enddrawstring" ); + } +} + +NotifyAfterTime( notifyString, killmestring, time ) +{ + self endon( "death" ); + self endon( killmestring ); + wait time; + self notify( notifyString ); +} + +// Utility function, made for MilestoneAnims(), which displays a string until killed. +drawString( stringtodraw ) +{ + self endon( "killanimscript" ); + self endon( "enddrawstring" ); + for ( ;; ) + { + wait .05; + print3d( ( self GetDebugEye() ) + ( 0, 0, 8 ), stringtodraw, ( 1, 1, 1 ), 1, 0.2 ); + } +} + +drawStringTime( msg, org, color, timer ) +{ + maxtime = timer * 20; + for ( i = 0;i < maxtime;i++ ) + { + print3d( org, msg, color, 1, 1 ); + wait .05; + } +} + +showLastEnemySightPos( string ) +{ + self notify( "got known enemy2" ); + self endon( "got known enemy2" ); + self endon( "death" ); + + if ( !isdefined( self.enemy ) ) + return; + + if ( self.enemy.team == "allies" ) + color = ( 0.4, 0.7, 1 ); + else + color = ( 1, 0.7, 0.4 ); + + while ( 1 ) + { + wait( 0.05 ); + if ( !isdefined( self.lastEnemySightPos ) ) + continue; + + print3d( self.lastEnemySightPos, string, color, 1, 2.15 ); // origin, text, RGB, alpha, scale + } +} + + /# +printDebugTextProc( string, org, printTime, color ) +{ + level notify( "stop debug print " + org ); + level endon( "stop debug print " + org ); + + if ( !isdefined( color ) ) + color = ( 0.3, 0.9, 0.6 ); + + timer = printTime * 20; + for ( i = 0;i < timer;i += 1 ) + { + wait( 0.05 ); + print3d( org, string, color, 1, 1 ); // origin, text, RGB, alpha, scale + } +} + +printDebugText( string, org, printTime, color ) +{ + if ( getdebugdvar( "anim_debug" ) != "" ) + level thread printDebugTextProc( string, org, printTime, color ); +} +#/ + +hasEnemySightPos() +{ + if ( isdefined( self.node ) ) + return( canSeeEnemyFromExposed() || canSuppressEnemyFromExposed() ); + else + return( canSeeEnemy() || canSuppressEnemy() ); +} + +getEnemySightPos() +{ + return self.goodShootPos; +} + + +util_ignoreCurrentSightPos() +{ + if ( !hasEnemySightPos() ) + return; + + self.ignoreSightPos = getEnemySightPos(); + self.ignoreOrigin = self.origin; +} + + +util_evaluateKnownEnemyLocation() +{ + if ( !hasEnemySightPos() ) + return false; + + myGunPos = self getMuzzlePos(); + myEyeOffset = ( self getShootAtPos() - myGunPos ); + + if ( ( isdefined( self.ignoreSightPos ) ) && ( isdefined( self.ignoreOrigin ) ) ) + { + // Ignore the current last sight pos if you've previously invalidated it from this position + if ( distance( self.origin, self.ignoreOrigin ) < 25 ) + return false; + } + + self.ignoreSightPos = undefined; + + canSee = self canshoot( getEnemySightPos(), myEyeOffset ); + if ( !canSee ) + { + self.ignoreSightPos = getEnemySightPos(); + return false; + } + + return true; + +} + + +debugTimeout() +{ + wait( 5 ); + self notify( "timeout" ); +} + +debugPosInternal( org, string, size ) +{ + self endon( "death" ); + self notify( "stop debug " + org ); + self endon( "stop debug " + org ); + ent = spawnstruct(); + ent thread debugTimeout(); + ent endon( "timeout" ); + if ( self.enemy.team == "allies" ) + color = ( 0.4, 0.7, 1 ); + else + color = ( 1, 0.7, 0.4 ); + + while ( 1 ) + { + wait( 0.05 ); + print3d( org, string, color, 1, size ); // origin, text, RGB, alpha, scale + } +} + +debugPos( org, string ) +{ + thread debugPosInternal( org, string, 2.15 ); +} + +debugPosSize( org, string, size ) +{ + thread debugPosInternal( org, string, size ); +} + +debugBurstPrint( numShots, maxShots ) +{ + burstSize = numShots / maxShots; + burstSizeStr = undefined; + + if ( numShots == self.bulletsInClip ) + burstSizeStr = "all rounds"; + else + if ( burstSize < 0.25 ) + burstSizeStr = "small burst"; + else + if ( burstSize < 0.5 ) + burstSizeStr = "med burst"; + else + burstSizeStr = "long burst"; + + thread animscripts\utility::debugPosSize( self.origin + ( 0, 0, 42 ), burstSizeStr, 1.5 ); + thread animscripts\utility::debugPos( self.origin + ( 0, 0, 60 ), "Suppressing" ); +} + + +printShootProc() +{ + self endon( "death" ); + self notify( "stop shoot " + self.export ); + self endon( "stop shoot " + self.export ); + + printTime = 0.25; + timer = printTime * 20; + for ( i = 0;i < timer;i += 1 ) + { + wait( 0.05 ); + print3d( self.origin + ( 0, 0, 70 ), "Shoot", ( 1, 0, 0 ), 1, 1 ); // origin, text, RGB, alpha, scale + } +} + +printShoot() +{ + /# + if ( getdebugdvar( "anim_debug" ) == "3" ) + self thread printShootProc(); + #/ +} + +showDebugProc( fromPoint, toPoint, color, printTime ) +{ + self endon( "death" ); +// self notify( "stop debugline " + self.export ); +// self endon( "stop debugline " + self.export ); + + timer = printTime * 20; + for ( i = 0;i < timer;i += 1 ) + { + wait( 0.05 ); + line( fromPoint, toPoint, color ); + } +} + +showDebugLine( fromPoint, toPoint, color, printTime ) +{ + self thread showDebugProc( fromPoint, toPoint + ( 0, 0, -5 ), color, printTime ); +} + +shootEnemyWrapper() +{ + [[ anim.shootEnemyWrapper_func ]](); +} + +shootEnemyWrapper_normal() +{ + self.a.lastShootTime = gettime(); + + // set accuracy at time of shoot rather than in a separate thread that is vulnerable to timing issues + maps\_gameskill::set_accuracy_based_on_situation(); + + self notify( "shooting" ); + self shoot(); +} + +shootEnemyWrapper_shootNotify() +{ + level notify( "an_enemy_shot", self ); + shootEnemyWrapper_normal(); +} + +shootPosWrapper( shootPos ) +{ + endpos = bulletSpread( self getMuzzlePos(), shootPos, 4 ); + + self.a.lastShootTime = gettime(); + + self notify( "shooting" ); + self shoot( 1, endpos ); +} + + +throwGun() +{ + org = spawn( "script_model", ( 0, 0, 0 ) ); + org setmodel( "temp" ); + org.origin = self getTagOrigin( "tag_weapon_right" ) + ( 50, 50, 0 ); + org.angles = self getTagAngles( "tag_weapon_right" ); + right = anglestoright( org.angles ); + right = vector_multiply( right, 15 ); + forward = anglestoforward( org.angles ); + forward = vector_multiply( forward, 15 ); + org moveGravity( ( 0, 50, 150 ), 100 ); + + weaponClass = "weapon_" + self.weapon; + weapon = spawn( weaponClass, org.origin ); + weapon.angles = self getTagAngles( "tag_weapon_right" ); + weapon linkto( org ); + +// org rotateVelocity( ( 100, 0, 0 ), 12 ); + lastOrigin = org.origin; + while ( ( isdefined( weapon ) ) && ( isdefined( weapon.origin ) ) ) + { + start = lastOrigin; + end = org.origin; + angles = vectortoangles( end - start ); + forward = anglestoforward( angles ); + forward = vector_multiply( forward, 4 ); + trace = bulletTrace( end, end + forward, true, weapon ); + if ( isalive( trace[ "entity" ] ) && trace[ "entity" ] == self ) + { + wait( 0.05 ); + continue; + } + + if ( trace[ "fraction" ] < 1.0 ) + break; + + lastOrigin = org.origin; + wait( 0.05 ); + } + /* + if( isdefined( trace[ "entity" ] ) ) + { + if( isSentient( trace[ "entity" ] ) ) + trace[ "entity" ] DoDamage( 300, weapon.origin ); + } + */ + if ( ( isdefined( weapon ) ) && ( isdefined( weapon.origin ) ) ) + weapon unlink(); + org delete(); +} + +setEnv( env ) +{ + anim.idleAnimTransition [ "stand" ][ "in" ] = %casual_stand_idle_trans_in; + // anim.idleAnimTransition [ "stand" ][ "out" ] = %casual_stand_idle_trans_out; + + anim.idleAnimArray [ "stand" ][ 0 ][ 0 ] = %casual_stand_idle; + anim.idleAnimArray [ "stand" ][ 0 ][ 1 ] = %casual_stand_idle_twitch; + anim.idleAnimArray [ "stand" ][ 0 ][ 2 ] = %casual_stand_idle_twitchB; + anim.idleAnimWeights [ "stand" ][ 0 ][ 0 ] = 2; + anim.idleAnimWeights [ "stand" ][ 0 ][ 1 ] = 1; + anim.idleAnimWeights [ "stand" ][ 0 ][ 2 ] = 1; + + anim.idleAnimArray [ "stand" ][ 1 ][ 0 ] = %casual_stand_v2_idle; + anim.idleAnimArray [ "stand" ][ 1 ][ 1 ] = %casual_stand_v2_twitch_radio; + anim.idleAnimArray [ "stand" ][ 1 ][ 2 ] = %casual_stand_v2_twitch_shift; + anim.idleAnimArray [ "stand" ][ 1 ][ 3 ] = %casual_stand_v2_twitch_talk; + anim.idleAnimWeights [ "stand" ][ 1 ][ 0 ] = 10; + anim.idleAnimWeights [ "stand" ][ 1 ][ 1 ] = 4; + anim.idleAnimWeights [ "stand" ][ 1 ][ 2 ] = 7; + anim.idleAnimWeights [ "stand" ][ 1 ][ 3 ] = 4; + + anim.idleAnimArray [ "stand_cqb" ][ 0 ][ 0 ] = %cqb_stand_idle; + anim.idleAnimArray [ "stand_cqb" ][ 0 ][ 1 ] = %cqb_stand_twitch; + anim.idleAnimWeights [ "stand_cqb" ][ 0 ][ 0 ] = 2; + anim.idleAnimWeights [ "stand_cqb" ][ 0 ][ 1 ] = 1; + + anim.idleAnimTransition [ "crouch" ][ "in" ] = %casual_crouch_idle_in; + // anim.idleAnimTransition [ "crouch" ][ "out" ] = %casual_crouch_idle_out; + + anim.idleAnimArray [ "crouch" ][ 0 ][ 0 ] = %casual_crouch_idle; + //anim.idleAnimArray [ "crouch" ][ 0 ][ 1 ] = %casual_crouch_twitch; // %casual_crouch_point + anim.idleAnimWeights [ "crouch" ][ 0 ][ 0 ] = 6; + //anim.idleAnimWeights [ "crouch" ][ 0 ][ 1 ] = 3; +} + + +/* +============= +///ScriptDocBegin +"Name: PersonalColdBreath()" +"Summary: Makes cold breath effect play on an AI" +"Module: AI" +"CallOn: An AI" +"Example: level.price thread PersonalColdBreath()" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +PersonalColdBreath() +{ + tag = "TAG_EYE"; + self endon( "death" ); + self notify( "stop personal effect" ); + self endon( "stop personal effect" ); + while ( isdefined( self ) ) + { + wait( 0.05 ); + if( !isdefined( self ) ) + break; + + //only play cold breath when stopped + if ( ( isdefined( self.a.movement ) ) && ( self.a.movement == "stop" ) ) + { + //don't play cold breath if indoors + if ( ( isdefined( self.isindoor ) ) && ( self.isindoor == 1 ) ) + continue; + + playfxOnTag( level._effect[ "cold_breath" ], self, tag ); + wait( 2.5 + randomfloat( 3 ) ); + } + else + wait( 0.5 ); + } +} + + +/* +============= +///ScriptDocBegin +"Name: PersonalColdBreathStop()" +"Summary: Stops cold breath effect playing on an AI" +"Module: AI" +"CallOn: An AI" +"Example: level.price thread PersonalColdBreathStop()" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +PersonalColdBreathStop() +{ + self notify( "stop personal effect" ); +} + +PersonalColdBreathSpawner() +{ + self endon( "death" ); + self notify( "stop personal effect" ); + self endon( "stop personal effect" ); + for ( ;; ) + { + self waittill( "spawned", spawn ); + if ( maps\_utility::spawn_failed( spawn ) ) + continue; + spawn thread PersonalColdBreath(); + } +} + +isSuppressedWrapper() +{ + if ( isdefined( self.forceSuppression ) ) + return self.forceSuppression; + + if ( self.suppressionMeter <= self.suppressionThreshold ) + return false; + return self issuppressed(); // takes into account .ignoreSuppression +} + +// if not suppressed, sometimes we still want to look cautious, like leaning out of a corner instead of stepping out. +// this determines whether we should do that or not. +isPartiallySuppressedWrapper() +{ + if ( self.suppressionMeter <= self.suppressionThreshold * 0.25 ) + return false; + return( self issuppressed() ); // takes into account .ignoreSuppression +} + +getNodeOffset( node ) +{ + if ( isdefined( node.offset ) ) + return node.offset; + + // ( right offset, forward offset, vertical offset ) + // you can get an actor's current eye offset by setting scr_eyeoffset to his entnum. + // this should be redone whenever animations change significantly. + cover_left_crouch_offset = ( -26, .4, 36 ); + cover_left_stand_offset = ( -32, 7, 63 ); + cover_right_crouch_offset = ( 43.5, 11, 36 ); + cover_right_stand_offset = ( 36, 8.3, 63 ); + cover_crouch_offset = ( 3.5, -12.5, 45 );// maybe we could account for the fact that in cover crouch he can stand if he needs to? + cover_stand_offset = ( -3.7, -22, 63 ); + + cornernode = false; + nodeOffset = ( 0, 0, 0 ); + + right = anglestoright( node.angles ); + forward = anglestoforward( node.angles ); + + switch( node.type ) + { + case "Cover Left": + if ( node getHighestNodeStance() == "crouch" ) + nodeOffset = calculateNodeOffset( right, forward, cover_left_crouch_offset ); + else + nodeOffset = calculateNodeOffset( right, forward, cover_left_stand_offset ); + break; + + case "Cover Right": + if ( node getHighestNodeStance() == "crouch" ) + nodeOffset = calculateNodeOffset( right, forward, cover_right_crouch_offset ); + else + nodeOffset = calculateNodeOffset( right, forward, cover_right_stand_offset ); + break; + + case "Cover Stand": + case "Conceal Stand": + case "Turret": + nodeOffset = calculateNodeOffset( right, forward, cover_stand_offset ); + break; + + case "Cover Crouch": + case "Cover Crouch Window": + case "Conceal Crouch": + nodeOffset = calculateNodeOffset( right, forward, cover_crouch_offset ); + break; + } + + node.offset = nodeOffset; + return node.offset; +} + +calculateNodeOffset( right, forward, baseoffset ) +{ + return vector_multiply( right, baseoffset[ 0 ] ) + vector_multiply( forward, baseoffset[ 1 ] ) + ( 0, 0, baseoffset[ 2 ] ); +} + +recentlySawEnemy() +{ + return( isdefined( self.enemy ) && self seeRecently( self.enemy, 5 ) ); +} + +canSeeEnemy( cacheDuration ) +{ + if ( !isdefined( self.enemy ) ) + return false; + + if ( (isDefined( cacheDuration ) && self canSee( self.enemy, cacheDuration )) || self canSee( self.enemy ) ) + { + if ( !checkPitchVisibility( self geteye(), self.enemy getshootatpos() ) ) + return false; + + self.goodShootPos = GetEnemyEyePos(); + + dontGiveUpOnSuppressionYet(); + + return true; + } + + return false; +} + +canSeeEnemyFromExposed() +{ + //prof_begin( "canSeeEnemyFromExposed" ); + + if ( !isdefined( self.enemy ) ) + { + self.goodShootPos = undefined; + //prof_end( "canSeeEnemyFromExposed" ); + return false; + } + + enemyEye = GetEnemyEyePos(); + if ( !isDefined( self.node ) ) + { + result = self canSee( self.enemy ); + } + else + { + result = canSeePointFromExposedAtNode( enemyEye, self.node ); + } + + if ( result ) + { + self.goodShootPos = enemyEye; + + dontGiveUpOnSuppressionYet(); + } + else + { + /# + if ( self getentnum() == getdebugdvarint( "anim_dotshow" ) ) + thread persistentDebugLine( self.node.origin + getNodeOffset( self.node ), enemyEye ); + #/ + } + + //prof_end( "canSeeEnemyFromExposed" ); + return result; +} + +canSeePointFromExposedAtNode( point, node ) +{ + if ( node.type == "Cover Left" || node.type == "Cover Right" ) + { + if ( !self animscripts\corner::canSeePointFromExposedAtCorner( point, node ) ) + return false; + } + + nodeOffset = getNodeOffset( node ); + lookFromPoint = node.origin + nodeOffset; + + if ( !checkPitchVisibility( lookFromPoint, point, node ) ) + return false; + + if ( !sightTracePassed( lookFromPoint, point, false, undefined ) ) + { + if ( node.type == "Cover Crouch" || node.type == "Conceal Crouch" ) + { + // also consider the ability to stand at crouch nodes + lookFromPoint = ( 0, 0, 64 ) + node.origin; + return sightTracePassed( lookFromPoint, point, false, undefined ); + } + + return false; + } + + return true; +} + +// check vertical angle is within our aiming abilities +checkPitchVisibility( fromPoint, toPoint, atNode ) +{ + // Compute Aiming Limits + Tolerance + minPitch = self.downAimLimit - anim.aimPitchDiffTolerance; + maxPitch = self.upAimLimit + anim.aimPitchDiffTolerance; + + pitch = AngleClamp180( vectorToAngles( toPoint - fromPoint )[ 0 ] ); + + if( pitch > maxPitch ) + return false; + + if ( pitch < minPitch ) + { + if ( isdefined( atNode ) && atNode.type != "Cover Crouch" && atNode.type != "Conceal Crouch" ) + return false; + if ( pitch < (anim.coverCrouchLeanPitch + minPitch) ) + return false; + } + + return true; +} + +dontGiveUpOnSuppressionYet() +{ + // we'll reset the giveUpOnSuppression timer the next time we want to suppress + self.a.shouldResetGiveUpOnSuppressionTimer = true; +} + +updateGiveUpOnSuppressionTimer() +{ + if ( !isdefined( self.a.shouldResetGiveUpOnSuppressionTimer ) ) + self.a.shouldResetGiveUpOnSuppressionTimer = true; + + if ( self.a.shouldResetGiveUpOnSuppressionTimer ) + { + // after this time, we will decide that our enemy might not be where we thought they were + // this will cause us to look for better cover + self.a.giveUpOnSuppressionTime = gettime() + randomintrange( 15000, 30000 ); + + self.a.shouldResetGiveUpOnSuppressionTimer = false; + } +} + +showLines( start, end, end2 ) +{ + for ( ;; ) + { + line( start, end, ( 1, 0, 0 ), 1 ); + wait( 0.05 ); + line( start, end2, ( 0, 0, 1 ), 1 ); + wait( 0.05 ); + } +} + +aiSuppressAI() +{ + if ( !self canAttackEnemyNode() ) + return false; + + shootPos = undefined; + if ( isdefined( self.enemy.node ) ) + { + nodeOffset = getNodeOffset( self.enemy.node ); + shootPos = self.enemy.node.origin + nodeOffset; + } + else + shootPos = self.enemy getShootAtPos(); + + // canAttackEnemyNode sometimes returns true even though we can't see the point, because + // our eye pos is not right at our node's offset + if ( !self canShoot( shootPos ) ) + return false; + if ( self.script == "combat" ) + { + // make sure we can also see the tip of our gun + if ( !sighttracepassed( self geteye(), self getMuzzlePos(), false, undefined ) ) + return false; + } + + self.goodShootPos = shootPos; + return true; +} + +canSuppressEnemyFromExposed() +{ + // FromExposed includes checking from the offset of the node the AI is at + + if ( !hasSuppressableEnemy() ) + { + self.goodShootPos = undefined; + return false; + } + + if ( !isplayer( self.enemy ) ) + return aiSuppressAI(); + + if ( isdefined( self.node ) ) + { + if ( self.node.type == "Cover Left" || self.node.type == "Cover Right" ) + { + // Don't try to shoot at stuff behind the node + if ( !self animscripts\corner::canSeePointFromExposedAtCorner( self GetEnemyEyePos(), self.node ) ) + return false; + } + + nodeOffset = getNodeOffset( self.node ); + startOffset = self.node.origin + nodeOffset; + } + else + startOffset = self getMuzzlePos(); + + if ( !checkPitchVisibility( startOffset, self.lastEnemySightPos ) ) + return false; + + return findGoodSuppressSpot( startOffset ); +} + +canSuppressEnemy() +{ + if ( !hasSuppressableEnemy() ) + { + self.goodShootPos = undefined; + return false; + } + + if ( !isplayer( self.enemy ) ) + return aiSuppressAI(); + + startOffset = self getMuzzlePos(); + + if ( !checkPitchVisibility( startOffset, self.lastEnemySightPos ) ) + return false; + + return findGoodSuppressSpot( startOffset ); +} + +hasSuppressableEnemy() +{ + if ( !isdefined( self.enemy ) ) + return false; + + if ( !isdefined( self.lastEnemySightPos ) ) + return false; + + updateGiveUpOnSuppressionTimer(); + if ( gettime() > self.a.giveUpOnSuppressionTime ) + return false; + + if ( !needRecalculateSuppressSpot() ) + return isdefined( self.goodShootPos ); + + return true; +} + +canSeeAndShootPoint( point ) +{ + if ( !sightTracePassed( self getShootAtPos(), point, false, undefined ) ) + return false; + + if ( self.a.weaponPos[ "right" ] == "none" ) + return false; + + gunpoint = self getMuzzlePos(); + + return sightTracePassed( gunpoint, point, false, undefined ); +} + +needRecalculateSuppressSpot() +{ + if ( isdefined( self.goodShootPos ) && !self canSeeAndShootPoint( self.goodShootPos ) ) + return true; + + // we need to recalculate the suppress spot + // if we've moved or if we saw our enemy in a different place than when we + // last calculated it + return( + !isdefined( self.lastEnemySightPosOld ) || + self.lastEnemySightPosOld != self.lastEnemySightPos || + distanceSquared( self.lastEnemySightPosSelfOrigin, self.origin ) > 1024// 1024 = 32 * 32 + ); +} + +findGoodSuppressSpot( startOffset ) +{ + if ( !needRecalculateSuppressSpot() ) + return isdefined( self.goodShootPos ); + + if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) > squared( self.enemy.maxVisibleDist ) ) + { + self.goodShootPos = undefined; + return false; + } + + // make sure we can see from our eye to our gun; if we can't then we really shouldn't be trying to suppress at all! + if ( !sightTracePassed( self getShootAtPos(), startOffset, false, undefined ) ) + { + self.goodShootPos = undefined; + return false; + } + + + self.lastEnemySightPosSelfOrigin = self.origin; + self.lastEnemySightPosOld = self.lastEnemySightPos; + + + currentEnemyPos = GetEnemyEyePos(); + + trace = bullettrace( self.lastEnemySightPos, currentEnemyPos, false, undefined ); + startTracesAt = trace[ "position" ]; + + percievedMovementVector = self.lastEnemySightPos - startTracesAt; + lookVector = vectorNormalize( self.lastEnemySightPos - startOffset ); + percievedMovementVector = percievedMovementVector - vector_multiply( lookVector, vectorDot( percievedMovementVector, lookVector ) ); + // percievedMovementVector is what self.lastEnemySightPos - startTracesAt looks like from our position( that is, projected perpendicular to the direction we're looking ). + + idealTraceInterval = 20.0; + numTraces = int( length( percievedMovementVector ) / idealTraceInterval + 0.5 );// one trace every 20 units, ideally + if ( numTraces < 1 ) + numTraces = 1; + if ( numTraces > 20 ) + numTraces = 20;// cap it + vectorDif = self.lastEnemySightPos - startTracesAt; + vectorDif = ( vectorDif[ 0 ] / numTraces, vectorDif[ 1 ] / numTraces, vectorDif[ 2 ] / numTraces ); + numTraces++ ;// to get both start and end points for traces + + traceTo = startTracesAt; + /# + if ( getdebugdvarint( "debug_dotshow" ) == self getentnum() ) + { + thread print3dtime( 15, self.lastEnemySightPos, "lastpos", ( 1, .2, .2 ), 1, 0.75 ); // origin, text, RGB, alpha, scale + thread print3dtime( 15, startTracesAt, "currentpos", ( 1, .2, .2 ), 1, 0.75 ); // origin, text, RGB, alpha, scale + } + #/ + + self.goodShootPos = undefined; + + goodTraces = 0; + neededGoodTraces = 2;// we stop at 3 good traces away from the cover where they disappeared, should be about 40 units + for ( i = 0; i < numTraces + neededGoodTraces; i++ ) + { + tracePassed = sightTracePassed( startOffset, traceTo, false, undefined ); + thisTraceTo = traceTo; + + /# + if ( getdebugdvarint( "debug_dotshow" ) == self getentnum() ) + { + if ( tracePassed ) + color = ( .2, .2, 1 ); + else + color = ( .2, .2, .2 ); + // showDebugLine( startOffset, traceTo, color, 0.75 ); + thread print3dtime( 15, traceTo, ".", color, 1, 0.75 ); // origin, text, RGB, alpha, scale + } + #/ + + // after we've hit self.lastEnemySightPos, look only perpendicular to our line of sight + if ( i == numTraces - 1 ) + { + vectorDif = vectorDif - vector_multiply( lookVector, vectorDot( vectorDif, lookVector ) ); + } + + traceTo += vectorDif;// for next time + + if ( tracePassed ) + { + goodTraces++ ; + + self.goodShootPos = thisTraceTo; + + // if first trace succeeded, we take it, because it probably means they're crouched under cover and we can shoot over it + if ( i > 0 && goodTraces < neededGoodTraces && i < numTraces + neededGoodTraces - 1 ) + continue; + + return true; + } + else + { + goodTraces = 0; + } + } + + return isdefined( self.goodShootPos ); +} + +// Returns an animation from an array of animations with a corrosponding array of weights. +anim_array( animArray, animWeights ) +{ + total_anims = animArray.size; + idleanim = randomint( total_anims ); + assert( total_anims ); + assert( animArray.size == animWeights.size ); + if ( total_anims == 1 ) + return animArray[ 0 ]; + + weights = 0; + total_weight = 0; + + for ( i = 0;i < total_anims;i++ ) + total_weight += animWeights[ i ]; + + anim_play = randomfloat( total_weight ); + current_weight = 0; + + for ( i = 0;i < total_anims;i++ ) + { + current_weight += animWeights[ i ]; + if ( anim_play >= current_weight ) + continue; + + idleanim = i; + break; + } + + return animArray[ idleanim ]; +} + +print3dtime( timer, org, msg, color, alpha, scale ) +{ + newtime = timer / 0.05; + for ( i = 0;i < newtime;i++ ) + { + print3d( org, msg, color, alpha, scale ); + wait( 0.05 ); + } +} + +print3drise( org, msg, color, alpha, scale ) +{ + newtime = 5 / 0.05; + up = 0; + org = org + randomvector( 30 ); + + for ( i = 0;i < newtime;i++ ) + { + up += 0.5; + print3d( org + ( 0, 0, up ), msg, color, alpha, scale ); + wait( 0.05 ); + } +} + +crossproduct( vec1, vec2 ) +{ + return( vec1[ 0 ] * vec2[ 1 ] - vec1[ 1 ] * vec2[ 0 ] > 0 ); +} + +getGrenadeModel() +{ + return getWeaponModel( self.grenadeweapon ); +} + +sawEnemyMove( timer ) +{ + if ( !isdefined( timer ) ) + timer = 500; + return( gettime() - self.personalSightTime < timer ); +} + +canThrowGrenade() +{ + if ( !self.grenadeAmmo ) + return false; + + if ( self.script_forceGrenade ) + return true; + + return( isplayer( self.enemy ) ); +} + +usingBoltActionWeapon() +{ + return( weaponIsBoltAction( self.weapon ) ); +} + +random_weight( array ) +{ + idleanim = randomint( array.size ); + if ( array.size > 1 ) + { + anim_weight = 0; + for ( i = 0;i < array.size;i++ ) + anim_weight += array[ i ]; + + anim_play = randomfloat( anim_weight ); + + anim_weight = 0; + for ( i = 0;i < array.size;i++ ) + { + anim_weight += array[ i ]; + if ( anim_play < anim_weight ) + { + idleanim = i; + break; + } + } + } + + return idleanim; +} + +setFootstepEffect( name, fx ) +{ + assertEx( isdefined( name ), "Need to define the footstep surface type." ); + assertEx( isdefined( fx ), "Need to define the footstep effect." ); + if ( !isdefined( anim.optionalStepEffects ) ) + anim.optionalStepEffects = []; + anim.optionalStepEffects[ anim.optionalStepEffects.size ] = name; + level._effect[ "step_" + name ] = fx; +} + +setFootstepEffectSmall( name, fx ) +{ + assertEx( isdefined( name ), "Need to define the footstep surface type." ); + assertEx( isdefined( fx ), "Need to define the mud footstep effect." ); + if ( !isdefined( anim.optionalStepEffectsSmall ) ) + anim.optionalStepEffectsSmall = []; + anim.optionalStepEffectsSmall[ anim.optionalStepEffectsSmall.size ] = name; + level._effect[ "step_small_" + name ] = fx; +} + +setNotetrackEffect( notetrack, tag, surfacename, fx, sound_prefix, sound_suffix ) +{ + assert( isdefined( notetrack ) ); + assert( isdefined( tag ) ); + assert( isdefined( fx ) ); + assertEx( isstring( notetrack ), "Notetrack name must be a string" ); + + if ( !isdefined( surfacename ) ) + surfacename = "all"; + + if ( !isdefined( level._notetrackFX ) ) + level._notetrackFX = []; + + if( !IsDefined( level._notetrackFX[ notetrack ] ) ) + { + level._notetrackFX[ notetrack ] = []; + } + + if( !IsDefined( level._notetrackFX[ notetrack ][ surfacename ] ) ) + { + level._notetrackFX[ notetrack ][ surfacename ] = []; + } + + fxItem = SpawnStruct(); + fxItem.tag = tag; + fxItem.fx = fx; + if ( isdefined( sound_prefix ) ) + fxItem.sound_prefix = sound_prefix; + if ( isdefined( sound_suffix ) ) + fxItem.sound_suffix = sound_suffix; + + level._notetrackFX[ notetrack ][ surfacename ][level._notetrackFX[ notetrack ][ surfacename ].size] = fxItem; +} + +//same as setNotetrackEffect, but refreshes, the anim.notetracks structure with the new settings +add_note_track_effect( notetrack, tag, surfacename, fx, sound_prefix, sound_suffix ) +{ + setNotetrackEffect( notetrack, tag, surfacename, fx, sound_prefix, sound_suffix ); + + animscripts\shared::refresh_custom_note_track_fx(); +} + +persistentDebugLine( start, end ) +{ + self endon( "death" ); + level notify( "newdebugline" ); + level endon( "newdebugline" ); + + for ( ;; ) + { + line( start, end, ( 0.3, 1, 0 ), 1 ); + wait( 0.05 ); + } +} + + +EnterProneWrapper( timer ) +{ + thread enterProneWrapperProc( timer ); +} + +enterProneWrapperProc( timer ) +{ + self endon( "death" ); + self notify( "anim_prone_change" ); + self endon( "anim_prone_change" ); + // wrapper so we can put a breakpoint on it + self EnterProne( timer, isDefined( self.a.onback ) ); + self waittill( "killanimscript" ); + + // in case we dont actually make it into prone by the time another script comes in + if ( self.a.pose != "prone" && !isdefined( self.a.onback ) ) + self.a.pose = "prone"; +} + +ExitProneWrapper( timer ) +{ + thread ExitProneWrapperProc( timer ); +} + +ExitProneWrapperProc( timer ) +{ + self endon( "death" ); + self notify( "anim_prone_change" ); + self endon( "anim_prone_change" ); + // wrapper so we can put a breakpoint on it + self ExitProne( timer ); + self waittill( "killanimscript" ); + + // in case we dont actually leave prone, change it out of prone + if ( self.a.pose == "prone" ) + self.a.pose = "crouch"; +} + +canBlindfire() +{ + if ( self.a.atConcealmentNode ) + return false; + + if ( !animscripts\weaponList::usingAutomaticWeapon() ) + return false; + + if ( weaponClass( self.weapon ) == "mg" ) + return false; + + if ( isdefined( self.disable_blindfire ) && self.disable_blindfire == true ) + return false; + + return true; +} + +canHitSuppressSpot() +{ + if ( !hasEnemySightPos() ) + return false; + myGunPos = self getMuzzlePos(); + return( sightTracePassed( myGunPos, getEnemySightPos(), false, undefined ) ); +} + + +moveAnim( animname ) /* string */ +{ + assert( isdefined( self.a.moveAnimSet ) ); + return self.a.moveAnimSet[ animname ]; +} + +randomAnimOfTwo( anim1, anim2 ) +{ + if ( randomint( 2 ) ) + return anim1; + else + return anim2; +} + +animArray( animname ) /* string */ +{ + // println( "playing anim: ", animname ); + + assert( isdefined( self.a.array ) ); + /# + if ( !isdefined( self.a.array[ animname ] ) ) + { + dumpAnimArray(); + assertex( isdefined( self.a.array[ animname ] ), "self.a.array[ \"" + animname + "\" ] is undefined" ); + } + #/ + + return self.a.array[ animname ]; +} + +animArrayAnyExist( animname ) +{ + assert( isdefined( self.a.array ) ); + /# + if ( !isdefined( self.a.array[ animname ] ) ) + { + dumpAnimArray(); + assertex( isdefined( self.a.array[ animname ] ), "self.a.array[ \"" + animname + "\" ] is undefined" ); + } + #/ + + return self.a.array[ animname ].size > 0; +} + +animArrayPickRandom( animname ) +{ + assert( isdefined( self.a.array ) ); + /# + if ( !isdefined( self.a.array[ animname ] ) ) + { + dumpAnimArray(); + assertex( isdefined( self.a.array[ animname ] ), "self.a.array[ \"" + animname + "\" ] is undefined" ); + } + #/ + assert( self.a.array[ animname ].size > 0 ); + + index = randomint( self.a.array[ animname ].size ); + + return self.a.array[ animname ][ index ]; +} + + /# +dumpAnimArray() +{ + println( "self.a.array:" ); + keys = getArrayKeys( self.a.array ); + for ( i = 0; i < keys.size; i++ ) + { + if ( isarray( self.a.array[ keys[ i ] ] ) ) + println( " array[ \"" + keys[ i ] + "\" ] = {array of size " + self.a.array[ keys[ i ] ].size + "}" ); + else + println( " array[ \"" + keys[ i ] + "\" ] = ", self.a.array[ keys[ i ] ] ); + } +} +#/ + +array( a, b, c, d, e, f, g, h, i, j, k, l, m, n ) +{ + array = []; + if ( isdefined( a ) ) array[ 0 ] = a; else return array; + if ( isdefined( b ) ) array[ 1 ] = b; else return array; + if ( isdefined( c ) ) array[ 2 ] = c; else return array; + if ( isdefined( d ) ) array[ 3 ] = d; else return array; + if ( isdefined( e ) ) array[ 4 ] = e; else return array; + if ( isdefined( f ) ) array[ 5 ] = f; else return array; + if ( isdefined( g ) ) array[ 6 ] = g; else return array; + if ( isdefined( h ) ) array[ 7 ] = h; else return array; + if ( isdefined( i ) ) array[ 8 ] = i; else return array; + if ( isdefined( j ) ) array[ 9 ] = j; else return array; + if ( isdefined( k ) ) array[ 10 ] = k; else return array; + if ( isdefined( l ) ) array[ 11 ] = l; else return array; + if ( isdefined( m ) ) array[ 12 ] = m; else return array; + if ( isdefined( n ) ) array[ 13 ] = n; + return array; +} + + +getAIPrimaryWeapon() +{ + return self.primaryweapon; +} + +getAISecondaryWeapon() +{ + return self.secondaryweapon; +} + +getAISidearmWeapon() +{ + return self.sidearm; +} + +getAICurrentWeapon() +{ + return self.weapon; +} + +usingPrimary() +{ + return( self.weapon == self.primaryweapon && self.weapon != "none" ); +} + +usingSecondary() +{ + return( self.weapon == self.secondaryweapon && self.weapon != "none" ); +} + +usingSidearm() +{ + return( self.weapon == self.sidearm && self.weapon != "none" ); +} + +getAICurrentWeaponSlot() +{ + if ( self.weapon == self.primaryweapon ) + return "primary"; + else if ( self.weapon == self.secondaryweapon ) + return "secondary"; + else if ( self.weapon == self.sidearm ) + return "sidearm"; + else + assertMsg( "self.weapon does not match any known slot" ); +} + +AIHasWeapon( weapon ) +{ + if ( isDefined( self.weaponInfo[ weapon ] ) ) + return true; + + return false; +} + +getAnimEndPos( theanim ) +{ + moveDelta = getMoveDelta( theanim, 0, 1 ); + return self localToWorldCoords( moveDelta ); +} + + +damageLocationIsAny( a, b, c, d, e, f, g, h, i, j, k, ovr ) +{ + /* possibile self.damageLocation's: + "torso_upper" + "torso_lower" + "helmet" + "head" + "neck" + "left_arm_upper" + "left_arm_lower" + "left_hand" + "right_arm_upper" + "right_arm_lower" + "right_hand" + "gun" + "none" + "left_leg_upper" + "left_leg_lower" + "left_foot" + "right_leg_upper" + "right_leg_lower" + "right_foot" + */ + + if ( !isdefined( a ) ) return false; if ( self.damageLocation == a ) return true; + if ( !isdefined( b ) ) return false; if ( self.damageLocation == b ) return true; + if ( !isdefined( c ) ) return false; if ( self.damageLocation == c ) return true; + if ( !isdefined( d ) ) return false; if ( self.damageLocation == d ) return true; + if ( !isdefined( e ) ) return false; if ( self.damageLocation == e ) return true; + if ( !isdefined( f ) ) return false; if ( self.damageLocation == f ) return true; + if ( !isdefined( g ) ) return false; if ( self.damageLocation == g ) return true; + if ( !isdefined( h ) ) return false; if ( self.damageLocation == h ) return true; + if ( !isdefined( i ) ) return false; if ( self.damageLocation == i ) return true; + if ( !isdefined( j ) ) return false; if ( self.damageLocation == j ) return true; + if ( !isdefined( k ) ) return false; if ( self.damageLocation == k ) return true; + assert( !isdefined( ovr ) ); + return false; +} + + +usingPistol() +{ + return weaponClass( self.weapon ) == "pistol"; +} + +usingStreamgun() +{ + return isStreamgun( self.weapon ); +} + +usingRocketLauncher() +{ + return weaponClass( self.weapon ) == "rocketlauncher"; +} + +usingMG() +{ + return weaponClass( self.weapon ) == "mg"; +} + +usingMinigun() +{ + return weaponClass( self.weapon ) == "mini"; +} + +usingShotGun() +{ + return weaponclass( self.weapon ) == "spread"; +} + +usingRifleLikeWeapon() +{ + class = weaponClass( self.weapon ); + + switch( class ) + { + case "mg": + case "smg": + case "sniper": + case "rifle": + case "spread": + return true; + } + + return false; +} + +ragdollDeath( moveAnim ) +{ + self endon( "killanimscript" ); + + lastOrg = self.origin; + moveVec = ( 0, 0, 0 ); + for ( ;; ) + { + wait( 0.05 ); + force = distance( self.origin, lastOrg ); + lastOrg = self.origin; + + if ( self.health == 1 ) + { + self.a.nodeath = true; + self startRagdoll(); + self clearAnim( moveAnim, 0.1 ); + wait( 0.05 ); + physicsExplosionSphere( lastOrg, 600, 0, force * 0.1 ); + self notify( "killanimscript" ); + return; + } + + } +} + +shouldCQB() +{ + return isdefined( self.cqbwalking ) && !isdefined( self.grenade ); +} + +isCQBWalking() +{ + return isdefined( self.cqbwalking ); +} + +isCQBWalkingOrFacingEnemy() +{ + return !self.faceMotion || isdefined( self.cqbwalking ); +} + +randomizeIdleSet() +{ + self.a.idleSet = randomint( 2 ); +} + +isShotgun( weapon ) +{ + return weaponclass( weapon ) == "spread"; +} + +isStreamgun( weapon ) +{ + return ( weapon == "streamgun" ); +} + +isSniperRifle( weapon ) +{ + return weaponclass( weapon ) == "sniper"; +} + +weapon_pump_action_shotgun() +{ + return self.weapon != "none" && weaponIsBoltAction( self.weapon ) && weaponclass( self.weapon ) == "spread"; +} + + +// meant to be used with any integer seed, for a small integer maximum (ideally one that divides anim.randomIntTableSize) +getRandomIntFromSeed( intSeed, intMax ) +{ + assert( intMax > 0 ); + + index = intSeed % anim.randomIntTableSize; + return anim.randomIntTable[ index ] % intMax; +} + + +getCurrentWeaponSlotName() +{ + assert( isDefined( self ) ); + + if ( self usingSecondary() ) + return "secondary"; + + if ( self usingSidearm() ) + return "sidearm"; + + // primary and unknowns/none return this slot by default + return "primary"; +} + +handle_move_transition_notes( animation ) +{ + move_transition_notes = []; + move_transition_points = []; + + if ( self IsCQBWalking() ) + { + move_transition_notes = anim.cqb_transition_notes; + move_transition_points = anim.cqb_transition_points; + } + else + { + move_transition_notes = anim.run_transition_notes; + move_transition_points = anim.run_transition_points; + } + + foreach ( i, note in move_transition_notes ) + { + if ( animHasNotetrack( animation, note ) ) + { + self.move_anim_start_point = move_transition_points[ i ]; + } + } +} + +set_move_anim_start_point() +{ + if ( isdefined( self.move_anim_start_point ) ) + { + self SetAnimTime( %walk_and_run_loops, self.move_anim_start_point ); + self.move_anim_start_point = undefined; + } +} + +actor_goto_node_and_wait_for_notify( node_targetname, notify_name ) +{ + self notify( "goto_node_and_wait_for_notify" ); + self endon( "goto_node_and_wait_for_notify" ); + + node = GetNode( node_targetname, "targetname" ); + self thread actor_goto_node( node ); + + self waittill( notify_name ); +} + +actor_goto_node( node ) +{ + self notify( "stop_going_to_node" ); + self notify( "actor_goto_node" ); + self endon( "actor_goto_node" ); + + self.goalradius = 24; + + self SetGoalNode( node ); + self waittill( "goal" ); +} \ No newline at end of file diff --git a/animscripts/walk.gsc b/animscripts/walk.gsc new file mode 100644 index 0000000..3f6880b --- /dev/null +++ b/animscripts/walk.gsc @@ -0,0 +1,122 @@ +#include animscripts\SetPoseMovement; +#include animscripts\Utility; +#include common_scripts\utility; +#using_animtree( "generic_human" ); + +MoveWalk() +{ + // Decide what pose to use + preferredPose = undefined; + if ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) > 64 * 64 ) + preferredPose = "stand"; + + desiredPose = [[ self.choosePoseFunc ]]( preferredPose ); + + switch( desiredPose ) + { + case "stand": + if ( BeginStandWalk() ) + return; + + if ( isdefined( self.walk_overrideanim ) ) + { + animscripts\move::MoveStandMoveOverride( self.walk_overrideanim, self.walk_override_weights ); + return; + } + + DoWalkAnim( GetWalkAnim( "straight" ) ); + break; + + case "crouch": + if ( BeginCrouchWalk() ) + return; + + DoWalkAnim( GetWalkAnim( "crouch" ) ); + break; + + default: + assert( desiredPose == "prone" ); + if ( BeginProneWalk() ) + return; + + self.a.movement = "walk"; + DoWalkAnim( GetWalkAnim( "prone" ) ); + break; + } +} + +DoWalkAnimOverride( walkAnim ) +{ + self endon( "movemode" ); + self clearanim( %combatrun, 0.6 ); + self setanimknoball( %combatrun, %body, 1, 0.5, self.moveplaybackrate ); + + if ( isarray( self.walk_overrideanim ) ) + { + if ( isdefined( self.walk_override_weights ) ) + moveAnim = choose_from_weighted_array( self.walk_overrideanim, self.walk_override_weights ); + else + moveAnim = self.walk_overrideanim[ randomint( self.walk_overrideanim.size ) ]; + } + else + { + moveAnim = self.walk_overrideanim; + } + + self setflaggedanimknob( "moveanim", moveAnim, 1, 0.2 ); + animscripts\shared::DoNoteTracks( "moveanim" ); +} + +GetWalkAnim( desiredAnim ) +{ + if ( self.stairsState == "up" ) + return moveAnim( "stairs_up" ); + else if ( self.stairsState == "down" ) + return moveAnim( "stairs_down" ); + + walkAnim = moveAnim( desiredAnim ); + + if ( isarray( walkAnim ) ) + walkAnim = walkAnim[ randomint( walkAnim.size ) ]; + + return walkAnim; +} + +DoWalkAnim( walkAnim ) +{ + self endon( "movemode" ); + + rate = self.moveplaybackrate; + + if ( self.stairsState != "none" ) + rate *= 0.6; + + if ( self.a.pose == "stand" ) + { + if ( isdefined( self.enemy ) ) + { + self thread animscripts\cqb::CQBTracking(); + // (we don't use %body because that would reset the aiming knobs) + self setFlaggedAnimKnobAll( "walkanim", animscripts\cqb::DetermineCQBAnim(), %walk_and_run_loops, 1, 1, rate, true ); + } + else + { + self setFlaggedAnimKnobAll( "walkanim", walkAnim, %body, 1, 1, rate, true ); + } + + self animscripts\run::SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + self thread animscripts\run::SetCombatStandMoveAnimWeights( "walk" ); + } + else + { + self setFlaggedAnimKnobAll( "walkanim", walkAnim, %body, 1, 1, rate, true ); + + self animscripts\run::SetMoveNonForwardAnims( moveAnim( "move_b" ), moveAnim( "move_l" ), moveAnim( "move_r" ) ); + self thread animscripts\run::SetCombatStandMoveAnimWeights( "walk" ); + } + + self animscripts\shared::DoNoteTracksForTime( 0.2, "walkanim" ); + + self thread animscripts\run::stopShootWhileMovingThreads(); +} + diff --git a/animscripts/weaponlist.gsc b/animscripts/weaponlist.gsc new file mode 100644 index 0000000..499621e --- /dev/null +++ b/animscripts/weaponlist.gsc @@ -0,0 +1,126 @@ +// Weapon configuration for anim scripts. +// Supplies information for all AI weapons. +#using_animtree( "generic_human" ); + + +usingAutomaticWeapon() +{ + return( WeaponIsAuto( self.weapon ) || WeaponBurstCount( self.weapon ) > 0 ); +} + +usingSemiAutoWeapon() +{ + return( weaponIsSemiAuto( self.weapon ) ); +} + +autoShootAnimRate() +{ + if ( usingAutomaticWeapon() ) + { + // The auto fire animation fires 10 shots a second, so we divide the weapon's fire rate by + // 10 to get the correct anim playback rate. +// return weaponFireTime( self.weapon ) * 10; + return 0.1 / weaponFireTime( self.weapon ); + } + else + { +// println ("weaponList::standAimShootAnims: No auto fire rate for "+self.weapon); + return 0.5; + } +} + +burstShootAnimRate() +{ + if ( usingAutomaticWeapon() ) + { + return 0.1 / weaponFireTime( self.weapon ); + } + else + { +// println ("weaponList::standAimShootAnims: No auto fire rate for "+self.weapon); + return 0.2; // Equates to 2 shots a second, decent for a non - auto weapon. + } +} + +waitAfterShot() +{ + return 0.25; +} + +shootAnimTime( semiAutoFire ) +{ + if ( !usingAutomaticWeapon() || ( isdefined( semiAutofire ) && ( semiAutofire == true ) ) ) + { + // We randomize the result a little from the real time, just to make things more + // interesting. In reality, the 20Hz server is going to make this much less variable. + rand = 0.5 + randomfloat( 1 );// 0.8 + 0.4 + return weaponFireTime( self.weapon ) * rand; + } + else + { + return weaponFireTime( self.weapon ); + } + +} + +RefillClip() +{ + assertEX( isDefined( self.weapon ), "self.weapon is not defined for " + self.model ); + + if ( self.weapon == "none" ) + { + self.bulletsInClip = 0; + return false; + } + + if ( weaponClass( self.weapon ) == "rocketlauncher" ) + { + if ( !self.a.rocketVisible ) + self thread animscripts\combat_utility::showRocketWhenReloadIsDone(); + /* + // TODO: proper rocket ammo tracking + if ( self.a.rockets < 1 ) + self animscripts\shared::placeWeaponOn( self.secondaryweapon, "right" ); + */ + } + + if ( !isDefined( self.bulletsInClip ) ) + { + self.bulletsInClip = weaponClipSize( self.weapon ); + } + else + { + self.bulletsInClip = weaponClipSize( self.weapon ); + } + + assertEX( isDefined( self.bulletsInClip ), "RefillClip failed" ); + + if ( self.bulletsInClip <= 0 ) + return false; + else + return true; +} + + +add_weapon( name, type, time, clipsize, anims ) +{ + assert( isdefined( name ) ); + assert( isdefined( type ) ); + if ( !isdefined( time ) ) + time = 3.0; + if ( !isdefined( clipsize ) ) + time = 1; + if ( !isdefined( anims ) ) + anims = "rifle"; + + name = tolower( name ); + anim.AIWeapon[ name ][ "type" ] = type; + anim.AIWeapon[ name ][ "time" ] = time; + anim.AIWeapon[ name ][ "clipsize" ] = clipsize; + anim.AIWeapon[ name ][ "anims" ] = anims; +} + +addTurret( turret ) +{ + anim.AIWeapon[ tolower( turret ) ][ "type" ] = "turret"; +} diff --git a/character/character_city_civ_female_a.gsc b/character/character_city_civ_female_a.gsc new file mode 100644 index 0000000..bca11c9 --- /dev/null +++ b/character/character_city_civ_female_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_city_civ_female_a"); + codescripts\character::attachHead( "alias_city_civ_female_heads", xmodelalias\alias_city_civ_female_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_city_civ_female_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_city_civ_female_heads::main()); +} diff --git a/character/character_city_civ_male_a.gsc b/character/character_city_civ_male_a.gsc new file mode 100644 index 0000000..d100df1 --- /dev/null +++ b/character/character_city_civ_male_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_city_civ_male_a"); + codescripts\character::attachHead( "alias_city_civ_male_heads", xmodelalias\alias_city_civ_male_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_city_civ_male_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_city_civ_male_heads::main()); +} diff --git a/character/character_city_civ_male_a_drone.gsc b/character/character_city_civ_male_a_drone.gsc new file mode 100644 index 0000000..fa2c4af --- /dev/null +++ b/character/character_city_civ_male_a_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_city_civ_male_a_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_city_civ_male_a_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_fem_a_drone.gsc b/character/character_civilian_urban_fem_a_drone.gsc new file mode 100644 index 0000000..d74e7cf --- /dev/null +++ b/character/character_civilian_urban_fem_a_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_female_a_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_fem_drone", xmodelalias\alias_civilian_urban_heads_fem_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_female_a_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_fem_drone::main()); +} diff --git a/character/character_civilian_urban_fem_b_drone.gsc b/character/character_civilian_urban_fem_b_drone.gsc new file mode 100644 index 0000000..ca8435b --- /dev/null +++ b/character/character_civilian_urban_fem_b_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_female_b_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_fem_drone", xmodelalias\alias_civilian_urban_heads_fem_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_female_b_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_fem_drone::main()); +} diff --git a/character/character_civilian_urban_female_a.gsc b/character/character_civilian_urban_female_a.gsc new file mode 100644 index 0000000..a40a67d --- /dev/null +++ b/character/character_civilian_urban_female_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_female_a"); + codescripts\character::attachHead( "alias_civilian_urban_heads_female", xmodelalias\alias_civilian_urban_heads_female::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_female_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_female::main()); +} diff --git a/character/character_civilian_urban_female_b.gsc b/character/character_civilian_urban_female_b.gsc new file mode 100644 index 0000000..5a13e5a --- /dev/null +++ b/character/character_civilian_urban_female_b.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_female_b"); + codescripts\character::attachHead( "alias_civilian_urban_heads_female", xmodelalias\alias_civilian_urban_heads_female::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_female_b"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_female::main()); +} diff --git a/character/character_civilian_urban_male_aa_drone.gsc b/character/character_civilian_urban_male_aa_drone.gsc new file mode 100644 index 0000000..c42db68 --- /dev/null +++ b/character/character_civilian_urban_male_aa_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_aa_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_aa_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_male_aa_wht.gsc b/character/character_civilian_urban_male_aa_wht.gsc new file mode 100644 index 0000000..924e5ad --- /dev/null +++ b/character/character_civilian_urban_male_aa_wht.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_aa"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht", xmodelalias\alias_civilian_urban_heads_wht::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_aa"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht::main()); +} diff --git a/character/character_civilian_urban_male_ab_drone.gsc b/character/character_civilian_urban_male_ab_drone.gsc new file mode 100644 index 0000000..b0d1c12 --- /dev/null +++ b/character/character_civilian_urban_male_ab_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_ab_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_ab_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_male_ab_wht.gsc b/character/character_civilian_urban_male_ab_wht.gsc new file mode 100644 index 0000000..bcd80a9 --- /dev/null +++ b/character/character_civilian_urban_male_ab_wht.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_ab"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht", xmodelalias\alias_civilian_urban_heads_wht::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_ab"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht::main()); +} diff --git a/character/character_civilian_urban_male_ac_drone.gsc b/character/character_civilian_urban_male_ac_drone.gsc new file mode 100644 index 0000000..6acfc4b --- /dev/null +++ b/character/character_civilian_urban_male_ac_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_ac_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_ac_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_male_ac_wht.gsc b/character/character_civilian_urban_male_ac_wht.gsc new file mode 100644 index 0000000..0903aad --- /dev/null +++ b/character/character_civilian_urban_male_ac_wht.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_ac"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht", xmodelalias\alias_civilian_urban_heads_wht::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_ac"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht::main()); +} diff --git a/character/character_civilian_urban_male_ba_drone.gsc b/character/character_civilian_urban_male_ba_drone.gsc new file mode 100644 index 0000000..97ea25f --- /dev/null +++ b/character/character_civilian_urban_male_ba_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_ba_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_ba_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_male_ba_wht.gsc b/character/character_civilian_urban_male_ba_wht.gsc new file mode 100644 index 0000000..752d458 --- /dev/null +++ b/character/character_civilian_urban_male_ba_wht.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_ba"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht", xmodelalias\alias_civilian_urban_heads_wht::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_ba"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht::main()); +} diff --git a/character/character_civilian_urban_male_bb_drone.gsc b/character/character_civilian_urban_male_bb_drone.gsc new file mode 100644 index 0000000..6df654d --- /dev/null +++ b/character/character_civilian_urban_male_bb_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_bb_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_bb_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_male_bb_wht.gsc b/character/character_civilian_urban_male_bb_wht.gsc new file mode 100644 index 0000000..4e85f81 --- /dev/null +++ b/character/character_civilian_urban_male_bb_wht.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_bb"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht", xmodelalias\alias_civilian_urban_heads_wht::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_bb"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht::main()); +} diff --git a/character/character_civilian_urban_male_bc_drone.gsc b/character/character_civilian_urban_male_bc_drone.gsc new file mode 100644 index 0000000..a218e7c --- /dev/null +++ b/character/character_civilian_urban_male_bc_drone.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_bc_drone"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht_drone", xmodelalias\alias_civilian_urban_heads_wht_drone::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_bc_drone"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht_drone::main()); +} diff --git a/character/character_civilian_urban_male_bc_wht.gsc b/character/character_civilian_urban_male_bc_wht.gsc new file mode 100644 index 0000000..1888212 --- /dev/null +++ b/character/character_civilian_urban_male_bc_wht.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_bc"); + codescripts\character::attachHead( "alias_civilian_urban_heads_wht", xmodelalias\alias_civilian_urban_heads_wht::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_urban_civ_male_bc"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_urban_heads_wht::main()); +} diff --git a/character/character_civilian_worker_a.gsc b/character/character_civilian_worker_a.gsc new file mode 100644 index 0000000..d0c0dca --- /dev/null +++ b/character/character_civilian_worker_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_work_civ_male_a"); + codescripts\character::attachHead( "alias_civilian_worker_heads", xmodelalias\alias_civilian_worker_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_work_civ_male_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_worker_heads::main()); +} diff --git a/character/character_civilian_worker_b.gsc b/character/character_civilian_worker_b.gsc new file mode 100644 index 0000000..bb2559e --- /dev/null +++ b/character/character_civilian_worker_b.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_work_civ_male_b"); + codescripts\character::attachHead( "alias_civilian_worker_heads", xmodelalias\alias_civilian_worker_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_work_civ_male_b"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_worker_heads::main()); +} diff --git a/character/character_civilian_worker_c.gsc b/character/character_civilian_worker_c.gsc new file mode 100644 index 0000000..a155382 --- /dev/null +++ b/character/character_civilian_worker_c.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_work_civ_male_c"); + codescripts\character::attachHead( "alias_civilian_worker_heads", xmodelalias\alias_civilian_worker_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_work_civ_male_c"); + codescripts\character::precacheModelArray(xmodelalias\alias_civilian_worker_heads::main()); +} diff --git a/character/character_hero_seal_soccom_ghost.gsc b/character/character_hero_seal_soccom_ghost.gsc new file mode 100644 index 0000000..96f6396 --- /dev/null +++ b/character/character_hero_seal_soccom_ghost.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_hero_ghost_socom"); + self attach("head_hero_ghost_soccom", "", true); + self.headModel = "head_hero_ghost_soccom"; + self.voice = "taskforce"; +} + +precache() +{ + precacheModel("body_hero_ghost_socom"); + precacheModel("head_hero_ghost_soccom"); +} diff --git a/character/character_hero_seal_soccom_soap.gsc b/character/character_hero_seal_soccom_soap.gsc new file mode 100644 index 0000000..6283d46 --- /dev/null +++ b/character/character_hero_seal_soccom_soap.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_hero_soap_socom"); + self attach("head_hero_soap_soccom_a", "", true); + self.headModel = "head_hero_soap_soccom_a"; + self.voice = "taskforce"; +} + +precache() +{ + precacheModel("body_hero_soap_socom"); + precacheModel("head_hero_soap_soccom_a"); +} diff --git a/character/character_opforce_fsb_assault_a.gsc b/character/character_opforce_fsb_assault_a.gsc new file mode 100644 index 0000000..1910ab0 --- /dev/null +++ b/character/character_opforce_fsb_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_opforce_fsb_assault_a"); + codescripts\character::attachHead( "alias_opforce_fsb_heads", xmodelalias\alias_opforce_fsb_heads::main() ); + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_opforce_fsb_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_opforce_fsb_heads::main()); +} diff --git a/character/character_opforce_merc_assault_a.gsc b/character/character_opforce_merc_assault_a.gsc new file mode 100644 index 0000000..b8eb246 --- /dev/null +++ b/character/character_opforce_merc_assault_a.gsc @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_opforce_merc_assault_a"); + codescripts\character::attachHead( "alias_opforce_merc_heads", xmodelalias\alias_opforce_merc_heads::main() ); + if ( issubstr( self.headModel, "hat" ) ) + { + self.hatModel = "hat_opforce_merc_b"; + self attach(self.hatModel); + } + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_opforce_merc_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_opforce_merc_heads::main()); + precacheModel("hat_opforce_merc_b"); +} diff --git a/character/character_opforce_merc_assault_b.gsc b/character/character_opforce_merc_assault_b.gsc new file mode 100644 index 0000000..3932022 --- /dev/null +++ b/character/character_opforce_merc_assault_b.gsc @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_opforce_merc_assault_b"); + codescripts\character::attachHead( "alias_opforce_merc_heads", xmodelalias\alias_opforce_merc_heads::main() ); + if ( issubstr( self.headModel, "hat" ) ) + { + self.hatModel = "hat_opforce_merc_b"; + self attach(self.hatModel); + } + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_opforce_merc_assault_b"); + codescripts\character::precacheModelArray(xmodelalias\alias_opforce_merc_heads::main()); + precacheModel("hat_opforce_merc_b"); +} diff --git a/character/character_opforce_merc_assault_c.gsc b/character/character_opforce_merc_assault_c.gsc new file mode 100644 index 0000000..d7138fc --- /dev/null +++ b/character/character_opforce_merc_assault_c.gsc @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_opforce_merc_assault_c"); + codescripts\character::attachHead( "alias_opforce_merc_heads", xmodelalias\alias_opforce_merc_heads::main() ); + if ( issubstr( self.headModel, "hat" ) ) + { + self.hatModel = "hat_opforce_merc_b"; + self attach(self.hatModel); + } + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_opforce_merc_assault_c"); + codescripts\character::precacheModelArray(xmodelalias\alias_opforce_merc_heads::main()); + precacheModel("hat_opforce_merc_b"); +} diff --git a/character/character_sp_opforce_a.gsc b/character/character_sp_opforce_a.gsc new file mode 100644 index 0000000..fec0bac --- /dev/null +++ b/character/character_sp_opforce_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_a"); + self attach("head_sp_opforce_ski_mask_body_a", "", true); + self.headModel = "head_sp_opforce_ski_mask_body_a"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_a"); + precacheModel("head_sp_opforce_ski_mask_body_a"); +} diff --git a/character/character_sp_opforce_b.gsc b/character/character_sp_opforce_b.gsc new file mode 100644 index 0000000..31bcafe --- /dev/null +++ b/character/character_sp_opforce_b.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_b"); + self attach("head_sp_opforce_3hole_ski_mask_body_b", "", true); + self.headModel = "head_sp_opforce_3hole_ski_mask_body_b"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_b"); + precacheModel("head_sp_opforce_3hole_ski_mask_body_b"); +} diff --git a/character/character_sp_opforce_c.gsc b/character/character_sp_opforce_c.gsc new file mode 100644 index 0000000..8c72d03 --- /dev/null +++ b/character/character_sp_opforce_c.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_c"); + self attach("head_sp_opforce_david_beanie_body_c", "", true); + self.headModel = "head_sp_opforce_david_beanie_body_c"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_c"); + precacheModel("head_sp_opforce_david_beanie_body_c"); +} diff --git a/character/character_sp_opforce_collins.gsc b/character/character_sp_opforce_collins.gsc new file mode 100644 index 0000000..7b14b10 --- /dev/null +++ b/character/character_sp_opforce_collins.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_d"); + self attach("head_sp_opforce_collins_headset_body_d", "", true); + self.headModel = "head_sp_opforce_collins_headset_body_d"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_d"); + precacheModel("head_sp_opforce_collins_headset_body_d"); +} diff --git a/character/character_sp_opforce_d.gsc b/character/character_sp_opforce_d.gsc new file mode 100644 index 0000000..d494f59 --- /dev/null +++ b/character/character_sp_opforce_d.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_d"); + self attach("head_sp_opforce_fullwrap_body_d", "", true); + self.headModel = "head_sp_opforce_fullwrap_body_d"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_d"); + precacheModel("head_sp_opforce_fullwrap_body_d"); +} diff --git a/character/character_sp_opforce_derik.gsc b/character/character_sp_opforce_derik.gsc new file mode 100644 index 0000000..a4d595c --- /dev/null +++ b/character/character_sp_opforce_derik.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_f"); + self attach("head_sp_opforce_derik_body_f", "", true); + self.headModel = "head_sp_opforce_derik_body_f"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_f"); + precacheModel("head_sp_opforce_derik_body_f"); +} diff --git a/character/character_sp_opforce_e.gsc b/character/character_sp_opforce_e.gsc new file mode 100644 index 0000000..1edcc82 --- /dev/null +++ b/character/character_sp_opforce_e.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_e"); + self attach("head_sp_opforce_justin_beanie_body_e", "", true); + self.headModel = "head_sp_opforce_justin_beanie_body_e"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_e"); + precacheModel("head_sp_opforce_justin_beanie_body_e"); +} diff --git a/character/character_sp_opforce_f.gsc b/character/character_sp_opforce_f.gsc new file mode 100644 index 0000000..d91a638 --- /dev/null +++ b/character/character_sp_opforce_f.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_f"); + self attach("head_sp_opforce_gas_mask_body_f", "", true); + self.headModel = "head_sp_opforce_gas_mask_body_f"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_f"); + precacheModel("head_sp_opforce_gas_mask_body_f"); +} diff --git a/character/character_sp_opforce_geoff.gsc b/character/character_sp_opforce_geoff.gsc new file mode 100644 index 0000000..f064e72 --- /dev/null +++ b/character/character_sp_opforce_geoff.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_opforce_c"); + self attach("head_sp_opforce_geoff_headset_body_c", "", true); + self.headModel = "head_sp_opforce_geoff_headset_body_c"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_opforce_c"); + precacheModel("head_sp_opforce_geoff_headset_body_c"); +} diff --git a/character/character_sp_pilot_zack_desert.gsc b/character/character_sp_pilot_zack_desert.gsc new file mode 100644 index 0000000..ea9eb51 --- /dev/null +++ b/character/character_sp_pilot_zack_desert.gsc @@ -0,0 +1,11 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_complete_sp_cobra_pilot_desert_zack"); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_complete_sp_cobra_pilot_desert_zack"); +} diff --git a/character/character_sp_spetsnaz_boris.gsc b/character/character_sp_spetsnaz_boris.gsc new file mode 100644 index 0000000..2db9347 --- /dev/null +++ b/character/character_sp_spetsnaz_boris.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_boris"); + self attach("head_sp_spetsnaz_boris_borisbody", "", true); + self.headModel = "head_sp_spetsnaz_boris_borisbody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_boris"); + precacheModel("head_sp_spetsnaz_boris_borisbody"); +} diff --git a/character/character_sp_spetsnaz_collins.gsc b/character/character_sp_spetsnaz_collins.gsc new file mode 100644 index 0000000..327ad79 --- /dev/null +++ b/character/character_sp_spetsnaz_collins.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_vlad"); + self attach("head_sp_spetsnaz_collins_vladbody", "", true); + self.headModel = "head_sp_spetsnaz_collins_vladbody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_vlad"); + precacheModel("head_sp_spetsnaz_collins_vladbody"); +} diff --git a/character/character_sp_spetsnaz_demetry.gsc b/character/character_sp_spetsnaz_demetry.gsc new file mode 100644 index 0000000..ff20f8f --- /dev/null +++ b/character/character_sp_spetsnaz_demetry.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_demetry"); + self attach("head_sp_spetsnaz_demetry_demetrybody", "", true); + self.headModel = "head_sp_spetsnaz_demetry_demetrybody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_demetry"); + precacheModel("head_sp_spetsnaz_demetry_demetrybody"); +} diff --git a/character/character_sp_spetsnaz_derik.gsc b/character/character_sp_spetsnaz_derik.gsc new file mode 100644 index 0000000..17c27d9 --- /dev/null +++ b/character/character_sp_spetsnaz_derik.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_boris"); + self attach("head_sp_spetsnaz_derik_borisbody", "", true); + self.headModel = "head_sp_spetsnaz_derik_borisbody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_boris"); + precacheModel("head_sp_spetsnaz_derik_borisbody"); +} diff --git a/character/character_sp_spetsnaz_geoff.gsc b/character/character_sp_spetsnaz_geoff.gsc new file mode 100644 index 0000000..b942d5c --- /dev/null +++ b/character/character_sp_spetsnaz_geoff.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_yuri"); + self attach("head_sp_spetsnaz_geoff_yuribody", "", true); + self.headModel = "head_sp_spetsnaz_geoff_yuribody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_yuri"); + precacheModel("head_sp_spetsnaz_geoff_yuribody"); +} diff --git a/character/character_sp_spetsnaz_vlad.gsc b/character/character_sp_spetsnaz_vlad.gsc new file mode 100644 index 0000000..dfff7e3 --- /dev/null +++ b/character/character_sp_spetsnaz_vlad.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_vlad"); + self attach("head_sp_spetsnaz_vlad_vladbody", "", true); + self.headModel = "head_sp_spetsnaz_vlad_vladbody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_vlad"); + precacheModel("head_sp_spetsnaz_vlad_vladbody"); +} diff --git a/character/character_sp_spetsnaz_yuri.gsc b/character/character_sp_spetsnaz_yuri.gsc new file mode 100644 index 0000000..ba48685 --- /dev/null +++ b/character/character_sp_spetsnaz_yuri.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_spetsnaz_yuri"); + self attach("head_sp_spetsnaz_yuri_yuribody", "", true); + self.headModel = "head_sp_spetsnaz_yuri_yuribody"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("body_sp_spetsnaz_yuri"); + precacheModel("head_sp_spetsnaz_yuri_yuribody"); +} diff --git a/character/character_sp_usmc_at4.gsc b/character/character_sp_usmc_at4.gsc new file mode 100644 index 0000000..f3d631f --- /dev/null +++ b/character/character_sp_usmc_at4.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_at4"); + self attach("head_sp_usmc_james_james_body", "", true); + self.headModel = "head_sp_usmc_james_james_body"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_at4"); + precacheModel("head_sp_usmc_james_james_body"); +} diff --git a/character/character_sp_usmc_james.gsc b/character/character_sp_usmc_james.gsc new file mode 100644 index 0000000..75bad82 --- /dev/null +++ b/character/character_sp_usmc_james.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_james"); + self attach("head_sp_usmc_james_james_body", "", true); + self.headModel = "head_sp_usmc_james_james_body"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_james"); + precacheModel("head_sp_usmc_james_james_body"); +} diff --git a/character/character_sp_usmc_ryan.gsc b/character/character_sp_usmc_ryan.gsc new file mode 100644 index 0000000..2e4a2f1 --- /dev/null +++ b/character/character_sp_usmc_ryan.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_ryan"); + self attach("head_sp_usmc_ryan_ryan_body", "", true); + self.headModel = "head_sp_usmc_ryan_ryan_body"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_ryan"); + precacheModel("head_sp_usmc_ryan_ryan_body"); +} diff --git a/character/character_sp_usmc_sami.gsc b/character/character_sp_usmc_sami.gsc new file mode 100644 index 0000000..11d4e0b --- /dev/null +++ b/character/character_sp_usmc_sami.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_james"); + self attach("head_sp_usmc_sami_zach_body", "", true); + self.headModel = "head_sp_usmc_sami_zach_body"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_james"); + precacheModel("head_sp_usmc_sami_zach_body"); +} diff --git a/character/character_sp_usmc_sami_goggles.gsc b/character/character_sp_usmc_sami_goggles.gsc new file mode 100644 index 0000000..bbb6cbf --- /dev/null +++ b/character/character_sp_usmc_sami_goggles.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_james"); + self attach("head_sp_usmc_sami_goggles_zach_body", "", true); + self.headModel = "head_sp_usmc_sami_goggles_zach_body"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_james"); + precacheModel("head_sp_usmc_sami_goggles_zach_body"); +} diff --git a/character/character_sp_usmc_zach.gsc b/character/character_sp_usmc_zach.gsc new file mode 100644 index 0000000..a9b6403 --- /dev/null +++ b/character/character_sp_usmc_zach.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_zach"); + self attach("head_sp_usmc_zach_zach_body", "", true); + self.headModel = "head_sp_usmc_zach_zach_body"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_zach"); + precacheModel("head_sp_usmc_zach_zach_body"); +} diff --git a/character/character_sp_usmc_zach_goggles.gsc b/character/character_sp_usmc_zach_goggles.gsc new file mode 100644 index 0000000..994ef56 --- /dev/null +++ b/character/character_sp_usmc_zach_goggles.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_sp_usmc_zach"); + self attach("head_sp_usmc_zach_zach_body_goggles", "", true); + self.headModel = "head_sp_usmc_zach_zach_body_goggles"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_sp_usmc_zach"); + precacheModel("head_sp_usmc_zach_zach_body_goggles"); +} diff --git a/character/character_us_army_assault_a.gsc b/character/character_us_army_assault_a.gsc new file mode 100644 index 0000000..700bd83 --- /dev/null +++ b/character/character_us_army_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_us_army_assault_a"); + codescripts\character::attachHead( "alias_us_army_heads", xmodelalias\alias_us_army_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_us_army_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_us_army_heads::main()); +} diff --git a/character/character_us_army_assault_b.gsc b/character/character_us_army_assault_b.gsc new file mode 100644 index 0000000..368f62a --- /dev/null +++ b/character/character_us_army_assault_b.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_us_army_assault_b"); + codescripts\character::attachHead( "alias_us_army_heads", xmodelalias\alias_us_army_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_us_army_assault_b"); + codescripts\character::precacheModelArray(xmodelalias\alias_us_army_heads::main()); +} diff --git a/character/character_us_army_assault_c.gsc b/character/character_us_army_assault_c.gsc new file mode 100644 index 0000000..101ac66 --- /dev/null +++ b/character/character_us_army_assault_c.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_us_army_assault_c"); + codescripts\character::attachHead( "alias_us_army_heads", xmodelalias\alias_us_army_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_us_army_assault_c"); + codescripts\character::precacheModelArray(xmodelalias\alias_us_army_heads::main()); +} diff --git a/character/character_us_army_assault_paul.gsc b/character/character_us_army_assault_paul.gsc new file mode 100644 index 0000000..7d415e2 --- /dev/null +++ b/character/character_us_army_assault_paul.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_us_army_assault_paul"); + self attach("nx_head_us_army_e", "", true); + self.headModel = "nx_head_us_army_e"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_us_army_assault_paul"); + precacheModel("nx_head_us_army_e"); +} diff --git a/character/nx_character_china_assault_a.gsc b/character/nx_character_china_assault_a.gsc new file mode 100644 index 0000000..cc3fa61 --- /dev/null +++ b/character/nx_character_china_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("china_army_body_assault_a"); + codescripts\character::attachHead( "alias_nx_china_army_heads", xmodelalias\alias_nx_china_army_heads::main() ); + self.voice = "arab"; +} + +precache() +{ + precacheModel("china_army_body_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_china_army_heads::main()); +} diff --git a/character/nx_character_china_assault_b.gsc b/character/nx_character_china_assault_b.gsc new file mode 100644 index 0000000..cc3fa61 --- /dev/null +++ b/character/nx_character_china_assault_b.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("china_army_body_assault_a"); + codescripts\character::attachHead( "alias_nx_china_army_heads", xmodelalias\alias_nx_china_army_heads::main() ); + self.voice = "arab"; +} + +precache() +{ + precacheModel("china_army_body_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_china_army_heads::main()); +} diff --git a/character/nx_character_china_assault_c.gsc b/character/nx_character_china_assault_c.gsc new file mode 100644 index 0000000..cc3fa61 --- /dev/null +++ b/character/nx_character_china_assault_c.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("china_army_body_assault_a"); + codescripts\character::attachHead( "alias_nx_china_army_heads", xmodelalias\alias_nx_china_army_heads::main() ); + self.voice = "arab"; +} + +precache() +{ + precacheModel("china_army_body_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_china_army_heads::main()); +} diff --git a/character/nx_character_china_space_assault_a.gsc b/character/nx_character_china_space_assault_a.gsc new file mode 100644 index 0000000..9133fcc --- /dev/null +++ b/character/nx_character_china_space_assault_a.gsc @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_china_space_assault_body"); + codescripts\character::attachHead( "alias_china_space_heads", xmodelalias\alias_china_space_heads::main() ); + codescripts\character::determineHeadshotModel( "alias_china_space_heads", xmodelalias\alias_china_space_heads_crack::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_china_space_assault_body"); + codescripts\character::precacheModelArray(xmodelalias\alias_china_space_heads::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_china_space_heads_crack::main()); +} diff --git a/character/nx_character_china_space_nohelm.gsc b/character/nx_character_china_space_nohelm.gsc new file mode 100644 index 0000000..a784951 --- /dev/null +++ b/character/nx_character_china_space_nohelm.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_china_space_assault_body"); + codescripts\character::attachHead( "alias_china_space_heads_nohelm", xmodelalias\alias_china_space_heads_nohelm::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_china_space_assault_body"); + codescripts\character::precacheModelArray(xmodelalias\alias_china_space_heads_nohelm::main()); +} diff --git a/character/nx_character_china_specops_assault_a.gsc b/character/nx_character_china_specops_assault_a.gsc new file mode 100644 index 0000000..8697838 --- /dev/null +++ b/character/nx_character_china_specops_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_china_specops_body_assault_a"); + codescripts\character::attachHead( "alias_nx_china_specops_heads", xmodelalias\alias_nx_china_specops_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_china_specops_body_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_china_specops_heads::main()); +} diff --git a/character/nx_character_ec_lab.gsc b/character/nx_character_ec_lab.gsc new file mode 100644 index 0000000..fba8d33 --- /dev/null +++ b/character/nx_character_ec_lab.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_ec_lab_body_a"); + self attach("nx_ec_lab_head_a", "", true); + self.headModel = "nx_ec_lab_head_a"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_ec_lab_body_a"); + precacheModel("nx_ec_lab_head_a"); +} diff --git a/character/nx_character_ec_lab_hazmat.gsc b/character/nx_character_ec_lab_hazmat.gsc new file mode 100644 index 0000000..320e760 --- /dev/null +++ b/character/nx_character_ec_lab_hazmat.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_ec_lab_body_hazmat_a"); + self attach("nx_ec_lab_head_mask_a", "", true); + self.headModel = "nx_ec_lab_head_mask_a"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_ec_lab_body_hazmat_a"); + precacheModel("nx_ec_lab_head_mask_a"); +} diff --git a/character/nx_character_ec_lab_mask.gsc b/character/nx_character_ec_lab_mask.gsc new file mode 100644 index 0000000..35ea362 --- /dev/null +++ b/character/nx_character_ec_lab_mask.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_ec_lab_body_a"); + self attach("nx_ec_lab_head_mask_a", "", true); + self.headModel = "nx_ec_lab_head_mask_a"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_ec_lab_body_a"); + precacheModel("nx_ec_lab_head_mask_a"); +} diff --git a/character/nx_character_ec_security_guard.gsc b/character/nx_character_ec_security_guard.gsc new file mode 100644 index 0000000..9e1d378 --- /dev/null +++ b/character/nx_character_ec_security_guard.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_secret_service_assault_a"); + codescripts\character::attachHead( "alias_nx_ec_security_guard_heads", xmodelalias\alias_nx_ec_security_guard_heads::main() ); + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("body_secret_service_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_ec_security_guard_heads::main()); +} diff --git a/character/nx_character_ec_security_swat.gsc b/character/nx_character_ec_security_swat.gsc new file mode 100644 index 0000000..12b38be --- /dev/null +++ b/character/nx_character_ec_security_swat.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_jk_swat_body_a"); + self attach("head_opforce_fsb_a", "", true); + self.headModel = "head_opforce_fsb_a"; + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("nx_jk_swat_body_a"); + precacheModel("head_opforce_fsb_a"); +} diff --git a/character/nx_character_ec_security_swat_bloody.gsc b/character/nx_character_ec_security_swat_bloody.gsc new file mode 100644 index 0000000..fc0e275 --- /dev/null +++ b/character/nx_character_ec_security_swat_bloody.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_jk_swat_body_a_bloody"); + self attach("head_opforce_fsb_a", "", true); + self.headModel = "head_opforce_fsb_a"; + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("nx_jk_swat_body_a_bloody"); + precacheModel("head_opforce_fsb_a"); +} diff --git a/character/nx_character_ec_space_assault_b.gsc b/character/nx_character_ec_space_assault_b.gsc new file mode 100644 index 0000000..fdcce7c --- /dev/null +++ b/character/nx_character_ec_space_assault_b.gsc @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_ec_space_assault_body"); + codescripts\character::attachHead( "alias_nx_ec_space_heads", xmodelalias\alias_nx_ec_space_heads::main() ); + codescripts\character::determineHeadshotModel( "alias_nx_ec_space_heads", xmodelalias\alias_nx_ec_space_heads_crack::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_ec_space_assault_body"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_ec_space_heads::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_ec_space_heads_crack::main()); +} diff --git a/character/nx_character_exfil_swat_a.gsc b/character/nx_character_exfil_swat_a.gsc new file mode 100644 index 0000000..26947d3 --- /dev/null +++ b/character/nx_character_exfil_swat_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_jk_swat_body_a"); + self attach("exfil_swat_head_a", "", true); + self.headModel = "exfil_swat_head_a"; + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("nx_jk_swat_body_a"); + precacheModel("exfil_swat_head_a"); +} diff --git a/character/nx_character_exfil_swat_b.gsc b/character/nx_character_exfil_swat_b.gsc new file mode 100644 index 0000000..eb3a93c --- /dev/null +++ b/character/nx_character_exfil_swat_b.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_jk_swat_body_a"); + self attach("exfil_swat_head_b", "", true); + self.headModel = "exfil_swat_head_b"; + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("nx_jk_swat_body_a"); + precacheModel("exfil_swat_head_b"); +} diff --git a/character/nx_character_exfil_swat_shot_a.gsc b/character/nx_character_exfil_swat_shot_a.gsc new file mode 100644 index 0000000..74016ea --- /dev/null +++ b/character/nx_character_exfil_swat_shot_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_jk_swat_body_a"); + self attach("exfil_swat_head_a_shot", "", true); + self.headModel = "exfil_swat_head_a_shot"; + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("nx_jk_swat_body_a"); + precacheModel("exfil_swat_head_a_shot"); +} diff --git a/character/nx_character_exfil_swat_shot_b.gsc b/character/nx_character_exfil_swat_shot_b.gsc new file mode 100644 index 0000000..69b3830 --- /dev/null +++ b/character/nx_character_exfil_swat_shot_b.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_jk_swat_body_a"); + self attach("exfil_swat_head_b_shot", "", true); + self.headModel = "exfil_swat_head_b_shot"; + self.voice = "indonesian"; +} + +precache() +{ + precacheModel("nx_jk_swat_body_a"); + precacheModel("exfil_swat_head_b_shot"); +} diff --git a/character/nx_character_hero_baker.gsc b/character/nx_character_hero_baker.gsc new file mode 100644 index 0000000..51d528a --- /dev/null +++ b/character/nx_character_hero_baker.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_baker_body_spec"); + self attach("nx_hero_baker_head", "", true); + self.headModel = "nx_hero_baker_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_baker_body_spec"); + precacheModel("nx_hero_baker_head"); +} diff --git a/character/nx_character_hero_baker_delta.gsc b/character/nx_character_hero_baker_delta.gsc new file mode 100644 index 0000000..e692aa2 --- /dev/null +++ b/character/nx_character_hero_baker_delta.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_baker_body_delta"); + self attach("nx_hero_baker_head_delta", "", true); + self.headModel = "nx_hero_baker_head_delta"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_baker_body_delta"); + precacheModel("nx_hero_baker_head_delta"); +} diff --git a/character/nx_character_hero_baker_exfil.gsc b/character/nx_character_hero_baker_exfil.gsc new file mode 100644 index 0000000..d80b3d4 --- /dev/null +++ b/character/nx_character_hero_baker_exfil.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_hero_soap_socom"); + self attach("nx_hero_baker_head_delta", "", true); + self.headModel = "nx_hero_baker_head_delta"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_hero_soap_socom"); + precacheModel("nx_hero_baker_head_delta"); +} diff --git a/character/nx_character_hero_baker_hdphone.gsc b/character/nx_character_hero_baker_hdphone.gsc new file mode 100644 index 0000000..5f608b7 --- /dev/null +++ b/character/nx_character_hero_baker_hdphone.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_baker_body_spec"); + self attach("nx_hero_baker_head_phone", "", true); + self.headModel = "nx_hero_baker_head_phone"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_baker_body_spec"); + precacheModel("nx_hero_baker_head_phone"); +} diff --git a/character/nx_character_hero_baker_pchute.gsc b/character/nx_character_hero_baker_pchute.gsc new file mode 100644 index 0000000..f7973d9 --- /dev/null +++ b/character/nx_character_hero_baker_pchute.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_baker_body_spec_pchute"); + self attach("nx_hero_baker_head", "", true); + self.headModel = "nx_hero_baker_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_baker_body_spec_pchute"); + precacheModel("nx_hero_baker_head"); +} diff --git a/character/nx_character_hero_baker_suit.gsc b/character/nx_character_hero_baker_suit.gsc new file mode 100644 index 0000000..e403017 --- /dev/null +++ b/character/nx_character_hero_baker_suit.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_secretservice_body_a"); + self attach("nx_hero_baker_head", "", true); + self.headModel = "nx_hero_baker_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_secretservice_body_a"); + precacheModel("nx_hero_baker_head"); +} diff --git a/character/nx_character_hero_gypsy.gsc b/character/nx_character_hero_gypsy.gsc new file mode 100644 index 0000000..8ae30cd --- /dev/null +++ b/character/nx_character_hero_gypsy.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_gypsy_body_spec"); + self attach("nx_hero_gypsy_head", "", true); + self.headModel = "nx_hero_gypsy_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_gypsy_body_spec"); + precacheModel("nx_hero_gypsy_head"); +} diff --git a/character/nx_character_hero_gypsy_pchute.gsc b/character/nx_character_hero_gypsy_pchute.gsc new file mode 100644 index 0000000..5515162 --- /dev/null +++ b/character/nx_character_hero_gypsy_pchute.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_gypsy_body_spec_pchute"); + self attach("nx_hero_gypsy_head", "", true); + self.headModel = "nx_hero_gypsy_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_gypsy_body_spec_pchute"); + precacheModel("nx_hero_gypsy_head"); +} diff --git a/character/nx_character_hero_high_baker.gsc b/character/nx_character_hero_high_baker.gsc new file mode 100644 index 0000000..26a3544 --- /dev/null +++ b/character/nx_character_hero_high_baker.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_baker_body_delta"); + self attach("nx_hero_baker_high_head", "", true); + self.headModel = "nx_hero_baker_high_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_baker_body_delta"); + precacheModel("nx_hero_baker_high_head"); +} diff --git a/character/nx_character_hero_hospital_doctor.gsc b/character/nx_character_hero_hospital_doctor.gsc new file mode 100644 index 0000000..3fbbe04 --- /dev/null +++ b/character/nx_character_hero_hospital_doctor.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hospital_doctor_body"); + self attach("nx_hospital_doctor_head", "", true); + self.headModel = "nx_hospital_doctor_head"; + self.voice = "russian"; +} + +precache() +{ + precacheModel("nx_hospital_doctor_body"); + precacheModel("nx_hospital_doctor_head"); +} diff --git a/character/nx_character_hero_jenkins.gsc b/character/nx_character_hero_jenkins.gsc new file mode 100644 index 0000000..4faec0b --- /dev/null +++ b/character/nx_character_hero_jenkins.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_specops_body_assault_a"); + self attach("nx_hero_jenkins_head", "", true); + self.headModel = "nx_hero_jenkins_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_specops_body_assault_a"); + precacheModel("nx_hero_jenkins_head"); +} diff --git a/character/nx_character_hero_jenkins_delta.gsc b/character/nx_character_hero_jenkins_delta.gsc new file mode 100644 index 0000000..c6a2142 --- /dev/null +++ b/character/nx_character_hero_jenkins_delta.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_baker_body_delta"); + self attach("nx_hero_jenkins_head", "", true); + self.headModel = "nx_hero_jenkins_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_baker_body_delta"); + precacheModel("nx_hero_jenkins_head"); +} diff --git a/character/nx_character_hero_squad1_jumpsuit.gsc b/character/nx_character_hero_squad1_jumpsuit.gsc new file mode 100644 index 0000000..40c6185 --- /dev/null +++ b/character/nx_character_hero_squad1_jumpsuit.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_work_civ_male_d"); + self attach("hero_baseman_head", "", true); + self.headModel = "hero_baseman_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_work_civ_male_d"); + precacheModel("hero_baseman_head"); +} diff --git a/character/nx_character_hero_squad2_jumpsuit.gsc b/character/nx_character_hero_squad2_jumpsuit.gsc new file mode 100644 index 0000000..87d5458 --- /dev/null +++ b/character/nx_character_hero_squad2_jumpsuit.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_work_civ_male_d"); + self attach("nx_hero_charlie_head", "", true); + self.headModel = "nx_hero_charlie_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_work_civ_male_d"); + precacheModel("nx_hero_charlie_head"); +} diff --git a/character/nx_character_hero_vp.gsc b/character/nx_character_hero_vp.gsc new file mode 100644 index 0000000..0b22bf1 --- /dev/null +++ b/character/nx_character_hero_vp.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_secretservice_body_a"); + self attach("nx_hero_vp_head", "", true); + self.headModel = "nx_hero_vp_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_secretservice_body_a"); + precacheModel("nx_hero_vp_head"); +} diff --git a/character/nx_character_hero_vp_inj.gsc b/character/nx_character_hero_vp_inj.gsc new file mode 100644 index 0000000..520894b --- /dev/null +++ b/character/nx_character_hero_vp_inj.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_hero_vp_body_inj"); + self attach("nx_hero_vp_head_inj", "", true); + self.headModel = "nx_hero_vp_head_inj"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_hero_vp_body_inj"); + precacheModel("nx_hero_vp_head_inj"); +} diff --git a/character/nx_character_mexican_army_assault_a.gsc b/character/nx_character_mexican_army_assault_a.gsc new file mode 100644 index 0000000..253c87d --- /dev/null +++ b/character/nx_character_mexican_army_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + codescripts\character::setModelFromArray(xmodelalias\alias_nx_mexican_army_body_ar::main()); + codescripts\character::attachHead( "alias_nx_mexican_army_heads", xmodelalias\alias_nx_mexican_army_heads::main() ); + self.voice = "mexican"; +} + +precache() +{ + codescripts\character::precacheModelArray(xmodelalias\alias_nx_mexican_army_body_ar::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_mexican_army_heads::main()); +} diff --git a/character/nx_character_mexican_army_lmg_a.gsc b/character/nx_character_mexican_army_lmg_a.gsc new file mode 100644 index 0000000..0a77503 --- /dev/null +++ b/character/nx_character_mexican_army_lmg_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_mexican_army_digital_body_lmg_a"); + codescripts\character::attachHead( "alias_nx_mexican_army_heads", xmodelalias\alias_nx_mexican_army_heads::main() ); + self.voice = "mexican"; +} + +precache() +{ + precacheModel("nx_mexican_army_digital_body_lmg_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_mexican_army_heads::main()); +} diff --git a/character/nx_character_mexican_army_shotgun_a.gsc b/character/nx_character_mexican_army_shotgun_a.gsc new file mode 100644 index 0000000..63f0b06 --- /dev/null +++ b/character/nx_character_mexican_army_shotgun_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_mexican_army_digital_body_shotgun_a"); + codescripts\character::attachHead( "alias_nx_mexican_army_heads", xmodelalias\alias_nx_mexican_army_heads::main() ); + self.voice = "mexican"; +} + +precache() +{ + precacheModel("nx_mexican_army_digital_body_shotgun_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_mexican_army_heads::main()); +} diff --git a/character/nx_character_mexican_army_smg_a.gsc b/character/nx_character_mexican_army_smg_a.gsc new file mode 100644 index 0000000..622f68c --- /dev/null +++ b/character/nx_character_mexican_army_smg_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_mexican_army_digital_body_smg_a"); + codescripts\character::attachHead( "alias_nx_mexican_army_heads", xmodelalias\alias_nx_mexican_army_heads::main() ); + self.voice = "mexican"; +} + +precache() +{ + precacheModel("nx_mexican_army_digital_body_smg_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_mexican_army_heads::main()); +} diff --git a/character/nx_character_us_army_assault_a.gsc b/character/nx_character_us_army_assault_a.gsc new file mode 100644 index 0000000..d749a1a --- /dev/null +++ b/character/nx_character_us_army_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + codescripts\character::setModelFromArray(xmodelalias\alias_nx_us_army_bodies::main()); + codescripts\character::attachHead( "alias_nx_us_army_heads", xmodelalias\alias_nx_us_army_heads::main() ); + self.voice = "american"; +} + +precache() +{ + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_army_bodies::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_army_heads::main()); +} diff --git a/character/nx_character_us_lunar_crew.gsc b/character/nx_character_us_lunar_crew.gsc new file mode 100644 index 0000000..0bb9df0 --- /dev/null +++ b/character/nx_character_us_lunar_crew.gsc @@ -0,0 +1,11 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_civ_astronaut_body_complete_a"); + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_civ_astronaut_body_complete_a"); +} diff --git a/character/nx_character_us_secretservice.gsc b/character/nx_character_us_secretservice.gsc new file mode 100644 index 0000000..bd53a68 --- /dev/null +++ b/character/nx_character_us_secretservice.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + codescripts\character::setModelFromArray(xmodelalias\alias_nx_us_secretservice_bodies::main()); + codescripts\character::attachHead( "alias_nx_us_secretservice_heads", xmodelalias\alias_nx_us_secretservice_heads::main() ); + self.voice = "american"; +} + +precache() +{ + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_secretservice_bodies::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_secretservice_heads::main()); +} diff --git a/character/nx_character_us_secretservice_a.gsc b/character/nx_character_us_secretservice_a.gsc new file mode 100644 index 0000000..26399d0 --- /dev/null +++ b/character/nx_character_us_secretservice_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_secretservice_body_a"); + self attach("nx_us_secretservice_head_a", "", true); + self.headModel = "nx_us_secretservice_head_a"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_secretservice_body_a"); + precacheModel("nx_us_secretservice_head_a"); +} diff --git a/character/nx_character_us_secretservice_b.gsc b/character/nx_character_us_secretservice_b.gsc new file mode 100644 index 0000000..a52f006 --- /dev/null +++ b/character/nx_character_us_secretservice_b.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_secretservice_body_b"); + self attach("nx_us_secretservice_head_b", "", true); + self.headModel = "nx_us_secretservice_head_b"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_secretservice_body_b"); + precacheModel("nx_us_secretservice_head_b"); +} diff --git a/character/nx_character_us_secretservice_c.gsc b/character/nx_character_us_secretservice_c.gsc new file mode 100644 index 0000000..777f6e1 --- /dev/null +++ b/character/nx_character_us_secretservice_c.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_secretservice_body_c"); + self attach("nx_us_secretservice_head_c", "", true); + self.headModel = "nx_us_secretservice_head_c"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_secretservice_body_c"); + precacheModel("nx_us_secretservice_head_c"); +} diff --git a/character/nx_character_us_space_assault_a.gsc b/character/nx_character_us_space_assault_a.gsc new file mode 100644 index 0000000..7a5b8ed --- /dev/null +++ b/character/nx_character_us_space_assault_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_space_assault_body"); + self attach("nx_us_space_assault_head", "", true); + self.headModel = "nx_us_space_assault_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_space_assault_body"); + precacheModel("nx_us_space_assault_head"); +} diff --git a/character/nx_character_us_space_assault_crack_a.gsc b/character/nx_character_us_space_assault_crack_a.gsc new file mode 100644 index 0000000..2881fd1 --- /dev/null +++ b/character/nx_character_us_space_assault_crack_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_space_assault_body_bloody"); + self attach("nx_us_space_assault_head_crack", "", true); + self.headModel = "nx_us_space_assault_head_crack"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_space_assault_body_bloody"); + precacheModel("nx_us_space_assault_head_crack"); +} diff --git a/character/nx_character_us_space_assault_nohelm_a.gsc b/character/nx_character_us_space_assault_nohelm_a.gsc new file mode 100644 index 0000000..a0977e5 --- /dev/null +++ b/character/nx_character_us_space_assault_nohelm_a.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_space_assault_body"); + self attach("nx_us_space_assault_head_a", "", true); + self.headModel = "nx_us_space_assault_head_a"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_space_assault_body"); + precacheModel("nx_us_space_assault_head_a"); +} diff --git a/character/nx_character_us_specops_assault_a.gsc b/character/nx_character_us_specops_assault_a.gsc new file mode 100644 index 0000000..f69c229 --- /dev/null +++ b/character/nx_character_us_specops_assault_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_us_specops_body_assault_a"); + codescripts\character::attachHead( "alias_nx_us_specops_heads", xmodelalias\alias_nx_us_specops_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_us_specops_body_assault_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_specops_heads::main()); +} diff --git a/character/nx_character_us_swat_a.gsc b/character/nx_character_us_swat_a.gsc new file mode 100644 index 0000000..52e28db --- /dev/null +++ b/character/nx_character_us_swat_a.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + codescripts\character::setModelFromArray(xmodelalias\alias_nx_us_swat_bodies::main()); + codescripts\character::attachHead( "alias_nx_us_swat_heads", xmodelalias\alias_nx_us_swat_heads::main() ); + self.voice = "american"; +} + +precache() +{ + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_swat_bodies::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_us_swat_heads::main()); +} diff --git a/character/nx_civ_china_cabdriver.gsc b/character/nx_civ_china_cabdriver.gsc new file mode 100644 index 0000000..5de7145 --- /dev/null +++ b/character/nx_civ_china_cabdriver.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_urban_civ_male_bb"); + self attach("nx_china_space_head_b_nohelm", "", true); + self.headModel = "nx_china_space_head_b_nohelm"; + self.voice = "american"; +} + +precache() +{ + precacheModel("body_urban_civ_male_bb"); + precacheModel("nx_china_space_head_b_nohelm"); +} diff --git a/character/nx_civ_china_suit.gsc b/character/nx_civ_china_suit.gsc new file mode 100644 index 0000000..1d13c08 --- /dev/null +++ b/character/nx_civ_china_suit.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("body_city_civ_male_a"); + codescripts\character::attachHead( "alias_nx_ec_security_guard_heads", xmodelalias\alias_nx_ec_security_guard_heads::main() ); + self.voice = "american"; +} + +precache() +{ + precacheModel("body_city_civ_male_a"); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_ec_security_guard_heads::main()); +} diff --git a/character/nx_civ_china_urban_male.gsc b/character/nx_civ_china_urban_male.gsc new file mode 100644 index 0000000..78ffe1d --- /dev/null +++ b/character/nx_civ_china_urban_male.gsc @@ -0,0 +1,13 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + codescripts\character::setModelFromArray(xmodelalias\alias_nx_civ_china_urban_male_body::main()); + codescripts\character::attachHead( "alias_nx_ec_security_guard_heads", xmodelalias\alias_nx_ec_security_guard_heads::main() ); + self.voice = "american"; +} + +precache() +{ + codescripts\character::precacheModelArray(xmodelalias\alias_nx_civ_china_urban_male_body::main()); + codescripts\character::precacheModelArray(xmodelalias\alias_nx_ec_security_guard_heads::main()); +} diff --git a/character/nx_russian_leader.gsc b/character/nx_russian_leader.gsc new file mode 100644 index 0000000..677025b --- /dev/null +++ b/character/nx_russian_leader.gsc @@ -0,0 +1,14 @@ +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY +main() +{ + self setModel("nx_russian_leader_body"); + self attach("nx_russian_leader_head", "", true); + self.headModel = "nx_russian_leader_head"; + self.voice = "american"; +} + +precache() +{ + precacheModel("nx_russian_leader_body"); + precacheModel("nx_russian_leader_head"); +} diff --git a/common_scripts/_atbr.gsc b/common_scripts/_atbr.gsc new file mode 100644 index 0000000..38a21a9 --- /dev/null +++ b/common_scripts/_atbr.gsc @@ -0,0 +1,311 @@ +#include common_scripts\utility; + +//dierction of atbr missile, just off perefectly straight up. +ATBR_MISSILE_DIRECTION = ( 1.0, 1.0, 20.0 ); + +ATBR_HUD_TIMER_POS_X = 0.0; +ATBR_HUD_TIMER_POS_Y = -85.0; + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_init() +{ + // TagZP Moved atbr weapons into a new array as defined in the comment below + // I did this because now mp and sp do not share the same weapon defs. + //level._atbr_weapons["base"]; + //level._atbr_weapons["detonate"]; + //level._atbr_weapons["missile"]; + //level._atbr_weapons["bullet"]; + + foreach( weapon in level._atbr_weapons ) + { + PreCacheItem( weapon ); + } + + // Misc. + VisionSetMissilecam( "missilecam" ); + PreCacheShader( "remotemissile_infantry_target" ); + PreCacheShader( "hud_fofbox_self_sp" ); +} + + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_give() +{ + + self giveWeapon( level._atbr_weapons["base"] ); + if( isSP( )) + { + self givemaxammo( level._atbr_weapons["base"] ); + } + else + { + //mp kill streak only supports one atbr missile + self givestartammo( level._atbr_weapons["base"] ); + } + + if( !isDefined( self.atbr_planted )) + { + self.atbr_planted = false; + } + + self SetActionSlot( 4, "weapon", level._atbr_weapons["base"] ); + self.atbr_planted = false; + + // Watch the ATBR for this player. + self thread atbr_common_watch_base(); +} + + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_watch_base() +{ + self endon( "death" ); + + for( ;; ) + { + self waittill( "grenade_fire", atbr_base, weapname ); + if ( weapname == level._atbr_weapons["base"] || weapname == "claymore_mp" ) + { + self.atbr_planted = true; + atbr_base.owner = self; + self.attached = false; + atbr_base thread atbr_common_watch_detonation( self ); + + self giveWeapon( level._atbr_weapons["detonate"] ); + self givemaxammo( level._atbr_weapons["detonate"] ); + + self switchtoweapon( level._atbr_weapons["detonate"] ); + + if( !isSP( )) + { + return; + } + } + } +} + + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_watch_detonation( player ) +{ + self endon( "death" ); + + for( ;; ) + { + player waittill( "detonate", weapname ); + if ( weapname == level._atbr_weapons["detonate"] ) + { + player.atbr_planted = false; + + atbr_takeoff_direction = ATBR_MISSILE_DIRECTION; + missile = MagicBullet( level._atbr_weapons["missile"], self.origin, self.origin + atbr_takeoff_direction, player ); + + missile.angles = (-90, self.angles[1], 0); + self hide(); + + atbr_common_attach_to_bullet( player, missile ); + + if( isSP( )) + { + player switchtoweapon( level._atbr_weapons["base"] ); + } + + player TakeWeapon( level._atbr_weapons["detonate"] ); + + // tagTMR Notifying complete switches weapons back to primary, causing incorrect + // killstreak and stat tallies as the player gets kills while following the missle + player notify( "atbr_complete" ); + + self delete(); + } + wait( 0.1 ); + } +} + + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_attach_to_bullet( player, missile ) +{ + wait( 0.25 ); + if( isdefined( missile )) + { + // Do not attach the player to multiple rockets. + if( player.attached == false ) + { + + if( isDefined( level._atbr_ammo_count )) + { + player._atbr_ammo_count = level._atbr_ammo_count; + } + + if( isAlive( player )) + { + player.attached = true; + player link_camera_and_controls( missile ); + player atbr_common_follow_missile( missile ); + missile waittill( "death" ); + player unlink_camera_and_controls(); + } + } + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_follow_missile( missile ) +{ + self endon( "death" ); + missile endon( "death" ); + + // Link player. + self thread atbr_common_fire_shots( missile ); +} + +link_camera_and_controls( missile ) +{ + self CameraLinkTo( missile, "tag_origin", 1); + self ControlsLinkTo( missile ); + + // Targets. + if( isdefined( level._atbr_callback_set_targets )) + { + self [[ level._atbr_callback_set_targets ]]( missile ); + } + + //if ammo count is defined create a hud elem for it. + if( isDefined( self._atbr_ammo_count )) + { + self createHudATBRAmmoCounter( self._atbr_ammo_count ); + } +} + +unlink_camera_and_controls( ) +{ + self CameraUnlink(); + self ControlsUnLink(); + + // Targets. + if( isdefined( level._atbr_callback_remove_targets )) + { + self [[ level._atbr_callback_remove_targets ]](); + } + + if( isDefined( self._atbr_ammo_count )) + { + self destroyHudATBRAmmoCounter(); + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* +atbr_common_fire_shots( missile ) +{ + missile endon( "death" ); + self endon( "death" ); + self NotifyOnPlayerCommand( "fire_shot", "+attack" ); + for( ;; ) + { + self waittill( "fire_shot" ); + + //if a ammo count is defined, update hud, and cut off when all ammo is used. + if( isDefined( self._atbr_ammo_count )) + { + if( self._atbr_ammo_count <= 0 ) + { + //keep the camera attached till the missile has span its entire life + //wait( 0.05 ); + //continue; + + //exit missile cam back to player + wait( 0.2 ); + break; + } + + self._atbr_ammo_count = self._atbr_ammo_count - 1; + self updateHudATBRAmmoCounter( self._atbr_ammo_count ); + } + + Earthquake( 0.2, 1, missile.origin, 5000 ); + start = missile.origin; + forward = AnglesToForward( missile.angles ); + forward = forward * ( 1, -1, -1 ); + end = start + ( forward * 2000.0 ); + MagicBullet( level._atbr_weapons["bullet"], start, end, self ); + wait( 0.1 ); + } +} + + +createHudATBRAmmoCounter( count ) +{ + self.atbrAmmoCounter = newClientHudElem( self ); + self.atbrAmmoCounter.x = ATBR_HUD_TIMER_POS_X; + self.atbrAmmoCounter.y = ATBR_HUD_TIMER_POS_Y; + self.atbrAmmoCounter.alignX = "center"; + self.atbrAmmoCounter.alignY = "bottom"; + self.atbrAmmoCounter.horzAlign = "center_adjustable"; + self.atbrAmmoCounter.vertAlign = "bottom_adjustable"; + self.atbrAmmoCounter.fontScale = 2.5; + self.atbrAmmoCounter setValue( count ); + self.atbrAmmoCounter.alpha = 1.0; +} + +updateHudATBRAmmoCounter( value ) +{ + if( isDefined( self.atbrAmmoCounter )) + { + self.atbrAmmoCounter setValue( value ); + } +} + +destroyHudATBRAmmoCounter() +{ + if( isDefined( self.atbrAmmoCounter )) + { + self.atbrAmmoCounter Destroy(); + } +} + +//returns true if sWeapon is an atbr weapon +is_atbr_weapons( sWeapon ) +{ + if( isDefined( level._atbr_weapons )) + { + foreach( weapon in level._atbr_weapons ) + { + if( weapon == sWeapon ) + { + return true; + } + } + } + return false; +} + + diff --git a/common_scripts/_destructible_types_anim_generator.gsc b/common_scripts/_destructible_types_anim_generator.gsc new file mode 100644 index 0000000..92425d4 --- /dev/null +++ b/common_scripts/_destructible_types_anim_generator.gsc @@ -0,0 +1,9 @@ +#using_animtree( "destructibles" ); +main() +{ + level._destructible_preanims[ "generator_explode" ] = %generator_explode; + level._destructible_preanims[ "generator_explode_02" ] = %generator_explode_02; + level._destructible_preanims[ "generator_explode_03" ] = %generator_explode_03; + level._destructible_preanims[ "generator_vibration" ] = %generator_vibration; + +} \ No newline at end of file diff --git a/common_scripts/_destructible_types_anim_light_fluo_on.gsc b/common_scripts/_destructible_types_anim_light_fluo_on.gsc new file mode 100644 index 0000000..8b80f87 --- /dev/null +++ b/common_scripts/_destructible_types_anim_light_fluo_on.gsc @@ -0,0 +1,9 @@ +#using_animtree( "destructibles" ); +main() +{ + level._destructible_preanims[ "light_fluorescent_swing" ] = %light_fluorescent_swing; + level._destructible_preanims[ "light_fluorescent_null" ] = %light_fluorescent_null; + level._destructible_preanims[ "light_fluorescent_swing_02" ] = %light_fluorescent_swing_02; + level._effect[ "spotlight_fx" ] = loadfx( "misc/fluorescent_spotlight" ); + +} \ No newline at end of file diff --git a/common_scripts/_destructible_types_anim_lockers.gsc b/common_scripts/_destructible_types_anim_lockers.gsc new file mode 100644 index 0000000..5fcc67c --- /dev/null +++ b/common_scripts/_destructible_types_anim_lockers.gsc @@ -0,0 +1,13 @@ +#using_animtree( "destructibles" ); +main() +{ + level._destructible_preanims[ "locker_broken_both_doors_1" ] = %locker_broken_both_doors_1; + level._destructible_preanims[ "locker_broken_both_doors_2" ] = %locker_broken_both_doors_2; + level._destructible_preanims[ "locker_broken_both_doors_3" ] = %locker_broken_both_doors_3; + level._destructible_preanims[ "locker_broken_both_doors_4" ] = %locker_broken_both_doors_4; + level._destructible_preanims[ "locker_broken_door1_slow" ] = %locker_broken_door1_slow; + level._destructible_preanims[ "locker_broken_door1_fast" ] = %locker_broken_door1_fast; + level._destructible_preanims[ "locker_broken_door2_slow" ] = %locker_broken_door2_slow; + level._destructible_preanims[ "locker_broken_door2_fast" ] = %locker_broken_door2_fast; + +} \ No newline at end of file diff --git a/common_scripts/_destructible_types_anim_wallfan.gsc b/common_scripts/_destructible_types_anim_wallfan.gsc new file mode 100644 index 0000000..4ec1d1a --- /dev/null +++ b/common_scripts/_destructible_types_anim_wallfan.gsc @@ -0,0 +1,8 @@ +#using_animtree( "destructibles" ); +main() +{ + level._destructible_preanims[ "wall_fan_rotate" ] = %wall_fan_rotate; + level._destructible_preanims[ "wall_fan_wobble" ] = %wall_fan_wobble; + level._destructible_preanims[ "wall_fan_stop" ] = %wall_fan_stop; +} + diff --git a/common_scripts/_nx_fx.gsc b/common_scripts/_nx_fx.gsc new file mode 100644 index 0000000..90d92b8 --- /dev/null +++ b/common_scripts/_nx_fx.gsc @@ -0,0 +1,646 @@ +//**************************************************************************** +// ** +// Confidential - (C) Activision Publishing, Inc. 2011 ** +// ** +//**************************************************************************** +// ** +// Module: NX FX Utility Scripts ** +// ** +// Created: 3/30/2011 - Johnny Ow ** +// ** +//**************************************************************************** + +#include common_scripts\utility; +#include maps\_utility; + +//******************************************************************* +// * +// * +//******************************************************************* + +/* +============= +///ScriptDocBegin +"Name: fx_delete_createFXEnt_by_fxID( fxID, removeFromArray, immediate )" +"Summary: Delete entities from level._createFXEnt[] by fxID" +"Module: FX Utility" +"CallOn: " +"MandatoryArg: fxID of the entities to delete" +"OptionalArg: whether the entities should also be removed from level.createFXEnt[]" +"OptionalArg: whether to delete particles immediately" +"Example: fx_delete_createFXEnt_by_fxID( "fx_test", false, false );" +"SPMP: both" +///ScriptDocEnd +============= +*/ + +fx_delete_createFXEnt_by_fxID( fxID, removeFromArray, immediate ) +{ + if ( !isDefined( removeFromArray ) ) + removeFromArray = false; + + if ( !isDefined( immediate ) ) + immediate = false; + + inc = 0; + + foreach ( ent in level._createFXEnt ) + { + if ( ent.v[ "fxid" ] == fxID ) + { + if ( isDefined( ent.looper ) ) + { + if ( removeFromArray ) + level._createFXEnt = array_Remove( level._createFXEnt, ent ); + + ent.looper delete( immediate ); + } + } + + inc++; + + if ( inc > 3 ) + { + inc = 0; + wait .05; + } + } +} + +//******************************************************************* +// * +// * +//******************************************************************* + +/* +============= +///ScriptDocBegin +"Name: fx_delete_createFXEnt_by_vol( volName, removeFromArray, immediate )" +"Summary: Delete entities from level._createFXEnt[] by volume" +"Module: FX Utility" +"CallOn: " +"MandatoryArg: name of volume that contains entities to delete" +"OptionalArg: whether the entities should also be removed from level.createFXEnt[]" +"OptionalArg: whether to delete particles immediately" +"Example: fx_delete_createFXEnt_by_fxID( "fx_test", false );" +"SPMP: both" +///ScriptDocEnd +============= +*/ + +fx_delete_createFXEnt_by_vol( volName, removeFromArray, immediate ) +{ + volume = getEnt( volName, "targetname" ); + assert( isDefined( volume ) ); + + if ( !isDefined( removeFromArray ) ) + removeFromArray = false; + + if ( !isDefined( immediate ) ) + immediate = false; + + tester = spawn( "script_origin", ( 0, 0, 0) ); + inc = 0; + + foreach ( ent in level._createFXEnt ) + { + if ( isDefined( ent.looper ) ) + { + tester.origin = ent.v[ "origin" ]; + + if (tester isTouching( volume ) ) + { + if ( removeFromArray ) + level._createFXEnt = array_Remove( level._createFXEnt, ent ); + + ent.looper delete( immediate ); + } + } + + inc++; + + if ( inc > 3 ) + { + inc = 0; + wait .05; + } + } + + tester delete(); +} + +//******************************************************************* +// * +// * +//******************************************************************* + +/* +============= +///ScriptDocBegin +"Name: fx_restart_createFXEnt_by_vol( volName )" +"Summary: Restart level._createFXEnt[] entities by volume" +"Module: FX Utility" +"CallOn: " +"MandatoryArg: name of volume that contains entities to delete" +"Example: fx_delete_createFXEnt_by_fxID( "fx_test" );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ + +fx_restart_createFXEnt_by_vol( volName ) +{ + volume = getEnt( volName, "targetname" ); + assert( isDefined( volume ) ); + + tester = spawn( "script_origin", ( 0, 0, 0) ); + inc = 0; + + foreach ( ent in level._createFXEnt ) + { + tester.origin = ent.v[ "origin" ]; + + if (tester isTouching( volume ) ) + { + ent restartEffect(); + } + + inc++; + + if ( inc > 3 ) + { + inc = 0; + wait .05; + } + } + + tester delete(); +} + +//******************************************************************* +// * +// * +//******************************************************************* + +/* +============= +///ScriptDocBegin +"Name: fx_delete_all( immediate )" +"Summary: Delete all entities from level._createFXEnt[]" +"Module: FX Utility" +"CallOn: " +"OptionalArg: kills all existing FX elements belong to this entity immediately." +"Example: fx_delete_all();" +"SPMP: both" +///ScriptDocEnd +============= +*/ + +fx_delete_all( immediate ) +{ + foreach ( entFx in level._createfxent ) + { + if ( isdefined( entFx.looper ) ) + { + if ( isdefined( immediate ) ) + { + entFx.looper delete( immediate ); + } + else + { + entFx.looper delete(); + } + } + entFx notify( "stop_loop" ); + } + level._createFXent = []; +} + + +/* +============= +///ScriptDocBegin +"Name: fx_set_skyfog( height_start, height_end, height_blend, transition_time )" +"Summary: Set new target skyfog parameters" +"Module: FX Utility" +"CallOn: " +"OptionalArg: " +"Example: fx_set_skyfog( 0.6, 1.4, 0.8, 3.0 );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ + +fx_set_skyfog( height_start, height_end, height_blend, transition_time ) +{ + if (transition_time > 0.2) + { + thread lerp_savedDvar( "r_fog_height_blend", height_blend, transition_time ); + thread lerp_savedDvar( "r_fog_height_start", height_start, transition_time ); + thread lerp_savedDvar( "r_fog_height_end", height_end, transition_time ); + } else { + // small values don't lerp well - just set the value + SetSavedDVar( "r_fog_height_blend", height_blend); + SetSavedDVar( "r_fog_height_start", height_start); + SetSavedDVar( "r_fog_height_end", height_end); + } + + wait transition_time; // wait until they're done transitioning to return +} + +/* +============= +///ScriptDocBegin +"Name: fx_secondary_damage_trigger( ent )" +"Summary: setup secondary effects trigger for this effect entity" +"Module: FX Utility" +"CallOn: " +"OptionalArg: " +"Example: fx_secondary_damage_trigger( ent );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +fx_secondary_damage_trigger( ent ) +{ + bDebugDraw = false; + + // Create a node at the FX's position + org = Spawn( "script_model", ent.v[ "origin"] + (0,0,10) ); + + // debug locations + if (bDebugDraw) + thread draw_circle_until_notify( org.origin, 1, 0, 1, 0, ent, "stop_drawing_circle" ); + + // Make it damageable + org setCanDamage( true ); + org SetCanRadiusDamage( true ); + + ent.v[ "new_org" ] = org; + + while( true ) + { + org waittill( "damage", damage, attacker, direction_vec, point, damageType, modelName, tagName ); + + wait_timer = 0; + damage_speed = 0; + delay_mult = 0.0; // delay factor to mult against distance and use for the wait timer. + + // ******************************************************** + // speeds of concussive forces + // speed of sound (for reference) = 13512 inches per second + + // dynamite = 11811 inches/second + // c4 = 354331 inches/second + // ******************************************************** + + switch( damageType ) + { + case "mod_melee": + case "mod_crush": + case "melee": + case "mod_pistol_bullet": + case "mod_rifle_bullet": + case "bullet": + case "mod_grenade": + case "MOD_GRENADE_SPLASH": + damage_speed = 11811; // dynamite + break; + case "mod_projectile": + case "mod_projectile_splash": + case "mod_explosive": + case "c4": + damage_speed = 354331; // c4 + break; + case "splash": + damage_speed = 11811; // dynamite + break; + case "mod_impact": + case "unknown": + default: + break; + } + + wait_factor = 2.0; // multiplier on the wait + if ( damage_speed > 0 ) + { + // Get distance from explosion to this fx origin + + distance_from_damage = distance( point, org.origin ); // raw distance + wait_timer = (distance_from_damage / damage_speed); // time to wait + direction_vec = vectornormalize(org.origin - point); + + wait (wait_timer * wait_factor); + PlayFX( level._effect[ ent.v["fxid"] ], org.origin, direction_vec, ent.v[ "up" ] ); + } + wait 1; // wait for reset + } +} + +/* +============= +///ScriptDocBegin +"Name: fx_setup_secondary_damage_effects()" +"Summary: setup secondary effects triggers for CreateFX placed nodes with a fxid that starts with nx_fx_react" +"Module: FX Utility" +"CallOn: " +"OptionalArg: " +"Example: fx_setup_secondary_damage_effects();" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +fx_setup_secondary_damage_effects() +{ + if ( !getdvarint( "r_reflectionProbeGenerate" ) ) + { + for ( i = 0; i < level._createFXent.size; i++ ) + { + ent = level._createFXent[ i ]; + + if ( ent.v[ "type" ] == "exploder" ) + { + if (string_starts_with(ent.v[ "fxid" ], "nx_fx_react") ) + { + thread fx_secondary_damage_trigger( ent ); // setup the trigger on this createfx entity + } + } + } + } + thread setup_reactive_animated_models(); +} + +#using_animtree( "animated_props" ); +setup_reactive_animated_models() +{ + if ( !getdvarint( "r_reflectionProbeGenerate" ) ) + { + reactive_models = GetEntArray( "reactive_animated_model", "targetname" ); + +/* for ( i=0; i 0 ) + { + // Get distance from explosion to this fx origin + distance_from_damage = distance( point, self.origin ); // raw distance + wait_timer = (distance_from_damage / damage_speed); // time to wait + direction_vec = (self.origin - point); + + dir_north = false; + dir_east = false; + direction_vec *= (1,1,0); // don't consider z for this + direction_vec = vectornormalize(direction_vec); + + if (bDebug) + { + thread draw_line_for_time( self.origin, self.origin + (direction_vec * 200), 1, 1, 1, 10.0 ); + //thread draw_line_for_time( self.origin, self.origin + (AnglesToForward(self.angles) * 50), 1, 0, 0, 10.0 ); + //thread draw_line_for_time( self.origin, self.origin + (AnglesToRight(self.angles) * 50), 0, 1, 0, 10.0 ); + //thread draw_line_for_time( self.origin, self.origin + (AnglesToUp(self.angles) * 50), 0, 0, 1, 10.0 ); + + thread draw_line_for_time( self.origin + (0,0,10), self.origin + (20,0,10), 1, 0, 0, 10.0 ); + thread draw_line_for_time( self.origin + (0,0,10), self.origin + (0,20,10), 0, 1, 0, 10.0 ); + thread draw_line_for_time( self.origin + (0,0,10), self.origin + (0,0, 30), 0, 0, 1, 10.0 ); + } + + + if ( direction_vec[0] > 0 ) + dir_east = true; + if ( direction_vec[1] > 0 ) + dir_north = true; + + // init weights + north_weight = 0; + east_weight = 0; + west_weight = 0; + south_weight = 0; + north_weight_angle = 0; + south_weight_angle = 0; + east_weight_angle = 0; + west_weight_angle = 0; + + if (dir_north) + { + north_weight_angle = acos( VectorDot(direction_vec, (0,1,0)) ); + } else { + south_weight_angle = acos( VectorDot(direction_vec, (0,-1,0)) ); + } + + if (dir_east) + { + east_weight_angle = acos( VectorDot(direction_vec, (1,0,0)) ); + } else { + west_weight_angle = acos( VectorDot(direction_vec, (-1,0,0)) ); + } + + //north_weight_angle = acos( VectorDot(direction_vec, (0,1,0)) ); + //south_weight_angle = acos( VectorDot(direction_vec, (0,-1,0)) ); + //east_weight_angle = acos( VectorDot(direction_vec, (-1,0,0)) ); + //west_weight_angle = acos( VectorDot(direction_vec, (1,0,0)) ); + + if (north_weight_angle > 90.0) {north_weight_angle = 0;} + if (south_weight_angle > 90.0) {south_weight_angle = 0;} + if (east_weight_angle > 90.0) {east_weight_angle = 0;} + if (west_weight_angle > 90.0) {west_weight_angle = 0;} + + north_weight = (north_weight_angle / 90.0); + south_weight = (south_weight_angle / 90.0); + east_weight = (east_weight_angle / 90.0); + west_weight = (west_weight_angle / 90.0); + + //if (bDebug) +// IPrintLn( ("weights: " + north_weight + ", " + south_weight + ", " + east_weight + ", " + west_weight)); + + + anim_speed = 0.2; + blend_to_time = 0.02; + + // calc weight for anim of the static (unmoving animation) + // we're dampening the amount of bend based on the distance from the explosion + static_weight = 0; // (wait_timer / 0.5); + dampen_amt = abs(1.0-static_weight); + + wait (wait_timer * 2.5); + + if ((north_weight > 0) && (east_weight > 0)) + { + if (bDebug) + IPrintLn( ("NE: " + north_weight + ", " + east_weight + ":" + (north_weight + east_weight))); + + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "static" ], static_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "north" ], north_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "east" ], east_weight, blend_to_time, anim_speed); + } + else if ((north_weight > 0) && (west_weight > 0)) + { + if (bDebug) + IPrintLn( ("NW: " + north_weight + ", " + west_weight + ":" + (north_weight + west_weight))); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "static" ], static_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "north" ], north_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "west" ], west_weight, blend_to_time, anim_speed); + } + else if ((south_weight > 0) && (east_weight > 0)) + { + if (bDebug) + IPrintLn( ("SE: " + south_weight + ", " + east_weight + ":" + (south_weight + east_weight))); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "static" ], static_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "south" ], south_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "east" ], east_weight, blend_to_time, anim_speed); + + } + else if ((south_weight > 0) && (west_weight > 0)) + { + if (bDebug) + IPrintLn( ("SW: " + south_weight + ", " + west_weight + ":" + (south_weight + west_weight))); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "static" ], static_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "south" ], south_weight, blend_to_time, anim_speed); + self SetAnimRestart( level._anim_prop_models[ model ][ "anims" ][ "west" ], west_weight, blend_to_time, anim_speed); + } + + // dust -------------------------------------------------------------------------------------------------------- + chance_to_create_dust = level._anim_prop_models[ model ][ "dust_chance" ]; + if (chance_to_create_dust > 0) + { + for (i=0; i 0) + { + for (i=0; i 0) + { + for (i=0; i timeToSteam ) + break; + } + + if ( !isSP() ) + self thread removeDeadSentry(); +} + +handle_sentry_on_carrier_death( sentry ) +{ + level endon( "game_ended" ); + self endon( "sentry_placement_finished" ); + self waittill( "death" ); + + if ( isSp() ) + { + sentry notify( "death" ); + return; + } + + if ( !self.canPlaceEntity ) + { + sentry sentry_place_mode_reset(); + sentry notify( "deleted" ); + + waittillframeend; + sentry delete(); + + return; + } + + if ( !isSp() ) + { + self thread place_sentry( sentry ); + } + +} + +kill_sentry_on_carrier_disconnect( sentry ) +{ + level endon( "game_ended" ); + self endon( "sentry_placement_finished" ); + self waittill( "disconnect" ); + + sentry notify( "death" ); +} + +sentry_player_use_wait() +{ + level endon( "game_ended" ); + self endon( "death" ); + + assert( isDefined( self.sentryType ) ); + + if ( self.health <= 0 ) + return; + + for ( ;; ) + { + self waittill( "trigger", player ); + + if ( isDefined( player.placingSentry ) ) + continue; + + // only owner of sentry can move sentry in MP + if ( !isSP() ) + { + // Checked through code now; Assert left for reference. + assert( isDefined( self.owner ) ); + assert( player == self.owner ); + } + + break; + } + + player thread handle_sentry_on_carrier_death( self ); + player thread kill_sentry_on_carrier_disconnect( self ); + player thread sentry_placement_endOfLevel_cancel_monitor( self ); + + if ( !isSP() && !isAlive( player ) ) + return; + + if ( !isSP() ) + self sentry_team_hide_icon(); + + self SentryPowerOff();// makes the turret non - operational while being moved + player.placingSentry = self; + self setSentryCarried( true ); + self.ignoreMe = true; + self SetCanDamage( false ); + + player _disableWeapon(); + //player _disableUsability(); + self makeSentryNotSolid(); + self sentry_badplace_delete(); + player thread move_sentry_wait( self ); + player thread updateSentryPositionThread( self ); +} + +sentry_badplace_create() +{ + if ( !isSP() ) + return; + self.badplace_name = "" + getTime(); + call [[ level._badplace_cylinder_func ]]( self.badplace_name, 0, self.origin, 32, 128, self.team, "neutral" ); +} + +sentry_badplace_delete() +{ + if ( !isSP() ) + return; + assert( isdefined( self.badplace_name ) ); + call [[ level._badplace_delete_func ]]( self.badplace_name ); + self.badplace_name = undefined; +} + +move_sentry_wait( sentry ) +{ + level endon( "game_ended" ); + sentry endon( "death" ); + sentry endon( "deleted" ); + + self endon( "death" ); + self endon( "disconnect" ); + assert( isdefined( sentry ) ); + + for ( ;; ) + { + //debounce + self waitActivateButton( false ); + + // wait for button press + self waitActivateButton( true ); + + updateSentryPosition( sentry ); + if ( self.canPlaceEntity ) + break; + } + + place_sentry( sentry ); +} + +place_sentry( sentry ) +{ + if ( !isSP() ) + { + self endon( "death" ); + level endon( "end_game" ); + } + + self.placingSentry = undefined; + sentry setSentryCarried( false ); + sentry SetCanDamage( true ); + sentry.ignoreMe = false; + + if ( !maps\_utility::is_coop() || !maps\_utility::is_player_down_and_out( self ) ) + { + self _enableWeapon(); + //self _enableUsability(); + } + else + { + // Manually decrease the disabledWeapon count because we didn't actually call _enableWeapon() + // This is necessary because co-op revive needs to prevent the enable from happening, but sentries need to + // still keep count as if they did get re-enabled so that the next time you place a turret it works. + self.disabledWeapon--; + } + + sentry makeSentrySolid(); + sentry setmodel( level._sentry_settings[ sentry.sentryType ].model ); + sentry sentry_badplace_create(); + assert( isdefined( sentry.contents ) ); + sentry setContents( sentry.contents ); + self notify( "sentry_placement_finished", sentry ); + + sentry notify( "sentry_carried" ); + sentry.overheated = false; + self sentry_placement_hint_hide(); + + if ( !isSP() ) + sentry sentry_team_show_icon(); + + sentry SentryPowerOn(); + thread play_sound_in_space( "sentry_gun_plant", sentry.origin ); + + //debounce + self waitActivateButton( false ); + sentry thread sentry_player_use_wait(); +} + +sentry_enemy_wait() +{ + level endon( "game_ended" ); + self endon( "death" ); + self thread sentry_overheat_monitor(); + + for ( ;; ) + { + self waittill_either( "turretstatechange", "cooled" ); + + if ( self isFiringTurret() ) + { + self thread sentry_burst_fire_start(); + if ( isdefined( level._laserOn_func ) ) + self call [[ level._laserOn_func ]](); + } + else + { + self thread sentry_burst_fire_stop(); + if ( isdefined( level._laserOff_func ) ) + self call [[ level._laserOff_func ]](); + } + } +} + +// Sentry overheat behavoir for SP ==================================================== +// Note: To enable for mp, take out the isSP() check in main() function for overheat override variables. +// However, there might be some unseen behavoiral conflicts with battery timer, currently SP only - Julian + +// Note 2: Turrets now have a code ersion of doing this, we probably don't want to mix and match both, so this should be +// cleaned up / removed after MW2 +sentry_overheat_monitor() +{ + self endon( "death" ); + + assert( isDefined( self ) ); + assert( isDefined( self.sentryType ) ); + if ( self.sentryType != "sentry_minigun" ) + return; + + if ( !isdefined( level._sentry_overheating_speed ) ) + return; + + self.overheat = 0; + self.overheated = false; + + if ( getdvarint( "sentry_overheat_debug" ) == 1 ) + self thread sentry_overheat_debug(); + + while ( true ) + { + if ( self.overheat >= ( level._sentry_fire_time * 10 ) ) + { + self thread sentry_overheat_deactivate(); + self waittill_either( "cooled", "sentry_carried" ); + } + + if ( self IsFiringTurret() ) + { + self.overheat += 1; + } + else + { + if ( self.overheat > 0 ) + self.overheat -= 1; + } + + wait 0.1/level._sentry_overheating_speed; + } +} + +sentry_cooling() +{ + self endon( "death" ); + + while ( self.overheated ) + { + if ( self.overheat > 0 ) + self.overheat -= 1; + + wait 0.1/level._sentry_overheating_speed; + } +} + +sentry_overheat_debug() +{ + self endon( "death" ); + while( true ) + { + overheat_value = self.overheat / (level._sentry_fire_time*10); + overheat_print_l = "[ "; + overheat_print_r = " ]"; + if( self.overheated ) + { + overheat_print_l = "{{{ "; + overheat_print_r = " }}}"; + } + + print3d( self.origin + ( 0,0,45 ), overheat_print_l + self.overheat + " / " + level._sentry_fire_time*10 + overheat_print_r, ( 0+overheat_value, 1-overheat_value, 1-overheat_value ), 1, 0.35, 4 ); + wait 0.2; + } +} + +sentry_overheat_deactivate() +{ + self endon( "death" ); + + self notify( "overheated" ); + self.overheated = true; + self sentry_burst_fire_stop(); + + self thread sentry_overheat_reactivate(); +} + +sentry_overheat_reactivate() +{ + self endon( "death" ); + self endon( "sentry_carried" ); + + self thread sentry_cooling(); + + wait level._sentry_cooldown_time; + self notify( "cooled" ); + self.overheat = 0; + self.overheated = false; +} + +// END of sentry overheat behavoir for SP ================================================= + +sentry_burst_fire_start() +{ + self endon( "death" ); + level endon( "game_ended" ); + + if ( isdefined( self.overheated ) && self.overheated ) + return; + + self thread fire_anim_start(); + + self endon( "stop_shooting" ); + self notify( "shooting" ); + + assert( isdefined( self.weaponinfo ) ); + fireTime = weaponFireTime( self.weaponinfo ); + assert( isdefined( fireTime ) && fireTime > 0 ); + + for ( ;; ) + { + self turret_start_anim_wait(); + numShots = randomintrange( level._sentry_settings[ self.sentryType ].burst_shots_min, level._sentry_settings[ self.sentryType ].burst_shots_max ); + for ( i = 0 ; i < numShots ; i++ ) + { + if ( self canFire() ) + self shootTurret(); + + wait fireTime; + } + wait randomfloatrange( level._sentry_settings[ self.sentryType ].burst_pause_min, level._sentry_settings[ self.sentryType ].burst_pause_max ); + } +} + +sentry_allowFire( bAllow, timeOut ) +{ + self notify( "allowFireThread" ); + self endon( "allowFireThread" ); + self endon( "death" ); + + self.taking_damage = bAllow; + + if ( isdefined( timeOut ) && !bAllow ) + { + wait timeOut; + if ( isdefined( self ) ) + self thread sentry_allowFire( true ); + } +} + +canFire() +{ + if ( !isdefined( self.taking_damage ) ) + return true; + + return self.taking_damage; +} + +sentry_burst_fire_stop() +{ + self thread fire_anim_stop(); + self notify( "stop_shooting" ); + self thread sentry_steam(); +} + +sentry_steam() +{ + self endon( "shooting" ); + self endon( "deleted" ); + + wait randomfloatrange( 0.0, 1.0 ); + + timeToSteam = 6 * 1000; + startTime = getTime(); + + // temp sound fx + if ( isdefined( self ) ) + self playsound( "sentry_steam" ); + + while ( isdefined( self ) ) + { + playfxOnTag( getfx( "sentry_turret_overheat_smoke_sp" ), self, "tag_flash" ); + wait .3; + if ( getTime() - startTime > timeToSteam ) + break; + } +} + +turret_start_anim_wait() +{ + if ( isdefined( self.allow_fire ) && self.allow_fire == false ) + self waittill( "allow_fire" ); +} + +fire_anim_start() +{ + self notify( "anim_state_change" ); + self endon( "anim_state_change" ); + self endon( "stop_shooting" ); + self endon( "deleted" ); + level endon( "game_ended" ); + self endon( "death" ); + + if ( !isdefined( level._sentry_settings[ self.sentryType ].anim_loop ) ) + return; + + self.allow_fire = false; + + //ramp up the animation from 0.1 speed to 1.0 speed over time + if ( !isdefined( self.momentum ) ) + self.momentum = 0; + + self thread fire_sound_spinup(); + for ( ;; ) + { + if ( self.momentum >= 1.0 ) + break; + self.momentum += 0.1; + self.momentum = cap_value( self.momentum, 0.0, 1.0 ); + if ( isSP() ) + self self_func( "setanim", level._sentry_settings[ self.sentryType ].anim_loop, 1.0, 0.2, self.momentum ); + wait 0.2; + } + self.allow_fire = true; + self notify( "allow_fire" ); +} + +delete_sentry_turret() +{ + self notify( "deleted" ); + wait .05; + self notify( "death" ); + + if ( isDefined( self.obj_overlay ) ) + self.obj_overlay delete(); + + if ( isDefined( self.cam ) ) + self.cam delete(); + + self delete(); +} + +fire_anim_stop() +{ + self notify( "anim_state_change" ); + self endon( "anim_state_change" ); + + if ( !isdefined( level._sentry_settings[ self.sentryType ].anim_loop ) ) + return; + + self thread fire_sound_spindown(); + + self.allow_fire = false; + + for ( ;; ) + { + if ( !isdefined( self.momentum ) ) + break; + if ( self.momentum <= 0.0 ) + break; + self.momentum -= 0.1; + self.momentum = cap_value( self.momentum, 0.0, 1.0 ); + if ( isSP() ) + self self_func( "setanim", level._sentry_settings[ self.sentryType ].anim_loop, 1.0, 0.2, self.momentum ); + wait 0.2; + } +} + +fire_sound_spinup() +{ + self notify( "sound_state_change" ); + self endon( "sound_state_change" ); + self endon( "deleted" ); + + if ( self.momentum < 0.25 ) + { + self playsound( "sentry_minigun_spinup1" ); + wait 0.6; + self playsound( "sentry_minigun_spinup2" ); + wait 0.5; + self playsound( "sentry_minigun_spinup3" ); + wait 0.5; + self playsound( "sentry_minigun_spinup4" ); + wait 0.5; + } + else + if ( self.momentum < 0.5 ) + { + self playsound( "sentry_minigun_spinup2" ); + wait 0.5; + self playsound( "sentry_minigun_spinup3" ); + wait 0.5; + self playsound( "sentry_minigun_spinup4" ); + wait 0.5; + } + else + if ( self.momentum < 0.75 ) + { + self playsound( "sentry_minigun_spinup3" ); + wait 0.5; + self playsound( "sentry_minigun_spinup4" ); + wait 0.5; + } + else + if ( self.momentum < 1 ) + { + self playsound( "sentry_minigun_spinup4" ); + wait 0.5; + } + + thread fire_sound_spinloop(); +} + +fire_sound_spinloop() +{ + self endon( "death" ); + self notify( "sound_state_change" ); + self endon( "sound_state_change" ); + + while ( 1 ) + { + self playsound( "sentry_minigun_spin" ); + wait 2.5; + } +} + +fire_sound_spindown() +{ + self notify( "sound_state_change" ); + self endon( "sound_state_change" ); + self endon( "deleted" ); + + if ( !isdefined( self.momentum ) ) + return; + + if ( self.momentum > 0.75 ) + { + self stopsounds(); + self playsound( "sentry_minigun_spindown4" ); + wait 0.5; + self playsound( "sentry_minigun_spindown3" ); + wait 0.5; + self playsound( "sentry_minigun_spindown2" ); + wait 0.5; + self playsound( "sentry_minigun_spindown1" ); + wait 0.65; + } + else + if ( self.momentum > 0.5 ) + { + self playsound( "sentry_minigun_spindown3" ); + wait 0.5; + self playsound( "sentry_minigun_spindown2" ); + wait 0.5; + self playsound( "sentry_minigun_spindown1" ); + wait 0.65; + } + else + if ( self.momentum > 0.25 ) + { + self playsound( "sentry_minigun_spindown2" ); + wait 0.5; + self playsound( "sentry_minigun_spindown1" ); + wait 0.65; + } + else + { + self playsound( "sentry_minigun_spindown1" ); + wait 0.65; + } +} + +sentry_beep_sounds() +{ + self endon( "death" ); + for ( ;; ) + { + wait randomfloatrange( 3.5, 4.5 ); + self thread play_sound_in_space( "sentry_gun_beep", self.origin + ( 0, 0, 40 ) ); + } +} + +spawn_and_place_sentry( sentryType ) +{ + level endon( "game_ended" ); + + assert( self.classname == "player" ); + assert( isdefined( sentryType ) ); + assert( isdefined( level._sentry_settings[ sentryType ] ) ); + assert( isdefined( level._sentry_settings[ sentryType ].placementmodel ) ); + assert( isdefined( level._sentry_settings[ sentryType ].placementmodelfail ) ); + + if ( isdefined( self.placingSentry ) ) + return; + + self _disableWeapon(); + //self _disableUsability(); + self notify( "placingSentry" ); + + assert( isdefined( level._sentry_settings[ sentryType ] ) ); + assert( isdefined( level._sentry_settings[ sentryType ].weaponInfo ) ); + assert( isdefined( level._sentry_settings[ sentryType ].model ) ); + assert( isdefined( level._sentry_settings[ sentryType ].targetname ) ); + + sentry_gun = spawnTurret( "misc_turret", self.origin, level._sentry_settings[ sentryType ].weaponInfo ); + sentry_gun setmodel( level._sentry_settings[ sentryType ].placementModel ); + sentry_gun.weaponinfo = level._sentry_settings[ sentryType ].weaponInfo; + sentry_gun.targetname = level._sentry_settings[ sentryType ].targetname; + sentry_gun.weaponName = level._sentry_settings[ sentryType ].weaponInfo; + sentry_gun.angles = self.angles; + sentry_gun.team = self.team; + sentry_gun.attacker = self; + sentry_gun.sentryType = sentryType; + + sentry_gun makeTurretInoperable(); + sentry_gun sentryPowerOff(); + sentry_gun setCanDamage( false ); + sentry_gun sentry_set_owner( self ); + sentry_gun setDefaultDropPitch( -89.0 ); // setting this mainly prevents Turret_RestoreDefaultDropPitch() from running + + self.placingSentry = sentry_gun; + sentry_gun setSentryCarried( true ); + sentry_gun SetCanDamage( false ); + sentry_gun.ignoreMe = true; + + if ( !isSP() ) + sentry_gun addToTurretList(); + + // wait to delete the sentry when cancelled + self thread sentry_placement_cancel_monitor( sentry_gun ); + + // wait to delete the sentry on end of level + self thread sentry_placement_endOfLevel_cancel_monitor( sentry_gun ); + + // wait until the player plants the sentry + self thread sentry_placement_initial_wait( sentry_gun ); + + // keep the indicator model positioned with traces forever until the thread is ended + self thread updateSentryPositionThread( sentry_gun ); + + // wait until the turret placement has been finished or canceled + if ( !isSP() ) + self waittill_any( "sentry_placement_finished", "sentry_placement_canceled", "death" ); + else + self waittill_any( "sentry_placement_finished", "sentry_placement_canceled" ); + + self sentry_placement_hint_hide(); + + if ( !maps\_utility::is_coop() || !maps\_utility::is_player_down_and_out( self ) ) + { + self _enableWeapon(); + //self _enableUsability(); + } + else + { + // Manually decrease the disabledWeapon count because we didn't actually call _enableWeapon() + // This is necessary because co-op revive needs to prevent the enable from happening, but sentries need to + // still keep count as if they did get re-enabled so that the next time you place a turret it works. + self.disabledWeapon--; + } + + self.placingSentry = undefined; + sentry_gun setSentryCarried( false ); + self SetCanDamage( true ); + sentry_gun.ignoreMe = false; +} + + +sentry_placement_cancel_monitor( sentry_gun ) +{ + self endon ( "sentry_placement_finished" ); + + if ( !isSP() ) + self waittill_any( "sentry_placement_canceled", "death", "disconnect"); + else + self waittill_any( "sentry_placement_canceled" ); + + waittillframeend; + sentry_gun delete(); +} + +sentry_placement_endOfLevel_cancel_monitor( sentry_gun ) +{ + self endon ( "sentry_placement_finished" ); + + if ( isSP() ) + return; + + level waittill( "game_ended" ); + + if ( !isDefined( sentry_gun ) ) + return; + + //sentry_gun notify( "deleted" ); + if ( !self.canPlaceEntity ) + { + sentry_gun notify( "deleted" ); + + waittillframeend; + sentry_gun delete(); + return; + } + + self thread place_sentry( sentry_gun ); +} + + +sentry_restock_wait() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + self endon( "restock_reset" ); + + // Cancel/restock on death or when toggling the killstreak + self notifyOnPlayerCommand( "cancel sentry", "+actionslot 4" ); + self waittill_any( "death", "cancel sentry" ); + assert( isdefined( self.last_sentry ) ); + + self notify( "sentry_placement_canceled" ); +} + + +sentry_placement_initial_wait( sentry_gun ) +{ + level endon( "game_ended" ); + + self endon( "sentry_placement_canceled" ); + + if ( !isSP() ) + { + self endon( "disconnect" ); + //self endon( "death" ); + sentry_gun thread sentry_reset_on_owner_death(); + self thread sentry_restock_wait(); + } + + //debounce from picking up the gun + while ( self useButtonPressed() ) + wait 0.05; + + for ( ;; ) + { + // couldn't place entity so wait until the buttons are unpressed before trying again + self waitActivateButton( false ); + + // wait until the button is pressed + self waitActivateButton( true ); + + updateSentryPosition( sentry_gun ); + if ( self.canPlaceEntity ) + break; + } + + if ( !isSP() ) //&& isAlive( self ) ) + self notify( "restock_reset" ); + + if ( !isSP() ) + { + sentry_gun.lifeId = self.lifeId; + self sentry_team_setup( sentry_gun ); + } + + thread play_sound_in_space( "sentry_gun_plant", sentry_gun.origin ); + + assert( isdefined( self.team ) ); + sentry_gun setmodel( level._sentry_settings[ sentry_gun.sentryType ].model ); + sentry_gun thread sentry_init( self.team, sentry_gun.sentryType, self ); + + self notify( "sentry_placement_finished", sentry_gun ); + waittillframeend; // wait so self.placingSentry can get cleared before notifying script that we can give the player another turret + + if ( !isSP() ) + sentry_gun thread sentry_die_on_batteryout(); +} + +updateSentryPositionThread( sentry_entity ) +{ + level endon( "game_ended" ); + + sentry_entity notify( "sentry_placement_started" ); + self endon( "sentry_placement_canceled" ); + self endon( "sentry_placement_finished" ); + + sentry_entity endon( "death" ); + sentry_entity endon( "deleted" ); + + if ( !isSP() ) + { + self endon( "disconnect" ); + self endon( "death" ); + } + + for ( ;; ) + { + updateSentryPosition( sentry_entity ); + wait sentry_updateTime; + } +} + +updateSentryPosition( sentry_entity ) +{ + placement = self canPlayerPlaceSentry(); + sentry_entity.origin = placement[ "origin" ]; + sentry_entity.angles = placement[ "angles" ]; + self.canPlaceEntity = self isonground() && placement[ "result" ]; + self sentry_placement_hint_show( self.canPlaceEntity ); + + if ( self.canPlaceEntity ) + sentry_entity setModel( level._sentry_settings[ sentry_entity.sentryType ].placementmodel ); + else + sentry_entity setModel( level._sentry_settings[ sentry_entity.sentryType ].placementmodelfail ); +} + +sentry_placement_hint_show( hint_valid ) +{ + assert( isDefined( self ) ); + assert( isDefined( hint_valid ) ); + + // return if not changed + if ( isdefined( self.forced_hint ) && (self.forced_hint == hint_valid) ) + return; + + self.forced_hint = hint_valid; + + if ( self.forced_hint ) + self ForceUseHintOn( &"SENTRY_PLACE" ); + else + self ForceUseHintOn( &"SENTRY_CANNOT_PLACE" ); +} + +sentry_placement_hint_hide() +{ + assert( isDefined( self ) ); + + // return if hidden already + if ( !isdefined( self.forced_hint ) ) + return; + + self ForceUseHintOff(); + self.forced_hint = undefined; +} + +folded_sentry_use_wait( sentryType ) +{ + // spawn another copy of the model so that it's not translucent + self.obj_overlay = spawn( "script_model", self.origin ); + self.obj_overlay.angles = self.angles; + self.obj_overlay setModel( level._sentry_settings[ sentryType ].pickupModelObj ); + + for ( ;; ) + { + self waittill( "trigger", player ); + + if ( !isdefined( player ) ) + continue; + + if ( isDefined( player.placingSentry ) ) + continue; + + if ( !isSP() ) + { + assert( isdefined( self.owner ) ); + if ( player != self.owner ) + continue; + } + + break; + } + + self thread play_sound_in_space( "sentry_pickup" ); + self.obj_overlay delete(); + self delete(); + + // put the player into placement mode + player thread spawn_and_place_sentry( sentryType ); +} + +sentry_health_monitor() +{ + self.healthbuffer = 20000; + self.health += self.healthbuffer; + self.currenthealth = self.health; + attacker = undefined; + type = undefined; + + while ( self.health > 0 ) + { + self waittill( "damage", amount, attacker, direction_vec, point, type, modelName, tagName ); + + if ( !isSP() && isdefined( attacker ) && isplayer( attacker ) && attacker sentry_attacker_is_friendly( self ) ) + { + self.health = self.currenthealth; + return; + } + + if ( isdefined( level._stat_track_damage_func ) && isdefined( attacker ) ) + attacker [[ level._stat_track_damage_func ]](); + + assertex( isdefined( level._func[ "damagefeedback" ] ), "damagefeedback display function is undefined" ); + if ( isdefined( attacker ) && isplayer( attacker ) ) + { + if ( !isSP() ) + attacker [[ level._func[ "damagefeedback" ] ]]( "false" ); + /* no more hit indicator in SP, commenting this out and replacing with the line above for MP only + if ( isSP() ) + attacker [[ level.func[ "damagefeedback" ] ]]( self ); + else + attacker [[ level.func[ "damagefeedback" ] ]]( "false" ); + */ + self thread sentry_allowFire( false, 2.0 ); + } + + if ( self sentry_hit_bullet_armor( type ) ) + { + //damage was to bullet armor, restore health and decrement bullet armor. + self.health = self.currenthealth; + self.bullet_armor -= amount; + } + else + self.currenthealth = self.health; + + if ( self.health < self.healthbuffer ) + break; + } + + if ( !isSP() && attacker sentry_attacker_can_get_xp( self ) ) + attacker thread [[ level._onXPEvent ]]( "kill" ); + + self notify( "death", attacker, type ); +} + +sentry_hit_bullet_armor( type ) +{ + if ( self.bullet_armor <= 0 ) + return false; + if ( !( isdefined( type ) ) ) + return false; + if ( ! issubstr( type, "BULLET" ) ) + return false; + else + return true; +} + +enemy_sentry_difficulty_settings() +{ + difficulty = "easy"; + + self SetConvergenceTime( level._sentryTurretSettings[ difficulty ][ "convergencePitchTime" ], "pitch" ); + self SetConvergenceTime( level._sentryTurretSettings[ difficulty ][ "convergenceYawTime" ], "yaw" ); + self SetSuppressionTime( level._sentryTurretSettings[ difficulty ][ "suppressionTime" ] ); + self SetAiSpread( level._sentryTurretSettings[ difficulty ][ "aiSpread" ] ); + self SetPlayerSpread( level._sentryTurretSettings[ difficulty ][ "playerSpread" ] ); + + self.maxrange = 1100; + self.bullet_armor = minigun_sentry_bullet_armor_enemy; +} + +waitActivateButton( bCheck ) +{ + if ( !isSP() ) + { + self endon( "death" ); + self endon( "disconnect" ); + } + + assert( isdefined( bCheck ) ); + + if ( bCheck == true ) + { + while ( !self attackButtonPressed() && !self useButtonPressed() ) + wait 0.05; + } + else if ( bCheck == false ) + { + while ( self attackButtonPressed() || self useButtonPressed() ) + wait 0.05; + } +} + +makeSentrySolid() +{ + self makeTurretSolid(); +} + +makeSentryNotSolid() +{ + self.contents = self setContents( 0 ); +} + + +SentryPowerOn() +{ + self setMode( sentry_mode_name_on ); + self.battery_usage = true; +} + +SentryPowerOff() +{ + self setMode( sentry_mode_name_off ); + self.battery_usage = false; +} + +// ============================================================================= +// MP functions: +// ============================================================================= + + +// MP sentry team and head icons +sentry_team_setup( sentry_gun ) +{ + // self == player + + assert( isDefined( sentry_gun ) ); + assert( isDefined( sentry_gun.sentryType ) ); + + if ( isdefined( self.pers[ "team" ] ) ) + sentry_gun.pers[ "team" ] = self.pers[ "team" ]; + + sentry_gun sentry_team_show_icon(); +} + + +sentry_team_show_icon() +{ + assert( isdefined( level._func[ "setTeamHeadIcon" ] ) ); + + sentry_headicon_offset = ( 0, 0, 65 ); + if ( self.sentryType == "sentry_gun" ) + sentry_headicon_offset = ( 0, 0, 75 ); + + self [[ level._func[ "setTeamHeadIcon" ] ]]( self.pers[ "team" ], sentry_headicon_offset ); +} + + +// MP clear team and head icons +sentry_team_hide_icon() +{ + assert( isdefined( level._func[ "setTeamHeadIcon" ] ) ); + self [[ level._func[ "setTeamHeadIcon" ] ]]( "none", (0, 0, 0) ); +} + + +// resets sentry placement mode when owner carrying sentry dies +sentry_place_mode_reset() +{ + if ( !isDefined(self.owner) ) + return; + + if ( isDefined( self.owner.placingSentry ) && (self.owner.placingSentry == self) ) + { + self.owner notify( "sentry_placement_canceled" ); + self.owner _enableWeapon(); + //self.owner _enableUsability(); + self.owner.placingSentry = undefined; + self setSentryCarried( false ); + self SetCanDamage( true ); + self.ignoreMe = false; + } +} + +sentry_set_owner( owner ) +{ + assert( isdefined( owner ) ); + assert( isPlayer( owner ) ); + + // don't need to set it twice. will happen for non-static sentries + if ( isDefined ( self.owner ) && self.owner == owner ) + return; + + owner.debug_sentry = self;// for debug + self.owner = owner; + self SetSentryOwner( owner ); + self SetTurretMinimapVisible( true ); +} + +sentry_destroy_on_owner_leave( owner ) +{ + level endon( "game_ended" ); + self endon( "death" ); + + owner waittill_any( "disconnect", "joined_team", "joined_spectators" ); + self notify( "death" ); +} + +// battery monitor, batter only used while sentry is on +sentry_die_on_batteryout() +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "deleted" ); + + // only one instance + self notify( "battery_count_started" ); + self endon( "battery_count_started" ); + + while ( self.sentry_battery_timer >= 0 ) + { + if ( self.battery_usage ) + self.sentry_battery_timer -= 1; + wait 1; + } + + self notify( "death" ); +} + +removeDeadSentry() +{ + self playsound( "sentry_explode" ); + playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); + self delete_sentry_turret(); +} + + +sentry_reset_on_owner_death() +{ + // self is sentry + assert( isDefined( self ) ); + self endon( "death" ); + self endon( "deleted" ); + + assert( isdefined( self.owner ) ); + self.owner waittill_any( "death", "disconnect" ); + + if ( isDefined( self.owner.placingSentry ) && (self.owner.placingSentry == self) ) + { + self.owner.placingSentry = undefined; + self setSentryCarried( false ); + self SetCanDamage( true ); + self.ignoreMe = false; + self notify( "death" ); + } +} + +sentry_attacker_can_get_xp( sentry ) +{ + assert( isdefined( sentry.owner ) ); + + // defensive much? + if ( !isdefined( self ) ) + return false; + + if ( !isPlayer( self ) ) + return false; + + if ( !isdefined( level._onXPEvent ) ) + return false; + + if ( !isdefined( self.pers[ "team" ] ) ) + return false; + + if ( !isdefined( sentry.team ) ) + return false; + + if ( !level._teambased && self == sentry.owner ) + return false; + + if ( level._teambased && ( self.pers[ "team" ] == sentry.team ) ) + return false; + + return true; +} + + +sentry_attacker_is_friendly( sentry ) +{ + assert( isdefined( sentry.owner ) ); + + // defensive much? + if ( !isdefined( self ) ) + return false; + + if ( !isPlayer( self ) ) + return false; + + if ( !level._teamBased ) + return false; + + if ( self == sentry.owner ) + return false; + + if ( self.team != sentry.team ) + return false; + + return true; +} + + +sentry_emp_damage_wait() +{ + self endon( "deleted" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "emp_damage", attacker, duration ); + + // TODO: friendly fire check here + + self thread sentry_burst_fire_stop(); + + if ( isdefined( level._laserOff_func ) ) + self call [[ level._laserOff_func ]](); + + self SentryPowerOff(); + playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); + + wait( duration ); + + self SentryPowerOn(); + } +} + + +sentry_emp_wait() +{ + self endon( "deleted" ); + self endon( "death" ); + + for ( ;; ) + { + level waittill( "emp_update" ); + + // TODO: make this work in FFA + if ( level._teamEMPed[self.team] ) + { + self thread sentry_burst_fire_stop(); + + if ( isdefined( level._laserOff_func ) ) + self call [[ level._laserOff_func ]](); + + self SentryPowerOff(); + playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" ); + } + else + { + self SentryPowerOn(); + } + } +} + +addToTurretList() +{ + level._turrets[self getEntityNumber()] = self; +} + +removeFromTurretList() +{ + level._turrets[self getEntityNumber()] = undefined; +} + +dual_waittill( ent1, msg1, ent2, msg2 ) +{ + ent1 endon ( msg1 ); + ent2 endon ( msg2 ); + + level waittill ( "hell_freezes_over_AND_THEN_thaws_out" ); +} \ No newline at end of file diff --git a/common_scripts/_viewmodel_ui.gsc b/common_scripts/_viewmodel_ui.gsc new file mode 100644 index 0000000..be86127 --- /dev/null +++ b/common_scripts/_viewmodel_ui.gsc @@ -0,0 +1,347 @@ +#include maps\_hud_util; +#include common_scripts\utility; + +//******************************************************************* +// * +// * +//******************************************************************* + +/* +============= +///ScriptDocBegin +"Name: viewmodel_create_ammo_counter()" +"Summary: create client hud elements to be applied to weapon view model" +"Module: Utility" +"CallOn: nothing" +"MandatoryArg:" +"Example: viewmodel_create_ammo_counter();" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +viewmodel_apply_ui( weapon_name ) +{ + // always cleanup our old stuff first + + // cleanup our update functions + self notify( "viewmodel_ui_update" ); + + // cleanup our old weapons stuff, value for _viewmodel_ui_weapon should already be set + if ( level._viewmodel_ui_weapon != "" ) + { + [[ level._viewmodel_ui[ level._viewmodel_ui_weapon ][ 0 ] ]](); + } + + // now we create our new stuff + if ( weapon_name != "" ) + { + // set our weapon for create process and subsequent destroy + level._viewmodel_ui_weapon = weapon_name; + + + // create all of our elements + [[ level._viewmodel_ui[ level._viewmodel_ui_weapon ][ 1 ] ]](); + + + // thread our update functions + self thread viewmodel_magazine_ammo_update(); + self thread viewmodel_reserve_ammo_update(); + self thread viewmodel_range_update(); + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +viewmodel_ui_setup() +{ + // dvar used to help see where you elements are currently placed. Usefull when working on UV mapping + //SetSavedDvar( "r_drawUiTextureOverlay", "1" ); + + + // make sure everything is setup properly + if ( !IsDefined( level._viewmodel_ui_weapon ) ) + { + level._viewmodel_ui_weapon = ""; + } + + + // tagMJS we should move all our assets into the GDT entry + // so we don't make our common.csv file larger than it needs to be + // make sure all our assets are loaded + viewmodel_precache_assets(); + + + // add to our level array all our needed functions + viewmodel_add_create_and_destroy_functions(); +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +viewmodel_precache_assets() +{ + // need this icon precached for viewmodel ui + PreCacheShader( "ammo_counter_med" ); +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +viewmodel_add_create_and_destroy_functions() +{ + level._viewmodel_ui[ "lunarrifle" ][ 0 ] = ::cleanup_lunarrifle_ui_elements; + level._viewmodel_ui[ "lunarrifle" ][ 1 ] = ::setup_lunarrifle_ui_elements; + level._viewmodel_ui[ "glo_reflex_mp" ][ 0 ] = ::delete_glo_ui_elements; + level._viewmodel_ui[ "glo_reflex_mp" ][ 1 ] = ::create_glo_ui_elements; +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +viewmodel_magazine_ammo_update() +{ + self endon( "viewmodel_ui_update" ); + + magazine_size = level._player GetMagazineMaxAmmoCount( level._viewmodel_ui_weapon ); + while( 1 ) + { + remaining = level._player GetMagazineAmmoCount( level._viewmodel_ui_weapon ); + + if ( IsDefined( level.magazine_counter ) ) + { + // update lens count + //level.ammo_counter SetText( " " + level._player getmagazineammocount( level._viewmodel_ui_weapon ) + "/" + level._player getmagazinemaxammocount( level._viewmodel_ui_weapon ) ); + level.magazine_counter SetText( remaining ); + } + + if ( IsDefined( level.magazine_pips ) ) + { + // update frame pips + for( index = 0; index < magazine_size; index++ ) + { + if ( index < remaining ) + { + level.magazine_pips[index].color = ( 1.0, 1.0, 1.0 ); + } + else + { + level.magazine_pips[index].color = ( 0.35, 0.35, 0.35 ); + } + } + } + wait( 0.05 ); + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +viewmodel_reserve_ammo_update() +{ + self endon( "viewmodel_ui_update" ); + + if( IsDefined( level.reserve_counter ) ) + { + while( 1 ) + { + level.reserve_counter SetText( level._player GetAmmoCount( level._viewmodel_ui_weapon ) - level._player GetMagazineAmmoCount( level._viewmodel_ui_weapon ) ); + wait( 0.05 ); + } + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +viewmodel_range_update() +{ + self endon( "viewmodel_ui_update" ); + + if( IsDefined( level.range_display ) ) + { + while( 1 ) + { + eye = self geteye(); + angles = self getplayerangles(); + + forward = anglestoforward( angles ); + end = eye + vector_multiply( forward, 7000 ); + trace = bullettrace( eye, end, true, self ); + level.range_display SetText( int( distance( level._player.origin, trace["position"] ) * ( 100 / 39.37 ) ) / 100 ); + + wait( 0.33 ); + } + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +// Create and Destroy Functions below here +// Please +// -MJS + +//******************************************************************* +// * +// * +//******************************************************************* + +setup_lunarrifle_ui_elements() +{ + level.reserve_counter = newClientHudElem( level._player ); + level.reserve_counter.elemType = "font"; + level.reserve_counter.font = "hudbig"; + level.reserve_counter.sort = 50; + level.reserve_counter.pre3d = true; + level.reserve_counter.horzAlign = "fullscreen"; + level.reserve_counter.vertAlign = "fullscreen"; + level.reserve_counter.alpha = 1; + level.reserve_counter.alignX = "center"; + level.reserve_counter.alignY = "top"; + level.reserve_counter.fontScale = 38; + level.reserve_counter.x = 3900; + level.reserve_counter.y = 768; + level.reserve_counter.color = ( 1.0, 1.0, 1.0 ); + + level.magazine_counter = newClientHudElem( level._player ); + level.magazine_counter.elemType = "font"; + level.magazine_counter.font = "hudbig"; + level.magazine_counter.sort = 50; + level.magazine_counter.pre3d = true; + level.magazine_counter.horzAlign = "fullscreen"; + level.magazine_counter.vertAlign = "fullscreen"; + level.magazine_counter.alpha = 1; + level.magazine_counter.alignX = "center"; + level.magazine_counter.alignY = "top"; + level.magazine_counter.fontScale = 64; + level.magazine_counter.x = 1385; + level.magazine_counter.y = -192; + level.magazine_counter.color = ( 1.0, 0.35, 0.35 ); + + magazine_size = level._player GetMagazineMaxAmmoCount( level._viewmodel_ui_weapon ); + level.magazine_pips = []; + spacing = 320; + for( index = 0; index < magazine_size; index++ ) + { + level.magazine_pips[index] = createIcon( "ammo_counter_med", 512, 2048 ); + level.magazine_pips[index].x = ( 6050 - ( spacing * index ) ); + level.magazine_pips[index].y = 1536; + level.magazine_pips[index].pre3d = true; + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +cleanup_lunarrifle_ui_elements() +{ + if ( IsDefined( level.reserve_counter ) ) + { + level.reserve_counter Destroy(); + level.reserve_counter = undefined; + } + if ( IsDefined( level.magazine_counter ) ) + { + level.magazine_counter Destroy(); + level.magazine_counter = undefined; + } + if ( IsDefined( level.magazine_pips ) ) + { + magazine_size = level.magazine_pips.size; + for( index = 0; index < magazine_size; index++ ) + { + level.magazine_pips[index] Destroy(); + } + level.magazine_pips = undefined; + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +delete_glo_ui_elements() +{ + if ( IsDefined( level.magazine_counter ) ) + { + level.magazine_counter Destroy(); + level.magazine_counter = undefined; + } + if ( IsDefined( level.range_display ) ) + { + level.range_display Destroy(); + level.range_display = undefined; + } +} + + +//******************************************************************* +// * +// * +//******************************************************************* + +create_glo_ui_elements() +{ + level.magazine_counter = newClientHudElem( level._player ); + level.magazine_counter.elemType = "font"; + level.magazine_counter.font = "hudbig"; + level.magazine_counter.sort = 50; + level.magazine_counter.pre3d = true; + level.magazine_counter.horzAlign = "fullscreen"; + level.magazine_counter.vertAlign = "fullscreen"; + level.magazine_counter.alpha = 1; + level.magazine_counter.alignX = "center"; + level.magazine_counter.alignY = "top"; + level.magazine_counter.fontScale = 80; + level.magazine_counter.x = 3000; + level.magazine_counter.y = 400; + level.magazine_counter.color = ( 0.0, 0.941176471, 1.0 ); + + level.range_display = newClientHudElem( level._player ); + level.range_display.elemType = "font"; + level.range_display.font = "hudbig"; + level.range_display.sort = 50; + level.range_display.pre3d = true; + level.range_display.horzAlign = "fullscreen"; + level.range_display.vertAlign = "fullscreen"; + level.range_display.alpha = 1; + level.range_display.alignX = "center"; + level.range_display.alignY = "top"; + level.range_display.fontScale = 32; + level.range_display.x = 2500; + level.range_display.y = -64; + level.range_display.color = ( 0.0, 0.941176471, 1.0 ); +} + + +//******************************************************************* +// * +// * +//******************************************************************* \ No newline at end of file diff --git a/maps/_80s_hatch1.gsc b/maps/_80s_hatch1.gsc new file mode 100644 index 0000000..68eb694 --- /dev/null +++ b/maps/_80s_hatch1.gsc @@ -0,0 +1,176 @@ +#include maps\_vehicle_aianim; +#include maps\_vehicle; +#using_animtree( "vehicles" ); +main( model, type ) +{ + //SNDFILE=vehicle_80s_car + build_template( "80s_hatch1", model, type ); + build_localinit( ::init_local ); + + build_destructible( "vehicle_80s_hatch1_brn_destructible_mp", "vehicle_80s_hatch1_brn" ); + build_destructible( "vehicle_80s_hatch1_green_destructible_mp", "vehicle_80s_hatch1_green" ); + build_destructible( "vehicle_80s_hatch1_red_destructible_mp", "vehicle_80s_hatch1_red" ); + build_destructible( "vehicle_80s_hatch1_silv_destructible_mp", "vehicle_80s_hatch1_silv" ); + build_destructible( "vehicle_80s_hatch1_tan_destructible_mp", "vehicle_80s_hatch1_tan" ); + build_destructible( "vehicle_80s_hatch1_yel_destructible_mp", "vehicle_80s_hatch1_yel" ); + + build_drive( %technical_driving_idle_forward, %technical_driving_idle_backward, 10 ); + +// build_treadfx(); + + //life is now determined by destructible. less _vehicle controled + build_life( 999, 500, 1500 ); + + build_team( "allies" ); + build_aianims( ::setanims, ::set_vehicle_anims ); + build_compassicon( "automobile", false ); + +} + +init_local() +{ + +} + +set_vehicle_anims( positions ) +{ + return positions; +} + + +#using_animtree( "generic_human" ); + +setanims() +{ + positions = []; + return positions;// no anims yet +/* + for(i=0;i<4;i++) + positions[i] = spawnstruct(); + + positions[0].sittag = "body_animate_jnt"; + positions[1].sittag = "body_animate_jnt"; + positions[2].sittag = "tag_passenger"; + positions[3].sittag = "body_animate_jnt"; + + positions[0].idle = %humvee_driver_climb_idle; + positions[1].idle = %humvee_passenger_idle_L; + positions[2].idle = %humvee_passenger_idle_R; + positions[3].idle = %humvee_passenger_idle_R; + + positions[0].getout = %humvee_driver_climb_out; + positions[1].getout = %humvee_passenger_out_L; + positions[2].getout = %humvee_passenger_out_R; + positions[3].getout = %humvee_passenger_out_R; + + positions[0].getin = %humvee_driver_climb_in; + positions[1].getin = %humvee_passenger_in_L; + positions[2].getin = %humvee_passenger_in_R; + positions[3].getin = %humvee_passenger_in_R; + +*/ + return positions; +} + +/*QUAKED script_vehicle_80s_hatch1_brn_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_hatch1::main( "vehicle_80s_hatch1_brn_destructible_mp" ); + + +include,vehicle_80s_hatch1_brn_destructible_mp_80s_hatch1 +include,destructible_vehicle_80s_hatch1_brn_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_hatch1_brn_destructible_mp" +default:"vehicletype" "80s_hatch1" +default:"script_team" "allies" + +*/ + +/*QUAKED script_vehicle_80s_hatch1_green_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_hatch1::main( "vehicle_80s_hatch1_green_destructible_mp" ); + + +include,vehicle_80s_hatch1_green_destructible_mp_80s_hatch1 +include,destructible_vehicle_80s_hatch1_green_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_hatch1_green_destructible_mp" +default:"vehicletype" "80s_hatch1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_hatch1_red_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_hatch1::main( "vehicle_80s_hatch1_red_destructible_mp" ); + + +include,vehicle_80s_hatch1_red_destructible_mp_80s_hatch1 +include,destructible_vehicle_80s_hatch1_red_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_hatch1_red_destructible_mp" +default:"vehicletype" "80s_hatch1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_hatch1_silv_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_hatch1::main( "vehicle_80s_hatch1_silv_destructible_mp" ); + + +include,vehicle_80s_hatch1_silv_destructible_mp_80s_hatch1 +include,destructible_vehicle_80s_hatch1_silv_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_hatch1_silv_destructible_mp" +default:"vehicletype" "80s_hatch1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_hatch1_tan_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_hatch1::main( "vehicle_80s_hatch1_tan_destructible_mp" ); + + +include,vehicle_80s_hatch1_tan_destructible_mp_80s_hatch1 +include,destructible_vehicle_80s_hatch1_tan_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_hatch1_tan_destructible_mp" +default:"vehicletype" "80s_hatch1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_hatch1_yel_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_hatch1::main( "vehicle_80s_hatch1_yel_destructible_mp" ); + + +include,vehicle_80s_hatch1_yel_destructible_mp_80s_hatch1 +include,destructible_vehicle_80s_hatch1_yel_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_hatch1_yel_destructible_mp" +default:"vehicletype" "80s_hatch1" +default:"script_team" "allies" +*/ diff --git a/maps/_80s_sedan1.gsc b/maps/_80s_sedan1.gsc new file mode 100644 index 0000000..23e1647 --- /dev/null +++ b/maps/_80s_sedan1.gsc @@ -0,0 +1,250 @@ +#include maps\_vehicle_aianim; +#include maps\_vehicle; +#using_animtree( "vehicles" ); +main( model, type ) +{ + //SNDFILE=vehicle_80s_car + build_template( "80s_sedan1", model, type ); + build_localinit( ::init_local ); + + build_deathmodel( "vehicle_80s_sedan1_brn", "vehicle_80s_sedan1_brn_destroyed" ); + build_deathmodel( "vehicle_80s_sedan1_green", "vehicle_80s_sedan1_green_destroyed" ); + build_deathmodel( "vehicle_80s_sedan1_red", "vehicle_80s_sedan1_red_destroyed" ); + build_deathmodel( "vehicle_80s_sedan1_silv", "vehicle_80s_sedan1_silv_destroyed" ); + build_deathmodel( "vehicle_80s_sedan1_tan", "vehicle_80s_sedan1_tan_destroyed" ); + build_deathmodel( "vehicle_80s_sedan1_yel", "vehicle_80s_sedan1_yel_destroyed" ); + +// vehicle_80s_sedan1_brn_destructible + build_destructible( "vehicle_80s_sedan1_brn_destructible_mp", "vehicle_80s_sedan1_brn" ); + build_destructible( "vehicle_80s_sedan1_green_destructible_mp", "vehicle_80s_sedan1_green" ); + build_destructible( "vehicle_80s_sedan1_red_destructible_mp", "vehicle_80s_sedan1_red" ); + build_destructible( "vehicle_80s_sedan1_silv_destructible_mp", "vehicle_80s_sedan1_silv" ); + build_destructible( "vehicle_80s_sedan1_tan_destructible_mp", "vehicle_80s_sedan1_tan" ); + build_destructible( "vehicle_80s_sedan1_yel_destructible_mp", "vehicle_80s_sedan1_yel" ); + build_drive( %technical_driving_idle_forward, %technical_driving_idle_backward, 10 ); + + build_treadfx(); + build_life( 999, 500, 1500 ); + build_team( "allies" ); + build_aianims( ::setanims, ::set_vehicle_anims ); + build_compassicon( "automobile", false ); + +} + +init_local() +{ + +} + +set_vehicle_anims( positions ) +{ + return positions; +} + + +#using_animtree( "generic_human" ); + +setanims() +{ + positions = []; + + for ( i = 0;i < 1;i++ ) + positions[ i ] = spawnstruct(); + positions[ 0 ].sittag = "tag_driver"; + positions[ 0 ].idle = %luxurysedan_driver_idle; + + return positions; +} + + +/*QUAKED script_vehicle_80s_sedan1_brn (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_brn" ); + + +include,vehicle_80s_sedan1_brn_80s_sedan1 +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_brn" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_brn_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_brn_destructible_mp" ); + + +include,vehicle_80s_sedan1_brn_destructible_mp_80s_sedan1 +include,destructible_vehicle_80s_sedan1_brn_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_brn_destructible_mp" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_green (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_green" ); + + +include,vehicle_80s_sedan1_green_80s_sedan1 +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_green" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_green_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_green_destructible_mp" ); + + +include,vehicle_80s_sedan1_green_destructible_mp_80s_sedan1 +include,destructible_vehicle_80s_sedan1_green_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_green_destructible_mp" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_red (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_red" ); + + +include,vehicle_80s_sedan1_red_80s_sedan1 +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_red" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_red_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_red_destructible_mp" ); + + +include,vehicle_80s_sedan1_red_destructible_mp_80s_sedan1 +include,destructible_vehicle_80s_sedan1_red_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_red_destructible_mp" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_silv (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_silv" ); + + +include,vehicle_80s_sedan1_silv_80s_sedan1 +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_silv" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_silv_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_silv_destructible_mp" ); + + +include,vehicle_80s_sedan1_silv_destructible_mp_80s_sedan1 +include,destructible_vehicle_80s_sedan1_silv_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_silv_destructible_mp" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_tan (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_tan" ); + + +include,vehicle_80s_sedan1_tan_80s_sedan1 +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_tan" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_tan_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_tan_destructible_mp" ); + + +include,vehicle_80s_sedan1_tan_destructible_mp_80s_sedan1 +include,destructible_vehicle_80s_sedan1_tan_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_tan_destructible_mp" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_yel (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_yel" ); + + +include,vehicle_80s_sedan1_yel_80s_sedan1 +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_yel" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_sedan1_yel_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_sedan1::main( "vehicle_80s_sedan1_yel_destructible_mp" ); + + +include,vehicle_80s_sedan1_yel_destructible_mp_80s_sedan1 +include,destructible_vehicle_80s_sedan1_yel_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_sedan1_yel_destructible_mp" +default:"vehicletype" "80s_sedan1" +default:"script_team" "allies" +*/ \ No newline at end of file diff --git a/maps/_80s_wagon1.gsc b/maps/_80s_wagon1.gsc new file mode 100644 index 0000000..eff0c1e --- /dev/null +++ b/maps/_80s_wagon1.gsc @@ -0,0 +1,177 @@ +#include maps\_vehicle_aianim; +#include maps\_vehicle; +#using_animtree( "vehicles" ); + + +main( model, type ) +{ + //SNDFILE=vehicle_80s_car + build_template( "80s_wagon1", model, type ); + build_localinit( ::init_local ); + build_destructible( "vehicle_80s_wagon1_brn_destructible_mp", "vehicle_80s_wagon1_brn" ); + build_destructible( "vehicle_80s_wagon1_green_destructible_mp", "vehicle_80s_wagon1_green" ); + build_destructible( "vehicle_80s_wagon1_red_destructible_mp", "vehicle_80s_wagon1_red" ); + build_destructible( "vehicle_80s_wagon1_silv_destructible_mp", "vehicle_80s_wagon1_silv" ); + build_destructible( "vehicle_80s_wagon1_tan_destructible_mp", "vehicle_80s_wagon1_tan" ); + build_destructible( "vehicle_80s_wagon1_yel_destructible_mp", "vehicle_80s_wagon1_yel" ); + + build_compassicon( "automobile", false ); + build_drive( %technical_driving_idle_forward, %technical_driving_idle_backward, 10 ); + + build_deathfx( "explosions/large_vehicle_explosion", undefined, "explo_metal_rand" ); +// build_drive( %technical_driving_idle_forward, %technical_driving_idle_backward, 10 ); + +// build_treadfx(); + build_life( 999, 500, 1500 ); + build_team( "allies" ); + build_aianims( ::setanims, ::set_vehicle_anims ); +} + +init_local() +{ + +} + +set_vehicle_anims( positions ) +{ + return positions; +} + + +#using_animtree( "generic_human" ); + +setanims() +{ + positions = []; + return positions;// no anims yet +/* + for(i=0;i<4;i++) + positions[i] = spawnstruct(); + + positions[0].sittag = "body_animate_jnt"; + positions[1].sittag = "body_animate_jnt"; + positions[2].sittag = "tag_passenger"; + positions[3].sittag = "body_animate_jnt"; + + positions[0].idle = %humvee_driver_climb_idle; + positions[1].idle = %humvee_passenger_idle_L; + positions[2].idle = %humvee_passenger_idle_R; + positions[3].idle = %humvee_passenger_idle_R; + + positions[0].getout = %humvee_driver_climb_out; + positions[1].getout = %humvee_passenger_out_L; + positions[2].getout = %humvee_passenger_out_R; + positions[3].getout = %humvee_passenger_out_R; + + positions[0].getin = %humvee_driver_climb_in; + positions[1].getin = %humvee_passenger_in_L; + positions[2].getin = %humvee_passenger_in_R; + positions[3].getin = %humvee_passenger_in_R; + +*/ + return positions; +} + + + +/*QUAKED script_vehicle_80s_wagon1_brn_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_wagon1::main( "vehicle_80s_wagon1_brn_destructible_mp" ); + + +include,vehicle_80s_wagon1_brn_destructible_mp_80s_wagon1 +include,destructible_vehicle_80s_wagon1_brn_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_wagon1_brn_destructible_mp" +default:"vehicletype" "80s_wagon1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_wagon1_green_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_wagon1::main( "vehicle_80s_wagon1_green_destructible_mp" ); + + +include,vehicle_80s_wagon1_green_destructible_mp_80s_wagon1 +include,destructible_vehicle_80s_wagon1_green_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_wagon1_green_destructible_mp" +default:"vehicletype" "80s_wagon1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_wagon1_red_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_wagon1::main( "vehicle_80s_wagon1_red_destructible_mp" ); + + +include,vehicle_80s_wagon1_red_destructible_mp_80s_wagon1 +include,destructible_vehicle_80s_wagon1_red_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_wagon1_red_destructible_mp" +default:"vehicletype" "80s_wagon1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_wagon1_silv_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_wagon1::main( "vehicle_80s_wagon1_silv_destructible_mp" ); + + +include,vehicle_80s_wagon1_silv_destructible_mp_80s_wagon1 +include,destructible_vehicle_80s_wagon1_silv_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_wagon1_silv_destructible_mp" +default:"vehicletype" "80s_wagon1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_wagon1_tan_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_wagon1::main( "vehicle_80s_wagon1_tan_destructible_mp" ); + + +include,vehicle_80s_wagon1_tan_destructible_mp_80s_wagon1 +include,destructible_vehicle_80s_wagon1_tan_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_wagon1_tan_destructible_mp" +default:"vehicletype" "80s_wagon1" +default:"script_team" "allies" +*/ + +/*QUAKED script_vehicle_80s_wagon1_yel_destructible_mp (1 0 0) (-16 -16 -24) (16 16 32) USABLE SPAWNER + + +maps\_80s_wagon1::main( "vehicle_80s_wagon1_yel_destructible_mp" ); + + +include,vehicle_80s_wagon1_yel_destructible_mp_80s_wagon1 +include,destructible_vehicle_80s_wagon1_yel_destructible_mp +sound,vehicle_car_exp,vehicle_standard,all_sp +sound,vehicle_80s_car,vehicle_standard,all_sp + + +defaultmdl="vehicle_80s_wagon1_yel_destructible_mp" +default:"vehicletype" "80s_wagon1" +default:"script_team" "allies" +*/ \ No newline at end of file diff --git a/maps/_ac130.gsc b/maps/_ac130.gsc new file mode 100644 index 0000000..6904c8b --- /dev/null +++ b/maps/_ac130.gsc @@ -0,0 +1,1987 @@ +#include maps\_utility; +#include common_scripts\utility; +#using_animtree( "ac130" ); + +init( player ) +{ + // init all context sensative dialog + maps\_ac130_snd::main(); + + // co-op: player not used anymore + //assert( isdefined( player ) ); + //assert( player.classname == "player" ); + + setDvarIfUninitialized( "ac130_enabled", "1" ); + setDvarIfUninitialized( "ac130_post_effects_enabled", "1" ); + setDvarIfUninitialized( "ac130_debug_weapons", "0" ); + setDvarIfUninitialized( "ac130_debug_context_sensative_dialog", "0" ); + setDvarIfUninitialized( "ac130_debug_friendly_count", "0" ); + setDvarIfUninitialized( "ac130_hud_text_misc", "1" ); + setDvarIfUninitialized( "ac130_hud_text_thermal", "1" ); + setDvarIfUninitialized( "ac130_hud_text_weapons", "1" ); + setDvarIfUninitialized( "ac130_target_markers", "0" ); + setDvarIfUninitialized( "ac130_thermal_enabled", "1" ); + + //0 - player can freely engage targets before being authorized + //1 - player fails the mission for engage targets before being authorized + //2 - player gets red X over crosshairs when trying to fire before being authorized + setDvarIfUninitialized( "ac130_pre_engagement_mode", "2" ); + setDvarIfUninitialized( "ac130_alternate_controls", "0" ); + setDvarIfUninitialized( "ac130_ragdoll_deaths", "1" ); + + precacheShader( "ac130_overlay_25mm" ); + precacheShader( "ac130_overlay_40mm" ); + precacheShader( "ac130_overlay_105mm" ); + precacheShader( "ac130_overlay_grain" ); + precacheShader( "ac130_overlay_nofire" ); + precacheShader( "ac130_hud_target" ); + precacheShader( "ac130_hud_target_flash" ); + precacheShader( "ac130_hud_target_offscreen" ); + precacheShader( "ac130_friendly_fire_icon" ); + precacheShader( "black" ); + + // \n0 A-G MAN NARO + precacheString( &"AC130_HUD_TOP_BAR" ); + // RAY\nFF 30\nLIR\n\nBORE + precacheString( &"AC130_HUD_LEFT_BLOCK" ); + // N\nT\n\nS\nF\n\nQ\nZ\n\nT\nG\nT + precacheString( &"AC130_HUD_RIGHT_BLOCK" ); + precacheString( &"AC130_HUD_RIGHT_BLOCK_SHORT" ); + // L1514 RDY + precacheString( &"AC130_HUD_BOTTOM_BLOCK" ); + // WHOT + precacheString( &"AC130_HUD_THERMAL_WHOT" ); + // BHOT + precacheString( &"AC130_HUD_THERMAL_BHOT" ); + // 105 mm + precacheString( &"AC130_HUD_WEAPON_105MM" ); + // 40 mm + precacheString( &"AC130_HUD_WEAPON_40MM" ); + // 25 mm + precacheString( &"AC130_HUD_WEAPON_25MM" ); + // &&1 AGL + precacheString( &"AC130_HUD_AGL" ); + // Press ^3[{weapnext}]^7 to cycle through weapons. + precachestring( &"AC130_HINT_CYCLE_WEAPONS" ); + // Friendlies: &&1 + precachestring( &"AC130_DEBUG_FRIENDLY_COUNT" ); + // Too many friendlies have been KIA. Mission failed. + precachestring( &"AC130_FRIENDLIES_DEAD" ); + // Friendly fire will not be tolerated!\nWatch for blinking IR strobes on friendly units! + precachestring( &"AC130_FRIENDLY_FIRE" ); + // Provide AC-130 air support for friendly SAS ground units. + + + // Pull [{+speed}] to control zoom and pull [{+attack}] to fire. + precachestring( &"SCRIPT_PLATFORM_AC130_HINT_ZOOM_AND_FIRE" ); + // Press [{+usereload}] to toggle thermal vision\nbetween white hot and black hot. + precachestring( &"SCRIPT_PLATFORM_AC130_HINT_TOGGLE_THERMAL" ); + + // Provide AC-130 air support for friendly ground units. + precachestring( &"CO_AC130_OBJECTIVE_COOP_AC130_GUNNER" ); + // Regroup with any survivors from Bravo Team at the crash site. + precachestring( &"CO_AC130_OBJECTIVE_COOP_GROUND_PLAYER" ); + + precacheShader( "popmenu_bg" ); + + if ( is_coop() ) + precacheModel( "vehicle_ac130_coop" ); + + if ( getdvar( "ac130_alternate_controls", "0" ) == "0" ) + { + precacheItem( "ac130_25mm" ); + precacheItem( "ac130_40mm" ); + precacheItem( "ac130_105mm" ); + } + else + { + precacheItem( "ac130_25mm_alt" ); + precacheItem( "ac130_40mm_alt" ); + precacheItem( "ac130_105mm_alt" ); + } + + precacheShellShock( "ac130" ); + + level._effect[ "cloud" ] = loadfx( "misc/ac130_cloud" ); + + //if ( is_coop() ) + level._effect[ "beacon" ] = loadfx( "misc/ir_beacon_coop" ); + //else + //level._effect[ "beacon" ] = loadfx( "misc/ir_beacon" ); + + // ac130 muzzleflash effects for player on ground to see + level._effect[ "coop_muzzleflash_105mm" ] = loadfx( "muzzleflashes/ac130_105mm" ); + level._effect[ "coop_muzzleflash_40mm" ] = loadfx( "muzzleflashes/ac130_40mm" ); + + level._custom_friendly_fire_message = "@AC130_FRIENDLY_FIRE"; + level._custom_friendly_fire_shader = "ac130_friendly_fire_icon"; + + level._spawnerCallbackThread = ::spawn_callback_thread; + level._vehicleSpawnCallbackThread = ::context_Sensative_Dialog_VehicleSpawn; + + level._enemiesKilledInTimeWindow = 0; + + level._radioForcedTransmissionQueue = []; + + level._lastRadioTransmission = getTime(); + + level._color[ "white" ] = ( 1, 1, 1 ); + level._color[ "red" ] = ( 1, 0, 0 ); + level._color[ "blue" ] = ( .1, .3, 1 ); + + level._cosine = []; + level._cosine[ "45" ] = cos( 45 ); + level._cosine[ "5" ] = cos( 5 ); + + level._badplaceCount = 0; + level._badplaceMax = 15; + + level._badplaceRadius[ "ac130_25mm" ] = 800; + level._badplaceRadius[ "ac130_40mm" ] = 1000; + level._badplaceRadius[ "ac130_105mm" ] = 1600; + level._badplaceRadius[ "ac130_25mm_alt" ] = level._badplaceRadius[ "ac130_25mm" ]; + level._badplaceRadius[ "ac130_40mm_alt" ] = level._badplaceRadius[ "ac130_40mm" ]; + level._badplaceRadius[ "ac130_105mm_alt" ] = level._badplaceRadius[ "ac130_105mm" ]; + + level._badplaceDuration[ "ac130_25mm" ] = 2.0; + level._badplaceDuration[ "ac130_40mm" ] = 9.0; + level._badplaceDuration[ "ac130_105mm" ] = 12.0; + level._badplaceDuration[ "ac130_25mm_alt" ] = level._badplaceDuration[ "ac130_25mm" ]; + level._badplaceDuration[ "ac130_40mm_alt" ] = level._badplaceDuration[ "ac130_40mm" ]; + level._badplaceDuration[ "ac130_105mm_alt" ] = level._badplaceDuration[ "ac130_105mm" ]; + + level._physicsSphereRadius[ "ac130_25mm" ] = 60; + level._physicsSphereRadius[ "ac130_40mm" ] = 600; + level._physicsSphereRadius[ "ac130_105mm" ] = 1000; + level._physicsSphereRadius[ "ac130_25mm_alt" ] = level._physicsSphereRadius[ "ac130_25mm" ]; + level._physicsSphereRadius[ "ac130_40mm_alt" ] = level._physicsSphereRadius[ "ac130_40mm" ]; + level._physicsSphereRadius[ "ac130_105mm_alt" ] = level._physicsSphereRadius[ "ac130_105mm" ]; + + level._physicsSphereForce[ "ac130_25mm" ] = 0; + level._physicsSphereForce[ "ac130_40mm" ] = 3.0; + level._physicsSphereForce[ "ac130_105mm" ] = 6.0; + level._physicsSphereForce[ "ac130_25mm_alt" ] = level._physicsSphereForce[ "ac130_25mm" ]; + level._physicsSphereForce[ "ac130_40mm_alt" ] = level._physicsSphereForce[ "ac130_40mm" ]; + level._physicsSphereForce[ "ac130_105mm_alt" ] = level._physicsSphereForce[ "ac130_105mm" ]; + + level._weaponReloadTime[ "ac130_25mm" ] = 0.05; + level._weaponReloadTime[ "ac130_40mm" ] = 0.5; + level._weaponReloadTime[ "ac130_105mm" ] = 6.0; + level._weaponReloadTime[ "ac130_25mm_alt" ] = level._weaponReloadTime[ "ac130_25mm" ]; + level._weaponReloadTime[ "ac130_40mm_alt" ] = level._weaponReloadTime[ "ac130_40mm" ]; + level._weaponReloadTime[ "ac130_105mm_alt" ] = level._weaponReloadTime[ "ac130_105mm" ]; + + level._weaponFriendlyCloseDistance[ "ac130_25mm" ] = 150; + level._weaponFriendlyCloseDistance[ "ac130_40mm" ] = 500; + level._weaponFriendlyCloseDistance[ "ac130_105mm" ] = 1000; + level._weaponFriendlyCloseDistance[ "ac130_25mm_alt" ] = level._weaponFriendlyCloseDistance[ "ac130_25mm" ]; + level._weaponFriendlyCloseDistance[ "ac130_40mm_alt" ] = level._weaponFriendlyCloseDistance[ "ac130_40mm" ]; + level._weaponFriendlyCloseDistance[ "ac130_105mm_alt" ] = level._weaponFriendlyCloseDistance[ "ac130_105mm" ]; + + level._weaponReadyToFire[ "ac130_25mm" ] = true; + level._weaponReadyToFire[ "ac130_40mm" ] = true; + level._weaponReadyToFire[ "ac130_105mm" ] = true; + level._weaponReadyToFire[ "ac130_25mm_alt" ] = level._weaponReadyToFire[ "ac130_25mm" ]; + level._weaponReadyToFire[ "ac130_40mm_alt" ] = level._weaponReadyToFire[ "ac130_40mm" ]; + level._weaponReadyToFire[ "ac130_105mm_alt" ] = level._weaponReadyToFire[ "ac130_105mm" ]; + + level._ac130_Speed[ "move" ] = 250; + level._ac130_Speed[ "rotate" ] = 70; + + level._enemiesKilledByPlayer = 0; + + //flag_init( "ir_beakons_on" ); + flag_init( "allow_context_sensative_dialog" ); + flag_init( "clear_to_engage" ); + flag_init( "player_changed_weapons" ); + + level._ac130 = spawn( "script_model", level._player getOrigin() ); + level._ac130 setModel( "c130_zoomrig" ); + level._ac130.angles = ( 0, level._player.angles[ 1 ], 0 ); + + // preaching done + // first wait command called below + // no more coop ac130 - just assume level.player is the gunner + if ( !isdefined( player ) ) + { + flag_wait( "character_selected" ); + level._ac130player = maps\_loadout::coop_gamesetup_ac130();// coop_gamesetup(); + } + else + { + level._ac130player = player; + } + + level._ac130gunner = level._ac130player; + + level._ac130player takeallweapons(); + level._ac130player.ignoreme = true; + + level._ac130player ThermalVisionOn(); + level._ac130player ThermalVisionFOFOverlayOn(); + level._ac130player LaserAltViewOn(); + + level._ac130_fontscale = 2.5; + level._ac130_crosshair_size_x = 640; + level._ac130_crosshair_size_y = 480; + level._ac130_right_hud_offset = 0; + level._ac130_left_hud_offset = 0; + + if ( IsSplitscreen() ) + { + level._ac130_fontscale = 1.5; + level._ac130_crosshair_size_x = int( 640 * 0.8 ); + level._ac130_crosshair_size_y = int( 480 * 0.8 ); + + if ( level._ac130player == level._player ) + { + level._ac130_left_hud_offset = 0; + level._ac130_right_hud_offset = -20; + } + else + { + level._ac130_left_hud_offset = 20; + level._ac130_right_hud_offset = 0; + } + } + + if ( is_coop() ) + { + level._ac130player hide(); + level._ac130player.has_no_ir = true; + } + + setsaveddvar( "scr_dof_enable", "0" ); + // reset ac130 position after character switch + level._ac130.origin = level._ac130player getOrigin(); + level._ac130 hide(); + + if ( getdvar( "ac130_alternate_controls", "0" ) == "0" ) + { + level._ac130player giveweapon( "ac130_105mm" ); + level._ac130player switchtoweapon( "ac130_105mm" ); + } + else + { + level._ac130player giveweapon( "ac130_105mm_alt" ); + level._ac130player switchtoweapon( "ac130_105mm_alt" ); + } + level._ac130player SetActionSlot( 1, "" ); + Setammo(); + + if ( getdvar( "ac130_enabled", "1" ) == "1" ) + { + thread overlay(); + thread HUDItemsOff(); + thread attachPlayer(); + thread changeWeapons(); + thread weaponFiredThread(); + thread thermalVision(); + if ( getdvar( "ac130_pre_engagement_mode", "2" ) == "1" ) + thread failMissionForEngaging(); + if ( getdvar( "ac130_pre_engagement_mode", "2" ) == "2" ) + thread nofireCrossHair(); + thread context_Sensative_Dialog(); + thread shotFired(); + thread clouds(); + thread maps\_ac130_amb::main(); + thread rotatePlane( "on" ); + thread hud_target_blink_timer(); + thread ac130_spawn(); + } +} + +ac130_spawn() +{ + wait 0.05; + if ( !is_coop() ) + return; + + ac130model = spawn( "script_model", level._ac130 getTagOrigin( "tag_player" ) ); + ac130model setModel( "vehicle_ac130_coop" ); + + ac130model playLoopSound( "veh_ac130_ext_dist" ); + + ac130model linkTo( level._ac130, "tag_player", ( 0, 0, 100 ), ( -25, 0, 0 ) ); +} + +overlay() +{ + wait 0.05; + if ( isdefined( level._doing_cinematic ) ) + level waittill( "introscreen_black" ); + + level._HUDItem = []; + + level._HUDItem[ "crosshairs" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "crosshairs" ].x = 0; + level._HUDItem[ "crosshairs" ].y = 0; + level._HUDItem[ "crosshairs" ].alignX = "center"; + level._HUDItem[ "crosshairs" ].alignY = "middle"; + level._HUDItem[ "crosshairs" ].horzAlign = "center"; + level._HUDItem[ "crosshairs" ].vertAlign = "middle"; + level._HUDItem[ "crosshairs" ] setshader( "ac130_overlay_105mm", level._ac130_crosshair_size_x, level._ac130_crosshair_size_y ); + level._HUDItem[ "crosshairs" ].sort = -2; + + if ( getdvar( "ac130_hud_text_misc", "1" ) == "1" ) + { + level._HUDItem[ "hud_text_top" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "hud_text_top" ].x = 0 + level._ac130_left_hud_offset; + level._HUDItem[ "hud_text_top" ].y = 0; + level._HUDItem[ "hud_text_top" ].alignX = "left"; + level._HUDItem[ "hud_text_top" ].alignY = "top"; + level._HUDItem[ "hud_text_top" ].horzAlign = "left"; + level._HUDItem[ "hud_text_top" ].vertAlign = "top"; + level._HUDItem[ "hud_text_top" ].fontScale = level._ac130_fontscale; + // \n0 A-G MAN NARO + level._HUDItem[ "hud_text_top" ] settext( &"AC130_HUD_TOP_BAR" ); + level._HUDItem[ "hud_text_top" ].alpha = 1.0; + + level._HUDItem[ "hud_text_left" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "hud_text_left" ].x = 0 + level._ac130_left_hud_offset; + level._HUDItem[ "hud_text_left" ].y = 60; + level._HUDItem[ "hud_text_left" ].alignX = "left"; + level._HUDItem[ "hud_text_left" ].alignY = "top"; + level._HUDItem[ "hud_text_left" ].horzAlign = "left"; + level._HUDItem[ "hud_text_left" ].vertAlign = "top"; + level._HUDItem[ "hud_text_left" ].fontScale = level._ac130_fontscale; + // RAY\nFF 30\nLIR\n\nBORE + level._HUDItem[ "hud_text_left" ] settext( &"AC130_HUD_LEFT_BLOCK" ); + level._HUDItem[ "hud_text_left" ].alpha = 1.0; + + level._HUDItem[ "hud_text_right" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "hud_text_right" ].x = 0 + level._ac130_right_hud_offset; + level._HUDItem[ "hud_text_right" ].y = 50; + level._HUDItem[ "hud_text_right" ].alignX = "right"; + level._HUDItem[ "hud_text_right" ].alignY = "top"; + level._HUDItem[ "hud_text_right" ].horzAlign = "right"; + level._HUDItem[ "hud_text_right" ].vertAlign = "top"; + level._HUDItem[ "hud_text_right" ].fontScale = level._ac130_fontscale; + + // N\nT\n\nS\nF\n\nQ\nZ\n\nT\nG\nT + if ( IsSplitscreen() ) + { + level._HUDItem[ "hud_text_right" ] settext( &"AC130_HUD_RIGHT_BLOCK_SHORT" ); + } + else + { + level._HUDItem[ "hud_text_right" ] settext( &"AC130_HUD_RIGHT_BLOCK" ); + } + + level._HUDItem[ "hud_text_right" ].alpha = 1.0; + + level._HUDItem[ "hud_text_bottom" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "hud_text_bottom" ].x = 0; + level._HUDItem[ "hud_text_bottom" ].y = 0; + level._HUDItem[ "hud_text_bottom" ].alignX = "center"; + level._HUDItem[ "hud_text_bottom" ].alignY = "bottom"; + level._HUDItem[ "hud_text_bottom" ].horzAlign = "center"; + level._HUDItem[ "hud_text_bottom" ].vertAlign = "bottom"; + level._HUDItem[ "hud_text_bottom" ].fontScale = level._ac130_fontscale; + // L1514 RDY + level._HUDItem[ "hud_text_bottom" ] settext( &"AC130_HUD_BOTTOM_BLOCK" ); + level._HUDItem[ "hud_text_bottom" ].alpha = 1.0; + } + + if ( getdvar( "ac130_hud_text_thermal", "1" ) == "1" ) + { + level._HUDItem[ "thermal_mode" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "thermal_mode" ].x = -80 + level._ac130_right_hud_offset; + level._HUDItem[ "thermal_mode" ].y = 50; + level._HUDItem[ "thermal_mode" ].alignX = "right"; + level._HUDItem[ "thermal_mode" ].alignY = "top"; + level._HUDItem[ "thermal_mode" ].horzAlign = "right"; + level._HUDItem[ "thermal_mode" ].vertAlign = "top"; + level._HUDItem[ "thermal_mode" ].fontScale = level._ac130_fontscale; + // WHOT + level._HUDItem[ "thermal_mode" ] settext( &"AC130_HUD_THERMAL_WHOT" ); + level._HUDItem[ "thermal_mode" ].alpha = 1.0; + } + + if ( getdvar( "ac130_hud_text_weapons", "1" ) == "1" ) + { + level._HUDItem[ "weapon_text" ][ 0 ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "weapon_text" ][ 0 ].x = 0 + level._ac130_left_hud_offset; + level._HUDItem[ "weapon_text" ][ 0 ].y = 0; + level._HUDItem[ "weapon_text" ][ 0 ].alignX = "left"; + level._HUDItem[ "weapon_text" ][ 0 ].alignY = "bottom"; + level._HUDItem[ "weapon_text" ][ 0 ].horzAlign = "left"; + level._HUDItem[ "weapon_text" ][ 0 ].vertAlign = "bottom"; + level._HUDItem[ "weapon_text" ][ 0 ].fontScale = level._ac130_fontscale; + // 105 mm + level._HUDItem[ "weapon_text" ][ 0 ] settext( &"AC130_HUD_WEAPON_105MM" ); + level._HUDItem[ "weapon_text" ][ 0 ].alpha = 1.0; + + level._HUDItem[ "weapon_text" ][ 1 ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "weapon_text" ][ 1 ].x = 0 + level._ac130_left_hud_offset; + level._HUDItem[ "weapon_text" ][ 1 ].y = -30; + level._HUDItem[ "weapon_text" ][ 1 ].alignX = "left"; + level._HUDItem[ "weapon_text" ][ 1 ].alignY = "bottom"; + level._HUDItem[ "weapon_text" ][ 1 ].horzAlign = "left"; + level._HUDItem[ "weapon_text" ][ 1 ].vertAlign = "bottom"; + level._HUDItem[ "weapon_text" ][ 1 ].fontScale = level._ac130_fontscale; + // 40 mm + level._HUDItem[ "weapon_text" ][ 1 ] settext( &"AC130_HUD_WEAPON_40MM" ); + level._HUDItem[ "weapon_text" ][ 1 ].alpha = 1.0; + + level._HUDItem[ "weapon_text" ][ 2 ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "weapon_text" ][ 2 ].x = 0 + level._ac130_left_hud_offset; + level._HUDItem[ "weapon_text" ][ 2 ].y = -60; + level._HUDItem[ "weapon_text" ][ 2 ].alignX = "left"; + level._HUDItem[ "weapon_text" ][ 2 ].alignY = "bottom"; + level._HUDItem[ "weapon_text" ][ 2 ].horzAlign = "left"; + level._HUDItem[ "weapon_text" ][ 2 ].vertAlign = "bottom"; + level._HUDItem[ "weapon_text" ][ 2 ].fontScale = level._ac130_fontscale; + // 25 mm + level._HUDItem[ "weapon_text" ][ 2 ] settext( &"AC130_HUD_WEAPON_25MM" ); + level._HUDItem[ "weapon_text" ][ 2 ].alpha = 1.0; + } + + thread hud_timer(); + thread overlay_coords(); + thread blink_hud_elem( 0 ); + + if ( getdvar( "ac130_thermal_enabled", "1" ) == "1" ) + { + level._HUDItem[ "grain" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "grain" ].x = 0; + level._HUDItem[ "grain" ].y = 0; + level._HUDItem[ "grain" ].alignX = "left"; + level._HUDItem[ "grain" ].alignY = "top"; + level._HUDItem[ "grain" ].horzAlign = "fullscreen"; + level._HUDItem[ "grain" ].vertAlign = "fullscreen"; + level._HUDItem[ "grain" ] setshader( "ac130_overlay_grain", 640, 480 ); + level._HUDItem[ "grain" ].alpha = 0.4; + level._HUDItem[ "grain" ].sort = -3; + } + + if ( getdvar( "ac130_thermal_enabled", "1" ) == "1" ) + thread ac130ShellShock(); + + wait 0.05; + + + if ( !is_coop() ) + setsaveddvar( "g_friendlynamedist", 0 ); + else + setsaveddvar( "g_friendlynamedist", 2000 ); + + // sets dvar for ac130 player number for menu to hide the correct HUD components + setdvar( "ac130_player_num", 1 ); + if ( level._ac130player == level._player ) + setdvar( "ac130_player_num", 0 ); + + if ( !is_coop() ) + setsaveddvar( "compass", 0 ); + + if ( getdvar( "ac130_thermal_enabled", "1" ) == "1" ) + { + level._ac130player SetBlurForPlayer( 1.2, 0 ); + } +} + +HUDItemsOff() +{ + for ( ;; ) + { + if ( getdvarint( "ac130_post_effects_enabled", 1 ) == 0 ) + break; + wait 1.0; + } + + level notify( "post_effects_disabled" ); + + level._ac130player SetBlurForPlayer( 0, 0 ); + + hud_items = []; + hud_items[ hud_items.size ] = "hud_text_top"; + hud_items[ hud_items.size ] = "hud_text_left"; + hud_items[ hud_items.size ] = "hud_text_right"; + hud_items[ hud_items.size ] = "hud_text_bottom"; + hud_items[ hud_items.size ] = "thermal_mode"; + hud_items[ hud_items.size ] = "grain"; + hud_items[ hud_items.size ] = "timer"; + hud_items[ hud_items.size ] = "coordinate_long"; + hud_items[ hud_items.size ] = "coordinate_lat"; + hud_items[ hud_items.size ] = "coordinate_agl"; + + for ( i = 0 ; i < hud_items.size ; i++ ) + { + if ( isdefined( level._HUDItem[ hud_items[ i ] ] ) ) + level._HUDItem[ hud_items[ i ] ] destroy(); + } +} + +hud_timer() +{ + if ( is_specialop() ) + { + return; + } + + if ( getdvar( "ac130_hud_text_misc", "1" ) == "0" ) + return; + + level endon( "post_effects_disabled" ); + + level._HUDItem[ "timer" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "timer" ].x = -100; + level._HUDItem[ "timer" ].y = 0; + level._HUDItem[ "timer" ].alignX = "right"; + level._HUDItem[ "timer" ].alignY = "bottom"; + level._HUDItem[ "timer" ].horzAlign = "right"; + level._HUDItem[ "timer" ].vertAlign = "bottom"; + level._HUDItem[ "timer" ].fontScale = level._ac130_fontscale; + level._HUDItem[ "timer" ] setTimer( 1.0 ); + level._HUDItem[ "timer" ].alpha = 1.0; + + level waittill( "start_clock" ); + + level._HUDItem[ "timer" ] setTimerUp( 1.0 ); +} + +overlay_coords() +{ + if ( getdvar( "ac130_hud_text_misc", "1" ) == "0" ) + return; + + level._HUDItem[ "coordinate_long" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "coordinate_long" ].x = -100 + level._ac130_right_hud_offset; + level._HUDItem[ "coordinate_long" ].y = 0; + level._HUDItem[ "coordinate_long" ].alignX = "right"; + level._HUDItem[ "coordinate_long" ].alignY = "top"; + level._HUDItem[ "coordinate_long" ].horzAlign = "right"; + level._HUDItem[ "coordinate_long" ].vertAlign = "top"; + level._HUDItem[ "coordinate_long" ].fontScale = level._ac130_fontscale; + level._HUDItem[ "coordinate_long" ].alpha = 1.0; + + level._HUDItem[ "coordinate_lat" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "coordinate_lat" ].x = 0 + level._ac130_right_hud_offset; + level._HUDItem[ "coordinate_lat" ].y = 0; + level._HUDItem[ "coordinate_lat" ].alignX = "right"; + level._HUDItem[ "coordinate_lat" ].alignY = "top"; + level._HUDItem[ "coordinate_lat" ].horzAlign = "right"; + level._HUDItem[ "coordinate_lat" ].vertAlign = "top"; + level._HUDItem[ "coordinate_lat" ].fontScale = level._ac130_fontscale; + level._HUDItem[ "coordinate_lat" ].alpha = 1.0; + + level._HUDItem[ "coordinate_agl" ] = newClientHudElem( level._ac130player ); + level._HUDItem[ "coordinate_agl" ].x = 0 + level._ac130_right_hud_offset; + level._HUDItem[ "coordinate_agl" ].y = 20; + level._HUDItem[ "coordinate_agl" ].alignX = "right"; + level._HUDItem[ "coordinate_agl" ].alignY = "top"; + level._HUDItem[ "coordinate_agl" ].horzAlign = "right"; + level._HUDItem[ "coordinate_agl" ].vertAlign = "top"; + level._HUDItem[ "coordinate_agl" ].fontScale = level._ac130_fontscale; + // &&1 AGL + level._HUDItem[ "coordinate_agl" ].label = ( &"AC130_HUD_AGL" ); + level._HUDItem[ "coordinate_agl" ].alpha = 1.0; + + level endon( "post_effects_disabled" ); + + wait 0.05; + for ( ;; ) + { + level._HUDItem[ "coordinate_long" ] setValue( abs( int( level._ac130player.origin[ 0 ] ) ) ); + level._HUDItem[ "coordinate_lat" ] setValue( abs( int( level._ac130player.origin[ 1 ] ) ) ); + + pos = physicstrace( level._ac130player.origin, level._ac130player.origin - ( 0, 0, 100000 ) ); + if ( ( isdefined( pos ) ) && ( isdefined( pos[ 2 ] ) ) ) + { + alt = ( ( level._ac130player.origin[ 2 ] - pos[ 2 ] ) * 1.5 ); + level._HUDItem[ "coordinate_agl" ] setValue( abs( int( alt ) ) ); + } + + wait( 0.75 + randomfloat( 2 ) ); + } +} + +ac130ShellShock() +{ + level endon( "post_effects_disabled" ); + duration = 5; + for ( ;; ) + { + level._ac130player shellshock( "ac130", duration ); + wait duration; + } +} + +rotatePlane( toggle ) +{ + level notify( "stop_rotatePlane_thread" ); + level endon( "stop_rotatePlane_thread" ); + + if ( toggle == "on" ) + { + rampupDegrees = 10; + rotateTime = ( level._ac130_Speed[ "rotate" ] / 360 ) * rampupDegrees; + level._ac130 rotateyaw( level._ac130.angles[ 2 ] + rampupDegrees, rotateTime, rotateTime, 0 ); + + for ( ;; ) + { + level._ac130 rotateyaw( 360, level._ac130_Speed[ "rotate" ] ); + wait level._ac130_Speed[ "rotate" ]; + } + } + else if ( toggle == "off" ) + { + slowdownDegrees = 10; + rotateTime = ( level._ac130_Speed[ "rotate" ] / 360 ) * slowdownDegrees; + level._ac130 rotateyaw( level._ac130.angles[ 2 ] + slowdownDegrees, rotateTime, 0, rotateTime ); + } +} + +attachPlayer() +{ + level._ac130player playerLinkToDelta( level._ac130, "tag_player", 1.0, 65, 65, 40, 40 ); + wait 0.05; + level._ac130player allowProne( false ); + level._ac130player allowCrouch( false ); + level._ac130player setplayerangles( level._ac130 getTagAngles( "tag_player" ) ); + + if ( !is_coop() ) + { + SetSavedDvar( "ammoCounterHide", "1" ); + SetSavedDvar( "hud_showStance", 0 ); + } +} + +getRealAC130Angles() +{ + angle = level._ac130.angles[ 1 ]; + while ( angle >= 360 ) + angle -= 360; + while ( angle < 0 ) + angle += 360; + return angle; +} + +getFlyingAC130AnglesToPoint( vec ) +{ + destAng = vectorToAngles( level._ac130.origin - vec ); + destAng = destAng[ 1 ] + 90; + while ( destAng >= 360 ) + destAng -= 360; + while ( destAng < 0 ) + destAng += 360; + return destAng; +} + +movePlaneToWaypoint( sWaypointTargetname, rotationWait ) +{ + assert( isdefined( sWaypointTargetname ) ); + waypoint = getent( sWaypointTargetname, "targetname" ); + assert( isdefined( waypoint ) ); + assert( isdefined( waypoint.origin ) ); + movePlaneToPoint( waypoint.origin, rotationWait ); +} + +movePlaneToPoint( coordinate, rotationWait ) +{ + level notify( "ac130_reposition" ); + level endon( "ac130_reposition" ); + + if ( !isdefined( rotationWait ) ) + rotationWait = false; + + d = distance( level._ac130.origin, coordinate ); + moveTime = ( d / level._ac130_Speed[ "move" ] ); + if ( moveTime <= 0 ) + return; + accel = moveTime / 2; + decel = moveTime / 2; + + if ( rotationWait ) + { + thread rotatePlane( "off" ); + + // find how many more degrees the plane should turn before facing the right direction + angDiff = getFlyingAC130AnglesToPoint( coordinate ) - getRealAC130Angles(); + if ( angDiff < 0 ) + angDiff = 360 - abs( angDiff ); + //iprintln( "angle differance: " + angDiff ); + + // if the plane isn't close enough to the desired angles then rotate it until the plane is facing it's flying direction + planeCanFly = false; + angleTollerance = 20; + if ( ( angDiff > 0 ) && ( angDiff <= angleTollerance ) ) + planeCanFly = true; + if ( ( angDiff > 360 - angleTollerance ) && ( angDiff < 360 ) ) + planeCanFly = true; + if ( !planeCanFly ) + { + //iprintln( "waiting for plane to rotate " + angDiff + " degrees" ); + //assert( angDiff - 20 > 0 ); + rotateTime = ( level._ac130_Speed[ "rotate" ] / 360 ) * angDiff; + decelTime = 0; + if ( rotateTime > 3.0 ) + decelTime = 3.0; + assert( rotateTime > 0 ); + level._ac130 rotateyaw( angDiff, rotateTime, 0, decelTime ); + wait rotateTime - decelTime; + thread ac130_move_out(); + } + } + + level._ac130 moveto( coordinate, moveTime, accel, decel ); + if ( moveTime > 2.0 ) + { + wait( moveTime - 2.0 ); + level notify( "ac130_almost_at_destination" ); + if ( rotationWait ) + thread rotatePlane( "on" ); + wait 2.0; + } + else + { + wait moveTime; + if ( rotationWait ) + thread rotatePlane( "on" ); + } +} + +ac130_move_in() +{ + if ( isdefined( level._ac130_moving_in ) ) + return; + level._ac130_moving_in = true; + level._ac130_moving_out = undefined; + + thread context_Sensative_Dialog_Play_Random_Group_Sound( "plane", "rolling_in", true ); + + level._ac130 useAnimTree( #animtree ); + level._ac130 setflaggedanim( "ac130_move_in", %ac130_move_in, 1.0, 0.2, 0.1 ); + level._ac130 waittillmatch( "ac130_move_in", "end" ); + + level._ac130_moving_in = undefined; +} + +ac130_move_out() +{ + if ( isdefined( level._ac130_moving_out ) ) + return; + level._ac130_moving_out = true; + level._ac130_moving_in = undefined; + + level._ac130 useAnimTree( #animtree ); + level._ac130 setflaggedanim( "ac130_move_out", %ac130_move_out, 1.0, 0.2, 0.3 ); + level._ac130 waittillmatch( "ac130_move_out", "end" ); + + level._ac130_moving_out = undefined; +} + +changeWeapons() +{ + level._ac130_weapon = []; + + level._ac130_weapon[ 0 ] = spawnstruct(); + level._ac130_weapon[ 0 ].overlay = "ac130_overlay_105mm"; + level._ac130_weapon[ 0 ].fov = "55"; + level._ac130_weapon[ 0 ].name = "105mm"; + // 105 mm + level._ac130_weapon[ 0 ].string = ( &"AC130_HUD_WEAPON_105MM" ); + level._ac130_weapon[ 0 ].hudelem_y = -20; + + level._ac130_weapon[ 1 ] = spawnstruct(); + level._ac130_weapon[ 1 ].overlay = "ac130_overlay_40mm"; + level._ac130_weapon[ 1 ].fov = "25"; + level._ac130_weapon[ 1 ].name = "40mm"; + // 40 mm + level._ac130_weapon[ 1 ].string = ( &"AC130_HUD_WEAPON_40MM" ); + level._ac130_weapon[ 1 ].hudelem_y = -40; + + level._ac130_weapon[ 2 ] = spawnstruct(); + level._ac130_weapon[ 2 ].overlay = "ac130_overlay_25mm"; + level._ac130_weapon[ 2 ].fov = "10"; + level._ac130_weapon[ 2 ].name = "25mm"; + // 25 mm + level._ac130_weapon[ 2 ].string = ( &"AC130_HUD_WEAPON_25MM" ); + level._ac130_weapon[ 2 ].hudelem_y = -60; + + if ( getdvar( "ac130_alternate_controls", "0" ) == "0" ) + { + level._ac130_weapon[ 0 ].weapon = "ac130_105mm"; + level._ac130_weapon[ 1 ].weapon = "ac130_40mm"; + level._ac130_weapon[ 2 ].weapon = "ac130_25mm"; + } + else + { + level._ac130_weapon[ 0 ].weapon = "ac130_105mm_alt"; + level._ac130_weapon[ 1 ].weapon = "ac130_40mm_alt"; + level._ac130_weapon[ 2 ].weapon = "ac130_25mm_alt"; + } + + currentWeapon = 0; + level._currentWeapon = level._ac130_weapon[ currentWeapon ].name; + thread fire_screenShake(); + + notifyOnCommand( "switch weapons", "weapnext" ); + + wait 0.05; + + level._initialFOV = int( getdvar( "cg_fov" ) ); + assert( isdefined( level._initialFOV ) ); + assert( level._initialFOV > 0 ); + + for ( ;; ) + { + level._ac130player waittill( "switch weapons" ); + + // no weapon changes allowed during cinematic + if ( isdefined( level._doing_cinematic ) ) + { + wait 0.05; + continue; + } + + level._ac130player notify( "shot weapon" ); + + currentWeapon++ ; + if ( currentWeapon >= level._ac130_weapon.size ) + currentWeapon = 0; + level._currentWeapon = level._ac130_weapon[ currentWeapon ].name; + + level._HUDItem[ "crosshairs" ] setshader( level._ac130_weapon[ currentWeapon ].overlay, level._ac130_crosshair_size_x, level._ac130_crosshair_size_y ); + + thread blink_crosshairs( level._ac130_weapon[ currentWeapon ].weapon ); + thread blink_hud_elem( currentWeapon ); + + if ( getdvar( "ac130_alternate_controls", "0" ) == "0" ) + { + //setsaveddvar( "cg_fov", level.ac130_weapon[currentWeapon].fov ); + + targetFOV = int( level._ac130_weapon[ currentWeapon ].fov ); + fovFraction = targetFOV / level._initialFOV; + fovFraction = cap_value( fovFraction, 0.2, 2.0 ); + + if ( level._ac130player == level._player ) + setsaveddvar( "cg_playerFovScale0", fovFraction ); + else + setsaveddvar( "cg_playerFovScale1", fovFraction ); + } + + level._ac130player takeallweapons(); + level._ac130player giveweapon( level._ac130_weapon[ currentWeapon ].weapon ); + level._playerWeapon = level._ac130_weapon[ currentWeapon ].weapon; + level._ac130player switchtoweapon( level._ac130_weapon[ currentWeapon ].weapon ); + setAmmo(); + + level._ac130player thread play_sound_on_entity( "ac130_weapon_switch" ); + + flag_set( "player_changed_weapons" ); + } +} + +blink_hud_elem( curentWeapon ) +{ + + level notify( "blinking_weapon_name_hud_elem" ); + level endon( "blinking_weapon_name_hud_elem" ); + + if ( !isdefined( level._HUDItem[ "weapon_text" ] ) ) + return; + + for ( i = 0 ; i < level._HUDItem[ "weapon_text" ].size ; i++ ) + level._HUDItem[ "weapon_text" ][ i ].alpha = 0.5; + + level._HUDItem[ "weapon_text" ][ curentWeapon ].alpha = 1; + for ( ;; ) + { + level._HUDItem[ "weapon_text" ][ curentWeapon ] fadeOverTime( 0.2 ); + level._HUDItem[ "weapon_text" ][ curentWeapon ].alpha = 0; + wait 0.2; + + level._HUDItem[ "weapon_text" ][ curentWeapon ] fadeOverTime( 0.2 ); + level._HUDItem[ "weapon_text" ][ curentWeapon ].alpha = 1; + wait 0.2; + } +} + +blink_crosshairs( weaponName ) +{ + level notify( "stop_blinking_crosshairs" ); + level endon( "stop_blinking_crosshairs" ); + + level._HUDItem[ "crosshairs" ].alpha = 1; + + if ( !issubstr( tolower( weaponName ), "105" ) ) + return; + + waittillframeend; + if ( level._weaponReadyToFire[ weaponName ] ) + return; + + for ( ;; ) + { + level._HUDItem[ "crosshairs" ] fadeOverTime( 0.3 ); + level._HUDItem[ "crosshairs" ].alpha = 0; + wait 0.3; + + level._HUDItem[ "crosshairs" ] fadeOverTime( 0.3 ); + level._HUDItem[ "crosshairs" ].alpha = 1; + wait 0.3; + } +} + +blink_crosshairs_stop() +{ + level notify( "stop_blinking_crosshairs" ); + level._HUDItem[ "crosshairs" ].alpha = 1; +} + +weaponFiredThread() +{ + for ( ;; ) + { + level._ac130player waittill( "weapon_fired" ); + + weaponList = level._ac130player GetWeaponsListPrimaries(); + assert( isdefined( weaponList[ 0 ] ) ); + + if ( !level._weaponReadyToFire[ weaponList[ 0 ] ] ) + continue; + + if ( is_coop() ) + thread weaponFiredCoOpTracer( weaponList[ 0 ] ); + + thread blink_crosshairs( weaponList[ 0 ] ); + + thread weaponReload( weaponList[ 0 ] ); + } +} + +weaponReload( weapon ) +{ + level._weaponReadyToFire[ weapon ] = false; + + wait level._weaponReloadTime[ weapon ] - 0.05; + + level._weaponReadyToFire[ weapon ] = true; + + setAmmo(); +} + +weaponFiredCoOpTracer( weaponName ) +{ + // Only play muzzle effects for 105mm and 40mm + muzzleFX = undefined; + if ( issubstr( tolower( weaponName ), "105" ) ) + muzzleFX = level._effect[ "coop_muzzleflash_105mm" ]; + else if ( issubstr( tolower( weaponName ), "40" ) ) + muzzleFX = level._effect[ "coop_muzzleflash_40mm" ]; + + if ( !isdefined( muzzleFX ) ) + return; + + // Trace to where the player is looking + direction = level._ac130player getPlayerAngles(); + direction_vec = anglesToForward( direction ); + eye = level._ac130player getEye(); + + // Play muzzleflash effect + playFX( muzzleFX, eye, direction_vec ); +} + +thermalVision() +{ + level._ac130player endon( "death" ); + + if ( getdvar( "ac130_thermal_enabled", "1" ) != "1" ) + return; + + level._ac130player visionSetThermalForPlayer( "ac130", 0 ); + inverted = "0"; + + notifyOnCommand( "switch thermal", "+usereload" ); + notifyOnCommand( "switch thermal", "+activate" ); + for ( ;; ) + { + level._ac130player waittill( "switch thermal" ); + + // no thermal changes allowed during cinematic + if ( isdefined( level._doing_cinematic ) ) + { + wait 0.05; + continue; + } + + if ( inverted == "0" ) + { + level._ac130player visionSetThermalForPlayer( "missilecam", 0.62 ); + if ( isdefined( level._HUDItem[ "thermal_mode" ] ) ) + // BHOT + level._HUDItem[ "thermal_mode" ] settext( &"AC130_HUD_THERMAL_BHOT" ); + inverted = "1"; + } + else + { + level._ac130player visionSetThermalForPlayer( "ac130", 0.51 ); + if ( isdefined( level._HUDItem[ "thermal_mode" ] ) ) + // WHOT + level._HUDItem[ "thermal_mode" ] settext( &"AC130_HUD_THERMAL_WHOT" ); + inverted = "0"; + } + } +} + +setAmmo() +{ + level notify( "setting_ammo" ); + level endon( "setting_ammo" ); + + if ( flag( "clear_to_engage" ) ) + ammoCount = 1; + else + ammoCount = 0; + + weaponList = level._ac130player GetWeaponsListPrimaries(); + for ( i = 0 ; i < weaponList.size ; i++ ) + { + // only add the ammo if the gun is reloaded + if ( level._weaponReadyToFire[ weaponList[ i ] ] ) + level._ac130player SetWeaponAmmoClip( weaponList[ i ], ammoCount ); + } +} + +failMissionForEngaging() +{ + level endon( "clear_to_engage" ); + + level._ac130player waittill( "weapon_fired" ); + + wait 2; + + if ( !flag( "mission_failed" ) ) + { + flag_set( "mission_failed" ); + setdvar( "ui_deadquote", "@AC130_DO_NOT_ENGAGE" ); + maps\_utility::missionFailedWrapper(); + } +} + +nofireCrossHair() +{ + level endon( "clear_to_engage" ); + + if ( flag( "clear_to_engage" ) ) + return; + + level._ac130_nofire = newClientHudElem( level._ac130player ); + level._ac130_nofire.x = 0; + level._ac130_nofire.y = 0; + level._ac130_nofire.alignX = "center"; + level._ac130_nofire.alignY = "middle"; + level._ac130_nofire.horzAlign = "center"; + level._ac130_nofire.vertAlign = "middle"; + level._ac130_nofire setshader( "ac130_overlay_nofire", 64, 64 ); + + thread nofireCrossHair_Remove(); + + level._ac130_nofire.alpha = 0; + + for ( ;; ) + { + while ( level._ac130player attackButtonPressed() ) + { + // no red x allowed during cinematic + if ( isdefined( level._doing_cinematic ) ) + { + wait 0.05; + break; + } + + level._ac130_nofire.alpha = 1; + level._ac130_nofire fadeOverTime( 1.0 ); + level._ac130_nofire.alpha = 0; + wait 1.0; + } + wait 0.05; + } +} + +nofireCrossHair_Remove() +{ + level waittill( "clear_to_engage" ); + level._ac130_nofire destroy(); + thread setAmmo(); +} + +fire_screenShake() +{ + for ( ;; ) + { + level._ac130player waittill( "weapon_fired" ); + + if ( level._currentWeapon == "105mm" ) + { + if ( ( getdvar( "ac130_pre_engagement_mode", "2" ) == "2" ) && ( !flag( "clear_to_engage" ) ) ) + continue; + + thread gun_fired_and_ready_105mm(); + + //earthquake(,,,) + earthquake( 0.2, 1, level._ac130player.origin, 1000 ); + } + else + if ( level._currentWeapon == "40mm" ) + { + if ( ( getdvar( "ac130_pre_engagement_mode", "2" ) == "2" ) && ( !flag( "clear_to_engage" ) ) ) + continue; + + //earthquake(,,,) + earthquake( 0.1, 0.5, level._ac130player.origin, 1000 ); + } + + wait 0.05; + } +} + +clouds() +{ + level endon( "stop_clounds" ); + wait 6; + clouds_create(); + for ( ;; ) + { + wait( randomfloatrange( 40, 80 ) ); + clouds_create(); + } +} + +clouds_create() +{ + if ( ( isdefined( level._playerWeapon ) ) && ( issubstr( tolower( level._playerWeapon ), "25" ) ) ) + return; + playfxontag( level._effect[ "cloud" ], level._ac130, "tag_player" ); +} + +gun_fired_and_ready_105mm() +{ + level notify( "gun_fired_and_ready_105mm" ); + level endon( "gun_fired_and_ready_105mm" ); + + wait 0.5; + + if ( randomint( 2 ) == 0 ) + thread context_Sensative_Dialog_Play_Random_Group_Sound( "weapons", "105mm_fired" ); + + wait 5.0; + + thread blink_crosshairs_stop(); + thread context_Sensative_Dialog_Play_Random_Group_Sound( "weapons", "105mm_ready" ); +} + +getFriendlysCenter() +{ + //returns vector which is the center mass of all friendlies + averageVec = undefined; + friendlies = getaiarray( "allies" ); + if ( !isdefined( friendlies ) ) + return( 0, 0, 0 ); + if ( friendlies.size <= 0 ) + return( 0, 0, 0 ); + for ( i = 0 ; i < friendlies.size ; i++ ) + { + if ( !isdefined( averageVec ) ) + averageVec = friendlies[ i ].origin; + else + averageVec += friendlies[ i ].origin; + } + averageVec = ( ( averageVec[ 0 ] / friendlies.size ), ( averageVec[ 1 ] / friendlies.size ), ( averageVec[ 2 ] / friendlies.size ) ); + return averageVec; +} + +shotFired() +{ + for ( ;; ) + { + level._ac130player waittill( "projectile_impact", weaponName, position, radius ); + + thread shotFiredFriendlyProximity( weaponName, position ); + + if ( issubstr( tolower( weaponName ), "105" ) ) + { + earthquake( 0.4, 1.0, position, 3500 ); + thread shotFiredDarkScreenOverlay(); + } + else if ( issubstr( tolower( weaponName ), "40" ) ) + { + earthquake( 0.2, 0.5, position, 2000 ); + } + + thread shotFiredBadPlace( position, weaponName ); + + if ( getdvar( "ac130_ragdoll_deaths", "1" ) == "1" ) + thread shotFiredPhysicsSphere( position, weaponName ); + wait 0.05; + } +} + +shotFiredFriendlyProximity( weaponName, position ) +{ + if ( !isdefined( level._weaponFriendlyCloseDistance[ weaponName ] ) ) + return; + + trigger_origin = position - ( 0, 0, 50 ); + trigger_radius = level._weaponFriendlyCloseDistance[ weaponName ]; + trigger_height = 300; + trigger_spawnflags = 2; // AI_ALLIES AND THE PLAYER // keept the ai if it ever get used with friendlies again. + trigger_lifetime = 1.0; + + prof_begin( "ac130_friendly_proximity_check" ); + trigger = spawn( "trigger_radius", trigger_origin, trigger_spawnflags, trigger_radius, trigger_height ); + prof_end( "ac130_friendly_proximity_check" ); + level thread shotFiredFriendlyProximity_trigger( trigger, trigger_lifetime ); + + if ( getdvar( "ac130_debug_weapons", "0" ) == "1" ) + { + thread debug_circle( trigger_origin, trigger_radius, trigger_lifetime, level._color[ "white" ], undefined, true ); + thread debug_circle( trigger_origin + ( 0, 0, trigger_height ), trigger_radius, trigger_lifetime, level._color[ "white" ], undefined, true ); + } +} + +shotFiredFriendlyProximity_trigger( trigger, trigger_lifetime ) +{ + trigger endon( "timeout" ); + level thread shotFiredFriendlyProximity_trigger_timeout( trigger, trigger_lifetime ); + trigger waittill( "trigger" ); + + // don't play warning dialog if one played within the last 5 seconds. + prof_begin( "ac130_friendly_proximity_check" ); + if ( ( isdefined( level._lastFriendlyProximityWarningPlayed ) ) && ( gettime() - level._lastFriendlyProximityWarningPlayed < 7000 ) ) + { + prof_end( "ac130_friendly_proximity_check" ); + return; + } + + level._lastFriendlyProximityWarningPlayed = gettime(); + prof_end( "ac130_friendly_proximity_check" ); + + thread playSoundOverRadio( level._scr_sound[ "fco" ][ "ac130_fco_firingtoclose" ], true, 5.0 ); +} + +shotFiredFriendlyProximity_trigger_timeout( trigger, trigger_lifetime ) +{ + wait trigger_lifetime; + trigger notify( "timeout" ); + trigger delete(); +} + +shotFiredBadPlace( center, weapon ) +{ + // no new badplace if more then 20 + if ( level._badplaceCount >= level._badplaceMax ) + return; + + assert( isdefined( level._badplaceRadius[ weapon ] ) ); + badplace_cylinder( "", level._badplaceDuration[ weapon ], center, level._badplaceRadius[ weapon ], level._badplaceRadius[ weapon ], "axis" ); + thread shotFiredBadPlaceCount( level._badplaceDuration[ weapon ] ); + + if ( getdvar( "ac130_debug_weapons", "0" ) == "1" ) + thread debug_circle( center, level._badplaceRadius[ weapon ], level._badplaceDuration[ weapon ], level._color[ "blue" ], undefined, true ); +} + +shotFiredBadPlaceCount( durration ) +{ + assert( level._badplaceCount >= 0 ); + assert( level._badplaceCount < level._badplaceMax ); + + level._badplaceCount++; + wait durration; + level._badplaceCount--; +} + +shotFiredPhysicsSphere( center, weapon ) +{ + wait 0.1; + physicsExplosionSphere( center, level._physicsSphereRadius[ weapon ], level._physicsSphereRadius[ weapon ] / 2, level._physicsSphereForce[ weapon ] ); +} + +shotFiredDarkScreenOverlay() +{ + level notify( "darkScreenOverlay" ); + level endon( "darkScreenOverlay" ); + + if ( !isdefined( level._darkScreenOverlay ) ) + { + level._darkScreenOverlay = newClientHudElem( level._ac130player ); + level._darkScreenOverlay.x = 0; + level._darkScreenOverlay.y = 0; + level._darkScreenOverlay.alignX = "left"; + level._darkScreenOverlay.alignY = "top"; + level._darkScreenOverlay.horzAlign = "fullscreen"; + level._darkScreenOverlay.vertAlign = "fullscreen"; + level._darkScreenOverlay setshader( "black", 640, 480 ); + level._darkScreenOverlay.sort = -10; + level._darkScreenOverlay.alpha = 0.0; + } + level._darkScreenOverlay.alpha = 0.0; + level._darkScreenOverlay fadeOverTime( 0.2 ); + level._darkScreenOverlay.alpha = 0.6; + wait 0.4; + level._darkScreenOverlay fadeOverTime( 0.8 ); + level._darkScreenOverlay.alpha = 0.0; +} + +add_beacon_effect() +{ + self endon( "death" ); + + flashDelay = 0.75; + + wait randomfloat( 3.0 ); + for ( ;; ) + { + if ( isdefined( level._ac130player ) ) + playfxontagforclients( level._effect[ "beacon" ], self, "j_spine4", level._ac130player ); + wait flashDelay; + } +} + +/* +breakable() +{ + self setcandamage( true ); + for (;;) + { + self waittill ( "damage", damage, attacker ); + if ( ( isplayer( attacker ) ) & ( damage >= 1000 ) ) + break; + } + self delete(); +} +*/ +/* +tree_fall() +{ + self setcandamage( true ); + for (;;) + { + self waittill( "damage", damage, attacker, direction_vec, point ); + if ( !isplayer( attacker ) ) + continue; + if ( randomint( 2 ) == 0 ) + continue; + break; + } + + tree = self; + + treeorg = spawn( "script_origin", tree.origin ); + treeorg.origin = tree.origin; + + org = point; + pos1 = (org[0],org[1],0); + org = tree.origin; + pos2 = (org[0],org[1],0); + treeorg.angles = vectortoangles( pos1 - pos2 ); + + treeang = tree.angles; + ang = treeorg.angles; + org = point; + pos1 = (org[0],org[1],0); + org = tree.origin; + pos2 = (org[0],org[1],0); + treeorg.angles = vectortoangles( pos1 - pos2 ); + tree linkto( treeorg ); + + treeorg rotatepitch( -90, 1.1, .05, .2 ); + treeorg waittill( "rotatedone" ); + treeorg rotatepitch( 5, .21, .05, .15 ); + treeorg waittill( "rotatedone" ); + treeorg rotatepitch( -5, .26, .15, .1 ); + treeorg waittill( "rotatedone" ); + tree unlink(); + treeorg delete(); +} +*/ + +spawn_callback_thread( guy ) +{ + if ( isdefined( level._LevelSpecificSpawnerCallbackThread ) ) + thread [[ level._LevelSpecificSpawnerCallbackThread ]]( guy ); + + if ( !isdefined( guy ) ) + return; + + if ( !isdefined( guy.team ) ) + return; + + if ( guy.team == "axis" ) + { + thread enemy_killed_thread( guy ); + } + + if ( getdvar( "ac130_target_markers", "0" ) == "1" ) + { + target_set( guy, ( 0, 0, 32 ) ); + thread hud_target_blink( guy ); + } +} + +hud_target_blink( guy ) +{ + guy endon( "death" ); + while ( isdefined( guy ) ) + { + target_setshader( guy, "ac130_hud_target" ); + target_setoffscreenshader( guy, "ac130_hud_target_offscreen" ); + level waittill( "hud_target_blink_off" ); + target_setshader( guy, "ac130_hud_target_flash" ); + target_setoffscreenshader( guy, "ac130_hud_target_flash" ); + level waittill( "hud_target_blink_on" ); + } +} + +hud_target_blink_timer() +{ + for ( ;; ) + { + level notify( "hud_target_blink_on" ); + wait 0.5; + level notify( "hud_target_blink_off" ); + wait 0.2; + } +} + +enemy_killed_thread( guy ) +{ + if ( guy.team != "axis" ) + return; + + if ( getdvar( "ac130_ragdoll_deaths", "1" ) == "1" ) + guy.skipDeathAnim = true; + + guy waittill( "death", attacker ); + + if ( ( isdefined( attacker ) ) && ( isplayer( attacker ) ) ) + level._enemiesKilledByPlayer++ ; + + if ( getdvar( "ac130_ragdoll_deaths", "1" ) == "1" ) + { + if ( ( isdefined( guy.damageweapon ) ) && ( issubstr( guy.damageweapon, "25mm" ) ) ) + guy.skipDeathAnim = undefined; + } + + // context kill dialog + thread context_Sensative_Dialog_Kill( guy, attacker ); +} + +context_Sensative_Dialog() +{ + thread context_Sensative_Dialog_Guy_In_Sight(); + thread context_Sensative_Dialog_Guy_Crawling(); + thread context_Sensative_Dialog_Guy_Pain(); + thread context_Sensative_Dialog_Guy_Pain_Falling(); + thread context_Sensative_Dialog_Secondary_Explosion_Vehicle(); + thread context_Sensative_Dialog_Kill_Thread(); + thread context_Sensative_Dialog_Locations(); + thread context_Sensative_Dialog_Filler(); +} + +context_Sensative_Dialog_Guy_In_Sight() +{ + for ( ;; ) + { + if ( context_Sensative_Dialog_Guy_In_Sight_Check() ) + thread context_Sensative_Dialog_Play_Random_Group_Sound( "ai", "in_sight" ); + wait randomfloatrange( 1, 3 ); + } +} + +context_Sensative_Dialog_Guy_In_Sight_Check() +{ + prof_begin( "AI_in_sight_check" ); + + enemies = getaiarray( "axis" ); + for ( i = 0 ; i < enemies.size ; i++ ) + { + if ( !isdefined( enemies[ i ] ) ) + continue; + + if ( !isalive( enemies[ i ] ) ) + continue; + + if ( within_fov( level._ac130player getEye(), level._ac130player getPlayerAngles(), enemies[ i ].origin, level._cosine[ "5" ] ) ) + { + prof_end( "AI_in_sight_check" ); + return true; + } + wait 0.05; + } + + prof_end( "AI_in_sight_check" ); + return false; +} + +context_Sensative_Dialog_Guy_Crawling() +{ + for ( ;; ) + { + level waittill( "ai_crawling", guy ); + + if ( ( isdefined( guy ) ) && ( isdefined( guy.origin ) ) ) + { + if ( getdvar( "ac130_debug_context_sensative_dialog", "0" ) == "1" ) + thread debug_line( level._ac130player.origin, guy.origin, 5.0, ( 0, 1, 0 ) ); + } + + thread context_Sensative_Dialog_Play_Random_Group_Sound( "ai", "wounded_crawl" ); + } +} + +context_Sensative_Dialog_Guy_Pain_Falling() +{ + for ( ;; ) + { + level waittill( "ai_pain_falling", guy ); + + if ( ( isdefined( guy ) ) && ( isdefined( guy.origin ) ) ) + { + if ( getdvar( "ac130_debug_context_sensative_dialog", "0" ) == "1" ) + thread debug_line( level._ac130player.origin, guy.origin, 5.0, ( 1, 0, 0 ) ); + } + + thread context_Sensative_Dialog_Play_Random_Group_Sound( "ai", "wounded_pain" ); + } +} + +context_Sensative_Dialog_Guy_Pain() +{ + for ( ;; ) + { + level waittill( "ai_pain", guy ); + + if ( ( isdefined( guy ) ) && ( isdefined( guy.origin ) ) ) + { + if ( getdvar( "ac130_debug_context_sensative_dialog", "0" ) == "1" ) + thread debug_line( level._ac130player.origin, guy.origin, 5.0, ( 1, 0, 0 ) ); + } + + thread context_Sensative_Dialog_Play_Random_Group_Sound( "ai", "wounded_pain" ); + } +} + +context_Sensative_Dialog_Secondary_Explosion_Vehicle() +{ + for ( ;; ) + { + level waittill( "vehicle_explosion", vehicle_origin ); + + wait 1; + + if ( isdefined( vehicle_origin ) ) + { + if ( getdvar( "ac130_debug_context_sensative_dialog", "0" ) == "1" ) + thread debug_line( level._ac130player.origin, vehicle_origin, 5.0, ( 0, 0, 1 ) ); + } + + thread context_Sensative_Dialog_Play_Random_Group_Sound( "explosion", "secondary" ); + } +} + +context_Sensative_Dialog_Kill( guy, attacker ) +{ + if ( !isdefined( attacker ) ) + return; + + if ( !isplayer( attacker ) ) + return; + + level._enemiesKilledInTimeWindow++ ; + level notify( "enemy_killed" ); + + if ( ( isdefined( guy ) ) && ( isdefined( guy.origin ) ) ) + { + if ( getdvar( "ac130_debug_context_sensative_dialog", "0" ) == "1" ) + thread debug_line( level._ac130player.origin, guy.origin, 5.0, ( 1, 1, 0 ) ); + } +} + +context_Sensative_Dialog_Kill_Thread() +{ + timeWindow = 1; + for ( ;; ) + { + level waittill( "enemy_killed" ); + wait timeWindow; + println( "guys killed in time window: " ); + println( level._enemiesKilledInTimeWindow ); + + soundAlias1 = "kill"; + soundAlias2 = undefined; + +// if ( level.enemiesKilledInTimeWindow >= 5 ) +// maps\_utility::giveachievement_wrapper( "STRAIGHT_FLUSH" ); + + if ( level._enemiesKilledInTimeWindow >= 3 ) + soundAlias2 = "large_group"; + else if ( level._enemiesKilledInTimeWindow == 2 ) + soundAlias2 = "small_group"; + else + { + soundAlias2 = "single"; + if ( randomint( 3 ) != 1 ) + { + level._enemiesKilledInTimeWindow = 0; + continue; + } + } + + level._enemiesKilledInTimeWindow = 0; + assert( isdefined( soundAlias2 ) ); + + thread context_Sensative_Dialog_Play_Random_Group_Sound( soundAlias1, soundAlias2, true ); + } +} + +context_Sensative_Dialog_Locations() +{ + array_thread( getentarray( "context_dialog_car", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "car" ); + array_thread( getentarray( "context_dialog_truck", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "truck" ); + array_thread( getentarray( "context_dialog_building", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "building" ); + array_thread( getentarray( "context_dialog_wall", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "wall" ); + array_thread( getentarray( "context_dialog_field", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "field" ); + array_thread( getentarray( "context_dialog_road", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "road" ); + array_thread( getentarray( "context_dialog_church", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "church" ); + array_thread( getentarray( "context_dialog_ditch", "targetname" ), ::context_Sensative_Dialog_Locations_Add_Notify_Event, "ditch" ); + + thread context_Sensative_Dialog_Locations_Thread(); +} + +context_Sensative_Dialog_Locations_Thread() +{ + for ( ;; ) + { + level waittill( "context_location", locationType ); + + if ( !isdefined( locationType ) ) + { + assertMsg( "LocationType " + locationType + " is not valid" ); + continue; + } + + if ( !flag( "allow_context_sensative_dialog" ) ) + continue; + + thread context_Sensative_Dialog_Play_Random_Group_Sound( "location", locationType ); + + wait( 5 + randomfloat( 10 ) ); + } +} + +context_Sensative_Dialog_Locations_Add_Notify_Event( locationType ) +{ + for ( ;; ) + { + self waittill( "trigger", triggerer ); + + if ( !isdefined( triggerer ) ) + continue; + + if ( ( !isdefined( triggerer.team ) ) || ( triggerer.team != "axis" ) ) + continue; + + level notify( "context_location", locationType ); + + wait 5; + } +} + +context_Sensative_Dialog_VehicleSpawn( vehicle ) +{ + if ( vehicle.script_team != "axis" ) + return; + + thread context_Sensative_Dialog_VehicleDeath( vehicle ); + + vehicle endon( "death" ); + + while ( !within_fov( level._ac130player getEye(), level._ac130player getPlayerAngles(), vehicle.origin, level._cosine[ "45" ] ) ) + wait 0.5; + + context_Sensative_Dialog_Play_Random_Group_Sound( "vehicle", "incoming" ); +} + +context_Sensative_Dialog_VehicleDeath( vehicle ) +{ + vehicle waittill( "death" ); + thread context_Sensative_Dialog_Play_Random_Group_Sound( "vehicle", "death" ); +} + +context_Sensative_Dialog_Filler() +{ + for ( ;; ) + { + if ( ( isdefined( level._radio_in_use ) ) && ( level._radio_in_use == true ) ) + level waittill( "radio_not_in_use" ); + + // if 3 seconds has passed and nothing has been transmitted then play a sound + currentTime = getTime(); + if ( ( currentTime - level._lastRadioTransmission ) >= 3000 ) + { + level._lastRadioTransmission = currentTime; + thread context_Sensative_Dialog_Play_Random_Group_Sound( "misc", "action" ); + } + + wait 0.25; + } +} + +context_Sensative_Dialog_Play_Random_Group_Sound( name1, name2, force_transmit_on_turn ) +{ + assert( isdefined( level._scr_sound[ name1 ] ) ); + assert( isdefined( level._scr_sound[ name1 ][ name2 ] ) ); + + if ( !isdefined( force_transmit_on_turn ) ) + force_transmit_on_turn = false; + + if ( !flag( "allow_context_sensative_dialog" ) ) + { + if ( force_transmit_on_turn ) + flag_wait( "allow_context_sensative_dialog" ); + else + return; + } + + validGroupNum = undefined; + + randGroup = randomint( level._scr_sound[ name1 ][ name2 ].size ); + + // if randGroup has already played + if ( level._scr_sound[ name1 ][ name2 ][ randGroup ].played == true ) + { + //loop through all groups and use the next one that hasn't played yet + + for ( i = 0 ; i < level._scr_sound[ name1 ][ name2 ].size ; i++ ) + { + randGroup++ ; + if ( randGroup >= level._scr_sound[ name1 ][ name2 ].size ) + randGroup = 0; + if ( level._scr_sound[ name1 ][ name2 ][ randGroup ].played == true ) + continue; + validGroupNum = randGroup; + break; + } + + // all groups have been played, reset all groups to false and pick a new random one + if ( !isdefined( validGroupNum ) ) + { + for ( i = 0 ; i < level._scr_sound[ name1 ][ name2 ].size ; i++ ) + level._scr_sound[ name1 ][ name2 ][ i ].played = false; + validGroupNum = randomint( level._scr_sound[ name1 ][ name2 ].size ); + } + } + else + validGroupNum = randGroup; + + assert( isdefined( validGroupNum ) ); + assert( validGroupNum >= 0 ); + + if ( context_Sensative_Dialog_Timedout( name1, name2, validGroupNum ) ) + return; + + level._scr_sound[ name1 ][ name2 ][ validGroupNum ].played = true; + randSound = randomint( level._scr_sound[ name1 ][ name2 ][ validGroupNum ].size ); + playSoundOverRadio( level._scr_sound[ name1 ][ name2 ][ validGroupNum ].sounds[ randSound ], force_transmit_on_turn ); +} + +context_Sensative_Dialog_Timedout( name1, name2, groupNum ) +{ + // dont play this sound if it has a timeout specified and the timeout has not expired + + if ( !isdefined( level._context_sensative_dialog_timeouts ) ) + return false; + + if ( !isdefined( level._context_sensative_dialog_timeouts[ name1 ] ) ) + return false; + + if ( !isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ] ) ) + return false; + + if ( ( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups ) ) && ( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ] ) ) ) + { + assert( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "timeoutDuration" ] ) ); + assert( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "lastPlayed" ] ) ); + + currentTime = getTime(); + if ( ( currentTime - level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "lastPlayed" ] ) < level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "timeoutDuration" ] ) + return true; + + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "lastPlayed" ] = currentTime; + } + else if ( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v ) ) + { + assert( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "timeoutDuration" ] ) ); + assert( isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "lastPlayed" ] ) ); + + currentTime = getTime(); + if ( ( currentTime - level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "lastPlayed" ] ) < level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "timeoutDuration" ] ) + return true; + + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "lastPlayed" ] = currentTime; + } + + return false; +} + +playSoundOverRadio( soundAlias, force_transmit_on_turn, timeout ) +{ + if ( !isdefined( level._radio_in_use ) ) + level._radio_in_use = false; + if ( !isdefined( force_transmit_on_turn ) ) + force_transmit_on_turn = false; + if ( !isdefined( timeout ) ) + timeout = 0; + timeout = timeout * 1000; + soundQueueTime = gettime(); + + soundPlayed = false; + soundPlayed = playAliasOverRadio( soundAlias ); + if ( soundPlayed ) + return; + + // Dont make the sound wait to be played if force transmit wasn't set to true + if ( !force_transmit_on_turn ) + return; + + level._radioForcedTransmissionQueue[ level._radioForcedTransmissionQueue.size ] = soundAlias; + while ( !soundPlayed ) + { + if ( level._radio_in_use ) + level waittill( "radio_not_in_use" ); + + if ( ( timeout > 0 ) && ( getTime() - soundQueueTime > timeout ) ) + break; + + soundPlayed = playAliasOverRadio( level._radioForcedTransmissionQueue[ 0 ] ); + if ( !level._radio_in_use && !soundPlayed ) + assertMsg( "The radio wasn't in use but the sound still did not play. This should never happen." ); + } + level._radioForcedTransmissionQueue = array_remove_index( level._radioForcedTransmissionQueue, 0 ); +} + +playAliasOverRadio( soundAlias ) +{ + if ( level._radio_in_use ) + return false; + + level._radio_in_use = true; + level._ac130player playLocalSound( soundAlias, "playSoundOverRadio_done", true ); + level._ac130player waittill( "playSoundOverRadio_done" ); + level._radio_in_use = false; + level._lastRadioTransmission = getTime(); + level notify( "radio_not_in_use" ); + return true; +} + +mission_fail_casualties() +{ + level endon( "stop_casualty_tracking" ); + + if ( !isdefined( level._friendlyCount ) ) + level._friendlyCount = 0; + level._friendlyCount++ ; + + self waittill( "death" ); + + level._friendlyCount -- ; + + if ( level._friendlyCount < level._minimumFriendlyCount ) + { + flag_set( "mission_failed" ); + setdvar( "ui_deadquote", "@AC130_FRIENDLIES_DEAD" ); + maps\_utility::missionFailedWrapper(); + } +} + +debug_friendly_count() +{ + while ( getdvar( "ac130_debug_friendly_count", "0" ) != "1" ) + wait 1; + + assert( isdefined( level._friendlyCount ) ); + + if ( !isdefined( level._friendlyCountHudElem ) ) + { + level._friendlyCountHudElem = newHudElem(); + level._friendlyCountHudElem.x = 0; + level._friendlyCountHudElem.y = 0; + level._friendlyCountHudElem.fontScale = level._ac130_fontscale; + level._friendlyCountHudElem.alignX = "left"; + level._friendlyCountHudElem.alignY = "bottom"; + level._friendlyCountHudElem.horzAlign = "left"; + level._friendlyCountHudElem.vertAlign = "bottom"; + // Friendlies: &&1 + level._friendlyCountHudElem.label = &"AC130_DEBUG_FRIENDLY_COUNT"; + level._friendlyCountHudElem.alpha = 1; + } + level._friendlyCountHudElem setValue( level._friendlyCount ); + + self waittill( "death" ); + + level._friendlyCountHudElem fadeOverTime( 0.3 ); + level._friendlyCountHudElem.alpha = 0; + waittillframeend; + level._friendlyCountHudElem setValue( level._friendlyCount ); + level._friendlyCountHudElem fadeOverTime( 0.3 ); + level._friendlyCountHudElem.alpha = 1; +} + +debug_circle( center, radius, duration, color, startDelay, fillCenter ) +{ + circle_sides = 16; + + angleFrac = 360 / circle_sides; + circlepoints = []; + for ( i = 0;i < circle_sides;i++ ) + { + angle = ( angleFrac * i ); + xAdd = cos( angle ) * radius; + yAdd = sin( angle ) * radius; + x = center[ 0 ] + xAdd; + y = center[ 1 ] + yAdd; + z = center[ 2 ]; + circlepoints[ circlepoints.size ] = ( x, y, z ); + } + + if ( isdefined( startDelay ) ) + wait startDelay; + + thread debug_circle_drawlines( circlepoints, duration, color, fillCenter, center ); +} + +debug_circle_drawlines( circlepoints, duration, color, fillCenter, center ) +{ + if ( !isdefined( fillCenter ) ) + fillCenter = false; + if ( !isdefined( center ) ) + fillCenter = false; + + for ( i = 0 ; i < circlepoints.size ; i++ ) + { + start = circlepoints[ i ]; + if ( i + 1 >= circlepoints.size ) + end = circlepoints[ 0 ]; + else + end = circlepoints[ i + 1 ]; + + thread debug_line( start, end, duration, color ); + + if ( fillCenter ) + thread debug_line( center, start, duration, color ); + } +} + +debug_line( start, end, duration, color ) +{ + if ( !isdefined( color ) ) + color = ( 1, 1, 1 ); + + for ( i = 0; i < ( duration * 20 );i++ ) + { + line( start, end, color ); + wait 0.05; + } +} diff --git a/maps/_ac130_amb.gsc b/maps/_ac130_amb.gsc new file mode 100644 index 0000000..1a530be --- /dev/null +++ b/maps/_ac130_amb.gsc @@ -0,0 +1,24 @@ +#include maps\_ambient; + +main() +{ + // Set the underlying ambient track + level._ambient_track [ "ac130" ] = "ambient_ac130_int1"; + thread maps\_utility::set_ambient( "ac130" ); + + ambientDelay( "ac130", 3.0, 6.0 );// Trackname, min and max delay between ambient events + ambientEvent( "ac130", "elm_ac130_rattles", 4.0 ); + ambientEvent( "ac130", "elm_ac130_beeps", 0.3 ); + ambientEvent( "ac130", "elm_ac130_hydraulics", 1.0 ); + ambientEvent( "ac130", "elm_ac130_metal_stress", 0.3 ); + ambientEvent( "ac130", "null", 1.0 ); + + + ambientEventStart( "ac130" ); + + level waittill( "action moment" ); + + ambientEventStart( "action ambient" ); +} + + diff --git a/maps/_ac130_snd.gsc b/maps/_ac130_snd.gsc new file mode 100644 index 0000000..57aed63 --- /dev/null +++ b/maps/_ac130_snd.gsc @@ -0,0 +1,179 @@ +#include maps\_utility; +main() +{ + //------------------------------------------------------------------------------------------------- + + // 1 ) Check your fire, you're shootin' at friendlies - watch for the blinking strobes those are our guys! + // 2 ) Uh, you're firing too close to the friendlies, I repeat, you're firing too close to the friendlies. Watch for those IR strobes. + // 3 ) Be careful! You almost killed our guys there! + level._scr_sound[ "fco" ][ "ac130_fco_firingtoclose" ] = "ac130_fco_firingtoclose"; + + //------------------------------------------------------------------------------------------------- + + //CONTEXT SENSATIVE DIALOG + //------------------------------------------------------------------------------------------------- + + add_context_sensative_dialog( "ai", "in_sight", 0, "ac130_fco_moreenemy" ); // More enemy personnel. + add_context_sensative_dialog( "ai", "in_sight", 1, "ac130_fco_getthatguy" ); // Get that guy. + add_context_sensative_dialog( "ai", "in_sight", 2, "ac130_fco_guymovin" ); // Roger, guy movin'. + add_context_sensative_dialog( "ai", "in_sight", 3, "ac130_fco_getperson" ); // Get that person. + add_context_sensative_dialog( "ai", "in_sight", 4, "ac130_fco_guyrunnin" ); // Guy runnin'. + add_context_sensative_dialog( "ai", "in_sight", 5, "ac130_fco_gotarunner" ); // Uh, we got a runner here. + add_context_sensative_dialog( "ai", "in_sight", 6, "ac130_fco_backonthose" ); // Get back on those guys. + add_context_sensative_dialog( "ai", "in_sight", 7, "ac130_fco_gonnagethim" ); // You gonna get him? + add_context_sensative_dialog( "ai", "in_sight", 8, "ac130_fco_personnelthere" ); // Personnel right there. + add_context_sensative_dialog( "ai", "in_sight", 9, "ac130_fco_nailthoseguys" ); // Nail those guys. + add_context_sensative_dialog( "ai", "in_sight", 10, "ac130_fco_clearedtoengage" ); // Cleared to engage enemy personnel. + add_context_sensative_dialog( "ai", "in_sight", 11, "ac130_fco_lightemup" ); // Light ‘em up. + add_context_sensative_dialog( "ai", "in_sight", 12, "ac130_fco_takehimout" ); // Yeah take him out. + add_context_sensative_dialog( "ai", "in_sight", 13, "ac130_plt_clearedtoengage" ); // Cleared to engage all of those. + add_context_sensative_dialog( "ai", "in_sight", 14, "ac130_plt_yeahcleared" ); // Yeah, cleared to engage. + add_context_sensative_dialog( "ai", "in_sight", 15, "ac130_plt_copysmoke" ); // Copy, smoke ‘em. + add_context_sensative_dialog( "ai", "in_sight", 16, "ac130_fco_rightthere" ); // Right there...tracking. + add_context_sensative_dialog( "ai", "in_sight", 17, "ac130_fco_tracking" ); // Tracking. + + add_context_sensative_dialog( "ai", "wounded_crawl", 0, "ac130_fco_movingagain" ); // Ok he’s moving again. + add_context_sensative_timeout( "ai", "wounded_crawl", undefined, 6 ); + + add_context_sensative_dialog( "ai", "wounded_pain", 0, "ac130_fco_doveonground" ); // Yeah, he just dove on the ground. + add_context_sensative_dialog( "ai", "wounded_pain", 1, "ac130_fco_knockedwind" ); // Probably just knocked the wind out of him. + add_context_sensative_dialog( "ai", "wounded_pain", 2, "ac130_fco_downstillmoving" ); // That guy's down but still moving. + add_context_sensative_dialog( "ai", "wounded_pain", 3, "ac130_fco_gettinbackup" ); // He's gettin' back up. + add_context_sensative_dialog( "ai", "wounded_pain", 4, "ac130_fco_yepstillmoving" ); // Yep, that guy’s still moving. + add_context_sensative_dialog( "ai", "wounded_pain", 5, "ac130_fco_stillmoving" ); // He's still moving. + add_context_sensative_timeout( "ai", "wounded_pain", undefined, 12 ); + + add_context_sensative_dialog( "weapons", "105mm_ready", 0, "ac130_gnr_gunready1" ); + + add_context_sensative_dialog( "weapons", "105mm_fired", 0, "ac130_gnr_shot1" ); + + add_context_sensative_dialog( "plane", "rolling_in", 0, "ac130_plt_rollinin" ); + + add_context_sensative_dialog( "explosion", "secondary", 0, "ac130_nav_secondaries1" ); + add_context_sensative_dialog( "explosion", "secondary", 1, "ac130_tvo_directsecondary1" ); + add_context_sensative_dialog( "explosion", "secondary", 1, "ac130_tvo_directsecondary2" ); + add_context_sensative_timeout( "explosion", "secondary", undefined, 7 ); + + add_context_sensative_dialog( "kill", "single", 0, "ac130_plt_gottahurt" ); // Ooo that's gotta hurt. + add_context_sensative_dialog( "kill", "single", 1, "ac130_fco_iseepieces" ); // Yeah, good kill. I see lots of little pieces down there. + add_context_sensative_dialog( "kill", "single", 2, "ac130_fco_oopsiedaisy" ); // ( chuckling ) Oopsie - daisy. + add_context_sensative_dialog( "kill", "single", 3, "ac130_fco_goodkill" ); // Good kill good kill. + add_context_sensative_dialog( "kill", "single", 4, "ac130_fco_yougothim" ); // You got him. + add_context_sensative_dialog( "kill", "single", 5, "ac130_fco_yougothim2" ); // You got him! + add_context_sensative_dialog( "kill", "single", 6, "ac130_fco_thatsahit" ); // That's a hit. + add_context_sensative_dialog( "kill", "single", 7, "ac130_fco_directhit" ); // Direct hit. + add_context_sensative_dialog( "kill", "single", 8, "ac130_fco_rightontarget" ); // Yep, that was right on target. + add_context_sensative_dialog( "kill", "single", 9, "ac130_fco_okyougothim" ); // Ok, you got him. Get back on the other guys. + add_context_sensative_dialog( "kill", "single", 10, "ac130_fco_within2feet" ); // All right you got the guy. That might have been within two feet of him. + + add_context_sensative_dialog( "kill", "small_group", 0, "ac130_fco_nice" ); // ( chuckling ) Niiiice. + add_context_sensative_dialog( "kill", "small_group", 1, "ac130_fco_directhits" ); // Yeah, direct hits right there. + add_context_sensative_dialog( "kill", "small_group", 2, "ac130_fco_iseepieces" ); // Yeah, good kill. I see lots of little pieces down there. + add_context_sensative_dialog( "kill", "small_group", 3, "ac130_fco_goodkill" ); // Good kill good kill. + add_context_sensative_dialog( "kill", "small_group", 4, "ac130_fco_yougothim" ); // You got him. + add_context_sensative_dialog( "kill", "small_group", 5, "ac130_fco_yougothim2" ); // You got him! + add_context_sensative_dialog( "kill", "small_group", 6, "ac130_fco_thatsahit" ); // That's a hit. + add_context_sensative_dialog( "kill", "small_group", 7, "ac130_fco_directhit" ); // Direct hit. + add_context_sensative_dialog( "kill", "small_group", 8, "ac130_fco_rightontarget" );// Yep, that was right on target. + add_context_sensative_dialog( "kill", "small_group", 9, "ac130_fco_okyougothim" ); // Ok, you got him. Get back on the other guys. + + add_context_sensative_dialog( "kill", "large_group", 0, "ac130_fco_hotdamn1" ); // Hot damn! + add_context_sensative_dialog( "kill", "large_group", 0, "ac130_fco_hotdamn2" ); // Hot damn! + add_context_sensative_dialog( "kill", "large_group", 0, "ac130_fco_hotdamn3" ); // Hot damn! + add_context_sensative_dialog( "kill", "large_group", 1, "ac130_tvo_whoa1" ); // Whoa!!! + add_context_sensative_dialog( "kill", "large_group", 1, "ac130_tvo_whoa2" ); // Whoa!!! + add_context_sensative_dialog( "kill", "large_group", 1, "ac130_tvo_whoa3" ); // Whoa!!! + add_context_sensative_dialog( "kill", "large_group", 2, "ac130_fco_kaboom" ); // Ka - boom. + + add_context_sensative_dialog( "location", "car", 0, "ac130_fco_guybycar" ); // There’s a guy by that car. + add_context_sensative_timeout( "location", "car", undefined, 40 ); + + add_context_sensative_dialog( "location", "truck", 0, "ac130_fco_guybytruck" ); // There’s one by that truck. + add_context_sensative_timeout( "location", "truck", undefined, 12 ); + + add_context_sensative_dialog( "location", "building", 0, "ac130_fco_nailbybuilding1" ); + add_context_sensative_timeout( "location", "building", undefined, 20 ); + + add_context_sensative_dialog( "location", "wall", 0, "ac130_tvo_coverbywall1" ); + add_context_sensative_timeout( "location", "wall", undefined, 20 ); + + add_context_sensative_dialog( "location", "field", 0, "ac130_fco_crossingfield" ); // Enemies crossing the field. + add_context_sensative_timeout( "location", "field", undefined, 20 ); + + add_context_sensative_dialog( "location", "road", 0, "ac130_fco_enemyonroad" ); // Enemy personnel on the road. + add_context_sensative_timeout( "location", "road", undefined, 20 ); + + add_context_sensative_dialog( "location", "church", 0, "ac130_fco_outofchurch" ); // There's armed personnel running out of the church. + add_context_sensative_timeout( "location", "church", undefined, 20 ); + + add_context_sensative_dialog( "location", "ditch", 0, "ac130_fco_headinforditch" ); // Yeah, he’s headin’ for the ditch. + add_context_sensative_timeout( "location", "ditch", undefined, 20 ); + + add_context_sensative_dialog( "vehicle", "incoming", 0, "ac130_fco_movingvehicle" ); // We got a moving vehicle here. + add_context_sensative_dialog( "vehicle", "incoming", 1, "ac130_fco_vehicleonmove" ); // We got a vehicle on the move. + add_context_sensative_dialog( "vehicle", "incoming", 2, "ac130_plt_engvehicle" ); // You are cleared to engage the moving vehicle. + add_context_sensative_dialog( "vehicle", "incoming", 3, "ac130_fco_getvehicle" ); // Crew, get the moving vehicle. + + add_context_sensative_dialog( "vehicle", "death", 0, "ac130_fco_confirmed" ); // Confirmed, vehicle neutralized. + add_context_sensative_dialog( "vehicle", "death", 1, "ac130_fco_fulltank" ); // ( chuckling ) Shit, must've been a full tank of gas. + + add_context_sensative_dialog( "misc", "action", 0, "ac130_plt_scanrange" ); // Set scan range. + add_context_sensative_timeout( "misc", "action", 0, 70 ); + + add_context_sensative_dialog( "misc", "action", 1, "ac130_plt_cleanup" ); // Clean up that signal. + add_context_sensative_timeout( "misc", "action", 1, 80 ); + + add_context_sensative_dialog( "misc", "action", 2, "ac130_plt_targetreset" ); // Target reset. + add_context_sensative_timeout( "misc", "action", 2, 55 ); + + add_context_sensative_dialog( "misc", "action", 3, "ac130_plt_azimuthsweep" ); // Recalibrate azimuth sweep angle. Adjust elevation scan. + add_context_sensative_timeout( "misc", "action", 3, 100 ); +} + +add_context_sensative_dialog( name1, name2, group, soundAlias ) +{ + assert( isdefined( name1 ) ); + assert( isdefined( name2 ) ); + assert( isdefined( group ) ); + assert( isdefined( soundAlias ) ); + assert( soundexists( soundAlias ) == true ); + + if ( ( !isdefined( level._scr_sound[ name1 ] ) ) || ( !isdefined( level._scr_sound[ name1 ][ name2 ] ) ) || ( !isdefined( level._scr_sound[ name1 ][ name2 ][ group ] ) ) ) + { + // creating group for the first time + level._scr_sound[ name1 ][ name2 ][ group ] = spawnStruct(); + level._scr_sound[ name1 ][ name2 ][ group ].played = false; + level._scr_sound[ name1 ][ name2 ][ group ].sounds = []; + } + + //group exists, add the sound to the array + index = level._scr_sound[ name1 ][ name2 ][ group ].sounds.size; + level._scr_sound[ name1 ][ name2 ][ group ].sounds[ index ] = soundAlias; +} + +add_context_sensative_timeout( name1, name2, groupNum, timeoutDuration ) +{ + if ( !isdefined( level._context_sensative_dialog_timeouts ) ) + level._context_sensative_dialog_timeouts = []; + + createStruct = false; + if ( !isdefined( level._context_sensative_dialog_timeouts[ name1 ] ) ) + createStruct = true; + else if ( !isdefined( level._context_sensative_dialog_timeouts[ name1 ][ name2 ] ) ) + createStruct = true; + if ( createStruct ) + level._context_sensative_dialog_timeouts[ name1 ][ name2 ] = spawnStruct(); + + if ( isdefined( groupNum ) ) + { + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups = []; + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ] = spawnStruct(); + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "timeoutDuration" ] = timeoutDuration * 1000; + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].groups[ string( groupNum ) ].v[ "lastPlayed" ] = ( timeoutDuration * - 1000 ); + } + else + { + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "timeoutDuration" ] = timeoutDuration * 1000; + level._context_sensative_dialog_timeouts[ name1 ][ name2 ].v[ "lastPlayed" ] = ( timeoutDuration * - 1000 ); + } +} \ No newline at end of file diff --git a/maps/_ambient.gsc b/maps/_ambient.gsc new file mode 100644 index 0000000..e598509 --- /dev/null +++ b/maps/_ambient.gsc @@ -0,0 +1,1475 @@ +#include maps\_utility; +#include maps\_equalizer; +#include common_scripts\utility; + +/* Example map_amb.gsc file: +main() +{ + // Set the underlying ambient track + level.ambient_track [ "exterior" ] = "ambient_test"; + thread maps\_utility::set_ambient( "exterior" ); + + // Set the eq filter for the ambient channels + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + // define a filter and give it a name + // or use one of the presets( see _equalizer.gsc ) + // arguments are: name, band, type, freq, gain, q + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + // maps\_equalizer::defineFilter( "test", 0, "lowshelf", 3000, 6, 2 ); + // maps\_equalizer::defineFilter( "test", 1, "highshelf", 3000, -12, 2 ); + // maps\_equalizer::defineFilter( "test", 2, "bell", 1500, 6, 3 ); + + // attach the filter to a region and channel + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + add_channel_to_filter( track, channel ) + + + ambientDelay( "exterior", 1.3, 3.4 );// Trackname, min and max delay between ambient events + ambientEvent( "exterior", "burnville_foley_13b", 0.3 ); + ambientEvent( "exterior", "boat_sink", 0.6 ); + ambientEvent( "exterior", "bullet_large_canvas", 0.3 ); + ambientEvent( "exterior", "explo_boat", 1.3 ); + ambientEvent( "exterior", "Stuka_hit", 0.1 ); + + ambientEventStart( "exterior" ); +} +*/ + +init() +{ + level._ambient_zones = []; + + + // this function can be overwritten to do custom stuff when an ambience trigger is hit + if ( !isdefined( level._global_ambience_blend_func ) ) + level._global_ambience_blend_func = ::empty_amb; + + add_zone( "ac130" ); + add_zone( "alley" ); + add_zone( "bunker" ); + add_zone( "city" ); + add_zone( "container" ); + add_zone( "exterior" ); + add_zone( "exterior1" ); + add_zone( "exterior2" ); + add_zone( "exterior3" ); + add_zone( "exterior4" ); + add_zone( "exterior5" ); + add_zone( "forrest" ); + add_zone( "hangar" ); + add_zone( "interior" ); + add_zone( "interior_metal" ); + add_zone( "interior_stone" ); + add_zone( "interior_vehicle" ); + add_zone( "interior_wood" ); + add_zone( "mountains" ); + add_zone( "pipe" ); + add_zone( "shanty" ); + add_zone( "snow_base" ); + add_zone( "snow_cliff" ); + add_zone( "tunnel" ); + add_zone( "underpass" ); + + /# + create_ambience_hud(); + #/ + + if ( !isdefined( level._ambientEventEnt ) ) + level._ambientEventEnt = []; + + if ( !isDefined( level._ambient_reverb ) ) + level._ambient_reverb = []; + + if ( !isDefined( level._ambient_eq ) ) + level._ambient_eq = []; + + if ( !isDefined( level._ambient_volume ) ) + { + level._ambient_volume = []; + } + + if ( !isDefined( level._fxfireloopmod ) ) + level._fxfireloopmod = 1; + + level._reverb_track = ""; + level._eq_main_track = 0; + level._eq_mix_track = 1; + level._volume_track = ""; + level._eq_track[ level._eq_main_track ] = ""; + level._eq_track[ level._eq_mix_track ] = ""; + + // used to change the meaning of interior / exterior / rain ambience midlevel. + level._ambient_modifier[ "interior" ] = ""; + level._ambient_modifier[ "exterior" ] = ""; + level._ambient_modifier[ "rain" ] = ""; + + // loads any predefined filters in _equalizer.gsc + loadPresets(); + + thread hud_hide_with_cg_draw_hud(); +} + +empty_amb( p, i, o ) +{ +} + + +// starts this ambient track +activateAmbient( ambient ) +{ + thread set_ambience_single( ambient ); +} + + +ambientVolume() +{ + for ( ;; ) + { + self waittill( "trigger" ); + activateAmbient( "interior" ); + while ( level._player isTouching( self ) ) + wait 0.1; + activateAmbient( "exterior" ); + } +} + +/* +============= +///ScriptDocBegin +"Name: create_ambient_event( , , )" +"Summary: Create an ambient event system. It plays random ambient sounds." +"Module: Ambient" +"MandatoryArg: : What to name it" +"MandatoryArg: : The minimum time between sounds" +"MandatoryArg: : The max time between sounds" +"Example: event = create_ambient_event( "dcburning_bunker1", 5.0, 15.0 );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +create_ambient_event( track, min_time, max_time ) +{ + assertex( isdefined( level._eq_defs ), "_load must run before loading the _amb file for a map." ); + assertex( !isdefined( level._ambientEventEnt[ track ] ), "Already created ambient event " + track ); + assertEX( max_time > min_time, "Ambient max must be greater than min for track " + track ); + + event = spawnstruct(); + event.min = min_time; + event.range = max_time - min_time; + event.event_alias = []; + event.event_alias_no_block = []; + event.track = track; + + level._ambientEventEnt[ track ] = event; + /# + event thread assert_event_has_aliases(); + #/ + return event; +} + +assert_event_has_aliases() +{ + waittillframeend; + assertex( self.event_alias.size > 0 || self.event_alias_no_block.size > 0, "Added ambient event system " + self.track + " with no aliases." ); +} + +/* +============= +///ScriptDocBegin +"Name: add_to_ambient_event( , )" +"Summary: Add a sound alias to an ambient event system." +"Module: Ambient" +"CallOn: An ambient event system (spawnstruct)" +"MandatoryArg: : The sound alias" +"MandatoryArg: : How often to play relative to other aliases in the system" +"Example: event add_to_ambient_event( "elm_quake_sub_rumble", 1.0 );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +add_to_ambient_event( name, weight ) +{ + assertex( !isdefined( self.event_alias[ name ] ), "Cant change an ambient event weight for an alias (track " + self.track + ", alias " + name + ")" ); + self.event_alias[ name ] = weight; +} + +/* +============= +///ScriptDocBegin +"Name: add_to_ambient_event_no_block( , )" +"Summary: Add a sound alias to an ambient event system. This sound will not block other ambiences from playing after it." +"Module: Ambient" +"CallOn: An ambient event system (spawnstruct)" +"MandatoryArg: : The sound alias" +"MandatoryArg: : How often to play relative to other aliases in the system" +"Example: event add_to_ambient_event( "elm_quake_sub_rumble", 1.0 );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +add_to_ambient_event_no_block( name, weight ) +{ + assertex( !isdefined( self.event_alias_no_block[ name ] ), "Cant change an ambient event weight for an alias (track " + self.track + ", alias " + name + ")" ); + self.event_alias_no_block[ name ] = weight; +} + +/* +============= +///ScriptDocBegin +"Name: map_to_reverb_eq( )" +"Summary: Map an ambient event system to reverb or eq tracks. So when the ambient event is activated, appropriate reverb and eq get activated too." +"Module: Ambient" +"CallOn: An ambient event system (spawnstruct)" +"MandatoryArg: : The eq/reverb type to map to. For example exterior, bunker, alley, etc." +"Example: event map_to_reverb_eq( "bunker" );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +map_to_reverb_eq( eqReverb ) +{ +// assertex( !isdefined( self.remap ), "Tried to remap reverb/eq mapping " + self.track ); +// self.remap = eqReverb; + + // copy the reverb/eq settings over the specified settings + level._eq_defs[ self.track ] = level._eq_defs[ eqReverb ]; + level._ambient_eq[ self.track ] = level._ambient_eq[ eqReverb ]; + level._ambient_reverb[ self.track ] = level._ambient_reverb[ eqReverb ]; +} + +map_to_reverb_eq_vol( eqReverbVol ) +{ + // copy the reverb/eq settings over the specified settings + level._eq_defs[ self.track ] = level._eq_defs[ eqReverbVol ]; + level._ambient_eq[ self.track ] = level._ambient_eq[ eqReverbVol ]; + level._ambient_reverb[ self.track ] = level._ambient_reverb[ eqReverbVol ]; + level._ambient_volume[ self.track ] = level._ambient_volume[ eqReverbVol ]; +} + +ambientDelay( track, min, max ) +{ + create_ambient_event( track, min, max ); +} + +ambientEvent( track, name, weight ) +{ + assertEX( isdefined( level._ambientEventEnt ), "ambientDelay has not been run" ); + assertEX( isdefined( level._ambientEventEnt[ track ] ), "ambientDelay has not been run" ); + + level._ambientEventEnt[ track ] add_to_ambient_event( name, weight ); +} + +ambientEvent_no_block( track, name, weight ) +{ + assertEX( isdefined( level._ambientEventEnt ), "ambientDelay has not been run" ); + assertEX( isdefined( level._ambientEventEnt[ track ] ), "ambientDelay has not been run" ); + + level._ambientEventEnt[ track ] add_to_ambient_event_no_block( name, weight ); +} + + + +getRemap( track ) +{ +// if ( isdefined( self.remap ) ) +// return self.remap; + if ( track == "exterior" && isdefined( level._remap_exterior ) ) + return level._remap_exterior; + + return track; +} + +deactivate_reverb() +{ + level._reverb_track = ""; + level._player deactivatereverb( "snd_enveffectsprio_level", 2 ); + clear_hud( "reverb" ); +} + +//ambientReverb( track ) +//{ +// level notify( "reverb_overwrite" ); +// level endon( "reverb_overwrite" ); +// +// // first check if this track is remapped to a specific reverb +// track = getRemap( track ); +// +// reverb = level._ambient_reverb[ track ]; +// +// if ( !isdefined( reverb ) ) +// { +// deactivate_reverb(); +// return; +// } +// +// if ( level._reverb_track == track ) +// { +// // already doing this one +// return; +// } +// +// level._reverb_track = track; +// +// use_reverb_settings( track ); +//} + +setup_new_reverb_settings( track ) +{ + if ( !isdefined( track ) || !isdefined( level._ambient_reverb[ track ] ) ) + { + deactivate_reverb(); + return false; + } + + if ( level._reverb_track == track ) + { + // already doing this one + return false; + } + + level._reverb_track = track; + + use_reverb_settings( track ); + return true; +} + +refresh_reverb_settings() +{ + if ( IsDefined(level._reverb_track) && IsDefined( level._ambient_reverb[ level._reverb_track ] ) ) + { + use_reverb_settings( level._reverb_track ); + } +} + +refresh_reverb_settings_fade( time ) +{ + if ( IsDefined(level._reverb_track) && IsDefined( level._ambient_reverb[ level._reverb_track ] ) ) + { + use_reverb_settings_fade( level._reverb_track, time ); + } +} + +use_reverb_settings( track ) +{ + // red flashing overwrites reverb + if ( level._player ent_flag( "player_has_red_flashing_overlay" ) ) + { + return; + } + + //get the new track name with the suit extension, and check if it exists. Change the track if it does exist. + suit = level._player GetSuitType(); + suit_track = track + "_" + suit; + if ( IsDefined( level._ambient_reverb[ suit_track ] ) ) + { + track = suit_track; + } + + if ( !IsDefined( level._ambient_reverb[ track ] ) ) + { + return; + } + + reverb = level._ambient_reverb[ track ]; + level._player setReverb( reverb[ "priority" ], reverb[ "roomtype" ], reverb[ "drylevel" ], reverb[ "wetlevel" ], reverb[ "fadetime" ] ); + + /# + set_hud_track( "reverb", track ); + #/ +} + +use_reverb_settings_fade( track, time ) +{ + // red flashing overwrites reverb + if ( level._player ent_flag( "player_has_red_flashing_overlay" ) ) + { + return; + } + + //get the new track name with the suit extension, and check if it exists. Change the track if it does exist. + //Ripping this out for DT3273 + //suit = level._player GetSuitType(); + //suit_track = track + "_" + suit; + //if ( IsDefined( level._ambient_reverb[ suit_track ] ) ) + //{ + // track = suit_track; + //} + + if ( !IsDefined( level._ambient_reverb[ track ] ) ) + { + return; + } + + reverb = level._ambient_reverb[ track ]; + level._player setReverb( reverb[ "priority" ], reverb[ "roomtype" ], reverb[ "drylevel" ], reverb[ "wetlevel" ], time ); + + /# + set_hud_track( "reverb", track ); + #/ +} + +/* +============= +///ScriptDocBegin +"Name: map_exterior_to_reverb_eq( )" +"Summary: Reverb and EQ will use this setting when "exterior" ambience is triggered." +"Module: Ambient" +"MandatoryArg: : The reverb/eq that exterior gets mapped to." +"Example: map_exterior_to_reverb_eq( "snow_base" );" +"SPMP: singleplayer" +///ScriptDocEnd +============= +*/ +map_exterior_to_reverb_eq( reverb_eq ) +{ + level._remap_exterior = reverb_eq; +} + +ambientMapTo( track, eqReverb ) +{ + assertEX( isdefined( level._ambientEventEnt ), "ambientDelay has not been run" ); + assertEX( isdefined( level._ambientEventEnt[ track ] ), "ambientDelay has not been run" ); + level._ambientEventEnt[ track ] map_to_reverb_eq( eqReverb ); +} + +setup_new_eq_settings( track, eqIndex ) +{ + // this track may be a remapped from an ambient event track. + track = getRemap( track ); + + if ( !isdefined( track ) || !isdefined( level._ambient_eq[ track ] ) ) + { + deactivate_index( eqIndex ); + return false; + } + + if ( level._eq_track[ eqIndex ] == track ) + { + // already doing this one + return false; + } + + level._eq_track[ eqIndex ] = track; + + use_eq_settings( track, eqIndex ); + return true; +} + +refresh_eq_settings( eqIndex ) +{ + refresh_eq_settings_fade( eqIndex, 1.0 ); +} + +refresh_eq_settings_fade( eqIndex, fade ) +{ + if ( IsDefined( level._eq_track ) && IsDefined(level._eq_track[ eqIndex ]) && IsDefined( level._ambient_eq[ level._eq_track[ eqIndex ] ] ) ) + { + use_eq_settings_fade( level._eq_track[ eqIndex ], eqIndex, fade ); + } +} + +/* +============= +///ScriptDocBegin +"Name: blend_to_eq_track( ,